diff --git a/.cargo/config.toml b/.cargo/config.toml index e7f653d39dcc3..76cf725f9e2e2 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,15 +1,11 @@ +[alias] +cheats = "test -p foundry-cheatcodes-spec --features schema tests::" +test-debugger = "test -p forge --test cli manual_debug_setup -- --include-ignored --nocapture" + +# Increase the stack size to 10MB for Windows targets, which is in line with Linux +# (whereas default for Windows is 1MB). [target.x86_64-pc-windows-msvc] -rustflags = [ - # Increases the stack size to 10MB, which is - # in line with Linux (whereas default for Windows is 1MB) - "-C", - "link-arg=/STACK:10000000", -] +rustflags = ["-Clink-arg=/STACK:10000000"] [target.i686-pc-windows-msvc] -rustflags = [ - # Increases the stack size to 10MB, which is - # in line with Linux (whereas default for Windows is 1MB) - "-C", - "link-arg=/STACK:10000000", -] +rustflags = ["-Clink-arg=/STACK:10000000"] diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000000000..929bd589b7112 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,3 @@ +[codespell] +skip = .git,target,testdata,Cargo.toml,Cargo.lock +ignore-words-list = crate,ser,ratatui,Caf,froms,strat diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 0000000000000..a90da8b186e19 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,11 @@ +[profile.default] +retries = { backoff = "exponential", count = 2, delay = "3s", jitter = true } +slow-timeout = { period = "1m", terminate-after = 3 } + +[[profile.default.overrides]] +filter = "test(/ext_integration|can_test_forge_std/)" +slow-timeout = { period = "5m", terminate-after = 4 } + +[[profile.default.overrides]] +filter = "package(foundry-cheatcodes-spec)" +retries = 0 diff --git a/.gitattributes b/.gitattributes index deea890e0c2da..0e0276a958df8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,5 @@ -# auto-detection may fail for human-readable files, like the ones in abi/abi/*.sol -**/*.sol linguist-language=Solidity +crates/cheatcodes/assets/*.json linguist-generated +testdata/cheats/Vm.sol linguist-generated -crates/abi/src/bindings/*.rs linguist-generated +# See +*.rs diff=rust diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..63b25bbbcb260 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @danipopes @klkvr @mattsse @grandizzy @yash-atreya @zerosnacks diff --git a/.github/INTEGRATION_FAILURE.md b/.github/INTEGRATION_FAILURE.md index afa055b18364e..bda129177f706 100644 --- a/.github/INTEGRATION_FAILURE.md +++ b/.github/INTEGRATION_FAILURE.md @@ -3,8 +3,8 @@ title: "bug: long-running integration tests failed" labels: P-high, T-bug --- -The heavy (long-running) integration tests have failed. This indicates a regression in foundry. +The heavy (long-running) integration tests have failed. This indicates a regression in Foundry. -Check the [heavy integration tests workflow page]({{env.WORKFLOW_URL}}) for details. +Check the [heavy integration tests workflow page]({{ env.WORKFLOW_URL }}) for details. -This issue was raised by the workflow at `.github/workflows/heavy-integration.yml`. \ No newline at end of file +This issue was raised by the workflow at `.github/workflows/heavy-integration.yml`. diff --git a/.github/ISSUE_TEMPLATE/BUG-FORM.yml b/.github/ISSUE_TEMPLATE/BUG-FORM.yml index 8067de8661809..5a5f7e7808994 100644 --- a/.github/ISSUE_TEMPLATE/BUG-FORM.yml +++ b/.github/ISSUE_TEMPLATE/BUG-FORM.yml @@ -1,6 +1,6 @@ name: Bug report description: File a bug report -labels: ["T-bug"] +labels: ["T-bug", "T-needs-triage"] body: - type: markdown attributes: @@ -32,6 +32,10 @@ body: attributes: label: What version of Foundry are you on? placeholder: "Run forge --version and paste the output here" + - type: input + attributes: + label: What version of Foundryup are you on? + placeholder: "Run foundryup --version and paste the output here" - type: input attributes: label: What command(s) is the bug in? @@ -51,4 +55,4 @@ body: label: Describe the bug description: Please include relevant Solidity snippets as well if relevant. validations: - required: true + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml index d7df540307ceb..581eb5c0d5eed 100644 --- a/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml +++ b/.github/ISSUE_TEMPLATE/FEATURE-FORM.yml @@ -1,6 +1,6 @@ name: Feature request description: Suggest a feature -labels: ["T-feature"] +labels: ["T-feature", "T-needs-triage"] body: - type: markdown attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ef4a037ad501d..16e0182b117ac 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,8 +3,15 @@ Thank you for your Pull Request. Please provide a description above and review the requirements below. Bug fixes and new features should include tests. + +Contributors guide: https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md + +The contributors guide includes instructions for running rustfmt and building the +documentation. --> + + ## Motivation + +## PR Checklist + +- [ ] Added Tests +- [ ] Added Documentation +- [ ] Breaking changes \ No newline at end of file diff --git a/.github/RELEASE_FAILURE_ISSUE_TEMPLATE.md b/.github/RELEASE_FAILURE_ISSUE_TEMPLATE.md index eb33b48971ef4..2ff598cc1b371 100644 --- a/.github/RELEASE_FAILURE_ISSUE_TEMPLATE.md +++ b/.github/RELEASE_FAILURE_ISSUE_TEMPLATE.md @@ -5,6 +5,6 @@ labels: P-high, T-bug The release workflow has failed. Some or all binaries might have not been published correctly. -Check the [release workflow page]({{env.WORKFLOW_URL}}) for details. +Check the [release workflow page]({{ env.WORKFLOW_URL }}) for details. -This issue was raised by the workflow at `.github/workflows/release.yml`. \ No newline at end of file +This issue was raised by the workflow at `.github/workflows/release.yml`. diff --git a/.github/assets/banner.png b/.github/assets/banner.png new file mode 100644 index 0000000000000..2a3752b97fc0c Binary files /dev/null and b/.github/assets/banner.png differ diff --git a/.github/assets/build_benchmark_openzeppelin_dark.png b/.github/assets/build_benchmark_openzeppelin_dark.png new file mode 100644 index 0000000000000..75d80d127c377 Binary files /dev/null and b/.github/assets/build_benchmark_openzeppelin_dark.png differ diff --git a/.github/assets/build_benchmark_openzeppelin_light.png b/.github/assets/build_benchmark_openzeppelin_light.png new file mode 100644 index 0000000000000..2772a6ab1093a Binary files /dev/null and b/.github/assets/build_benchmark_openzeppelin_light.png differ diff --git a/.github/assets/build_benchmark_solady_dark.png b/.github/assets/build_benchmark_solady_dark.png new file mode 100644 index 0000000000000..123c51f9132f8 Binary files /dev/null and b/.github/assets/build_benchmark_solady_dark.png differ diff --git a/.github/assets/build_benchmark_solady_light.png b/.github/assets/build_benchmark_solady_light.png new file mode 100644 index 0000000000000..b2836d81f73f0 Binary files /dev/null and b/.github/assets/build_benchmark_solady_light.png differ diff --git a/.github/assets/demo.gif b/.github/assets/demo.gif new file mode 100644 index 0000000000000..bebdb8148ce19 Binary files /dev/null and b/.github/assets/demo.gif differ diff --git a/.github/changelog.json b/.github/changelog.json index 4fe255e699ee4..6c84eb1b8e5d4 100644 --- a/.github/changelog.json +++ b/.github/changelog.json @@ -1,16 +1,66 @@ { "categories": [ { - "title": "## Features", - "labels": ["T-feature"] + "title": "## Breaking changes", + "labels": ["T-likely-breaking "] }, { - "title": "## Fixes", - "labels": ["T-bug", "T-fix"] + "title": "## Anvil Features", + "labels": ["C-anvil", "T-feature"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Anvil Fixes", + "labels": ["C-anvil", "T-bug"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Cast Features", + "labels": ["C-cast", "T-feature"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Cast Fixes", + "labels": ["C-cast", "T-bug"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Chisel Features", + "labels": ["C-chisel", "T-feature"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Chisel Fixes", + "labels": ["C-chisel", "T-bug"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Forge Features", + "labels": ["C-forge", "T-feature"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Forge Fixes", + "labels": ["C-forge", "T-bug"], + "exhaustive": true, + "exhaustive_rules": false + }, + { + "title": "## Performance improvements", + "labels": ["T-perf"] } ], "ignore_labels": ["L-ignore"], - "template": "${{CHANGELOG}}\n## Other\n\n${{UNCATEGORIZED}}", - "pr_template": "- ${{TITLE}} (#${{NUMBER}})", - "empty_template": "- No changes" + "template": "${{CHANGELOG}}\n## Other\n\n${{UNCATEGORIZED}}\n## Full Changelog:\n ${{RELEASE_DIFF}}", + "pr_template": "- ${{TITLE}} (#${{NUMBER}}) by @${{AUTHOR}}", + "empty_template": "- No changes", + "max_pull_requests": 500, + "max_back_track_time_days": 120 } diff --git a/.github/demo.gif b/.github/demo.gif deleted file mode 100644 index c45469b11abc9..0000000000000 Binary files a/.github/demo.gif and /dev/null differ diff --git a/.github/logo.png b/.github/logo.png deleted file mode 100644 index 8e24b87ef689e..0000000000000 Binary files a/.github/logo.png and /dev/null differ diff --git a/.github/scripts/format.sh b/.github/scripts/format.sh new file mode 100755 index 0000000000000..9bd1f950fdeaf --- /dev/null +++ b/.github/scripts/format.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -eo pipefail + +# We have to ignore at shell level because testdata/ is not a valid Foundry project, +# so running `forge fmt` with `--root testdata` won't actually check anything +cargo run --bin forge -- fmt "$@" \ + $(find testdata -name '*.sol' ! -name Vm.sol ! -name console.sol) diff --git a/.github/scripts/matrices.py b/.github/scripts/matrices.py new file mode 100755 index 0000000000000..4ad0c3acc34dc --- /dev/null +++ b/.github/scripts/matrices.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +import json +import os + + +# A runner target +class Target: + # GHA runner + runner_label: str + # Rust target triple + target: str + # SVM Solc target + svm_target_platform: str + + def __init__(self, runner_label: str, target: str, svm_target_platform: str): + self.runner_label = runner_label + self.target = target + self.svm_target_platform = svm_target_platform + + +# A single test suite to run. +class Case: + # Name of the test suite. + name: str + # Nextest filter expression. + filter: str + # Number of partitions to split the test suite into. + n_partitions: int + # Whether to run on non-Linux platforms for PRs. All platforms and tests are run on pushes. + pr_cross_platform: bool + + def __init__( + self, name: str, filter: str, n_partitions: int, pr_cross_platform: bool + ): + self.name = name + self.filter = filter + self.n_partitions = n_partitions + self.pr_cross_platform = pr_cross_platform + + +# GHA matrix entry +class Expanded: + name: str + runner_label: str + target: str + svm_target_platform: str + flags: str + partition: int + + def __init__( + self, + name: str, + runner_label: str, + target: str, + svm_target_platform: str, + flags: str, + partition: int, + ): + self.name = name + self.runner_label = runner_label + self.target = target + self.svm_target_platform = svm_target_platform + self.flags = flags + 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 +# t_linux_arm = Target("ubuntu-latest", "aarch64-unknown-linux-gnu", "linux-aarch64") +t_macos = Target("macos-latest", "aarch64-apple-darwin", "macosx-aarch64") +t_windows = Target("windows-latest", "x86_64-pc-windows-msvc", "windows-amd64") +targets = [t_linux_x86, t_windows] if is_pr else [t_linux_x86, t_macos, t_windows] + +config = [ + Case( + name="unit", + filter="!kind(test)", + n_partitions=1, + pr_cross_platform=True, + ), + Case( + name="integration", + filter="kind(test) & !test(/\\b(issue|ext_integration)/)", + n_partitions=3, + pr_cross_platform=True, + ), + Case( + name="integration / issue-repros", + filter="package(=forge) & test(/\\bissue/)", + n_partitions=2, + pr_cross_platform=False, + ), + Case( + name="integration / external", + filter="package(=forge) & test(/\\bext_integration/)", + n_partitions=2, + pr_cross_platform=False, + ), +] + + +def main(): + expanded = [] + for target in targets: + for case in config: + if is_pr and (not case.pr_cross_platform and target != t_linux_x86): + continue + + for partition in range(1, case.n_partitions + 1): + os_str = "" + if len(targets) > 1: + os_str = f" ({target.target})" + + name = case.name + flags = f"-E '{case.filter}'" + if case.n_partitions > 1: + 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( + name=name, + runner_label=target.runner_label, + target=target.target, + svm_target_platform=target.svm_target_platform, + flags=flags, + partition=partition, + ) + expanded.append(vars(obj)) + + print_json({"include": expanded}) + + +def print_json(obj): + print(json.dumps(obj), end="", flush=True) + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/prune-prereleases.js b/.github/scripts/prune-prereleases.js index 0537fbf1380ae..d0d6bf465e81a 100644 --- a/.github/scripts/prune-prereleases.js +++ b/.github/scripts/prune-prereleases.js @@ -1,3 +1,23 @@ +// In case node 21 is not used. +function groupBy(array, keyOrIterator) { + var iterator; + + // use the function passed in, or create one + if(typeof keyOrIterator !== 'function') { + const key = String(keyOrIterator); + iterator = function (item) { return item[key]; }; + } else { + iterator = keyOrIterator; + } + + return array.reduce(function (memo, item) { + const key = iterator(item); + memo[key] = memo[key] || []; + memo[key].push(item); + return memo; + }, {}); +} + module.exports = async ({ github, context }) => { console.log("Pruning old prereleases"); @@ -11,16 +31,25 @@ module.exports = async ({ github, context }) => { release => // Only consider releases tagged `nightly-${SHA}` for deletion release.tag_name.includes("nightly") && - release.tag_name !== "nightly" && - // ref: https://github.com/foundry-rs/foundry/issues/3881 - // Skipping pruning the build on 1st day of each month - !release.created_at.includes("-01T") + release.tag_name !== "nightly" ); - // Keep newest 3 nightlies - nightlies = nightlies.slice(3); + // Pruning rules: + // 1. only keep the earliest (by created_at) release of the month + // 2. to keep the newest 30 nightlies (to make sure nightlies are kept until the next monthly release) + // Notes: + // - This addresses https://github.com/foundry-rs/foundry/issues/6732 + // - Name of the release may deviate from created_at due to the usage of different timezones. + + // Group releases by months. + // Per doc: + // > The latest release is the most recent non-prerelease, non-draft release, sorted by the created_at attribute. + const groups = groupBy(nightlies, i => i.created_at.slice(0, 7)); + const nightliesToPrune = Object.values(groups) + .reduce((acc, cur) => acc.concat(cur.slice(0, -1)), []) // rule 1 + .slice(30); // rule 2 - for (const nightly of nightlies) { + for (const nightly of nightliesToPrune) { console.log(`Deleting nightly: ${nightly.tag_name}`); await github.rest.repos.deleteRelease({ owner: context.repo.owner, diff --git a/.github/workflows/bump-forge-std.yml b/.github/workflows/bump-forge-std.yml new file mode 100644 index 0000000000000..137e8c465e914 --- /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/cross-platform.yml b/.github/workflows/cross-platform.yml deleted file mode 100644 index bd9e19fc21db3..0000000000000 --- a/.github/workflows/cross-platform.yml +++ /dev/null @@ -1,206 +0,0 @@ -on: - workflow_dispatch: - workflow_call: - -name: cross-platform - -env: - CARGO_TERM_COLOR: always - -jobs: - build-tests: - name: building ${{ matrix.job.target }} (${{ matrix.job.os }}) / ${{ matrix.archive.name }} - runs-on: ${{ matrix.job.os }} - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - archive: - - name: unit-tests - file: nextest-unit.tar.zst - flags: --workspace --all-features --lib --bins - - name: integration-tests - file: nextest-integration.tar.zst - flags: --workspace - job: - # The OS is used for the runner - # The target is used by Cargo - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - - os: macos-latest - target: x86_64-apple-darwin - - os: macos-latest - target: aarch64-apple-darwin - - os: windows-latest - target: x86_64-pc-windows-msvc - wsl: wsl - - os: windows-latest - target: x86_64-pc-windows-msvc - - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.job.target }} - - uses: Swatinem/rust-cache@v2 - - name: Install nextest - uses: taiki-e/install-action@nextest - - # Required for forge commands that use git - - name: Setup git - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: Apple M1 setup - if: ${{ matrix.job.target == 'aarch64-apple-darwin' }} - run: | - echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV - echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV - - - name: Linux ARM setup - if: ${{ matrix.job.target == 'aarch64-unknown-linux-gnu' }} - run: | - sudo apt-get update -y - sudo apt-get install -y gcc-aarch64-linux-gnu - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - - # For some reason the FFI cheatcode uses WSL bash instead of Git bash, so we need to install a WSL distribution - - name: Windows setup - if: ${{ matrix.job.wsl == 'wsl' }} - uses: Vampire/setup-wsl@v1 - with: - distribution: Ubuntu-20.04 - set-as-default: true - - - name: Build archive (unit tests) - run: | - cargo nextest archive --locked ${{ matrix.job.flags }} --archive-file ${{ matrix.job.target }}-${{ matrix.archive.file }} - - name: Upload archive - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.job.target }}-${{ matrix.archive.name }} - path: ${{ matrix.job.target }}-${{ matrix.archive.file }} - - unit: - name: unit tests ${{ matrix.job.target }} (${{ matrix.job.os }}) / ${{ matrix.archive.name }} / ${{ matrix.nextest.name }} - runs-on: ${{ matrix.job.os }} - needs: build-tests - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - archive: - - name: unit-tests - file: nextest-unit.tar.zst - job: - # The OS is used for the runner - # The target is used by Cargo - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - - os: macos-latest - target: x86_64-apple-darwin - - os: macos-latest - target: aarch64-apple-darwin - - os: windows-latest - target: x86_64-pc-windows-msvc - nextest: - - name: non-forking - filter: "!test(~fork) & !test(~live) & !test(~forge_std) & !test(~deploy_and_simulate)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.job.target }} - - uses: taiki-e/install-action@nextest - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: ${{ matrix.job.target }}-${{ matrix.archive.name }} - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: cargo nextest - shell: bash - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file ${{ matrix.job.target }}-${{ matrix.archive.file }} -E '${{ matrix.nextest.filter }}' - - integration: - name: - integration tests ${{ matrix.job.target }} (${{ matrix.job.os }}) / ${{ - matrix.archive.name }} / ${{ matrix.nextest.name }} - runs-on: ${{ matrix.job.os }} - timeout-minutes: 60 - needs: build-tests - strategy: - fail-fast: false - matrix: - archive: - - name: integration-tests - file: nextest-integration.tar.zst - job: - # The OS is used for the runner - # The target is used by Cargo - - os: ubuntu-latest - target: x86_64-unknown-linux-gnu - - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - - os: macos-latest - target: x86_64-apple-darwin - - os: macos-latest - target: aarch64-apple-darwin - - os: windows-latest - target: x86_64-pc-windows-msvc - nextest: - - name: non-forking - # skip fork,verify,forge-std and script tests that use heavy simulation - filter: "!test(~fork) & !test(~live) & !test(~forge_std) & !test(~deploy_and_simulate)" - # the aarch64-apple-darwin runner is particularly slow with script related tests - macos-aarch-filter: "!test(~fork) & !test(~live) & !test(~forge_std) & !test(~deploy_)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: ${{ matrix.job.target }}-${{ matrix.archive.name }} - - - name: Forge RPC cache - uses: actions/cache@v3 - if: matrix.nextest.name != 'non-forking' - with: - path: "$HOME/.foundry/cache" - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: cargo nextest - if: ${{ matrix.job.target == 'aarch64-apple-darwin' }} - shell: bash - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file ${{ matrix.job.target }}-${{ matrix.archive.file }} -E '${{ matrix.nextest.macos-aarch-filter }}' - - - name: cargo nextest - if: ${{ matrix.job.target != 'aarch64-apple-darwin' }} - shell: bash - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file ${{ matrix.job.target }}-${{ matrix.archive.file }} -E '${{ matrix.nextest.filter }}' diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml deleted file mode 100644 index 9fac75ef56019..0000000000000 --- a/.github/workflows/deny.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: deny - -on: - push: - branches: [master] - paths: [Cargo.lock] - pull_request: - branches: [master] - paths: [Cargo.lock] - -env: - CARGO_TERM_COLOR: always - -jobs: - cargo-deny: - name: cargo deny check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check all - # Clear out arguments to not pass `--all-features` to `cargo deny`. - # many crates have an `openssl` feature which enables banned dependencies - arguments: "" diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index e731a7a56923f..d22bbffcab714 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -1,6 +1,6 @@ -# Automatically run `cargo update` periodically +# Runs `cargo update` periodically. -name: Update Dependencies +name: dependencies on: schedule: @@ -9,53 +9,12 @@ on: workflow_dispatch: # Needed so we can run it manually -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: cargo-update - TITLE: "chore(deps): weekly `cargo update`" - BODY: | - Automation to keep dependencies in `Cargo.lock` current. - -
cargo update log -

- - ```log - $cargo_update_log - ``` - -

-
+permissions: + contents: write + pull-requests: write jobs: update: - name: Update - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - - name: cargo update - # Remove first line that always just says "Updating crates.io index" - run: cargo update --color never 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log - - - name: craft commit message and PR body - id: msg - run: | - export cargo_update_log="$(cat cargo_update.log)" - - echo "commit_message<> $GITHUB_OUTPUT - printf "$TITLE\n\n$cargo_update_log\n" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - echo "body<> $GITHUB_OUTPUT - echo "$BODY" | envsubst >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 - with: - add-paths: ./Cargo.lock - commit-message: ${{ steps.msg.outputs.commit_message }} - title: ${{ env.TITLE }} - body: ${{ steps.msg.outputs.body }} - branch: ${{ env.BRANCH }} + uses: ithacaxyz/ci/.github/workflows/cargo-update-pr.yml@main + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index ae4382f3e6a91..a1015c1ce0f67 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,97 +1,82 @@ -name: Build and Publish Docker +name: docker on: - push: - tags: - - "v*.*.*" - schedule: - - cron: "0 0 * * *" - # Trigger without any parameters a proactive rebuild - workflow_dispatch: {} - workflow_call: + push: + tags: + - "v*.*.*" + schedule: + - cron: "0 0 * * *" + # Trigger without any parameters a proactive rebuild + workflow_dispatch: {} + workflow_call: + inputs: + tag_name: + required: true + type: string env: - REGISTRY: ghcr.io - # Will resolve to foundry-rs/foundry - IMAGE_NAME: ${{ github.repository }} + REGISTRY: ghcr.io + # Will resolve to foundry-rs/foundry + IMAGE_NAME: ${{ github.repository }} jobs: - container: - runs-on: ubuntu-20.04 - # https://docs.github.com/en/actions/reference/authentication-in-a-workflow - permissions: - id-token: write - packages: write - contents: read - timeout-minutes: 60 + build: + name: build and push + runs-on: Linux-20.04 + permissions: + id-token: write + packages: write + contents: read + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - uses: taiki-e/install-action@cross + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Login into registry ${{ env.REGISTRY }} + # Ensure this doesn't trigger on PR's + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Checkout repository - id: checkout - uses: actions/checkout@v3 + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - name: Install Docker BuildX - uses: docker/setup-buildx-action@v2 - id: buildx - with: - install: true + # Creates an additional 'latest' or 'nightly' tag + # If the job is triggered via cron schedule, tag nightly and nightly-{SHA} + # If the job is triggered via workflow dispatch and on a master branch, tag branch and latest + # Otherwise, just tag as the branch name + - name: Finalize Docker Metadata + id: docker_tagging + run: | + if [[ "${{ github.event_name }}" == 'schedule' ]]; then + echo "cron trigger, assigning nightly tag" + echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT + elif [[ "${GITHUB_REF##*/}" == "main" ]] || [[ ${GITHUB_REF##*/} == "master" ]]; then + echo "manual trigger from master/main branch, assigning latest tag" + echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_OUTPUT + else + echo "Neither scheduled nor manual release from main branch. Just tagging as branch name" + echo "docker_tags=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/}" >> $GITHUB_OUTPUT + fi - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - # Ensure this doesn't trigger on PR's - if: github.event_name != 'pull_request' - uses: docker/login-action@v2 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + # Log docker metadata to explicitly know what is being pushed + - name: Inspect Docker Metadata + run: | + echo "TAGS -> ${{ steps.docker_tagging.outputs.docker_tags }}" + echo "LABELS -> ${{ steps.meta.outputs.labels }}" - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - # Creates an additional 'latest' or 'nightly' tag - # If the job is triggered via cron schedule, tag nightly and nightly-{SHA} - # If the job is triggered via workflow dispatch and on a master branch, tag branch and latest - # Otherwise, just tag as the branch name - - name: Finalize Docker Metadata - id: docker_tagging - run: | - if [[ "${{ github.event_name }}" == 'schedule' ]]; then - echo "cron trigger, assigning nightly tag" - echo "::set-output name=docker_tags::${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${GITHUB_SHA}" - elif [[ "${GITHUB_REF##*/}" == "main" ]] || [[ ${GITHUB_REF##*/} == "master" ]]; then - echo "manual trigger from master/main branch, assigning latest tag" - echo "::set-output name=docker_tags::${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" - else - echo "Neither scheduled nor manual release from main branch. Just tagging as branch name" - echo "::set-output name=docker_tags::${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${GITHUB_REF##*/}" - fi - - # Log docker metadata to explicitly know what is being pushed - - name: Inspect Docker Metadata - run: | - echo "TAGS -> ${{ steps.docker_tagging.outputs.docker_tags }}" - echo "LABELS -> ${{ steps.meta.outputs.labels }}" - - # Build and push Docker image - # https://github.com/docker/build-push-action - # https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md - - name: Build and push Docker image - uses: docker/build-push-action@v3 - with: - context: . - push: true - tags: ${{ steps.docker_tagging.outputs.docker_tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} - VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} - REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} + - name: Build and push foundry image + run: make DOCKER_IMAGE_NAME=${{ steps.docker_tagging.outputs.docker_tags }} CARGO_TAG_NAME=${{ inputs.tag_name }} PROFILE=maxperf docker-build-push diff --git a/.github/workflows/heavy-integration.yml b/.github/workflows/heavy-integration.yml deleted file mode 100644 index e051e706bc81d..0000000000000 --- a/.github/workflows/heavy-integration.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Heavy (long-running) integration tests - -on: - schedule: - # Runs at 10PM utc - - cron: "0 22 * * *" - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always - -jobs: - build-tests: - name: build tests / ${{ matrix.archive.name }} - runs-on: ubuntu-latest - strategy: - matrix: - archive: - - name: heavy-integration-tests - file: heavy-integration.tar.zst - flags: -p forge --features heavy-integration-tests - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - uses: taiki-e/install-action@nextest - - name: Build archive (long-running tests) - run: | - cargo nextest archive \ - --locked \ - --archive-file ${{ matrix.archive.file }} \ - ${{ matrix.archive.flags }} - - name: Upload archive - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.archive.name }} - path: ${{ matrix.archive.file }} - - install-svm-solc: - name: install svm and solidity / ${{ matrix.job.name }} - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Install svm - run: cargo install svm-rs - - name: Install Solidity 0.8.19 - run: svm install 0.8.19 - - name: Install Solidity 0.8.20 - run: svm install 0.8.20 - - name: Use Solidity 0.8.19 - run: svm use 0.8.19 - - heavy-integration: - name: heavy (long-running) integration tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests - strategy: - matrix: - job: - - name: Long-running integration tests - filter: "!test(~live)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: heavy-integration-tests - - - name: Forge RPC cache - uses: actions/cache@v3 - if: matrix.job.name != 'non-forking' - with: - path: "$HOME/.foundry/cache" - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: Force use of HTTPS for submodules - run: git config --global url."https://github.com/".insteadOf "git@github.com:" - - - name: cargo nextest - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file heavy-integration.tar.zst -E '${{ matrix.job.filter }}' - - # If any of the steps fail, this will create a high-priority issue - # to signal so. - - uses: JasonEtco/create-an-issue@v2 - if: ${{ failure() }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - update_existing: true - filename: .github/INTEGRATION_FAILURE.md diff --git a/.github/workflows/nextest.yml b/.github/workflows/nextest.yml new file mode 100644 index 0000000000000..522ebb5a1a6a7 --- /dev/null +++ b/.github/workflows/nextest.yml @@ -0,0 +1,97 @@ +# Reusable workflow for running tests via `cargo nextest` + +name: nextest + +on: + workflow_call: + inputs: + profile: + required: true + type: string + +concurrency: + group: tests-${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full + +jobs: + matrices: + name: build matrices + runs-on: ubuntu-latest + outputs: + test-matrix: ${{ steps.gen.outputs.test-matrix }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + - name: Generate matrices + id: gen + env: + EVENT_NAME: ${{ github.event_name }} + PROFILE: ${{ inputs.profile }} + run: | + output=$(python3 .github/scripts/matrices.py) + echo "::debug::test-matrix=$output" + echo "test-matrix=$output" >> $GITHUB_OUTPUT + + test: + name: test ${{ matrix.name }} + runs-on: ${{ matrix.runner_label }} + timeout-minutes: 60 + needs: matrices + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.matrices.outputs.test-matrix) }} + env: + ETH_RPC_URL: https://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 + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Install Vyper + 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 }} + HTTP_ARCHIVE_URLS: ${{ secrets.HTTP_ARCHIVE_URLS }} + WS_ARCHIVE_URLS: ${{ secrets.WS_ARCHIVE_URLS }} + run: cargo nextest run ${{ matrix.flags }} diff --git a/.github/workflows/project.yml b/.github/workflows/project.yml deleted file mode 100644 index f1dfd2727bb5f..0000000000000 --- a/.github/workflows/project.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: project -on: - issues: - types: [opened, transferred] - -jobs: - add-to-project: - name: add issue - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/foundry-rs/projects/2 - github-token: ${{ secrets.GH_PROJECTS_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43dc1178a1087..793502fd2ea90 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,239 +1,294 @@ -name: Release version +name: release on: - push: - tags: - - "v*.*.*" - schedule: - - cron: "0 0 * * *" - workflow_dispatch: + push: + tags: + - "stable" + - "rc" + - "v*.*.*" + schedule: + - cron: "0 0 * * *" + workflow_dispatch: env: - IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} - CARGO_TERM_COLOR: always + CARGO_TERM_COLOR: always + IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + PROFILE: maxperf + STABLE_VERSION: "v0.3.0" jobs: - prepare: - name: Prepare release - runs-on: ubuntu-20.04 - - outputs: - tag_name: ${{ steps.release_info.outputs.tag_name }} - release_name: ${{ steps.release_info.outputs.release_name }} - changelog: ${{ steps.build_changelog.outputs.changelog }} - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Compute release name and tag - id: release_info - run: | - if [[ $IS_NIGHTLY ]]; then - echo "::set-output name=tag_name::nightly-${GITHUB_SHA}" - echo "::set-output name=release_name::Nightly ($(date '+%Y-%m-%d'))" - else - echo "::set-output name=tag_name::${GITHUB_REF_NAME}" - echo "::set-output name=release_name::${GITHUB_REF_NAME}" - fi - - # Creates a `nightly-SHA` tag for this specific nightly - # This tag is used for this specific nightly version's release - # which allows users to roll back. It is also used to build - # the changelog. - - name: Create build-specific nightly tag - if: ${{ env.IS_NIGHTLY }} - uses: actions/github-script@v5 - env: - TAG_NAME: ${{ steps.release_info.outputs.tag_name }} - with: - script: | - const createTag = require('./.github/scripts/create-tag.js') - await createTag({ github, context }, process.env.TAG_NAME) - - - name: Build changelog - id: build_changelog - uses: mikepenz/release-changelog-builder-action@v2 - with: - configuration: "./.github/changelog.json" - fromTag: ${{ env.IS_NIGHTLY && 'nightly' || '' }} - toTag: ${{ steps.release_info.outputs.tag_name }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - release-docker: - name: Release Docker - uses: ./.github/workflows/docker-publish.yml - - release: - name: ${{ matrix.job.target }} (${{ matrix.job.os }}) - runs-on: ${{ matrix.job.os }} - needs: prepare - strategy: - matrix: - job: - # The OS is used for the runner - # The platform is a generic platform name - # The target is used by Cargo - # The arch is either 386, arm64 or amd64 - # The svm target platform to use for the binary https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 - - os: ubuntu-20.04 - platform: linux - target: x86_64-unknown-linux-gnu - arch: amd64 - svm_target_platform: linux-amd64 - - os: ubuntu-20.04 - platform: linux - target: aarch64-unknown-linux-gnu - arch: arm64 - svm_target_platform: linux-aarch64 - - os: macos-latest - platform: darwin - target: x86_64-apple-darwin - arch: amd64 - svm_target_platform: macosx-amd64 - - os: macos-latest - platform: darwin - target: aarch64-apple-darwin - arch: arm64 - svm_target_platform: macosx-aarch64 - - os: windows-latest - platform: win32 - target: x86_64-pc-windows-msvc - arch: amd64 - svm_target_platform: windows-amd64 - - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.job.target }} - - uses: Swatinem/rust-cache@v2 - - - name: Apple M1 setup - if: ${{ matrix.job.target == 'aarch64-apple-darwin' }} - run: | - echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV - echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV - - - name: Linux ARM setup - if: ${{ matrix.job.target == 'aarch64-unknown-linux-gnu' }} - run: | - sudo apt-get update -y - sudo apt-get install -y gcc-aarch64-linux-gnu - echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - - - name: Build binaries - uses: actions-rs/cargo@v1 - env: - SVM_TARGET_PLATFORM: ${{ matrix.job.svm_target_platform }} - with: - command: build - args: --release --bins --target ${{ matrix.job.target }} - - - name: Archive binaries - id: artifacts - env: - PLATFORM_NAME: ${{ matrix.job.platform }} - TARGET: ${{ matrix.job.target }} - ARCH: ${{ matrix.job.arch }} - VERSION_NAME: ${{ (env.IS_NIGHTLY && 'nightly') || needs.prepare.outputs.tag_name }} - run: | - if [ "$PLATFORM_NAME" == "linux" ]; then - tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C ./target/${TARGET}/release forge cast anvil chisel - echo "::set-output name=file_name::foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" - elif [ "$PLATFORM_NAME" == "darwin" ]; then - # We need to use gtar here otherwise the archive is corrupt. - # See: https://github.com/actions/virtual-environments/issues/2619 - gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C ./target/${TARGET}/release forge cast anvil chisel - echo "::set-output name=file_name::foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" - else - cd ./target/${TARGET}/release - 7z a -tzip "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" forge.exe cast.exe anvil.exe chisel.exe - mv "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../ - echo "::set-output name=file_name::foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" - fi - shell: bash - - - name: Build man page - id: man - if: ${{ matrix.job.target == 'x86_64-unknown-linux-gnu' }} - env: - PLATFORM_NAME: ${{ matrix.job.platform }} - TARGET: ${{ matrix.job.target }} - VERSION_NAME: ${{ (env.IS_NIGHTLY && 'nightly') || needs.prepare.outputs.tag_name }} - run: | - sudo apt-get -y install help2man - help2man -N ./target/${TARGET}/release/forge > forge.1 - help2man -N ./target/${TARGET}/release/cast > cast.1 - help2man -N ./target/${TARGET}/release/anvil > anvil.1 - help2man -N ./target/${TARGET}/release/chisel > chisel.1 - gzip forge.1 - gzip cast.1 - gzip anvil.1 - gzip chisel.1 - tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz - echo "::set-output name=foundry_man::foundry_man_${VERSION_NAME}.tar.gz" - shell: bash - - # Creates the release for this specific version - - name: Create release - uses: softprops/action-gh-release@v1 - with: - name: ${{ needs.prepare.outputs.release_name }} - tag_name: ${{ needs.prepare.outputs.tag_name }} - prerelease: ${{ env.IS_NIGHTLY }} - body: ${{ needs.prepare.outputs.changelog }} - files: | - ${{ steps.artifacts.outputs.file_name }} - ${{ steps.man.outputs.foundry_man }} - - # If this is a nightly release, it also updates the release - # tagged `nightly` for compatibility with `foundryup` - - name: Update nightly release - if: ${{ env.IS_NIGHTLY }} - uses: softprops/action-gh-release@v1 - with: - name: "Nightly" - tag_name: "nightly" - prerelease: true - body: ${{ needs.prepare.outputs.changelog }} - files: | - ${{ steps.artifacts.outputs.file_name }} - ${{ steps.man.outputs.foundry_man }} - - # If any of the steps fail, this will create a high-priority issue - # to signal so. - - uses: JasonEtco/create-an-issue@v2 - if: ${{ failure() }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - update_existing: true - filename: .github/RELEASE_FAILURE_ISSUE_TEMPLATE.md - - cleanup: - name: Release cleanup - runs-on: ubuntu-20.04 - needs: release - - steps: - - uses: actions/checkout@v3 - - # Moves the `nightly` tag to `HEAD` - - name: Move nightly tag - if: ${{ env.IS_NIGHTLY }} - uses: actions/github-script@v5 - with: - script: | - const moveTag = require('./.github/scripts/move-tag.js') - await moveTag({ github, context }, 'nightly') - - - name: Delete old nightlies - uses: actions/github-script@v5 - with: - script: | - const prunePrereleases = require('./.github/scripts/prune-prereleases.js') - await prunePrereleases({github, context}) \ No newline at end of file + prepare: + name: Prepare release + runs-on: ubuntu-latest + timeout-minutes: 30 + outputs: + tag_name: ${{ steps.release_info.outputs.tag_name }} + release_name: ${{ steps.release_info.outputs.release_name }} + changelog: ${{ steps.build_changelog.outputs.changelog }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Compute release name and tag + id: release_info + run: | + if [[ ${IS_NIGHTLY} == 'true' ]]; then + echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT + echo "release_name=Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT + else + echo "tag_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + echo "release_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + fi + + # Creates a `nightly-SHA` tag for this specific nightly + # This tag is used for this specific nightly version's release + # which allows users to roll back. It is also used to build + # the changelog. + - name: Create build-specific nightly tag + if: ${{ env.IS_NIGHTLY == 'true' }} + uses: actions/github-script@v7 + env: + TAG_NAME: ${{ steps.release_info.outputs.tag_name }} + with: + script: | + const createTag = require('./.github/scripts/create-tag.js') + await createTag({ github, context }, process.env.TAG_NAME) + + - name: Build changelog + id: build_changelog + uses: mikepenz/release-changelog-builder-action@v4 + with: + configuration: "./.github/changelog.json" + fromTag: ${{ env.IS_NIGHTLY == 'true' && 'nightly' || env.STABLE_VERSION }} + toTag: ${{ steps.release_info.outputs.tag_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release-docker: + name: Release Docker + needs: prepare + uses: ./.github/workflows/docker-publish.yml + with: + tag_name: ${{ needs.prepare.outputs.tag_name }} + + release: + permissions: + id-token: write + contents: write + attestations: write + name: ${{ matrix.target }} (${{ matrix.runner }}) + runs-on: ${{ matrix.runner }} + timeout-minutes: 240 + needs: prepare + strategy: + fail-fast: false + matrix: + include: + # `runner`: GHA runner label + # `target`: Rust build target triple + # `platform` and `arch`: Used in tarball names + # `svm`: target platform to use for the Solc binary: https://github.com/roynalnaruto/svm-rs/blob/84cbe0ac705becabdc13168bae28a45ad2299749/svm-builds/build.rs#L4-L24 + - runner: Linux-20.04 + target: x86_64-unknown-linux-gnu + svm_target_platform: linux-amd64 + platform: linux + arch: amd64 + - runner: Linux-20.04 + target: aarch64-unknown-linux-gnu + svm_target_platform: linux-aarch64 + platform: linux + arch: arm64 + # This is pinned to `macos-13-large` to support old SDK versions. + # If the runner is deprecated it should be pinned to the oldest available version of the runner. + - runner: macos-13-large + target: x86_64-apple-darwin + svm_target_platform: macosx-amd64 + platform: darwin + arch: amd64 + - runner: macos-latest-large + target: aarch64-apple-darwin + svm_target_platform: macosx-aarch64 + platform: darwin + arch: arm64 + - runner: Windows + target: x86_64-pc-windows-msvc + svm_target_platform: windows-amd64 + platform: win32 + arch: amd64 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + cache-on-failure: true + + - name: Apple M1 setup + if: matrix.target == 'aarch64-apple-darwin' + run: | + echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV + echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV + + - name: Linux ARM setup + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get update -y + sudo apt-get install -y gcc-aarch64-linux-gnu + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + + - name: Build binaries + env: + TAG_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + SVM_TARGET_PLATFORM: ${{ matrix.svm_target_platform }} + PLATFORM_NAME: ${{ matrix.platform }} + TARGET: ${{ matrix.target }} + OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + shell: bash + run: | + set -eo pipefail + flags=(--target $TARGET --profile $PROFILE --bins + --no-default-features --features aws-kms,cli,asm-keccak) + + # `jemalloc` is not fully supported on MSVC or aarch64 Linux. + if [[ "$TARGET" != *msvc* && "$TARGET" != "aarch64-unknown-linux-gnu" ]]; then + flags+=(--features jemalloc) + fi + + [[ "$TARGET" == *windows* ]] && ext=".exe" + + cargo build "${flags[@]}" + + bins=(anvil cast chisel forge) + for name in "${bins[@]}"; do + bin=$OUT_DIR/$name$ext + echo "" + file "$bin" || true + du -h "$bin" || true + ldd "$bin" || true + $bin --version || true + echo "${name}_bin_path=${bin}" >> $GITHUB_ENV + done + + - name: Archive binaries + id: artifacts + env: + PLATFORM_NAME: ${{ matrix.platform }} + OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + ARCH: ${{ matrix.arch }} + shell: bash + run: | + if [ "$PLATFORM_NAME" == "linux" ]; then + tar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C $OUT_DIR forge cast anvil chisel + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT + elif [ "$PLATFORM_NAME" == "darwin" ]; then + # We need to use gtar here otherwise the archive is corrupt. + # See: https://github.com/actions/virtual-environments/issues/2619 + gtar -czvf "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" -C $OUT_DIR forge cast anvil chisel + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.tar.gz" >> $GITHUB_OUTPUT + else + cd $OUT_DIR + 7z a -tzip "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" forge.exe cast.exe anvil.exe chisel.exe + mv "foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" ../../../ + echo "file_name=foundry_${VERSION_NAME}_${PLATFORM_NAME}_${ARCH}.zip" >> $GITHUB_OUTPUT + fi + + - name: Build man page + id: man + if: matrix.target == 'x86_64-unknown-linux-gnu' + env: + OUT_DIR: target/${{ matrix.target }}/${{ env.PROFILE }} + VERSION_NAME: ${{ (env.IS_NIGHTLY == 'true' && 'nightly') || needs.prepare.outputs.tag_name }} + shell: bash + run: | + sudo apt-get -y install help2man + help2man -N $OUT_DIR/forge > forge.1 + help2man -N $OUT_DIR/cast > cast.1 + help2man -N $OUT_DIR/anvil > anvil.1 + help2man -N $OUT_DIR/chisel > chisel.1 + gzip forge.1 + gzip cast.1 + gzip anvil.1 + gzip chisel.1 + tar -czvf "foundry_man_${VERSION_NAME}.tar.gz" forge.1.gz cast.1.gz anvil.1.gz chisel.1.gz + echo "foundry_man=foundry_man_${VERSION_NAME}.tar.gz" >> $GITHUB_OUTPUT + + # Creates the release for this specific version + - name: Create release + uses: softprops/action-gh-release@v2 + with: + name: ${{ needs.prepare.outputs.release_name }} + tag_name: ${{ needs.prepare.outputs.tag_name }} + prerelease: ${{ env.IS_NIGHTLY == 'true' }} + body: ${{ needs.prepare.outputs.changelog }} + files: | + ${{ steps.artifacts.outputs.file_name }} + ${{ steps.man.outputs.foundry_man }} + + - name: Binaries attestation + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + ${{ env.anvil_bin_path }} + ${{ env.cast_bin_path }} + ${{ env.chisel_bin_path }} + ${{ env.forge_bin_path }} + + # If this is a nightly release, it also updates the release + # tagged `nightly` for compatibility with `foundryup` + - name: Update nightly release + if: ${{ env.IS_NIGHTLY == 'true' }} + uses: softprops/action-gh-release@v2 + with: + name: "Nightly" + tag_name: "nightly" + prerelease: true + body: ${{ needs.prepare.outputs.changelog }} + files: | + ${{ steps.artifacts.outputs.file_name }} + ${{ steps.man.outputs.foundry_man }} + + cleanup: + name: Release cleanup + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: release + if: always() + steps: + - uses: actions/checkout@v4 + + # Moves the `nightly` tag to `HEAD` + - name: Move nightly tag + if: ${{ env.IS_NIGHTLY == 'true' }} + uses: actions/github-script@v7 + with: + script: | + const moveTag = require('./.github/scripts/move-tag.js') + await moveTag({ github, context }, 'nightly') + + - name: Delete old nightlies + uses: actions/github-script@v7 + with: + script: | + const prunePrereleases = require('./.github/scripts/prune-prereleases.js') + await prunePrereleases({github, context}) + + # If any of the jobs fail, this will create a high-priority issue to signal so. + issue: + name: Open an issue + runs-on: ubuntu-latest + needs: [ prepare, release-docker, release, cleanup ] + if: failure() + steps: + - uses: actions/checkout@v4 + - uses: JasonEtco/create-an-issue@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WORKFLOW_URL: | + ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + update_existing: true + filename: .github/RELEASE_FAILURE_ISSUE_TEMPLATE.md diff --git a/.github/workflows/test-isolate.yml b/.github/workflows/test-isolate.yml new file mode 100644 index 0000000000000..119a6bd55adec --- /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 bb89338c160f2..d5e21a495e6b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,294 +1,135 @@ +name: test + on: - push: - branches: - - master - pull_request: + push: + branches: + - master + pull_request: -name: test +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true env: - CARGO_TERM_COLOR: always + CARGO_TERM_COLOR: always + RUST_BACKTRACE: full jobs: - build-tests: - name: build tests / ${{ matrix.archive.name }} - runs-on: ubuntu-latest - strategy: - matrix: - archive: - - name: unit-tests - file: nextest-unit.tar.zst - flags: --workspace --all-features --lib --bins - - name: integration-tests - file: nextest-integration.tar.zst - flags: --workspace - - name: external-integration-tests - file: nextest-external-integration.tar.zst - flags: -p forge --features external-integration-tests - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - uses: taiki-e/install-action@nextest - - name: Build archive (unit tests) - run: | - cargo nextest archive \ - --locked \ - --archive-file ${{ matrix.archive.file }} \ - ${{ matrix.archive.flags }} - - name: Upload archive - uses: actions/upload-artifact@v3 - with: - name: ${{ matrix.archive.name }} - path: ${{ matrix.archive.file }} - - install-svm-solc: - name: install svm and solidity / ${{ matrix.job.name }} - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Install svm - run: cargo install svm-rs - - name: Install Solidity 0.8.19 - run: svm install 0.8.19 - - name: Install Solidity 0.8.20 - run: svm install 0.8.20 - - name: Use Solidity 0.8.19 - run: svm use 0.8.19 - - unit: - name: unit tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests - timeout-minutes: 60 - strategy: - matrix: - job: - - name: non-forking - filter: "!test(~fork) & !test(~live)" - - name: forking - filter: "test(~fork) & !test(~live)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: unit-tests - - name: cargo nextest - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file nextest-unit.tar.zst -E '${{ matrix.job.filter }}' - - issue-repros-tests: - name: issue reproduction tests / ${{ matrix.job.name }} / ${{ matrix.job.partition }} - runs-on: ubuntu-latest - needs: build-tests - strategy: - matrix: - job: - - name: issue-repros - filter: "test(~issue)" - partition: [1, 2] + nextest: + uses: ./.github/workflows/nextest.yml + with: + profile: default + secrets: inherit + + docs: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: Build documentation + run: cargo doc --workspace --all-features --no-deps --document-private-items env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: integration-tests - - - name: Forge RPC cache - uses: actions/cache@v3 - if: matrix.job.name != 'issue-repros' - with: - path: "$HOME/.foundry/cache" - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: cargo nextest - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --partition count:${{ matrix.partition }}/2 --retries 3 --archive-file nextest-integration.tar.zst -E '${{ matrix.job.filter }}' - - forge-std-tests: - name: forge std tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests - strategy: - matrix: - job: - - name: forge-std-test - filter: "test(~forge_std)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: integration-tests - - - name: Forge RPC cache - uses: actions/cache@v3 - if: matrix.job.name != 'forge-std-test' - with: - path: "$HOME/.foundry/cache" - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: cargo nextest - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file nextest-integration.tar.zst -E '${{ matrix.job.filter }}' - - integration: - name: integration tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests - strategy: - matrix: - job: - - name: non-forking - filter: "!test(~fork) & !test(~live) & !test(~issue) & !test(~forge_std)" - - name: forking - filter: "test(~fork) & !test(~live)" - partition: [1, 2] + RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options + - name: Deploy documentation + uses: peaceiris/actions-gh-pages@v3 + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/doc + force_orphan: true + + doctest: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo test --workspace --doc + + codespell: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: codespell-project/actions-codespell@v2 + with: + skip: "*.json" + + clippy: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@clippy + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo clippy --workspace --all-targets --all-features env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: integration-tests - - - name: Forge RPC cache - uses: actions/cache@v3 - if: matrix.job.name != 'non-forking' - with: - path: "$HOME/.foundry/cache" - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: cargo nextest - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --partition count:${{ matrix.partition }}/2 --retries 3 --archive-file nextest-integration.tar.zst -E '${{ matrix.job.filter }}' - - external-integration: - name: external integration tests / ${{ matrix.job.name }} - runs-on: ubuntu-latest - needs: build-tests - strategy: - matrix: - job: - - name: non-forking - filter: "!test(~fork_integration) & !test(~live)" - - name: forking - filter: "test(~fork_integration) & !test(~live)" - env: - ETH_RPC_URL: https://eth-mainnet.alchemyapi.io/v2/C3JEvfW6VgtqZQa-Qp1E-2srEiIc02sD - steps: - - uses: actions/checkout@v3 - - uses: taiki-e/install-action@nextest - - uses: dtolnay/rust-toolchain@stable - - name: Download archives - uses: actions/download-artifact@v3 - with: - name: external-integration-tests - - - name: Forge RPC cache - uses: actions/cache@v3 - if: matrix.job.name != 'non-forking' - with: - path: "$HOME/.foundry/cache" - key: rpc-cache-${{ hashFiles('crates/forge/tests/rpc-cache-keyfile') }} - - - name: Setup git config - run: | - git config --global user.name "GitHub Actions Bot" - git config --global user.email "<>" - - - name: Force use of HTTPS for submodules - run: git config --global url."https://github.com/".insteadOf "git@github.com:" - - - name: cargo nextest - run: | - # see https://github.com/foundry-rs/foundry/pull/3959 - export LD_LIBRARY_PATH="$(rustc --print sysroot)/lib" - cargo nextest run --retries 3 --archive-file nextest-external-integration.tar.zst -E '${{ matrix.job.filter }}' - - doctests: - name: doc tests - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: cargo test - run: cargo test --locked --workspace --all-features --doc - - cross-platform: - name: Cross-platform tests - if: github.event_name != 'pull_request' - needs: [unit, integration, doctests] - uses: ./.github/workflows/cross-platform.yml - - clippy: - name: clippy - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@clippy - - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --workspace --all-targets --all-features - env: - RUSTFLAGS: -Dwarnings - - fmt: - name: fmt - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - run: cargo fmt --all --check - - forge-fmt: - name: forge fmt - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - name: forge fmt - run: cargo run --bin forge -- fmt --check testdata/ + RUSTFLAGS: -Dwarnings + + rustfmt: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + with: + toolchain: nightly-2024-02-03 + components: rustfmt + - run: cargo fmt --all --check + + forge-fmt: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: forge fmt + shell: bash + run: ./.github/scripts/format.sh --check + + crate-checks: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: taiki-e/install-action@cargo-hack + - uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - run: cargo hack check + + deny: + uses: ithacaxyz/ci/.github/workflows/deny.yml@main + + ci-success: + runs-on: ubuntu-latest + if: always() + needs: + - nextest + - docs + - doctest + - codespell + - clippy + - rustfmt + - forge-fmt + - crate-checks + - deny + timeout-minutes: 30 + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index 19f666e451bf9..9297bbbc7d4f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_STORE -/target +/target* out/ +snapshots/ out.json .idea .vscode diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4365c0b9badac..98b36e8539a05 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,8 +4,6 @@ Thanks for your interest in improving Foundry! There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust or are the most weathered expert, we can use your help. -**No contribution is too small and all contributions are valued.** - This document will help you get started. **Do not let the document intimidate you**. It should be considered as a guide to help you navigate the process. @@ -24,7 +22,7 @@ There are fundamentally four ways an individual can contribute: 1. **By opening an issue:** For example, if you believe that you have uncovered a bug in Foundry, creating a new issue in the issue tracker is the way to report it. 2. **By adding context:** Providing additional context to existing issues, - such as screenshots, code snippets and helps resolve issues. + such as screenshots and code snippets, which help resolve issues. 3. **By resolving issues:** Typically this is done in the form of either demonstrating that the issue reported is not a problem after all, or more often, by opening a pull request that fixes the underlying problem, in a concrete and @@ -33,6 +31,11 @@ There are fundamentally four ways an individual can contribute: **Anybody can participate in any stage of contribution**. We urge you to participate in the discussion around bugs and participate in reviewing PRs. +### Contributions Related to Spelling and Grammar + +At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or +elsewhere. + ### Asking for help If you have reviewed existing documentation and still have questions, or you are having problems, you can get help in the following ways: @@ -47,8 +50,7 @@ The [Foundry Book][foundry-book] is our current best-effort attempt at keeping u When filing a new bug report in the issue tracker, you will be presented with a basic form to fill out. -If you believe that you have uncovered a bug, please fill out the form to the best of your ability. Do not worry if you cannot answer every detail, -just fill in what you can. Contributors will ask follow-up questions if something is unclear. +If you believe that you have uncovered a bug, please fill out the form to the best of your ability. Do not worry if you cannot answer every detail; just fill in what you can. Contributors will ask follow-up questions if something is unclear. The most important pieces of information we need in a bug report are: @@ -74,7 +76,7 @@ If you have examples of other tools that have the feature you are requesting, pl Pull requests are the way concrete changes are made to the code, documentation, and dependencies of Foundry. -Even tiny pull requests, like fixing wording, are greatly appreciated. Before making a large change, it is usually +Even minor pull requests, such as those fixing wording, are greatly appreciated. Before making a large change, it is usually a good idea to first open an issue describing the change to solicit feedback and guidance. This will increase the likelihood of the PR getting merged. @@ -84,7 +86,7 @@ Please also make sure that the following commands pass if you have changed the c cargo check --all cargo test --all --all-features cargo +nightly fmt -- --check -cargo +nightly clippy --all --all-features -- -D warnings +cargo +nightly clippy --all --all-targets --all-features -- -D warnings ``` If you are working in VSCode, we recommend you install the [rust-analyzer](https://rust-analyzer.github.io/) extension, and use the following VSCode user settings: @@ -101,6 +103,8 @@ If you are working on a larger feature, we encourage you to open up a draft pull If you would like to test the binaries built from your change, see [foundryup](https://github.com/foundry-rs/foundry/tree/master/foundryup). +If you would like to use a debugger with breakpoints to debug a patch you might be working on, keep in mind we currently strip debug info for faster builds, which is *not* the default. Therefore, to use a debugger, you need to enable it on the workspace [`Cargo.toml`'s `dev` profile](https://github.com/foundry-rs/foundry/tree/master/Cargo.toml#L15-L18). + #### Adding tests If the change being proposed alters code, it is either adding new functionality to Foundry, or fixing existing, broken functionality. diff --git a/Cargo.lock b/Cargo.lock index 1ac2a96a23d8d..d4729c9262e22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -27,11 +27,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -40,2604 +46,4156 @@ dependencies = [ [[package]] name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.10", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.3" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", + "getrandom 0.2.15", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "1.0.4" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] -name = "alloy-rlp" -version = "0.3.2" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f938f00332d63a5b0ac687bd6f46d03884638948921d9f8b50c59563d421ae25" -dependencies = [ - "arrayvec", - "bytes", - "smol_str", -] +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] -name = "ammonia" -version = "3.3.0" +name = "alloy-chains" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170" +checksum = "3a754dbb534198644cb8355b8c23f4aaecf03670fb9409242be1fa1e25897ee9" dependencies = [ - "html5ever", - "maplit", - "once_cell", - "tendril", - "url", + "alloy-primitives", + "num_enum", + "serde", + "strum", ] [[package]] -name = "android-tzdata" -version = "0.1.1" +name = "alloy-consensus" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" +checksum = "69e32ef5c74bbeb1733c37f4ac7f866f8c8af208b7b4265e21af609dcac5bd5e" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "auto_impl", + "c-kzg", + "derive_more", + "k256", + "serde", +] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "alloy-consensus-any" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "0fa13b7b1e1e3fedc42f0728103bfa3b4d566d3d42b606db449504d88dbdbdcf" dependencies = [ - "libc", + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", ] [[package]] -name = "anes" -version = "0.1.6" +name = "alloy-contract" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +checksum = "ee6180fb232becdea70fad57c63b6967f01f74ab9595671b870f504116dd29de" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures", + "futures-util", + "thiserror 2.0.11", +] [[package]] -name = "anstream" -version = "0.3.2" +name = "alloy-dyn-abi" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "555896f0b8578adb522b1453b6e6cc6704c3027bd0af20058befdde992cee8e9" dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-type-parser", + "alloy-sol-types", + "arbitrary", + "const-hex", + "derive_arbitrary", + "derive_more", + "itoa", + "proptest", + "serde", + "serde_json", + "winnow 0.7.2", ] [[package]] -name = "anstyle" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" - -[[package]] -name = "anstyle-parse" -version = "0.2.1" +name = "alloy-eip2124" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" +checksum = "675264c957689f0fd75f5993a73123c2cc3b5c235a38f5b9037fe6c826bfb2c0" dependencies = [ - "utf8parse", + "alloy-primitives", + "alloy-rlp", + "crc", + "thiserror 2.0.11", ] [[package]] -name = "anstyle-query" -version = "1.0.0" +name = "alloy-eip2930" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ - "windows-sys 0.48.0", + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "rand", + "serde", ] [[package]] -name = "anstyle-wincon" -version = "1.0.2" +name = "alloy-eip7702" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "cabf647eb4650c91a9d38cb6f972bb320009e7e9d61765fb688a86f1563b33e8" dependencies = [ - "anstyle", - "windows-sys 0.48.0", + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "derive_more", + "k256", + "rand", + "serde", ] [[package]] -name = "anvil" -version = "0.2.0" +name = "alloy-eips" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5591581ca2ab0b3e7226a4047f9a1bfcf431da1d0cce3752fda609fea3c27e37" dependencies = [ - "anvil-core", - "anvil-rpc", - "anvil-server", - "async-trait", + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", "auto_impl", - "axum", - "bytes", - "chrono", - "clap", - "clap_complete", - "clap_complete_fig", - "crc 3.0.1", - "ctrlc", - "ethereum-forkid", - "ethers", - "ethers-solc", - "fdlimit", - "flate2", - "foundry-common", - "foundry-config", - "foundry-evm", - "foundry-utils", - "futures", - "hash-db", - "hyper", - "itertools 0.11.0", - "memory-db", - "parking_lot", - "pretty_assertions", + "c-kzg", + "derive_more", + "once_cell", "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", - "tower", - "tower-http 0.4.3", - "tracing", - "tracing-subscriber", - "trie-db", - "vergen", - "yansi 0.5.1", + "sha2", ] [[package]] -name = "anvil-core" -version = "0.2.0" +name = "alloy-genesis" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cded3a2d4bd7173f696458c5d4c98c18a628dfcc9f194385e80a486e412e2e0" dependencies = [ - "bytes", - "ethers-core", - "foundry-evm", - "hash-db", - "hash256-std-hasher", - "keccak-hasher", - "open-fastrlp", - "reference-trie", - "revm", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", "serde", - "serde_json", - "triehash", ] [[package]] -name = "anvil-rpc" -version = "0.2.0" +name = "alloy-json-abi" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4012581681b186ba0882007ed873987cc37f86b1b488fe6b91d5efd0b585dc41" dependencies = [ - "rand 0.8.5", + "alloy-primitives", + "alloy-sol-type-parser", "serde", "serde_json", ] [[package]] -name = "anvil-server" -version = "0.2.0" +name = "alloy-json-rpc" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "762414662d793d7aaa36ee3af6928b6be23227df1681ce9c039f6f11daadef64" dependencies = [ - "anvil-rpc", - "async-trait", - "axum", - "bytes", - "clap", - "futures", - "hyper", - "parity-tokio-ipc", - "parking_lot", - "pin-project", + "alloy-primitives", + "alloy-sol-types", "serde", "serde_json", - "thiserror", - "tokio-util", - "tower-http 0.4.3", + "thiserror 2.0.11", "tracing", ] [[package]] -name = "anyhow" -version = "1.0.72" +name = "alloy-network" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "8be03f2ebc00cf88bd06d3c6caf387dceaa9c7e6b268216779fa68a9bf8ab4e6" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.11", +] [[package]] -name = "ariadne" -version = "0.2.0" +name = "alloy-network-primitives" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702" +checksum = "3a00ce618ae2f78369918be0c20f620336381502c83b6ed62c2f7b2db27698b0" dependencies = [ - "unicode-width", - "yansi 0.5.1", + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", ] [[package]] -name = "ark-ff" -version = "0.3.0" +name = "alloy-node-bindings" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +checksum = "81a3906afb50446392eb798dae4b918ba4ffcca47542efda7215776ddc8b5037" dependencies = [ - "ark-ff-asm 0.3.0", - "ark-ff-macros 0.3.0", - "ark-serialize 0.3.0", - "ark-std 0.3.0", - "derivative", - "num-bigint", - "num-traits", - "paste", - "rustc_version 0.3.3", - "zeroize", + "alloy-genesis", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-signer-local", + "k256", + "rand", + "serde_json", + "tempfile", + "thiserror 2.0.11", + "tracing", + "url", ] [[package]] -name = "ark-ff" -version = "0.4.2" +name = "alloy-primitives" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +checksum = "478bedf4d24e71ea48428d1bc278553bd7c6ae07c30ca063beb0b09fe58a9e74" dependencies = [ - "ark-ff-asm 0.4.2", - "ark-ff-macros 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", - "digest 0.10.7", - "itertools 0.10.5", - "num-bigint", - "num-traits", + "alloy-rlp", + "arbitrary", + "bytes", + "cfg-if", + "const-hex", + "derive_arbitrary", + "derive_more", + "foldhash", + "getrandom 0.2.15", + "hashbrown 0.15.2", + "indexmap 2.7.1", + "itoa", + "k256", + "keccak-asm", "paste", - "rustc_version 0.4.0", - "zeroize", + "proptest", + "proptest-derive", + "rand", + "ruint", + "rustc-hash", + "serde", + "sha3", + "tiny-keccak", ] [[package]] -name = "ark-ff-asm" -version = "0.3.0" +name = "alloy-provider" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" -dependencies = [ - "quote", - "syn 1.0.109", +checksum = "cbe0a2acff0c4bd1669c71251ce10fc455cbffa1b4d0a817d5ea4ba7e5bb3db7" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types-debug", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "futures", + "futures-utils-wasm", + "lru 0.13.0", + "parking_lot", + "pin-project 1.1.9", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.11", + "tokio", + "tracing", + "url", + "wasmtimer", ] [[package]] -name = "ark-ff-asm" -version = "0.4.2" +name = "alloy-pubsub" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +checksum = "de3a68996f193f542f9e29c88dfa8ed1369d6ee04fa764c1bf23dc11b2f9e4a2" dependencies = [ - "quote", - "syn 1.0.109", + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "bimap", + "futures", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.2", + "tracing", ] [[package]] -name = "ark-ff-macros" -version = "0.3.0" +name = "alloy-rlp" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" dependencies = [ - "num-bigint", - "num-traits", - "quote", - "syn 1.0.109", + "alloy-rlp-derive", + "arrayvec", + "bytes", ] [[package]] -name = "ark-ff-macros" -version = "0.4.2" +name = "alloy-rlp-derive" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ - "num-bigint", - "num-traits", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] -name = "ark-serialize" -version = "0.3.0" +name = "alloy-rpc-client" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +checksum = "b37cc3c7883dc41be1b01460127ad7930466d0a4bb6ba15a02ee34d2745e2d7c" dependencies = [ - "ark-std 0.3.0", - "digest 0.9.0", + "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "futures", + "pin-project 1.1.9", + "reqwest", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower 0.5.2", + "tracing", + "url", + "wasmtimer", ] [[package]] -name = "ark-serialize" -version = "0.4.2" +name = "alloy-rpc-types" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +checksum = "6f18e68a3882f372e045ddc89eb455469347767d17878ca492cfbac81e71a111" dependencies = [ - "ark-std 0.4.0", - "digest 0.10.7", - "num-bigint", + "alloy-primitives", + "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 = "ark-std" -version = "0.3.0" +name = "alloy-rpc-types-anvil" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +checksum = "10d06300df4a87d960add35909240fc72da355dd2ac926fa6999f9efafbdc5a7" dependencies = [ - "num-traits", - "rand 0.8.5", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", ] [[package]] -name = "ark-std" -version = "0.4.0" +name = "alloy-rpc-types-any" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +checksum = "318ae46dd12456df42527c3b94c1ae9001e1ceb707f7afe2c7807ac4e49ebad9" dependencies = [ - "num-traits", - "rand 0.8.5", + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", ] [[package]] -name = "array-init" -version = "0.0.4" +name = "alloy-rpc-types-debug" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +checksum = "2834b7012054cb2f90ee9893b7cc97702edca340ec1ef386c30c42e55e6cd691" dependencies = [ - "nodrop", + "alloy-primitives", + "serde", ] [[package]] -name = "arrayvec" -version = "0.7.4" +name = "alloy-rpc-types-engine" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "e83dde9fcf1ccb9b815cc0c89bba26bbbbaae5150a53ae624ed0fc63cb3676c1" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "jsonwebtoken", + "rand", + "serde", + "strum", +] [[package]] -name = "ascii-canvas" -version = "3.0.0" +name = "alloy-rpc-types-eth" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "8b4dbee4d82f8a22dde18c28257bed759afeae7ba73da4a1479a039fd1445d04" dependencies = [ - "term", + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "thiserror 2.0.11", ] [[package]] -name = "async-priority-channel" -version = "0.1.0" +name = "alloy-rpc-types-trace" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21678992e1b21bebfe2bc53ab5f5f68c106eddab31b24e0bb06e9b715a86640" +checksum = "7bd951155515fa452a2ca4b5434d4b3ab742bcd3d1d1b9a91704bcef5b8d2604" dependencies = [ - "event-listener", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", + "serde_json", + "thiserror 2.0.11", ] [[package]] -name = "async-recursion" -version = "1.0.4" +name = "alloy-rpc-types-txpool" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +checksum = "21d8dd5bd94993eda3d56a8c4c0d693548183a35462523ffc4385c0b020d3b0c" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", ] [[package]] -name = "async-trait" -version = "0.1.73" +name = "alloy-serde" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "8732058f5ca28c1d53d241e8504620b997ef670315d7c8afab856b3e3b80d945" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", + "alloy-primitives", + "serde", + "serde_json", ] [[package]] -name = "async_io_stream" -version = "0.3.3" +name = "alloy-signer" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" +checksum = "f96b3526fdd779a4bd0f37319cfb4172db52a7ac24cdbb8804b72091c18e1701" dependencies = [ - "futures", - "pharos", - "rustc_version 0.4.0", + "alloy-dyn-abi", + "alloy-primitives", + "alloy-sol-types", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror 2.0.11", ] [[package]] -name = "atomic" -version = "0.5.3" +name = "alloy-signer-aws" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" +checksum = "f6f7fae8dec636317c89e08498675c9421a214f1791a59dcaea2094c1a2dbf07" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-sdk-kms", + "k256", + "spki", + "thiserror 2.0.11", + "tracing", +] [[package]] -name = "atomic-take" -version = "1.1.0" +name = "alloy-signer-gcp" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" +checksum = "d7fcc881fb827d16c1379d375e7f5f26159039fb6956b9f1408989be6bf91444" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "gcloud-sdk", + "k256", + "spki", + "thiserror 2.0.11", + "tracing", +] [[package]] -name = "auto_impl" -version = "1.1.0" +name = "alloy-signer-ledger" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "327a0cfa3d68a60862aeaa42fbdfb5cc7d66275a79e01a3f9cfe38d293b6215a" dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "coins-ledger", + "futures-util", + "semver 1.0.25", + "thiserror 2.0.11", + "tracing", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "alloy-signer-local" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "fe8f78cd6b7501c7e813a1eb4a087b72d23af51f5bb66d4e948dc840bdd207d8" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "coins-bip32", + "coins-bip39", + "eth-keystore", + "k256", + "rand", + "thiserror 2.0.11", +] [[package]] -name = "axum" -version = "0.5.17" +name = "alloy-signer-trezor" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +checksum = "0bc3cf38010eeadfe37b34b58d0a53e36f3289dca68d16cbfaa62018e610b2ad" dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", "async-trait", - "axum-core", - "base64 0.13.1", - "bitflags 1.3.2", - "bytes", - "futures-util", - "http", - "http-body", - "hyper", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "sha-1", - "sync_wrapper", - "tokio", - "tokio-tungstenite 0.17.2", - "tower", - "tower-http 0.3.5", - "tower-layer", - "tower-service", + "semver 1.0.25", + "thiserror 2.0.11", + "tracing", + "trezor-client", ] [[package]] -name = "axum-core" -version = "0.2.9" +name = "alloy-sol-macro" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +checksum = "a2708e27f58d747423ae21d31b7a6625159bd8d867470ddd0256f396a68efa11" dependencies = [ - "async-trait", - "bytes", - "futures-util", - "http", - "http-body", - "mime", - "tower-layer", - "tower-service", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "backtrace" -version = "0.3.68" +name = "alloy-sol-macro-expander" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "c6b7984d7e085dec382d2c5ef022b533fcdb1fe6129200af30ebf5afddb6a361" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", + "alloy-json-abi", + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.7.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.98", + "syn-solidity", + "tiny-keccak", ] [[package]] -name = "base16ct" -version = "0.2.0" +name = "alloy-sol-macro-input" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "33d6a9fc4ed1a3c70bdb2357bec3924551c1a59f24e5a04a74472c755b37f87d" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.98", + "syn-solidity", +] [[package]] -name = "base64" -version = "0.13.1" +name = "alloy-sol-type-parser" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "1b1b3e9a48a6dd7bb052a111c8d93b5afc7956ed5e2cb4177793dc63bb1d2a36" +dependencies = [ + "serde", + "winnow 0.7.2", +] [[package]] -name = "base64" -version = "0.21.2" +name = "alloy-sol-types" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "6044800da35c38118fd4b98e18306bd3b91af5dedeb54c1b768cf1b4fb68f549" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] [[package]] -name = "base64ct" -version = "1.6.0" +name = "alloy-transport" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "5a8d762eadce3e9b65eac09879430c6f4fce3736cac3cac123f9b1bf435ddd13" +dependencies = [ + "alloy-json-rpc", + "base64 0.22.1", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.11", + "tokio", + "tower 0.5.2", + "tracing", + "url", + "wasmtimer", +] [[package]] -name = "bech32" -version = "0.7.3" +name = "alloy-transport-http" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dabbe35f96fb9507f7330793dc490461b2962659ac5d427181e451a623751d1" +checksum = "20819c4cb978fb39ce6ac31991ba90f386d595f922f42ef888b4a18be190713e" +dependencies = [ + "alloy-json-rpc", + "alloy-transport", + "reqwest", + "serde_json", + "tower 0.5.2", + "tracing", + "url", +] [[package]] -name = "bincode" -version = "1.3.3" +name = "alloy-transport-ipc" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "5e88304aa8b796204e5e2500dfe235933ed692745e3effd94c3733643db6d218" dependencies = [ + "alloy-json-rpc", + "alloy-pubsub", + "alloy-transport", + "bytes", + "futures", + "interprocess", + "pin-project 1.1.9", "serde", + "serde_json", + "tempfile", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "bit-set" -version = "0.5.3" +name = "alloy-transport-ws" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "b9653ea9aa06d0e02fcbe2f04f1c47f35a85c378ccefa98e54ae85210bc8bbfa" dependencies = [ - "bit-vec", + "alloy-pubsub", + "alloy-transport", + "futures", + "http 1.2.0", + "rustls 0.23.23", + "serde_json", + "tokio", + "tokio-tungstenite 0.26.1", + "tracing", + "ws_stream_wasm", ] [[package]] -name = "bit-vec" -version = "0.6.3" +name = "alloy-trie" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "d95a94854e420f07e962f7807485856cde359ab99ab6413883e15235ad996e8b" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] [[package]] -name = "bitflags" -version = "1.3.2" +name = "ammonia" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "1ab99eae5ee58501ab236beb6f20f6ca39be615267b014899c89b2f0bc18a459" +dependencies = [ + "html5ever", + "maplit", + "once_cell", + "tendril", + "url", +] [[package]] -name = "bitflags" -version = "2.4.0" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] -name = "bitvec" -version = "0.17.4" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "either", - "radium 0.3.0", + "libc", ] [[package]] -name = "bitvec" -version = "1.0.1" +name = "annotate-snippets" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium 0.7.0", - "serde", - "tap", - "wyz", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" -dependencies = [ - "sha2 0.9.9", -] - -[[package]] -name = "bstr" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ + "anstyle", "memchr", - "regex-automata 0.3.6", - "serde", + "unicode-width 0.2.0", ] [[package]] -name = "btoi" -version = "0.4.3" +name = "anstream" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ - "num-traits", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] [[package]] -name = "build_const" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" - -[[package]] -name = "bumpalo" -version = "3.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.4.0" +name = "anstyle" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" -dependencies = [ - "serde", -] +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] -name = "bzip2" -version = "0.4.4" +name = "anstyle-lossy" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "934ff8719effd2023a48cf63e69536c1c3ced9d3895068f6f5cc9a4ff845e59b" dependencies = [ - "bzip2-sys", - "libc", + "anstyle", ] [[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" +name = "anstyle-parse" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ - "cc", - "libc", - "pkg-config", + "utf8parse", ] [[package]] -name = "camino" -version = "1.1.6" +name = "anstyle-query" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "serde", + "windows-sys 0.59.0", ] [[package]] -name = "cargo-platform" -version = "0.1.3" +name = "anstyle-svg" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +checksum = "d3607949e9f6de49ea4bafe12f5e4fd73613ebf24795e48587302a8cc0e4bb35" dependencies = [ - "serde", + "anstream", + "anstyle", + "anstyle-lossy", + "html-escape", + "unicode-width 0.2.0", ] [[package]] -name = "cargo_metadata" -version = "0.17.0" +name = "anstyle-wincon" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7daec1a2a2129eeba1644b220b4647ec537b0b5d4bfd6876fcc5a540056b592" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.18", - "serde", - "serde_json", - "thiserror", + "anstyle", + "once_cell", + "windows-sys 0.59.0", ] [[package]] -name = "cassowary" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" - -[[package]] -name = "cast" -version = "0.2.0" +name = "anvil" +version = "1.0.0" dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-contract", + "alloy-dyn-abi", + "alloy-eips", + "alloy-genesis", + "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rlp", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", + "anvil-core", + "anvil-rpc", + "anvil-server", "async-trait", + "axum", "bytes", "chrono", "clap", "clap_complete", "clap_complete_fig", - "comfy-table", - "const-hex", - "criterion", - "dunce", - "eth-keystore", - "ethers", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "evm-disassembler", + "ctrlc", "eyre", + "fdlimit", + "flate2", "foundry-cli", "foundry-common", "foundry-config", "foundry-evm", "foundry-test-utils", - "foundry-utils", "futures", - "indicatif", - "itertools 0.11.0", - "rayon", - "regex", - "rpassword", - "rusoto_core", - "rusoto_kms", - "semver 1.0.18", + "hyper 1.6.0", + "itertools 0.14.0", + "k256", + "op-alloy-consensus", + "op-alloy-rpc-types", + "parking_lot", + "rand", + "revm", "serde", "serde_json", + "serde_repr", + "similar-asserts", "tempfile", + "thiserror 2.0.11", + "tikv-jemallocator", "tokio", + "tower 0.5.2", "tracing", - "vergen", - "yansi 0.5.1", -] - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" -dependencies = [ - "jobserver", - "libc", + "tracing-subscriber", + "yansi", ] [[package]] -name = "cfg-if" +name = "anvil-core" version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chisel" -version = "0.2.0" dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types", + "alloy-serde", + "alloy-trie", "bytes", - "clap", - "criterion", - "dirs 5.0.1", - "ethers", - "ethers-solc", - "eyre", - "forge-fmt", - "foundry-cli", "foundry-common", - "foundry-config", "foundry-evm", - "once_cell", - "regex", - "reqwest", + "op-alloy-consensus", + "rand", "revm", - "rustyline", - "semver 1.0.18", "serde", "serde_json", - "serial_test", - "solang-parser", - "strum 0.25.0", - "time", - "tokio", - "vergen", - "yansi 0.5.1", + "thiserror 2.0.11", ] [[package]] -name = "chrono" -version = "0.4.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +name = "anvil-rpc" +version = "1.0.0" dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", "serde", - "winapi", + "serde_json", ] [[package]] -name = "ciborium" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +name = "anvil-server" +version = "1.0.0" dependencies = [ - "ciborium-io", - "ciborium-ll", + "anvil-rpc", + "async-trait", + "axum", + "bytes", + "clap", + "futures", + "interprocess", + "parking_lot", + "pin-project 1.1.9", "serde", + "serde_json", + "thiserror 2.0.11", + "tokio-util", + "tower-http", + "tracing", ] [[package]] -name = "ciborium-io" -version = "0.2.1" +name = "anyhow" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] -name = "ciborium-ll" -version = "0.2.1" +name = "arbitrary" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ - "ciborium-io", - "half", + "derive_arbitrary", ] [[package]] -name = "cipher" -version = "0.4.4" +name = "ariadne" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "31beedec3ce83ae6da3a79592b3d8d7afd146a5b15bb9bb940279aced60faa89" dependencies = [ - "crypto-common", - "inout", + "unicode-width 0.1.14", + "yansi", ] [[package]] -name = "clap" -version = "4.3.21" +name = "ark-ff" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" dependencies = [ - "clap_builder", - "clap_derive", - "once_cell", + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", ] [[package]] -name = "clap_builder" -version = "4.3.21" +name = "ark-ff" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "once_cell", - "strsim", - "terminal_size", - "unicase", - "unicode-width", + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", ] [[package]] -name = "clap_complete" -version = "4.3.2" +name = "ark-ff-asm" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc443334c81a804575546c5a8a79b4913b50e28d69232903604cada1de817ce" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" dependencies = [ - "clap", + "quote", + "syn 1.0.109", ] [[package]] -name = "clap_complete_fig" -version = "4.3.1" +name = "ark-ff-asm" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fee1d30a51305a6c2ed3fc5709be3c8af626c9c958e04dd9ae94e27bcbce9f" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" dependencies = [ - "clap", - "clap_complete", + "quote", + "syn 1.0.109", ] [[package]] -name = "clap_derive" -version = "4.3.12" +name = "ark-ff-macros" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "heck", - "proc-macro2", + "num-bigint", + "num-traits", "quote", - "syn 2.0.28", + "syn 1.0.109", ] [[package]] -name = "clap_lex" -version = "0.5.0" +name = "ark-ff-macros" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "clearscreen" -version = "2.0.1" +name = "ark-serialize" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" dependencies = [ - "nix", - "terminfo", - "thiserror", - "which", - "winapi", + "ark-std 0.3.0", + "digest 0.9.0", ] [[package]] -name = "clipboard-win" -version = "4.5.0" +name = "ark-serialize" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "error-code", - "str-buf", - "winapi", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", ] [[package]] -name = "coins-bip32" -version = "0.8.3" +name = "ark-std" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30a84aab436fcb256a2ab3c80663d8aec686e6bae12827bb05fef3e1e439c9f" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ - "bincode", - "bs58", - "coins-core", - "digest 0.10.7", - "getrandom 0.2.10", - "hmac 0.12.1", - "k256", - "lazy_static", - "serde", - "sha2 0.10.7", - "thiserror", + "num-traits", + "rand", ] [[package]] -name = "coins-bip39" -version = "0.8.6" +name = "ark-std" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f4d04ee18e58356accd644896aeb2094ddeafb6a713e056cef0c0a8e468c15" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ - "bitvec 0.17.4", - "coins-bip32", - "getrandom 0.2.10", - "hmac 0.12.1", - "once_cell", - "pbkdf2 0.12.2", - "rand 0.8.5", - "sha2 0.10.7", - "thiserror", + "num-traits", + "rand", ] [[package]] -name = "coins-core" -version = "0.8.3" +name = "arrayvec" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b949a1c63fb7eb591eb7ba438746326aedf0ae843e51ec92ba6bec5bb382c4f" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ - "base64 0.21.2", - "bech32", - "bs58", - "digest 0.10.7", - "generic-array", - "hex", - "ripemd", "serde", - "serde_derive", - "sha2 0.10.7", - "sha3", - "thiserror", ] [[package]] -name = "coins-ledger" -version = "0.8.3" +name = "ascii-canvas" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863cc93703bfc6f02f4401b42663b767783179f4080d89a0c4876766c7c0fb78" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ - "async-trait", - "byteorder", - "cfg-if", - "futures", - "hex", - "hidapi-rusb", - "js-sys", - "lazy_static", - "libc", - "log", - "matches", - "nix", - "serde", - "tap", - "thiserror", - "tracing", - "wasm-bindgen", - "wasm-bindgen-futures", + "term", ] [[package]] -name = "color-eyre" -version = "0.6.2" +name = "async-compression" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", ] [[package]] -name = "color-spantrace" +name = "async-priority-channel" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +checksum = "acde96f444d31031f760c5c43dc786b97d3e1cb2ee49dd06898383fe9a999758" dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", + "event-listener", ] [[package]] -name = "colorchoice" -version = "1.0.0" +name = "async-recursion" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] [[package]] -name = "comfy-table" -version = "6.2.0" +name = "async-stream" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ - "crossterm 0.26.1", - "strum 0.24.1", - "strum_macros 0.24.3", - "unicode-width", + "async-stream-impl", + "futures-core", + "pin-project-lite", ] [[package]] -name = "command-group" -version = "2.1.0" +name = "async-stream-impl" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "async-trait", - "nix", - "tokio", - "winapi", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "console" -version = "0.15.7" +name = "async-trait" +version = "0.1.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "unicode-width", - "windows-sys 0.45.0", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "const-hex" -version = "1.6.1" +name = "async_io_stream" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" +checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ - "cfg-if", - "cpufeatures", - "hex", - "serde", + "futures", + "pharos", + "rustc_version 0.4.1", ] [[package]] -name = "const-oid" -version = "0.9.5" +name = "atomic" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "atomic-take" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" [[package]] -name = "convert_case" -version = "0.4.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "core-foundation" -version = "0.9.3" +name = "aurora-engine-modexp" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" dependencies = [ - "core-foundation-sys", - "libc", + "hex", + "num", ] [[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "cpufeatures" -version = "0.2.9" +name = "auto_impl" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ - "libc", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "crc" -version = "1.8.1" +name = "autocfg" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" -dependencies = [ - "build_const", -] +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "crc" -version = "3.0.1" +name = "aws-config" +version = "1.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "50236e4d60fe8458de90a71c0922c761e41755adf091b1b03de1cef537179915" dependencies = [ - "crc-catalog", + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", ] [[package]] -name = "crc-catalog" -version = "2.2.0" +name = "aws-credential-types" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "aws-runtime" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "76dd04d39cc12844c0994f2c9c5a6f5184c22e9188ec1ff723de41910a21dcad" dependencies = [ - "cfg-if", + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.13.1", ] [[package]] -name = "criterion" -version = "0.5.1" +name = "aws-sdk-kms" +version = "1.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "adc36035f7393a24719069c9a2f52e20972f7ee8472bd788e863968736acc449" dependencies = [ - "anes", - "cast 0.3.0", - "ciborium", - "clap", - "criterion-plot", - "futures", - "is-terminal", - "itertools 0.10.5", - "num-traits", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "tokio", - "walkdir", + "regex-lite", + "tracing", ] [[package]] -name = "criterion-plot" -version = "0.5.0" +name = "aws-sdk-sso" +version = "1.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "00a35fc7e74f5be45839eb753568535c074a592185dd0a2d406685018d581c43" dependencies = [ - "cast 0.3.0", - "itertools 0.10.5", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "crossbeam-channel" -version = "0.5.8" +name = "aws-sdk-ssooidc" +version = "1.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +checksum = "f8fa655b4f313124ce272cbc38c5fef13793c832279cec750103e5e6b71a54b8" dependencies = [ - "cfg-if", - "crossbeam-utils", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "crossbeam-deque" -version = "0.8.3" +name = "aws-sdk-sts" +version = "1.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "dc1cfe5e16b90421ea031f4c6348b534ef442e76f6bf4a1b2b592c12cc2c6af9" dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.15" +name = "aws-sigv4" +version = "1.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "9bfe75fad52793ce6dec0dc3d4b1f388f038b5eb866c8d4d7f3a8e21b5ea5051" dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.2.0", + "once_cell", + "percent-encoding", + "sha2", + "time", + "tracing", ] [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "aws-smithy-async" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "fa59d1327d8b5053c54bf2eaae63bf629ba9e904434d0835a28ed3c0ed0a614e" dependencies = [ - "cfg-if", + "futures-util", + "pin-project-lite", + "tokio", ] [[package]] -name = "crossterm" -version = "0.25.0" +name = "aws-smithy-http" +version = "0.60.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +checksum = "7809c27ad8da6a6a68c454e651d4962479e81472aa19ae99e59f9aba1f9713cc" dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", ] [[package]] -name = "crossterm" -version = "0.26.1" +name = "aws-smithy-json" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "623a51127f24c30776c8b374295f2df78d92517386f77ba30773f15a30ce1422" dependencies = [ - "bitflags 1.3.2", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", + "aws-smithy-types", ] [[package]] -name = "crossterm_winapi" -version = "0.9.1" +name = "aws-smithy-query" +version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" dependencies = [ - "winapi", + "aws-smithy-types", + "urlencoding", ] [[package]] -name = "crunchy" -version = "0.2.2" +name = "aws-smithy-runtime" +version = "1.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "d526a12d9ed61fadefda24abe2e682892ba288c2018bcb38b1b4c111d13f6d92" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] [[package]] -name = "crypto-bigint" -version = "0.5.2" +name = "aws-smithy-runtime-api" +version = "1.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.2.0", + "pin-project-lite", + "tokio", + "tracing", "zeroize", ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "aws-smithy-types" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "c7b8a53819e42f10d0821f56da995e1470b199686a1809168db6ca485665f042" dependencies = [ - "generic-array", - "typenum", + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.2.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", ] [[package]] -name = "crypto-mac" -version = "0.11.1" +name = "aws-smithy-xml" +version = "0.60.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" dependencies = [ - "generic-array", - "subtle", + "xmlparser", ] [[package]] -name = "ctr" -version = "0.9.2" +name = "aws-types" +version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "dfbd0a668309ec1f66c0f6bda4840dd6d4796ae26d699ebc266d7cc95c6d040f" dependencies = [ - "cipher", + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", ] [[package]] -name = "ctrlc" -version = "3.4.0" +name = "axum" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ - "nix", - "windows-sys 0.48.0", + "async-trait", + "axum-core", + "base64 0.22.1", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sha1", + "sync_wrapper", + "tokio", + "tokio-tungstenite 0.24.0", + "tower 0.5.2", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "curl" -version = "0.4.44" +name = "axum-core" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ - "curl-sys", - "libc", - "schannel", - "socket2 0.4.9", - "winapi", + "async-trait", + "bytes", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "curl-sys" -version = "0.4.65+curl-8.2.1" +name = "backtrace" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ + "addr2line", "cc", + "cfg-if", "libc", - "libnghttp2-sys", - "libz-sys", - "pkg-config", - "vcpkg", - "winapi", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", ] [[package]] -name = "dashmap" -version = "5.5.0" +name = "base16ct" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" -dependencies = [ - "cfg-if", - "hashbrown 0.14.0", - "lock_api", - "once_cell", - "parking_lot_core", -] +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] -name = "data-encoding" -version = "2.4.0" +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "der" -version = "0.7.8" +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" dependencies = [ - "const-oid", - "zeroize", + "outref", + "vsimd", ] [[package]] -name = "deranged" -version = "0.3.7" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] -name = "derivative" -version = "2.2.0" +name = "bech32" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" [[package]] -name = "derive_more" -version = "0.99.17" +name = "bimap" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version 0.4.0", - "syn 1.0.109", -] +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" [[package]] -name = "dialoguer" -version = "0.10.4" +name = "bit-set" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ - "console", - "shell-words", + "bit-vec", ] [[package]] -name = "diff" -version = "0.1.13" +name = "bit-vec" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] -name = "digest" -version = "0.9.0" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" dependencies = [ - "generic-array", + "arbitrary", + "serde", ] [[package]] -name = "digest" -version = "0.10.7" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "block-buffer 0.10.4", - "const-oid", - "crypto-common", - "subtle", + "funty", + "radium", + "serde", + "tap", + "wyz", ] [[package]] -name = "dirs" -version = "4.0.0" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "dirs-sys 0.3.7", + "generic-array", ] [[package]] -name = "dirs" -version = "5.0.1" +name = "blst" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" dependencies = [ - "dirs-sys 0.4.1", + "cc", + "glob", + "threadpool", + "zeroize", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "bon" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "fe7acc34ff59877422326db7d6f2d845a582b16396b6b08194942bf34c6528ab" dependencies = [ - "cfg-if", - "dirs-sys-next", + "bon-macros", + "rustversion", ] [[package]] -name = "dirs-sys" -version = "0.3.7" +name = "bon-macros" +version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "4159dd617a7fbc9be6a692fe69dc2954f8e6bb6bb5e4d7578467441390d77fd0" dependencies = [ - "libc", - "redox_users", - "winapi", + "darling", + "ident_case", + "prettyplease", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.98", ] [[package]] -name = "dirs-sys" -version = "0.4.1" +name = "bs58" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", + "sha2", + "tinyvec", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "bstr" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ - "libc", - "redox_users", - "winapi", + "memchr", + "regex-automata 0.4.9", + "serde", ] [[package]] -name = "dotenvy" -version = "0.15.7" +name = "build_const" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] -name = "dunce" -version = "1.0.4" +name = "bumpalo" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] -name = "ecdsa" -version = "0.16.8" +name = "byte-slice-cast" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] -name = "either" -version = "1.9.0" +name = "bytemuck" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] -name = "elasticlunr-rs" -version = "3.0.2" +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e83863a500656dfa214fee6682de9c5b9f03de6860fec531235ed2ae9f6571" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" dependencies = [ - "regex", "serde", - "serde_derive", - "serde_json", ] [[package]] -name = "elliptic-curve" -version = "0.13.5" +name = "bytes-utils" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", + "bytes", + "either", ] [[package]] -name = "ena" -version = "0.14.2" +name = "bzip2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" dependencies = [ - "log", + "bzip2-sys", + "libc", ] [[package]] -name = "encode_unicode" -version = "0.3.6" +name = "bzip2-sys" +version = "0.1.12+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] -name = "encoding_rs" -version = "0.8.32" +name = "c-kzg" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928" dependencies = [ - "cfg-if", + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", ] [[package]] -name = "endian-type" -version = "0.1.2" +name = "cassowary" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] -name = "enr" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be7b2ac146c1f99fe245c02d16af0696450d8e06c135db75e10eeb9e642c20d" +name = "cast" +version = "1.0.0" dependencies = [ - "base64 0.21.2", - "bytes", - "hex", - "k256", - "log", - "rand 0.8.5", - "rlp", + "alloy-chains", + "alloy-consensus", + "alloy-contract", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", + "alloy-node-bindings", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-types", + "alloy-transport", + "anvil", + "async-trait", + "aws-sdk-kms", + "chrono", + "clap", + "clap_complete", + "clap_complete_fig", + "comfy-table", + "divan", + "dunce", + "evmole", + "eyre", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm", + "foundry-test-utils", + "foundry-wallets", + "futures", + "indicatif", + "itertools 0.14.0", + "rand", + "rayon", + "regex", + "rpassword", + "semver 1.0.25", "serde", - "serde-hex", - "sha3", - "zeroize", + "serde_json", + "tempfile", + "tikv-jemallocator", + "tokio", + "tracing", + "yansi", ] [[package]] -name = "enumn" -version = "0.1.11" +name = "castaway" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b893c4eb2dc092c811165f84dc7447fae16fb66521717968c34c509b39b1a5c5" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", + "rustversion", ] [[package]] -name = "env_logger" -version = "0.10.0" +name = "cc" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", + "shlex", ] [[package]] -name = "equivalent" -version = "1.0.1" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "errno" -version = "0.3.2" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +name = "chisel" +version = "1.0.0" dependencies = [ - "cc", - "libc", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "clap", + "dirs 6.0.0", + "eyre", + "forge-fmt", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm", + "regex", + "reqwest", + "revm", + "rustyline", + "semver 1.0.25", + "serde", + "serde_json", + "serial_test", + "solang-parser", + "solar-parse", + "strum", + "tikv-jemallocator", + "time", + "tokio", + "tracing", + "tracing-subscriber", + "walkdir", + "yansi", ] [[package]] -name = "error-code" -version = "2.3.1" +name = "chrono" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ - "libc", - "str-buf", + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", ] [[package]] -name = "eth-keystore" -version = "0.5.0" +name = "ciborium" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ - "aes", - "ctr", - "digest 0.10.7", - "hex", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "rand 0.8.5", - "scrypt", + "ciborium-io", + "ciborium-ll", "serde", - "serde_json", - "sha2 0.10.7", - "sha3", - "thiserror", - "uuid", ] [[package]] -name = "ethabi" -version = "18.0.0" +name = "ciborium-io" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" -dependencies = [ - "ethereum-types", - "hex", - "once_cell", - "regex", - "serde", - "serde_json", - "sha3", - "thiserror", - "uint", -] +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] -name = "ethbloom" -version = "0.13.0" +name = "ciborium-ll" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ - "crunchy", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", - "tiny-keccak", + "ciborium-io", + "half", ] [[package]] -name = "ethereum-forkid" -version = "0.12.0" +name = "cipher" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcace6f36a8fd79d06c535444b42c8966e10733165fca6dec3542abfc3e00318" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crc 1.8.1", - "fastrlp", - "maplit", - "primitive-types", - "thiserror", + "crypto-common", + "inout", ] [[package]] -name = "ethereum-types" -version = "0.14.1" +name = "clap" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" dependencies = [ - "ethbloom", - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "primitive-types", - "scale-info", - "uint", + "clap_builder", + "clap_derive", ] [[package]] -name = "ethers" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "clap_builder" +version = "4.5.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" dependencies = [ - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-signers", - "ethers-solc", + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", + "unicase", + "unicode-width 0.2.0", ] [[package]] -name = "ethers-addressbook" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "clap_complete" +version = "4.5.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "375f9d8255adeeedd51053574fd8d4ba875ea5fa558e86617b07f09f1680c8b6" dependencies = [ - "ethers-core", - "once_cell", - "serde", - "serde_json", + "clap", ] [[package]] -name = "ethers-contract" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "clap_complete_fig" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d494102c8ff3951810c72baf96910b980fb065ca5d3101243e6a8dc19747c86b" dependencies = [ - "const-hex", - "ethers-contract-abigen", - "ethers-contract-derive", - "ethers-core", - "ethers-providers", - "ethers-signers", - "futures-util", - "once_cell", - "pin-project", - "serde", - "serde_json", - "thiserror", + "clap", + "clap_complete", ] [[package]] -name = "ethers-contract-abigen" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" dependencies = [ - "Inflector", - "const-hex", - "dunce", - "ethers-core", - "ethers-etherscan", - "eyre", - "prettyplease", + "heck", "proc-macro2", "quote", - "regex", - "reqwest", - "serde", - "serde_json", - "syn 2.0.28", - "toml 0.7.6", - "walkdir", + "syn 2.0.98", ] [[package]] -name = "ethers-contract-derive" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "clearscreen" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c41dc435a7b98e4608224bbf65282309f5403719df9113621b30f8b6f74e2f4" dependencies = [ - "Inflector", - "const-hex", - "ethers-contract-abigen", - "ethers-core", - "proc-macro2", - "quote", - "serde_json", - "syn 2.0.28", + "nix 0.29.0", + "terminfo", + "thiserror 2.0.11", + "which", + "windows-sys 0.59.0", ] [[package]] -name = "ethers-core" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "cliclack" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a80570d35684e725e9d2d4aaaf32bc0cbfcfb8539898f9afea3da0d2e5189e4" dependencies = [ - "arrayvec", - "bytes", - "cargo_metadata", - "chrono", - "const-hex", - "elliptic-curve", - "ethabi", - "generic-array", - "k256", - "num_enum", + "console", + "indicatif", "once_cell", - "open-fastrlp", - "rand 0.8.5", - "rlp", - "serde", - "serde_json", - "strum 0.25.0", - "syn 2.0.28", - "tempfile", - "thiserror", - "tiny-keccak", - "unicode-xid", + "strsim", + "textwrap", + "zeroize", ] [[package]] -name = "ethers-etherscan" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" dependencies = [ - "ethers-core", - "ethers-solc", - "reqwest", - "semver 1.0.18", - "serde", - "serde_json", - "thiserror", - "tracing", + "error-code", ] [[package]] -name = "ethers-middleware" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "coins-bip32" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2073678591747aed4000dd468b97b14d7007f7936851d3f2f01846899f5ebf08" dependencies = [ - "async-trait", - "auto_impl", - "ethers-contract", - "ethers-core", - "ethers-etherscan", - "ethers-providers", - "ethers-signers", - "futures-channel", - "futures-locks", - "futures-util", - "instant", - "reqwest", + "bs58", + "coins-core", + "digest 0.10.7", + "hmac", + "k256", "serde", - "serde_json", - "thiserror", - "tokio", - "tracing", - "tracing-futures", - "url", + "sha2", + "thiserror 1.0.69", ] [[package]] -name = "ethers-providers" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "coins-bip39" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b169b26623ff17e9db37a539fe4f15342080df39f129ef7631df7683d6d9d4" dependencies = [ - "async-trait", - "auto_impl", - "base64 0.21.2", - "bytes", - "const-hex", - "enr", - "ethers-core", - "futures-channel", - "futures-core", - "futures-timer", - "futures-util", - "hashers", - "http", - "instant", - "jsonwebtoken", + "bitvec", + "coins-bip32", + "hmac", "once_cell", - "pin-project", - "reqwest", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-tungstenite 0.19.0", - "tracing", - "tracing-futures", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winapi", - "ws_stream_wasm", + "pbkdf2 0.12.2", + "rand", + "sha2", + "thiserror 1.0.69", ] [[package]] -name = "ethers-signers" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "coins-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b962ad8545e43a28e14e87377812ba9ae748dd4fd963f4c10e9fcc6d13475b" dependencies = [ - "async-trait", - "coins-bip32", - "coins-bip39", - "coins-ledger", + "base64 0.21.7", + "bech32", + "bs58", "const-hex", - "elliptic-curve", - "eth-keystore", - "ethers-core", - "futures-executor", - "futures-util", - "home", - "rand 0.8.5", - "rusoto_core", - "rusoto_kms", - "semver 1.0.18", - "sha2 0.10.7", - "spki", - "thiserror", - "tracing", - "trezor-client", + "digest 0.10.7", + "generic-array", + "ripemd", + "serde", + "sha2", + "sha3", + "thiserror 1.0.69", ] [[package]] -name = "ethers-solc" -version = "2.0.8" -source = "git+https://github.com/gakonst/ethers-rs#fa3017715a298728d9fb341933818a5d0d84c2dc" +name = "coins-ledger" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9bc0994d0aa0f4ade5f3a9baf4a8d936f250278c85a1124b401860454246ab" dependencies = [ + "async-trait", + "byteorder", "cfg-if", "const-hex", - "dirs 5.0.1", - "dunce", - "ethers-core", - "fs_extra", - "futures-util", - "glob", - "home", - "md-5 0.10.5", - "num_cpus", + "getrandom 0.2.15", + "hidapi-rusb", + "js-sys", + "log", + "nix 0.26.4", "once_cell", - "path-slash", - "rand 0.8.5", - "rayon", - "regex", - "semver 1.0.18", - "serde", - "serde_json", - "sha2 0.10.7", - "solang-parser", - "svm-rs", - "svm-rs-builds", - "tempfile", - "thiserror", - "tiny-keccak", + "thiserror 1.0.69", "tokio", "tracing", - "walkdir", - "yansi 0.5.1", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "evm-disassembler" -version = "0.2.0" +name = "color-eyre" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584e7334177d2dc76c4d618967e5ef102be68c35f454ac363d88b72b854db4a9" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ + "backtrace", + "color-spantrace", "eyre", - "hex", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", ] [[package]] -name = "eyre" -version = "0.6.8" +name = "color-spantrace" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" dependencies = [ - "indenter", "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", ] [[package]] -name = "fastrand" -version = "2.0.0" +name = "colorchoice" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] -name = "fastrlp" -version = "0.3.1" +name = "comfy-table" +version = "7.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "fastrlp-derive", + "crossterm", + "unicode-segmentation", + "unicode-width 0.2.0", ] [[package]] -name = "fastrlp-derive" -version = "0.2.0" +name = "compact_str" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4caf31122bfc780557fdcd80897e95f12cc4d7217f8ac6b9d150df828a38ee8" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 2.0.28", + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", ] [[package]] -name = "fd-lock" -version = "3.0.13" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "cfg-if", - "rustix 0.38.8", - "windows-sys 0.48.0", + "crossbeam-utils", ] [[package]] -name = "fdlimit" -version = "0.2.1" +name = "condtype" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" + +[[package]] +name = "console" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4c9e43643f5a3be4ca5b67d26b98031ff9db6806c3440ae32e02e3ceac3f1b" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ + "encode_unicode", "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] -name = "ff" -version = "0.13.0" +name = "console_error_panic_hook" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "rand_core 0.6.4", - "subtle", + "cfg-if", + "wasm-bindgen", ] [[package]] -name = "figment" -version = "0.10.10" +name = "const-hex" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4547e226f4c9ab860571e070a9034192b3175580ecea38da34fcdb53a018c9a5" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ - "atomic", - "parking_lot", - "pear", + "cfg-if", + "cpufeatures", + "hex", + "proptest", "serde", - "tempfile", - "toml 0.7.6", - "uncased", - "version_check", ] [[package]] -name = "filetime" -version = "0.2.22" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "windows-sys 0.48.0", + "const_format_proc_macros", ] [[package]] -name = "fixed-hash" -version = "0.8.0" +name = "const_format_proc_macros" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", + "proc-macro2", + "quote", + "unicode-xid", ] [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "convert_case" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] [[package]] -name = "flate2" -version = "1.0.26" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ - "crc32fast", - "miniz_oxide", + "core-foundation-sys", + "libc", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "core-foundation" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.8.0", + "crossterm_winapi", + "mio 1.0.3", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctrlc" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3" +dependencies = [ + "nix 0.29.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.98", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.98", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.98", + "unicode-xid", +] + +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "divan" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4" +dependencies = [ + "cfg-if", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", +] + +[[package]] +name = "divan-macros" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elasticlunr-rs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e83863a500656dfa214fee6682de9c5b9f03de6860fec531235ed2ae9f6571" +dependencies = [ + "regex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "email-address-parser" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe19a4967eca30062be4abaf813d929ba48b3bfb21830367f7e1baae37f213a" +dependencies = [ + "console_error_panic_hook", + "pest", + "pest_derive", + "quick-xml 0.18.1", + "wasm-bindgen", +] + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" + +[[package]] +name = "eth-keystore" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fda3bf123be441da5260717e0661c25a2fd9cb2b2c1d20bf2e05580047158ab" +dependencies = [ + "aes", + "ctr", + "digest 0.10.7", + "hex", + "hmac", + "pbkdf2 0.11.0", + "rand", + "scrypt", + "serde", + "serde_json", + "sha2", + "sha3", + "thiserror 1.0.69", + "uuid 0.8.2", +] + +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "evm-disassembler" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded685d9f07315ff689ba56e7d84e6f1e782db19b531a46c34061a733bba7258" +dependencies = [ + "eyre", + "hex", +] + +[[package]] +name = "evmole" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19906a94bb5656904a6c9c0f36d492cb1da96f284d59bb56f555bd472d96e51" +dependencies = [ + "alloy-dyn-abi", + "alloy-primitives", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fd-lock" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "fdlimit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182f7dbc2ef73d9ef67351c5fbbea084729c48362d3ce9dd44c28e32e277fe5" +dependencies = [ + "libc", + "thiserror 1.0.69", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic", + "parking_lot", + "pear", + "serde", + "tempfile", + "toml 0.8.20", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.4", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "forge" +version = "1.0.0" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "alloy-transport", + "anvil", + "async-trait", + "axum", + "chrono", + "clap", + "clap_complete", + "clap_complete_fig", + "clearscreen", + "comfy-table", + "dialoguer", + "dunce", + "evm-disassembler", + "eyre", + "forge-doc", + "forge-fmt", + "forge-script", + "forge-script-sequence", + "forge-sol-macro-gen", + "forge-verify", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-debugger", + "foundry-evm", + "foundry-linking", + "foundry-test-utils", + "foundry-wallets", + "futures", + "globset", + "humantime-serde", + "hyper 1.6.0", + "indicatif", + "inferno", + "itertools 0.14.0", + "mockall", + "opener", + "parking_lot", + "paste", + "path-slash", + "proptest", + "quick-junit", + "rayon", + "regex", + "reqwest", + "revm-inspectors", + "semver 1.0.25", + "serde", + "serde_json", + "similar", + "similar-asserts", + "solang-parser", + "solar-parse", + "soldeer-commands", + "strum", + "svm-rs", + "tempfile", + "thiserror 2.0.11", + "tikv-jemallocator", + "tokio", + "toml 0.8.20", + "toml_edit", + "tower-http", + "tracing", + "tracing-subscriber", + "watchexec", + "watchexec-events", + "watchexec-signals", + "yansi", +] + +[[package]] +name = "forge-doc" +version = "1.0.0" +dependencies = [ + "alloy-primitives", + "derive_more", + "eyre", + "forge-fmt", + "foundry-common", + "foundry-compilers", + "foundry-config", + "itertools 0.14.0", + "mdbook", + "rayon", + "regex", + "serde", + "serde_json", + "solang-parser", + "thiserror 2.0.11", + "toml 0.8.20", + "tracing", +] + +[[package]] +name = "forge-fmt" +version = "1.0.0" +dependencies = [ + "alloy-primitives", + "ariadne", + "foundry-config", + "itertools 0.14.0", + "similar-asserts", + "solang-parser", + "thiserror 2.0.11", + "toml 0.8.20", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "forge-script" +version = "1.0.0" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-dyn-abi", + "alloy-eips", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "clap", + "dialoguer", + "dunce", + "eyre", + "forge-script-sequence", + "forge-verify", + "foundry-cheatcodes", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-debugger", + "foundry-evm", + "foundry-linking", + "foundry-wallets", + "futures", + "indicatif", + "itertools 0.14.0", + "parking_lot", + "revm-inspectors", + "semver 1.0.25", + "serde", + "serde_json", + "tempfile", + "tokio", + "tracing", + "yansi", +] + +[[package]] +name = "forge-script-sequence" +version = "1.0.0" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-rpc-types", + "eyre", + "foundry-common", + "foundry-compilers", + "foundry-config", + "revm-inspectors", + "serde", + "serde_json", + "tracing", + "walkdir", +] + +[[package]] +name = "forge-sol-macro-gen" +version = "1.0.0" +dependencies = [ + "alloy-json-abi", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "eyre", + "foundry-common", + "prettyplease", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.98", +] + +[[package]] +name = "forge-verify" +version = "1.0.0" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "async-trait", + "ciborium", + "clap", + "eyre", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm", + "foundry-test-utils", + "futures", + "itertools 0.14.0", + "regex", + "reqwest", + "revm-primitives", + "semver 1.0.25", + "serde", + "serde_json", + "tempfile", + "tokio", + "tracing", + "yansi", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "foundry-block-explorers" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d7f2a3d90ff85c164c0eb05fdf19b22e68b9190b5449d1aa0eef8314c1874e9" +dependencies = [ + "alloy-chains", + "alloy-json-abi", + "alloy-primitives", + "foundry-compilers", + "reqwest", + "semver 1.0.25", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "foundry-cheatcodes" +version = "1.0.0" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-genesis", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-types", + "base64 0.22.1", + "dialoguer", + "ecdsa", + "eyre", + "forge-script-sequence", + "foundry-cheatcodes-spec", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm-core", + "foundry-evm-traces", + "foundry-wallets", + "itertools 0.14.0", + "jsonpath_lib", + "k256", + "memchr", + "p256", + "parking_lot", + "proptest", + "rand", + "revm", + "revm-inspectors", + "semver 1.0.25", + "serde", + "serde_json", + "thiserror 2.0.11", + "toml 0.8.20", + "tracing", + "walkdir", +] + +[[package]] +name = "foundry-cheatcodes-spec" +version = "1.0.0" +dependencies = [ + "alloy-sol-types", + "foundry-macros", + "schemars", + "serde", + "serde_json", +] + +[[package]] +name = "foundry-cli" +version = "1.0.0" +dependencies = [ + "alloy-chains", + "alloy-dyn-abi", + "alloy-eips", + "alloy-json-abi", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "clap", + "color-eyre", + "dotenvy", + "eyre", + "forge-fmt", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-debugger", + "foundry-evm", + "foundry-wallets", + "futures", + "indicatif", + "itertools 0.14.0", + "rayon", + "regex", + "serde", + "serde_json", + "strsim", + "strum", + "tempfile", + "tokio", + "tracing", + "tracing-subscriber", + "tracing-tracy", + "yansi", +] + +[[package]] +name = "foundry-common" +version = "1.0.0" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-dyn-abi", + "alloy-eips", + "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-serde", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "anstream", + "anstyle", + "async-trait", + "axum", + "chrono", + "clap", + "comfy-table", + "dunce", + "eyre", + "foundry-block-explorers", + "foundry-common-fmt", + "foundry-compilers", + "foundry-config", + "foundry-macros", + "itertools 0.14.0", + "num-format", + "reqwest", + "semver 1.0.25", + "serde", + "serde_json", + "similar-asserts", + "terminal_size", + "thiserror 2.0.11", + "tokio", + "tower 0.5.2", + "tracing", + "url", + "vergen", + "walkdir", + "yansi", +] + +[[package]] +name = "foundry-common-fmt" +version = "1.0.0" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types", + "alloy-serde", + "chrono", + "comfy-table", + "foundry-macros", + "revm-primitives", + "serde", + "serde_json", + "similar-asserts", + "yansi", +] + +[[package]] +name = "foundry-compilers" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8f0bab060fd7c1764c4be2563e6933d39ec8c2b8a8d6c08aaf45ab29d08310" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "auto_impl", + "derive_more", + "dirs 6.0.0", + "dyn-clone", + "foundry-compilers-artifacts", + "foundry-compilers-core", + "fs_extra", + "futures-util", + "home", + "itertools 0.14.0", + "md-5", + "path-slash", + "rand", + "rayon", + "semver 1.0.25", + "serde", + "serde_json", + "sha2", + "solar-parse", + "svm-rs", + "svm-rs-builds", + "tempfile", + "thiserror 2.0.11", + "tokio", + "tracing", + "winnow 0.7.2", + "yansi", +] + +[[package]] +name = "foundry-compilers-artifacts" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102dd131e939d80cc5c85214d2f0f6ba20ed75cf098019c4d995791b4ebae05" +dependencies = [ + "foundry-compilers-artifacts-solc", + "foundry-compilers-artifacts-vyper", +] + +[[package]] +name = "foundry-compilers-artifacts-solc" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9ef02de4fda04ae3ed098afb6e16edd742d1073f972197a4566836b453bdcd" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "foundry-compilers-core", + "futures-util", + "md-5", + "path-slash", + "rayon", + "semver 1.0.25", + "serde", + "serde_json", + "serde_repr", + "thiserror 2.0.11", + "tokio", + "tracing", + "walkdir", + "yansi", +] [[package]] -name = "foreign-types" -version = "0.3.2" +name = "foundry-compilers-artifacts-vyper" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "3bd9d33cbeda448af917105920fefdc3ac42f9ffdaa2d840d58207db7807ea29" dependencies = [ - "foreign-types-shared", + "alloy-json-abi", + "alloy-primitives", + "foundry-compilers-artifacts-solc", + "foundry-compilers-core", + "path-slash", + "semver 1.0.25", + "serde", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "foundry-compilers-core" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "forge" -version = "0.2.0" +checksum = "746d121f7b86b84b20e582a27a56c49435768ad3b8005e9afeaf68b53a77fb5c" dependencies = [ - "anvil", - "async-trait", - "bytes", - "clap", - "clap_complete", - "clap_complete_fig", - "comfy-table", - "const-hex", - "criterion", - "dialoguer", + "alloy-primitives", + "cfg-if", "dunce", - "ethers", - "eyre", - "forge-doc", - "forge-fmt", - "foundry-cli", - "foundry-common", - "foundry-config", - "foundry-evm", - "foundry-test-utils", - "foundry-utils", - "futures", - "globset", - "indicatif", - "itertools 0.11.0", - "once_cell", - "parking_lot", + "fs_extra", "path-slash", - "pretty_assertions", - "proptest", - "rayon", "regex", - "reqwest", - "semver 1.0.18", + "semver 1.0.25", "serde", "serde_json", - "serial_test", - "similar", - "solang-parser", - "strum 0.25.0", "svm-rs", - "thiserror", + "tempfile", + "thiserror 2.0.11", "tokio", - "tracing", - "ui", - "vergen", - "watchexec", - "yansi 0.5.1", + "walkdir", ] [[package]] -name = "forge-doc" -version = "0.2.0" +name = "foundry-config" +version = "1.0.0" dependencies = [ - "auto_impl", - "derive_more", - "ethers-core", - "ethers-solc", + "Inflector", + "alloy-chains", + "alloy-primitives", + "dirs 6.0.0", + "dunce", "eyre", - "forge-fmt", - "foundry-config", - "foundry-utils", - "futures-util", - "itertools 0.11.0", - "mdbook", - "once_cell", - "rayon", + "figment", + "foundry-block-explorers", + "foundry-compilers", + "glob", + "globset", + "itertools 0.14.0", + "mesc", + "number_prefix", + "path-slash", + "regex", + "reqwest", + "revm-primitives", + "semver 1.0.25", "serde", "serde_json", - "solang-parser", - "thiserror", - "tokio", - "toml 0.7.6", + "serde_regex", + "similar-asserts", + "solar-parse", + "tempfile", + "thiserror 2.0.11", + "toml 0.8.20", + "toml_edit", "tracing", - "warp", + "walkdir", + "yansi", ] [[package]] -name = "forge-fmt" -version = "0.2.0" +name = "foundry-debugger" +version = "1.0.0" dependencies = [ - "ariadne", - "ethers-core", - "foundry-config", - "itertools 0.11.0", - "pretty_assertions", - "solang-parser", - "thiserror", - "toml 0.7.6", + "alloy-primitives", + "crossterm", + "eyre", + "foundry-common", + "foundry-compilers", + "foundry-evm-core", + "foundry-evm-traces", + "ratatui", + "revm", + "revm-inspectors", + "serde", "tracing", - "tracing-subscriber", ] [[package]] -name = "form_urlencoded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +name = "foundry-evm" +version = "1.0.0" dependencies = [ - "percent-encoding", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-types", + "eyre", + "foundry-cheatcodes", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm-core", + "foundry-evm-coverage", + "foundry-evm-fuzz", + "foundry-evm-traces", + "indicatif", + "parking_lot", + "proptest", + "revm", + "revm-inspectors", + "serde", + "thiserror 2.0.11", + "tracing", ] [[package]] -name = "foundry-abi" -version = "0.2.0" +name = "foundry-evm-abi" +version = "1.0.0" dependencies = [ - "ethers-contract", - "ethers-contract-abigen", - "ethers-core", - "ethers-providers", - "eyre", + "alloy-primitives", + "alloy-sol-types", + "derive_more", + "foundry-common-fmt", "foundry-macros", - "syn 2.0.28", + "foundry-test-utils", + "itertools 0.14.0", ] [[package]] -name = "foundry-binder" -version = "0.2.0" +name = "foundry-evm-core" +version = "1.0.0" dependencies = [ - "curl", - "ethers-contract", - "ethers-solc", + "alloy-consensus", + "alloy-dyn-abi", + "alloy-genesis", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-sol-types", + "auto_impl", "eyre", + "foundry-cheatcodes-spec", + "foundry-common", "foundry-config", - "git2", - "tempfile", + "foundry-evm-abi", + "foundry-fork-db", + "foundry-test-utils", + "futures", + "itertools 0.14.0", + "parking_lot", + "revm", + "revm-inspectors", + "serde", + "serde_json", + "thiserror 2.0.11", + "tokio", "tracing", "url", ] [[package]] -name = "foundry-cli" -version = "0.2.0" +name = "foundry-evm-coverage" +version = "1.0.0" dependencies = [ - "async-trait", - "clap", - "color-eyre", - "const-hex", - "dotenvy", - "ethers", + "alloy-primitives", "eyre", "foundry-common", - "foundry-config", - "foundry-evm", - "indicatif", - "itertools 0.11.0", - "once_cell", - "regex", - "rpassword", - "rusoto_core", - "rusoto_kms", - "serde", - "strsim", - "strum 0.25.0", - "tempfile", - "thiserror", - "tokio", + "foundry-compilers", + "foundry-evm-core", + "rayon", + "revm", + "semver 1.0.25", "tracing", - "tracing-error", - "tracing-subscriber", - "ui", - "yansi 0.5.1", ] [[package]] -name = "foundry-common" -version = "0.2.0" +name = "foundry-evm-fuzz" +version = "1.0.0" dependencies = [ - "auto_impl", - "clap", - "comfy-table", - "dunce", - "ethers-core", - "ethers-etherscan", - "ethers-middleware", - "ethers-providers", - "ethers-solc", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", "eyre", + "foundry-common", + "foundry-compilers", "foundry-config", - "foundry-macros", - "globset", - "once_cell", - "regex", - "reqwest", - "semver 1.0.18", + "foundry-evm-core", + "foundry-evm-coverage", + "foundry-evm-traces", + "itertools 0.14.0", + "parking_lot", + "proptest", + "rand", + "revm", "serde", - "serde_json", - "tempfile", - "thiserror", - "tokio", + "thiserror 2.0.11", "tracing", - "walkdir", - "yansi 0.5.1", ] [[package]] -name = "foundry-config" -version = "0.2.0" +name = "foundry-evm-traces" +version = "1.0.0" dependencies = [ - "Inflector", - "dirs-next", - "ethers-core", - "ethers-etherscan", - "ethers-solc", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-types", "eyre", - "figment", - "globset", - "number_prefix", - "once_cell", - "open-fastrlp", - "path-slash", - "pretty_assertions", - "regex", - "reqwest", - "semver 1.0.18", + "foundry-block-explorers", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm-core", + "foundry-linking", + "futures", + "itertools 0.14.0", + "rayon", + "revm", + "revm-inspectors", "serde", "serde_json", - "serde_regex", + "solar-parse", "tempfile", - "thiserror", - "toml 0.7.6", - "toml_edit", + "tokio", "tracing", - "walkdir", ] [[package]] -name = "foundry-evm" -version = "0.2.0" +name = "foundry-fork-db" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e9c37fd94d2b6ac89c9b83ec3e4fca43fa04866002da250aa97255ff261778" dependencies = [ - "auto_impl", - "bytes", - "const-hex", - "ethers", + "alloy-consensus", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", "eyre", - "foundry-abi", - "foundry-common", - "foundry-config", - "foundry-macros", - "foundry-utils", "futures", - "hashbrown 0.13.2", - "itertools 0.11.0", - "jsonpath_lib", - "once_cell", - "ordered-float", "parking_lot", - "proptest", "revm", - "semver 1.0.18", "serde", "serde_json", - "tempfile", - "thiserror", + "thiserror 2.0.11", "tokio", "tracing", "url", - "walkdir", - "yansi 0.5.1", ] [[package]] -name = "foundry-macros" -version = "0.2.0" +name = "foundry-linking" +version = "1.0.0" dependencies = [ - "ethers-core", - "foundry-macros-impl", - "serde", - "serde_json", + "alloy-primitives", + "foundry-compilers", + "semver 1.0.25", + "thiserror 2.0.11", ] [[package]] -name = "foundry-macros-impl" -version = "0.2.0" +name = "foundry-macros" +version = "1.0.0" dependencies = [ + "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "foundry-test-utils" -version = "0.2.0" +version = "1.0.0" dependencies = [ - "ethers", - "ethers-solc", + "alloy-primitives", + "alloy-provider", "eyre", + "fd-lock", + "foundry-block-explorers", "foundry-common", + "foundry-compilers", "foundry-config", - "once_cell", "parking_lot", - "pretty_assertions", + "rand", "regex", "serde_json", + "snapbox", "tempfile", - "walkdir", + "tokio", + "tracing", + "tracing-subscriber", ] [[package]] -name = "foundry-utils" -version = "0.2.0" +name = "foundry-wallets" +version = "1.0.0" dependencies = [ - "const-hex", - "dunce", - "ethers-addressbook", - "ethers-contract", - "ethers-core", - "ethers-providers", - "ethers-solc", + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-signer-aws", + "alloy-signer-gcp", + "alloy-signer-ledger", + "alloy-signer-local", + "alloy-signer-trezor", + "alloy-sol-types", + "async-trait", + "aws-config", + "aws-sdk-kms", + "clap", + "derive_builder", + "eth-keystore", "eyre", - "forge-fmt", - "foundry-common", - "futures", - "glob", - "once_cell", - "pretty_assertions", - "rand 0.8.5", - "serde_json", + "foundry-config", + "gcloud-sdk", + "rpassword", + "serde", + "thiserror 2.0.11", + "tokio", "tracing", ] [[package]] -name = "fs2" -version = "0.4.3" +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "fs4" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +checksum = "c29c30684418547d476f0b48e84f4821639119c483b1eccd566c8cd0cd05f521" dependencies = [ - "libc", - "winapi", + "rustix", + "windows-sys 0.52.0", ] [[package]] @@ -2673,9 +4231,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -2688,9 +4246,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -2698,15 +4256,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -2714,59 +4272,39 @@ dependencies = [ ] [[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-locks" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06" -dependencies = [ - "futures-channel", - "futures-task", -] - -[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-timer" -version = "3.0.2" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" -dependencies = [ - "gloo-timers", - "send_wrapper 0.4.0", -] +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -2781,12 +4319,50 @@ dependencies = [ ] [[package]] -name = "fxhash" -version = "0.2.1" +name = "futures-utils-wasm" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + +[[package]] +name = "gcloud-sdk" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6392faf01950e198a204b13034efd7aadda1877e7c174f5442ee39bad5d99bd" dependencies = [ - "byteorder", + "async-trait", + "bytes", + "chrono", + "futures", + "hyper 1.6.0", + "jsonwebtoken", + "once_cell", + "prost", + "prost-types", + "reqwest", + "secret-vault-value", + "serde", + "serde_json", + "tokio", + "tonic", + "tower 0.5.2", + "tower-layer", + "tower-util", + "tracing", + "url", +] + +[[package]] +name = "generator" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bd114ceda131d3b1d665eba35788690ad37f5916457286b32ab6fd3c438dd" +dependencies = [ + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.58.0", ] [[package]] @@ -2802,66 +4378,54 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.9.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.2.10" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", - "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "git2" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b989d6a7ca95a362cf2cfc5ad688b3a467be1f87e480b8dad07fee8c79b0044" -dependencies = [ - "bitflags 1.3.2", - "libc", - "libgit2-sys", - "log", - "url", -] +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix-actor" -version = "0.20.0" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3" +checksum = "20018a1a6332e065f1fcc8305c1c932c6b8c9985edea2284b3c79dc6fa3ee4b2" dependencies = [ "bstr", - "btoi", "gix-date", + "gix-utils", "itoa", - "nom", - "thiserror", + "thiserror 2.0.11", + "winnow 0.6.26", ] [[package]] name = "gix-config" -version = "0.22.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1" +checksum = "6649b406ca1f99cb148959cf00468b231f07950f8ec438cc0903cda563606f19" dependencies = [ "bstr", "gix-config-value", @@ -2870,68 +4434,72 @@ dependencies = [ "gix-path", "gix-ref", "gix-sec", - "log", "memchr", - "nom", "once_cell", - "smallvec 1.11.0", - "thiserror", + "smallvec", + "thiserror 2.0.11", "unicode-bom", + "winnow 0.6.26", ] [[package]] name = "gix-config-value" -version = "0.12.5" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e874f41437441c02991dcea76990b9058fadfc54b02ab4dd06ab2218af43897" +checksum = "11365144ef93082f3403471dbaa94cfe4b5e72743bdb9560719a251d439f4cee" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.8.0", "bstr", "gix-path", "libc", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "gix-date" -version = "0.5.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc164145670e9130a60a21670d9b6f0f4f8de04e5dd256c51fa5a0340c625902" +checksum = "c57c477b645ee248b173bb1176b52dd528872f12c50375801a58aaf5ae91113f" dependencies = [ "bstr", "itoa", - "thiserror", - "time", + "jiff", + "thiserror 2.0.11", ] [[package]] name = "gix-features" -version = "0.29.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897" +checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f" dependencies = [ "gix-hash", + "gix-trace", + "gix-utils", "libc", + "prodash", "sha1_smol", "walkdir", ] [[package]] name = "gix-fs" -version = "0.1.1" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9" +checksum = "3b3d4fac505a621f97e5ce2c69fdc425742af00c0920363ca4074f0eb48b1db9" dependencies = [ + "fastrand", "gix-features", + "gix-utils", ] [[package]] name = "gix-glob" -version = "0.7.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" +checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.8.0", "bstr", "gix-features", "gix-path", @@ -2939,62 +4507,75 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.11.4" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b422ff2ad9a0628baaad6da468cf05385bf3f5ab495ad5a33cce99b9f41092f" +checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce" dependencies = [ - "hex", - "thiserror", + "faster-hex", + "thiserror 2.0.11", +] + +[[package]] +name = "gix-hashtable" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe" +dependencies = [ + "gix-hash", + "hashbrown 0.14.5", + "parking_lot", ] [[package]] name = "gix-lock" -version = "5.0.1" +version = "15.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c693d7f05730fa74a7c467150adc7cea393518410c65f0672f80226b8111555" +checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "gix-object" -version = "0.29.2" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d96bd620fd08accdd37f70b2183cfa0b001b4f1c6ade8b7f6e15cb3d9e261ce" +checksum = "e42d58010183ef033f31088479b4eb92b44fe341b35b62d39eb8b185573d77ea" dependencies = [ "bstr", - "btoi", "gix-actor", + "gix-date", "gix-features", "gix-hash", + "gix-hashtable", + "gix-path", + "gix-utils", "gix-validate", - "hex", "itoa", - "nom", - "smallvec 1.11.0", - "thiserror", + "smallvec", + "thiserror 2.0.11", + "winnow 0.6.26", ] [[package]] name = "gix-path" -version = "0.8.4" +version = "0.10.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18609c8cbec8508ea97c64938c33cd305b75dfc04a78d0c3b78b8b3fd618a77c" +checksum = "c40f12bb65a8299be0cfb90fe718e3be236b7a94b434877012980863a883a99f" dependencies = [ "bstr", "gix-trace", "home", "once_cell", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "gix-ref" -version = "0.29.1" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5" +checksum = "a91b61776c839d0f1b7114901179afb0947aa7f4d30793ca1c56d335dfef485f" dependencies = [ "gix-actor", "gix-features", @@ -3004,29 +4585,30 @@ dependencies = [ "gix-object", "gix-path", "gix-tempfile", + "gix-utils", "gix-validate", "memmap2", - "nom", - "thiserror", + "thiserror 2.0.11", + "winnow 0.6.26", ] [[package]] name = "gix-sec" -version = "0.8.4" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615cbd6b456898aeb942cd75e5810c382fbfc48dbbff2fa23ebd2d33dcbe9c7" +checksum = "d84dae13271f4313f8d60a166bf27e54c968c7c33e2ffd31c48cafe5da649875" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.8.0", "gix-path", "libc", - "windows", + "windows-sys 0.52.0", ] [[package]] name = "gix-tempfile" -version = "5.0.3" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71a0d32f34e71e86586124225caefd78dabc605d0486de580d717653addf182" +checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82" dependencies = [ "gix-fs", "libc", @@ -3037,58 +4619,47 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.3" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b6d623a1152c3facb79067d6e2ecdae48130030cf27d6eb21109f13bd7b836" +checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" [[package]] name = "gix-utils" -version = "0.1.5" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85d89dc728613e26e0ed952a19583744e7f5240fcd4aa30d6c824ffd8b52f0f" +checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" dependencies = [ "fastrand", + "unicode-normalization", ] [[package]] name = "gix-validate" -version = "0.7.7" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba9b3737b2cef3dcd014633485f0034b0f1a931ee54aeb7d8f87f177f3c89040" +checksum = "9eaa01c3337d885617c0a42e92823922a2aea71f4caeace6fe87002bdcadbd90" dependencies = [ "bstr", - "thiserror", + "thiserror 2.0.11", ] [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", -] - -[[package]] -name = "gloo-timers" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -3098,23 +4669,42 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core 0.6.4", + "rand_core", "subtle", ] [[package]] name = "h2" -version = "0.3.20" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 1.9.3", + "http 0.2.12", + "indexmap 2.7.1", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.2.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -3123,37 +4713,28 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] [[package]] name = "handlebars" -version = "4.3.7" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" +checksum = "d752747ddabc4c1a70dd28e72f2e3c218a816773e0d7faf67433f1acfa6cba7c" dependencies = [ + "derive_builder", "log", + "num-order", "pest", "pest_derive", "serde", "serde_json", - "thiserror", -] - -[[package]] -name = "hash-db" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" - -[[package]] -name = "hash256-std-hasher" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" -dependencies = [ - "crunchy", + "thiserror 2.0.11", ] [[package]] @@ -3161,71 +4742,46 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.6", -] [[package]] name = "hashbrown" -version = "0.13.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.3", - "serde", + "ahash", + "allocator-api2", ] [[package]] name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "hashers" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2bca93b15ea5a746f220e56587f71e73c6165eab783df9e26590069953e3c30" -dependencies = [ - "fxhash", -] - -[[package]] -name = "headers" -version = "0.3.8" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", + "allocator-api2", + "equivalent", + "foldhash", + "serde", ] [[package]] -name = "headers-core" -version = "0.2.0" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "heck" -version = "0.4.1" +name = "hermit-abi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -3236,17 +4792,11 @@ dependencies = [ "serde", ] -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - [[package]] name = "hidapi-rusb" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9fc48be9eab25c28b413742b38b57b85c10b5efd2d47ef013f82335cbecc8e" +checksum = "efdc2ec354929a6e8f3c6b6923a4d97427ec2f764cfee8cd4bfe890946cdf08b" dependencies = [ "cc", "libc", @@ -3256,51 +4806,61 @@ dependencies = [ [[package]] name = "hmac" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "crypto-mac", - "digest 0.9.0", + "digest 0.10.7", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "home" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "digest 0.10.7", + "windows-sys 0.59.0", ] [[package]] -name = "home" -version = "0.5.5" +name = "html-escape" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" dependencies = [ - "windows-sys 0.48.0", + "utf8-width", ] [[package]] name = "html5ever" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3309,26 +4869,49 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "http", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -3342,84 +4925,140 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" -version = "0.14.27" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.23.2" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "http", - "hyper", + "futures-util", + "http 0.2.12", + "hyper 0.14.32", "log", - "rustls 0.20.8", - "rustls-native-certs", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls 0.23.4", + "tokio-rustls 0.24.1", ] [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", - "http", - "hyper", - "rustls 0.21.6", + "http 1.2.0", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pki-types", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.1", + "tower-service", + "webpki-roots", ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.6.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", - "hyper", - "native-tls", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", "tokio", - "tokio-native-tls", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core 0.52.0", ] [[package]] @@ -3431,47 +5070,182 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" -version = "0.4.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "ignore" -version = "0.4.20" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ + "crossbeam-deque", "globset", - "lazy_static", "log", "memchr", - "regex", + "regex-automata 0.4.9", "same-file", - "thread_local", "walkdir", "winapi-util", ] [[package]] name = "ignore-files" -version = "1.3.1" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a4d73056a8d335492938cabeef794f38968ef43e6db9bc946638cfd6281003b" +checksum = "0a20552979c32c84b0c7f6bb8d3e235627011e68eb9f6d59592f14a76b6b48ea" dependencies = [ "dunce", "futures", "gix-config", "ignore", "miette", + "normalize-path", "project-origins", "radix_trie", - "thiserror", + "thiserror 2.0.11", "tokio", "tracing", ] @@ -3485,33 +5259,15 @@ dependencies = [ "parity-scale-codec", ] -[[package]] -name = "impl-rlp" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" -dependencies = [ - "rlp", -] - -[[package]] -name = "impl-serde" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" -dependencies = [ - "serde", -] - [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.98", ] [[package]] @@ -3520,6 +5276,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "index_vec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" + [[package]] name = "indexmap" version = "1.9.3" @@ -3532,25 +5294,49 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ + "arbitrary", "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.15.2", + "serde", ] [[package]] name = "indicatif" -version = "0.17.6" +version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730" +checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ "console", - "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "web-time", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "inferno" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692eda1cc790750b9f5a5e3921ef9c117fd5498b97cfacbc910693e5b29002dc" +dependencies = [ + "ahash", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml 0.37.2", + "rgb", + "str_stack", ] [[package]] @@ -3589,42 +5375,56 @@ dependencies = [ ] [[package]] -name = "instant" -version = "0.1.12" +name = "instability" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" dependencies = [ - "cfg-if", + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "interprocess" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb" dependencies = [ - "hermit-abi", + "doctest-file", + "futures-core", "libc", - "windows-sys 0.48.0", + "recvmsg", + "tokio", + "widestring", + "windows-sys 0.52.0", ] [[package]] name = "ipnet" -version = "2.8.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ - "hermit-abi", - "rustix 0.38.8", - "windows-sys 0.48.0", + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.59.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.10.5" @@ -3643,27 +5443,66 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] -name = "jobserver" -version = "0.1.26" +name = "jiff" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +checksum = "c04ef77ae73f3cf50510712722f0c4e8b46f5aaa1bf5ffad2ae213e6495e78e5" dependencies = [ - "libc", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2cec2f5d266af45a071ece48b1fb89f3b00b2421ac3a5fe10285a6caaa60d3" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a63c62e404e7b92979d2792352d885a7f8f83fd1d0d31eea582d77b2ceca697e" +dependencies = [ + "jiff-tzdb", ] [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3680,11 +5519,12 @@ dependencies = [ [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.2", + "base64 0.22.1", + "js-sys", "pem", "ring", "serde", @@ -3694,36 +5534,35 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", "once_cell", - "sha2 0.10.7", + "sha2", "signature", ] [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] -name = "keccak-hasher" -version = "0.15.3" +name = "keccak-asm" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711adba9940a039f4374fc5724c0a5eaca84a2d558cce62256bfe26f0dbef05e" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" dependencies = [ - "hash-db", - "hash256-std-hasher", - "tiny-keccak", + "digest 0.10.7", + "sha3-asm", ] [[package]] @@ -3748,92 +5587,91 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.7.4", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", "unicode-xid", + "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.0" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.9", +] + +[[package]] +name = "lasso" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb" +dependencies = [ + "dashmap", + "hashbrown 0.14.5", +] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "libc" -version = "0.2.147" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] -name = "libgit2-sys" -version = "0.15.2+1.6.4" +name = "libdbus-sys" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a80df2e11fb4a61f4ba2ab42dbe7f74468da143f1a75c74e11dee7c813f694fa" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ "cc", - "libc", - "libz-sys", "pkg-config", ] [[package]] name = "libm" -version = "0.2.7" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] -name = "libnghttp2-sys" -version = "0.1.8+1.55.1" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fae956c192dadcdb5dace96db71fa0b827333cce7c7b38dc71446f024d8a340" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "cc", + "bitflags 2.8.0", "libc", + "redox_syscall", ] [[package]] name = "libusb1-sys" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.12" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" dependencies = [ "cc", "libc", @@ -3843,31 +5681,68 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] -name = "linux-raw-sys" -version = "0.4.5" +name = "litemap" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" -version = "0.4.20" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "lru" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "227748d55f2f0ab4735d87fd623798cb6b664512fe979705f829c9f81c934465" +dependencies = [ + "hashbrown 0.15.2", +] [[package]] name = "mac" @@ -3883,70 +5758,54 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf", + "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "matchit" -version = "0.5.0" +name = "match_cfg" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] -name = "maybe-uninit" -version = "2.0.0" +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] [[package]] -name = "md-5" -version = "0.9.1" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", -] +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest 0.10.7", ] [[package]] name = "mdbook" -version = "0.4.34" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55eb7c4dad20cc5bc15181c2aaf43d5689d5c3e0b80b50cc4cf0b7fe72a26d9" +checksum = "f9da1e54401fe5d45a664c57e112e70f18e8c5a73e268c179305b932ee864574" dependencies = [ "ammonia", "anyhow", @@ -3972,15 +5831,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.5.10" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] @@ -3995,46 +5854,37 @@ dependencies = [ ] [[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memory-db" -version = "0.29.0" +name = "mesc" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +checksum = "d04b0347d2799ef17df4623dbcb03531031142105168e0c549e0bf1f980e9e7e" dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "parity-util-mem", + "serde", + "serde_json", + "thiserror 1.0.69", ] [[package]] name = "miette" -version = "5.10.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" dependencies = [ + "cfg-if", "miette-derive", - "once_cell", - "thiserror", - "unicode-width", + "thiserror 1.0.69", + "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "5.10.0" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" +checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] @@ -4045,9 +5895,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -4061,18 +5911,27 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" +dependencies = [ + "adler2", +] + [[package]] name = "mio" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -4081,28 +5940,57 @@ dependencies = [ ] [[package]] -name = "native-tls" -version = "0.2.11" +name = "mio" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "lazy_static", "libc", "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "newtype-uuid" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "ee3224f0e8be7c2a1ebc77ef9c3eecb90f55c6594399ee825de964526b3c9056" +dependencies = [ + "uuid 1.13.1", +] [[package]] name = "nibble_vec" @@ -4110,28 +5998,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ - "smallvec 1.11.0", + "smallvec", ] [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.7.1", + "memoffset", "pin-utils", - "static_assertions", ] [[package]] -name = "nodrop" -version = "0.1.14" +name = "nix" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.8.0", + "cfg-if", + "cfg_aliases", + "libc", +] [[package]] name = "nom" @@ -4143,6 +6036,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" @@ -4151,29 +6050,30 @@ checksum = "f5438dd2b2ff4c6df6e1ce22d825ed2fa93ee2922235cc45186991717f0a892d" [[package]] name = "normpath" -version = "1.1.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec60c60a693226186f5d6edf073232bfb6464ed97eb22cf3b01c1e8198fd97f5" +checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "notify" -version = "5.2.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", - "mio", + "log", + "mio 0.8.11", "walkdir", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4188,9 +6088,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -4202,52 +6102,80 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -4255,9 +6183,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -4269,36 +6197,35 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] @@ -4310,111 +6237,84 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] -name = "object" -version = "0.31.1" +name = "nybbles" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307" dependencies = [ - "memchr", + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", ] [[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "oorandom" -version = "11.1.3" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "once_cell" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] -name = "open-fastrlp" -version = "0.1.4" +name = "op-alloy-consensus" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +checksum = "23f7ff02e5f3ba62c8dd5d9a630c818f50147bca7b0d78e89de59ed46b5d02e1" dependencies = [ - "arrayvec", - "auto_impl", - "bytes", - "ethereum-types", - "open-fastrlp-derive", + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "serde", + "thiserror 2.0.11", ] [[package]] -name = "open-fastrlp-derive" -version = "0.1.1" +name = "op-alloy-rpc-types" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +checksum = "9ed9af4583c4b3ea93f54092ebfe41172974de2042672e9850500f4d1f99844e" dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 1.0.109", + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "op-alloy-consensus", + "serde", + "serde_json", ] [[package]] name = "opener" -version = "0.6.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788" +checksum = "d0812e5e4df08da354c851a3376fead46db31c2214f849d3de356d774d057681" dependencies = [ "bstr", + "dbus", "normpath", - "winapi", -] - -[[package]] -name = "openssl" -version = "0.10.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729b745ad4a5575dd06a3e1af1414bd330ee561c01b3899eb584baeaa8def17e" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", + "windows-sys 0.59.0", ] [[package]] name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.91" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "866b5f16f90776b9bb8dc1e1802ac6f0513de3a7a7465867bfbc563dc737faac" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "option-ext" @@ -4423,13 +6323,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] -name = "ordered-float" -version = "3.7.0" +name = "outref" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" -dependencies = [ - "num-traits", -] +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" [[package]] name = "overload" @@ -4443,76 +6340,57 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parity-scale-codec" -version = "3.6.4" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +checksum = "c9fde3d0718baf5bc92f577d652001da0f8d54cd03a7974e118d04fc888dc23d" dependencies = [ "arrayvec", - "bitvec 1.0.1", + "bitvec", "byte-slice-cast", + "const_format", "impl-trait-for-tuples", "parity-scale-codec-derive", + "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.6.4" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +checksum = "581c837bb6b9541ce7faa9377c20616e4fb7650f6b0f68bc93c827ee504fb7b3" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", -] - -[[package]] -name = "parity-tokio-ipc" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" -dependencies = [ - "futures", - "libc", - "log", - "rand 0.7.3", - "tokio", - "winapi", -] - -[[package]] -name = "parity-util-mem" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" -dependencies = [ - "cfg-if", - "hashbrown 0.12.3", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot", - "winapi", + "syn 2.0.98", ] [[package]] -name = "parity-util-mem-derive" -version = "0.1.0" +name = "parking" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2", - "syn 1.0.109", - "synstructure", -] +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -4520,33 +6398,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec 1.11.0", - "windows-targets 0.48.2", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "path-slash" @@ -4561,9 +6428,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", - "password-hash", - "sha2 0.10.7", ] [[package]] @@ -4573,62 +6437,73 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", ] [[package]] name = "pear" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a386cd715229d399604b50d1361683fe687066f42d56f54be995bc6868f71c" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", - "yansi 1.0.0-rc.1", + "yansi", ] [[package]] name = "pear_codegen" -version = "0.2.7" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f0f13dac8069c139e8300a6510e3f4143ecf5259c60b116a9b271b4ca0d54" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "pem" -version = "1.1.1" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ - "base64 0.13.1", + "base64ct", ] [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.2" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ - "thiserror", + "memchr", + "thiserror 2.0.11", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.2" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666d00490d4ac815001da55838c500eafb0320019bbaa44444137c48b443a853" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -4636,36 +6511,36 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.2" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca01446f50dbda87c1786af8770d535423fa8a53aec03b8f4e3d7eb10e0929" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "pest_meta" -version = "2.7.2" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56af0a30af74d0445c0bf6d9d051c979b516a1a5af790d251daee76005420a48" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", - "sha2 0.10.7", + "sha2", ] [[package]] name = "petgraph" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 1.9.3", + "indexmap 2.7.1", ] [[package]] @@ -4675,124 +6550,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version 0.4.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", + "rustc_version 0.4.1", ] [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_shared", ] [[package]] name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", + "phf_generator", + "phf_shared", ] [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", - "rand 0.8.5", + "phf_shared", + "rand", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", + "phf_generator", + "phf_shared", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] -name = "phf_shared" -version = "0.11.2" +name = "pin-project" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" dependencies = [ - "siphasher", + "pin-project-internal 0.4.30", ] [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" dependencies = [ - "pin-project-internal", + "pin-project-internal 1.1.9", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -4812,97 +6669,108 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] -name = "plotters" -version = "0.3.5" +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", + "portable-atomic", ] [[package]] -name = "plotters-backend" -version = "0.3.5" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "plotters-svg" -version = "0.3.5" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "plotters-backend", + "zerocopy", ] [[package]] -name = "portable-atomic" -version = "1.4.2" +name = "precomputed-hash" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32154ba0af3a075eefa1eda8bb414ee928f62303a54ea85b8d6638ff1a6ee9e" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "predicates" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "predicates-core", +] [[package]] -name = "precomputed-hash" -version = "0.1.1" +name = "predicates-core" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] -name = "pretty_assertions" -version = "1.4.0" +name = "predicates-tree" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ - "diff", - "yansi 0.5.1", + "predicates-core", + "termtree", ] [[package]] name = "prettyplease" -version = "0.2.12" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.28", + "syn 2.0.98", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", ] [[package]] name = "primitive-types" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "impl-rlp", - "impl-serde", - "scale-info", "uint", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "once_cell", "toml_edit", ] @@ -4930,11 +6798,33 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -4947,16 +6837,40 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", "version_check", - "yansi 1.0.0-rc.1", + "yansi", +] + +[[package]] +name = "process-wrap" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d35f4dc9988d1326b065b4def5e950c3ed727aa03e3151b86cc9e2aec6b03f54" +dependencies = [ + "futures", + "indexmap 2.7.1", + "nix 0.29.0", + "tokio", + "tracing", + "windows 0.59.0", +] + +[[package]] +name = "prodash" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a266d8d6020c61a437be704c5e618037588e1985c7dbb7bf8d265db84cffe325" +dependencies = [ + "log", + "parking_lot", ] [[package]] name = "project-origins" -version = "1.2.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629e0d57f265ca8238345cb616eea8847b8ecb86b5d97d155be2c8963a314379" +checksum = "f1a0207163ace81dd9ff23a5225188a4eef8eb7de7b570f609407e521a8c9c2c" dependencies = [ "futures", "tokio", @@ -4965,55 +6879,105 @@ dependencies = [ [[package]] name = "proptest" -version = "1.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", - "bitflags 1.3.2", - "byteorder", + "bit-vec", + "bitflags 2.8.0", "lazy_static", "num-traits", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand", + "rand_chacha", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", ] +[[package]] +name = "proptest-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "protobuf" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" dependencies = [ "once_cell", "protobuf-support", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "protobuf-support" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "pulldown-cmark" -version = "0.9.3" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", "memchr", + "pulldown-cmark-escape", "unicase", ] +[[package]] +name = "pulldown-cmark-escape" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" + [[package]] name = "quick-error" version = "1.2.3" @@ -5021,87 +6985,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quote" -version = "1.0.32" +name = "quick-junit" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "3ed1a693391a16317257103ad06a88c6529ac640846021da7c435a06fffdacd7" dependencies = [ - "proc-macro2", + "chrono", + "indexmap 2.7.1", + "newtype-uuid", + "quick-xml 0.37.2", + "strip-ansi-escapes", + "thiserror 2.0.11", + "uuid 1.13.1", ] [[package]] -name = "radium" -version = "0.3.0" +name = "quick-xml" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" +checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82" +dependencies = [ + "memchr", +] [[package]] -name = "radium" -version = "0.7.0" +name = "quick-xml" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", +] [[package]] -name = "radix_trie" -version = "0.2.1" +name = "quinn" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "endian-type", - "nibble_vec", + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.23", + "socket2", + "thiserror 2.0.11", + "tokio", + "tracing", ] [[package]] -name = "rand" -version = "0.7.3" +name = "quinn-proto" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", + "bytes", + "getrandom 0.2.15", + "rand", + "ring", + "rustc-hash", + "rustls 0.23.23", + "rustls-pki-types", + "slab", + "thiserror 2.0.11", + "tinyvec", + "tracing", + "web-time", ] [[package]] -name = "rand" -version = "0.8.5" +name = "quinn-udp" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" dependencies = [ + "cfg_aliases", "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", ] [[package]] -name = "rand_chacha" -version = "0.2.2" +name = "quote" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", + "proc-macro2", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", + "endian-type", + "nibble_vec", ] [[package]] -name = "rand_core" -version = "0.5.1" +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ - "getrandom 0.1.16", + "ppv-lite86", + "rand_core", ] [[package]] @@ -5110,32 +7122,44 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.15", ] [[package]] -name = "rand_hc" -version = "0.2.0" +name = "rand_xorshift" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.5.1", + "rand_core", ] [[package]] -name = "rand_xorshift" -version = "0.3.0" +name = "ratatui" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "rand_core 0.6.4", + "bitflags 2.8.0", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools 0.13.0", + "lru 0.12.5", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", ] [[package]] name = "rayon" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -5143,69 +7167,61 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] -name = "redox_syscall" -version = "0.2.16" +name = "recvmsg" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", - "thiserror", + "getrandom 0.2.15", + "libredox", + "thiserror 1.0.69", ] [[package]] -name = "reference-trie" -version = "0.25.0" +name = "redox_users" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f63dfce83d1e0e80cf2dc5222df5c2bc30992b30c44d1e66e7c9793d3a418e4" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "hash-db", - "hash256-std-hasher", - "keccak-hasher", - "parity-scale-codec", - "trie-db", - "trie-root", + "getrandom 0.2.15", + "libredox", + "thiserror 2.0.11", ] [[package]] name = "regex" -version = "1.9.3" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.6", - "regex-syntax 0.7.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -5219,15 +7235,21 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.4", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -5236,112 +7258,142 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ - "base64 0.21.2", + "async-compression", + "base64 0.22.1", "bytes", - "encoding_rs", + "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls 0.24.1", - "hyper-tls", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-util", "ipnet", "js-sys", "log", "mime", - "native-tls", + "mime_guess", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.6", - "rustls-native-certs", - "rustls-pemfile", + "quinn", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "tokio", - "tokio-native-tls", - "tokio-rustls 0.24.1", + "tokio-rustls 0.26.1", + "tokio-socks", + "tokio-util", + "tower 0.5.2", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "webpki-roots 0.22.6", - "winreg", + "webpki-roots", + "windows-registry", ] [[package]] name = "revm" -version = "3.3.0" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +version = "19.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc5bef3c95fadf3b6a24a253600348380c169ef285f9780a793bb7090c8990d" dependencies = [ "auto_impl", + "cfg-if", + "dyn-clone", + "once_cell", "revm-interpreter", "revm-precompile", "serde", "serde_json", ] +[[package]] +name = "revm-inspectors" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d87cdf1c0d878b48423f8a86232950657abaf72a2d0d14af609467542313b1a" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-sol-types", + "anstyle", + "colorchoice", + "revm", + "serde", + "serde_json", + "thiserror 2.0.11", +] + [[package]] name = "revm-interpreter" -version = "1.1.2" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +version = "15.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcab7ef2064057acfc84731205f4bc77f4ec1b35630800b26ff6a185731c5ab" dependencies = [ - "derive_more", - "enumn", "revm-primitives", "serde", - "sha3", ] [[package]] name = "revm-precompile" -version = "2.0.3" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +version = "16.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6caa1a7ff2cc4a09a263fcf9de99151706f323d30f33d519ed329f017a02b046" dependencies = [ + "aurora-engine-modexp", + "blst", + "c-kzg", + "cfg-if", "k256", - "num", "once_cell", + "p256", "revm-primitives", "ripemd", "secp256k1", - "sha2 0.10.7", - "sha3", + "sha2", "substrate-bn", ] [[package]] name = "revm-primitives" -version = "1.1.2" -source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" +version = "15.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f987564210317706def498421dfba2ae1af64a8edce82c6102758b48133fcb" dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", "auto_impl", - "bitvec 1.0.1", - "bytes", - "derive_more", + "bitflags 2.8.0", + "bitvec", + "c-kzg", + "cfg-if", + "dyn-clone", "enumn", - "fixed-hash", - "hashbrown 0.13.2", "hex", - "hex-literal", - "primitive-types", - "rlp", - "ruint", "serde", - "sha3", ] [[package]] @@ -5350,23 +7402,31 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" -version = "0.16.20" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "e75ec5e92c4d8aede845126adc388046234541629e76029599ed35a003c7ed24" dependencies = [ "cc", + "cfg-if", + "getrandom 0.2.15", "libc", - "once_cell", - "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -5385,58 +7445,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", - "rlp-derive", "rustc-hex", ] -[[package]] -name = "rlp-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "rpassword" -version = "7.2.0" +version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "rtoolbox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "ruint" -version = "1.10.1" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95294d6e3a6192f3aabf91c38f56505a625aa495533442744185a36d75a790c4" +checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" dependencies = [ "alloy-rlp", + "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp", + "fastrlp 0.3.1", + "fastrlp 0.4.0", "num-bigint", + "num-integer", + "num-traits", "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", + "rand", "rlp", "ruint-macro", "serde", @@ -5446,109 +7498,35 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rusb" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a8c36914f9b1a3be712c1dfa48c9b397131f9a75707e570a391735f785c5d1" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" dependencies = [ "libc", "libusb1-sys", ] [[package]] -name = "rusoto_core" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2" -dependencies = [ - "async-trait", - "base64 0.13.1", - "bytes", - "crc32fast", - "futures", - "http", - "hyper", - "hyper-rustls 0.23.2", - "lazy_static", - "log", - "rusoto_credential", - "rusoto_signature", - "rustc_version 0.4.0", - "serde", - "serde_json", - "tokio", - "xml-rs", -] - -[[package]] -name = "rusoto_credential" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05" -dependencies = [ - "async-trait", - "chrono", - "dirs-next", - "futures", - "hyper", - "serde", - "serde_json", - "shlex", - "tokio", - "zeroize", -] - -[[package]] -name = "rusoto_kms" -version = "0.48.0" +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1fc19cfcfd9f6b2f96e36d5b0dddda9004d2cbfc2d17543e3b9f10cc38fce8" -dependencies = [ - "async-trait", - "bytes", - "futures", - "rusoto_core", - "serde", - "serde_json", -] +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] -name = "rusoto_signature" -version = "0.48.0" +name = "rustc-hash" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" dependencies = [ - "base64 0.13.1", - "bytes", - "chrono", - "digest 0.9.0", - "futures", - "hex", - "hmac 0.11.0", - "http", - "hyper", - "log", - "md-5 0.9.1", - "percent-encoding", - "pin-project-lite", - "rusoto_credential", - "rustc_version 0.4.0", - "serde", - "sha2 0.9.9", - "tokio", + "rand", ] -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - [[package]] name = "rustc-hex" version = "2.1.0" @@ -5566,62 +7544,51 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.18", -] - -[[package]] -name = "rustix" -version = "0.37.23" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "semver 1.0.25", ] [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.8.0", "errno", "libc", - "linux-raw-sys 0.4.5", - "windows-sys 0.48.0", + "linux-raw-sys", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.20.8" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", + "rustls-webpki 0.101.7", "sct", - "webpki", ] [[package]] name = "rustls" -version = "0.21.6" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "log", + "once_cell", "ring", - "rustls-webpki 0.101.3", - "sct", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", ] [[package]] @@ -5631,25 +7598,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ - "base64 0.21.2", + "web-time", ] [[package]] name = "rustls-webpki" -version = "0.100.1" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", @@ -5657,19 +7654,20 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.101.3" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", + "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" @@ -5685,32 +7683,31 @@ dependencies = [ [[package]] name = "rustyline" -version = "11.0.0" +version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece" +checksum = "2ee1e066dc922e513bda599c6ccb5f3bb2b0ea5870a579448f2622993f0a9a2f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.8.0", "cfg-if", "clipboard-win", - "dirs-next", "fd-lock", + "home", "libc", "log", "memchr", - "nix", + "nix 0.29.0", "radix_trie", - "scopeguard", "unicode-segmentation", - "unicode-width", + "unicode-width 0.2.0", "utf8parse", - "winapi", + "windows-sys 0.59.0", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "salsa20" @@ -5731,36 +7728,55 @@ dependencies = [ ] [[package]] -name = "scale-info" -version = "2.9.0" +name = "sanitize-filename" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" +checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" dependencies = [ - "cfg-if", - "derive_more", - "parity-scale-codec", - "scale-info-derive", + "lazy_static", + "regex", ] [[package]] -name = "scale-info-derive" -version = "2.9.0" +name = "scc" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" +checksum = "ea091f6cac2595aa38993f04f4ee692ed43757035c36e67c180b6828356385b1" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", + "sdd", ] [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.98", ] [[package]] @@ -5781,22 +7797,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "salsa20", - "sha2 0.10.7", + "sha2", ] [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", ] +[[package]] +name = "sdd" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07779b9b918cc05650cb30f404d4d7835d26df37c235eded8a6832e2fb82cca" + [[package]] name = "sec1" version = "0.7.3" @@ -5813,30 +7835,57 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.27.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ + "rand", "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.8.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] +[[package]] +name = "secret-vault-value" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc32a777b53b3433b974c9c26b6d502a50037f8da94e46cb8ce2ced2cfdfaea0" +dependencies = [ + "prost", + "prost-types", + "serde", + "serde_json", + "zeroize", +] + [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", - "core-foundation", + "bitflags 2.8.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.8.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -5844,9 +7893,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -5863,28 +7912,22 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.18" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] [[package]] name = "semver-parser" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" dependencies = [ "pest", ] -[[package]] -name = "send_wrapper" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" - [[package]] name = "send_wrapper" version = "0.6.0" @@ -5893,47 +7936,58 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.183" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] -name = "serde-hex" -version = "0.1.0" +name = "serde_derive" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ - "array-init", - "serde", - "smallvec 0.6.14", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "serde_derive" -version = "1.0.183" +name = "serde_derive_internals" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.7.1", "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_regex" version = "1.1.0" @@ -5944,11 +7998,22 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -5967,45 +8032,34 @@ dependencies = [ [[package]] name = "serial_test" -version = "2.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ - "dashmap", "futures", - "lazy_static", "log", + "once_cell", "parking_lot", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "2.0.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", -] - -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", + "syn 2.0.98", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -6014,28 +8068,15 @@ dependencies = [ [[package]] name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.9.9" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -6053,10 +8094,20 @@ dependencies = [ ] [[package]] -name = "sharded-slab" +name = "sha3-asm" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -6069,9 +8120,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "shlex" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" @@ -6085,141 +8136,323 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio", + "mio 1.0.3", "signal-hook", ] [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest 0.10.7", - "rand_core 0.6.4", + "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" -version = "2.2.1" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +dependencies = [ + "bstr", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" +checksum = "9f08357795f0d604ea7d7130f22c74b03838c959bdb14adde3142aab4d18a293" +dependencies = [ + "console", + "similar", +] [[package]] name = "simple_asn1" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror", + "thiserror 2.0.11", "time", ] [[package]] name = "siphasher" -version = "0.3.10" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "0.6.14" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" dependencies = [ - "maybe-uninit", + "serde", ] [[package]] -name = "smallvec" -version = "1.11.0" +name = "smawk" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] -name = "smol_str" -version = "0.2.0" +name = "snapbox" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74212e6bbe9a4352329b2f68ba3130c15a3f26fe88ff22dbdc6cdd58fa85e99c" +checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" dependencies = [ + "anstream", + "anstyle", + "anstyle-svg", + "normalize-line-endings", + "regex", "serde", + "serde_json", + "similar", + "snapbox-macros", ] [[package]] -name = "socket2" -version = "0.4.9" +name = "snapbox-macros" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" dependencies = [ - "libc", - "winapi", + "anstream", ] [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "solang-parser" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c792fe9fae2a2f716846f214ca10d5a1e21133e0bf36cef34bcc4a852467b21" +checksum = "c425ce1c59f4b154717592f0bdf4715c3a1d55058883622d3157e1f0908a5b26" dependencies = [ - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop", "lalrpop-util", - "phf 0.11.2", - "thiserror", + "phf", + "thiserror 1.0.69", "unicode-xid", ] [[package]] -name = "spin" +name = "solar-ast" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3f6c4a476a16dcd36933a70ecdb0a807f8949cc5f3c4c1984e3748666bd714" +dependencies = [ + "alloy-primitives", + "bumpalo", + "either", + "num-bigint", + "num-rational", + "semver 1.0.25", + "solar-data-structures", + "solar-interface", + "solar-macros", + "strum", + "typed-arena", +] + +[[package]] +name = "solar-config" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40434a61f2c14a9e3777fbc478167bddee9828532fc26c57e416e9277916b09" +dependencies = [ + "strum", +] + +[[package]] +name = "solar-data-structures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d07263243b313296eca18f18eda3a190902dc3284bf67ceff29b8b54dac3e6" +dependencies = [ + "bumpalo", + "index_vec", + "indexmap 2.7.1", + "parking_lot", + "rayon", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "solar-interface" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a87009b6989b2cc44d8381e3b86ff3b90280d54a60321919b6416214cd602f3" +dependencies = [ + "annotate-snippets", + "anstream", + "anstyle", + "const-hex", + "derive_builder", + "dunce", + "itertools 0.14.0", + "itoa", + "lasso", + "match_cfg", + "normalize-path", + "rayon", + "scc", + "scoped-tls", + "solar-config", + "solar-data-structures", + "solar-macros", + "thiserror 2.0.11", + "tracing", + "unicode-width 0.2.0", +] + +[[package]] +name = "solar-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970d7c774741f786d62cab78290e47d845b0b9c0c9d094a1642aced1d7946036" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "solar-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1e2d07fae218aca1b4cca81216e5c9ad7822516d48a28f11e2eaa8ffa5b249" +dependencies = [ + "alloy-primitives", + "bitflags 2.8.0", + "bumpalo", + "itertools 0.14.0", + "memchr", + "num-bigint", + "num-rational", + "num-traits", + "smallvec", + "solar-ast", + "solar-data-structures", + "solar-interface", + "tracing", +] + +[[package]] +name = "soldeer-commands" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4bd924da31914871820d1404b63a89b100097957f6dc7f3bbb9c094f16d8f4e" +dependencies = [ + "bon", + "clap", + "cliclack", + "derive_more", + "email-address-parser", + "rayon", + "soldeer-core", +] + +[[package]] +name = "soldeer-core" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "c7a3129568ab6b38132efa9c956b5ae14c09c0a1a1167353e337081d1d7f0c32" +dependencies = [ + "bon", + "chrono", + "cliclack", + "const-hex", + "derive_more", + "dunce", + "home", + "ignore", + "path-slash", + "rayon", + "regex", + "reqwest", + "sanitize-filename", + "semver 1.0.25", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.11", + "tokio", + "toml_edit", + "uuid 1.13.1", + "zip", + "zip-extract", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -6227,82 +8460,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] -name = "str-buf" -version = "1.0.6" +name = "str_stack" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", - "phf_shared 0.10.0", + "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +checksum = "244292f3441c89febe5b5bdfbb6863aeaf4f64da810ea3050fd927b27b8d92ce" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] [[package]] -name = "strsim" -version = "0.10.0" +name = "strip-ansi-escapes" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] [[package]] -name = "strum" -version = "0.24.1" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.2", -] - -[[package]] -name = "strum_macros" -version = "0.24.3" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", + "strum_macros", ] [[package]] name = "strum_macros" -version = "0.25.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] @@ -6314,45 +8536,45 @@ dependencies = [ "byteorder", "crunchy", "lazy_static", - "rand 0.8.5", + "rand", "rustc-hex", ] [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svm-rs" -version = "0.3.0" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597e3a746727984cb7ea2487b6a40726cad0dbe86628e7d429aa6b8c4c153db4" +checksum = "4197826bb07b996788b9860a95a1fe2c1307b2404a8c66f5ba825c42532b7c3c" dependencies = [ + "const-hex", "dirs 5.0.1", - "fs2", - "hex", - "once_cell", + "fs4", "reqwest", - "semver 1.0.18", + "semver 1.0.25", "serde", "serde_json", - "sha2 0.10.7", - "thiserror", + "sha2", + "tempfile", + "thiserror 2.0.11", "url", "zip", ] [[package]] name = "svm-rs-builds" -version = "0.2.0" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2271abd7d01895a3e5bfa4b578e32f09155002ce1ec239532e297f82aafad06b" +checksum = "074faea21171905847a96135b3896e2e0b74373758ca07b96a41c646cc04a8e5" dependencies = [ "build_const", - "hex", - "semver 1.0.18", + "const-hex", + "semver 1.0.25", "serde_json", "svm-rs", ] @@ -6370,31 +8592,45 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2de690018098e367beeb793991c7d4dc7270f42c9d2ac4ccc876c1368ca430" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" -version = "0.12.6" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", - "unicode-xid", + "syn 2.0.98", ] [[package]] @@ -6405,15 +8641,16 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.7.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", - "rustix 0.38.8", - "windows-sys 0.48.0", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.59.0", ] [[package]] @@ -6439,77 +8676,135 @@ dependencies = [ ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "terminal_size" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ - "winapi-util", + "rustix", + "windows-sys 0.59.0", ] [[package]] -name = "terminal_size" -version = "0.2.6" +name = "terminfo" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ - "rustix 0.37.23", - "windows-sys 0.48.0", + "fnv", + "nom", + "phf", + "phf_codegen", ] [[package]] -name = "terminfo" -version = "0.8.0" +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "textwrap" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666cd3a6681775d22b200409aad3b089c5b99fb11ecdd8a204d9d62f8148498f" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ - "dirs 4.0.0", - "fnv", - "nom", - "phf 0.11.2", - "phf_codegen 0.11.2", + "smawk", + "unicode-linebreak", + "unicode-width 0.1.14", ] [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tikv-jemalloc-sys" +version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" -version = "0.3.25" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", "libc", + "num-conv", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -6517,16 +8812,17 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ + "num-conv", "time-core", ] @@ -6540,20 +8836,20 @@ dependencies = [ ] [[package]] -name = "tinytemplate" -version = "1.2.1" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "serde", - "serde_json", + "displaydoc", + "zerovec", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -6566,129 +8862,116 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] -name = "tokio-native-tls" -version = "0.3.1" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "native-tls", + "rustls 0.21.12", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ - "rustls 0.20.8", + "rustls 0.23.23", "tokio", - "webpki", ] [[package]] -name = "tokio-rustls" -version = "0.24.1" +name = "tokio-socks" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" dependencies = [ - "rustls 0.21.6", + "either", + "futures-util", + "thiserror 1.0.69", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] name = "tokio-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.17.3", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.18.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", "tokio", - "tungstenite 0.18.0", + "tungstenite 0.24.0", ] [[package]] name = "tokio-tungstenite" -version = "0.19.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +checksum = "be4bf6fecd69fcdede0ec680aaf474cdab988f9de6bc73d3758f0160e3b7025a" dependencies = [ "futures-util", "log", - "native-tls", - "rustls 0.21.6", + "rustls 0.23.23", + "rustls-pki-types", "tokio", - "tokio-native-tls", - "tokio-rustls 0.24.1", - "tungstenite 0.19.0", - "webpki-roots 0.23.1", + "tokio-rustls 0.26.1", + "tungstenite 0.26.1", + "webpki-roots", ] [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -6702,11 +8985,11 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.6" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -6715,24 +8998,57 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.0.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.7.2", +] + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2 0.4.7", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project 1.1.9", + "prost", + "rustls-native-certs 0.8.1", + "rustls-pemfile 2.2.0", + "socket2", + "tokio", + "tokio-rustls 0.26.1", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", ] [[package]] @@ -6749,47 +9065,54 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "pin-project", + "indexmap 1.9.3", + "pin-project 1.1.9", "pin-project-lite", + "rand", + "slab", "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", ] [[package]] -name = "tower-http" -version = "0.3.5" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ - "bitflags 1.3.2", - "bytes", "futures-core", "futures-util", - "http", - "http-body", - "http-range-header", "pin-project-lite", - "tower", + "sync_wrapper", + "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "tower-http" -version = "0.4.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.8.0", "bytes", - "futures-core", "futures-util", - "http", - "http-body", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", "pin-project-lite", + "tokio", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -6797,23 +9120,34 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tower-util" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674" +dependencies = [ + "futures-core", + "futures-util", + "pin-project 0.4.30", + "tower-service", +] [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -6822,20 +9156,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -6843,47 +9177,37 @@ dependencies = [ [[package]] name = "tracing-error" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ "tracing", "tracing-subscriber", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", - "smallvec 1.11.0", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -6891,155 +9215,112 @@ dependencies = [ ] [[package]] -name = "trezor-client" -version = "0.1.0" +name = "tracing-tracy" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cddb76a030b141d9639470eca2a236f3057a651bba78227cfa77830037a8286" +checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ - "byteorder", - "hex", - "primitive-types", - "protobuf", - "rusb", - "thiserror", - "tracing", + "tracing-core", + "tracing-subscriber", + "tracy-client", ] [[package]] -name = "trie-db" -version = "0.23.1" +name = "tracy-client" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" +checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db" dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "log", - "rustc-hex", - "smallvec 1.11.0", + "loom", + "once_cell", + "tracy-client-sys", ] [[package]] -name = "trie-root" -version = "0.17.0" +name = "tracy-client-sys" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" dependencies = [ - "hash-db", + "cc", + "windows-targets 0.52.6", ] [[package]] -name = "triehash" -version = "0.8.4" +name = "trezor-client" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +checksum = "10636211ab89c96ed2824adc5ec0d081e1080aeacc24c37abb318dcb31dcc779" dependencies = [ - "hash-db", - "rlp", + "byteorder", + "hex", + "protobuf", + "rusb", + "thiserror 1.0.69", + "tracing", ] [[package]] name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "tui" -version = "0.19.0" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" -dependencies = [ - "bitflags 1.3.2", - "cassowary", - "crossterm 0.25.0", - "unicode-segmentation", - "unicode-width", -] +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", - "http", + "data-encoding", + "http 1.2.0", "httparse", "log", - "rand 0.8.5", - "sha-1", - "thiserror", - "url", + "rand", + "sha1", + "thiserror 1.0.69", "utf-8", ] [[package]] name = "tungstenite" -version = "0.18.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" +checksum = "413083a99c579593656008130e29255e54dcaae495be556cc26888f211648c24" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", - "http", + "data-encoding", + "http 1.2.0", "httparse", "log", - "rand 0.8.5", + "rand", + "rustls 0.23.23", + "rustls-pki-types", "sha1", - "thiserror", - "url", + "thiserror 2.0.11", "utf-8", ] [[package]] -name = "tungstenite" -version = "0.19.0" +name = "typed-arena" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "rustls 0.21.6", - "sha1", - "thiserror", - "url", - "utf-8", - "webpki", -] +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "ui" -version = "0.2.0" -dependencies = [ - "crossterm 0.26.1", - "ethers", - "eyre", - "foundry-common", - "foundry-evm", - "revm", - "tui", -] +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -7060,96 +9341,134 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] -name = "uncased" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicase" -version = "2.6.0" +name = "uncased" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "version_check", ] [[package]] -name = "unicode-bidi" -version = "0.3.13" +name = "unicase" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bom" -version = "2.0.2" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e90c70c9f0d4d1ee6d0a7d04aa06cb9bbd53d8cfbdd62a0269a7c2eb640552" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.1.10" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -7157,15 +9476,25 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.15", + "serde", +] + +[[package]] +name = "uuid" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0" +dependencies = [ + "getrandom 0.3.1", "serde", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -7175,36 +9504,51 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "8.2.4" +version = "8.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbc5ad0d9d26b2c49a5ab7da76c3e79d3ee37e7821799f8223fcb8f2f391a2e7" +checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" dependencies = [ "anyhow", - "git2", + "cfg-if", "rustversion", "time", ] [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "vte" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "walkdir" -version = "2.3.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -7220,400 +9564,663 @@ dependencies = [ ] [[package]] -name = "warp" -version = "0.3.5" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba431ef570df1287f7f8b07e376491ad54f84d26ac473489427231e1718e1f69" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "headers", - "http", - "hyper", + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", "log", - "mime", - "mime_guess", - "percent-encoding", - "pin-project", - "rustls-pemfile", - "scoped-tls", - "serde", - "serde_json", - "serde_urlencoded", + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmtimer" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "watchexec" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e682bb1fe9526a6c78ffcfc6bb662ab36c213764fdd173babfbaf05cc56254" +dependencies = [ + "async-priority-channel", + "async-recursion", + "atomic-take", + "futures", + "ignore-files", + "miette", + "nix 0.29.0", + "normalize-path", + "notify", + "once_cell", + "process-wrap", + "project-origins", + "thiserror 1.0.69", "tokio", - "tokio-stream", - "tokio-tungstenite 0.18.0", - "tokio-util", - "tower-service", "tracing", + "watchexec-events", + "watchexec-signals", + "watchexec-supervisor", ] [[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +name = "watchexec-events" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2404ed3aa5e4a8f6139a2ee137926886c9144234c945102143ef9bf65309a751" +dependencies = [ + "nix 0.29.0", + "notify", + "watchexec-signals", +] + +[[package]] +name = "watchexec-signals" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834ddd08f1ce18ea85e4ccbdafaea733851c7dc6afefd50037aea17845a861a" +dependencies = [ + "miette", + "nix 0.29.0", + "thiserror 2.0.11", +] + +[[package]] +name = "watchexec-supervisor" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6026815bdc9653d7820f6499b83ecadacd97a804dfabf2b2c55b061557f5f1f4" +dependencies = [ + "futures", + "nix 0.29.0", + "process-wrap", + "tokio", + "tracing", + "watchexec-events", + "watchexec-signals", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] -name = "wasm-bindgen" -version = "0.2.87" +name = "winapi-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "windows-sys 0.59.0", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.28", - "wasm-bindgen-shared", -] +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "wasm-bindgen-futures" -version = "0.4.37" +name = "windows" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", + "windows-core 0.58.0", + "windows-targets 0.52.6", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.87" +name = "windows" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ - "quote", - "wasm-bindgen-macro-support", + "windows-core 0.59.0", + "windows-targets 0.53.0", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.87" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", - "wasm-bindgen-backend", - "wasm-bindgen-shared", + "windows-targets 0.52.6", ] [[package]] -name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "watchexec" -version = "2.3.0" +name = "windows-core" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b97d05a9305a9aa6a7bedef64cd012ebc9b6f1f5ed0368fb48f0fe58f96988" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "async-priority-channel", - "async-recursion", - "atomic-take", - "clearscreen", - "command-group", - "futures", - "ignore-files", - "miette", - "nix", - "normalize-path", - "notify", - "once_cell", - "project-origins", - "thiserror", - "tokio", - "tracing", - "watchexec-events", - "watchexec-signals", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] -name = "watchexec-events" -version = "1.0.0" +name = "windows-core" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01603bbe02fd75918f010dadad456d47eda14fb8fdcab276b0b4b8362f142ae3" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ - "nix", - "notify", - "watchexec-signals", + "windows-implement 0.59.0", + "windows-interface 0.59.0", + "windows-result 0.3.0", + "windows-strings 0.3.0", + "windows-targets 0.53.0", ] [[package]] -name = "watchexec-signals" -version = "1.0.0" +name = "windows-implement" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2a5df96c388901c94ca04055fcd51d4196ca3e971c5e805bd4a4b61dd6a7e5" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ - "miette", - "nix", - "thiserror", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "web-sys" -version = "0.3.64" +name = "windows-implement" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" dependencies = [ - "js-sys", - "wasm-bindgen", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "webpki" -version = "0.22.0" +name = "windows-interface" +version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ - "ring", - "untrusted", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "webpki-roots" -version = "0.22.6" +name = "windows-interface" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" dependencies = [ - "webpki", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "webpki-roots" -version = "0.23.1" +name = "windows-registry" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ - "rustls-webpki 0.100.1", + "windows-result 0.2.0", + "windows-strings 0.1.0", + "windows-targets 0.52.6", ] [[package]] -name = "which" -version = "4.4.0" +name = "windows-result" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ - "either", - "libc", - "once_cell", + "windows-targets 0.52.6", ] [[package]] -name = "winapi" -version = "0.3.9" +name = "windows-result" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "d08106ce80268c4067c0571ca55a9b4e9516518eaa1a1fe9b37ca403ae1d1a34" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "windows-targets 0.53.0", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows-strings" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] [[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows-strings" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "b888f919960b42ea4e11c2f408fadb55f78a9f236d5eef084103c8ce52893491" dependencies = [ - "winapi", + "windows-targets 0.53.0", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows-sys" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] [[package]] -name = "windows" -version = "0.48.0" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.2", + "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.6", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows-targets 0.48.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" -version = "0.48.2" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1eeca1c172a285ee6c2c84c341ccea837e7c01b12fbb2d0fe3c9e550ce49ec8" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ - "windows_aarch64_gnullvm 0.48.2", - "windows_aarch64_msvc 0.48.2", - "windows_i686_gnu 0.48.2", - "windows_i686_msvc 0.48.2", - "windows_x86_64_gnu 0.48.2", - "windows_x86_64_gnullvm 0.48.2", - "windows_x86_64_msvc 0.48.2", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.2" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10d0c968ba7f6166195e13d593af609ec2e3d24f916f081690695cf5eaffb2f" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.48.2" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571d8d4e62f26d4932099a9efe89660e8bd5087775a2ab5cdd8b747b811f1058" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.48.2" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2229ad223e178db5fbbc8bd8d3835e51e566b8474bfca58d2e6150c48bb723cd" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.48.2" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "600956e2d840c194eedfc5d18f8242bc2e17c7775b6684488af3a9fff6fe3287" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.48.2" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea99ff3f8b49fb7a8e0d305e5aec485bd068c2ba691b6e277d29eaeac945868a" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.2" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1a05a1ece9a7a0d5a7ccf30ba2c33e3a61a30e042ffd247567d1de1d94120d" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.48.2" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d419259aba16b663966e29e6d7c6ecfa0bb8425818bb96f6f1f3c3eb71a6e7b9" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.5.10" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5504cc7644f4b593cbc05c4a55bf9bd4e94b867c3c0bd440934174d50482427d" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.10.1" +name = "winnow" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ - "winapi", + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -7625,9 +10232,9 @@ dependencies = [ "js-sys", "log", "pharos", - "rustc_version 0.4.0", - "send_wrapper 0.6.0", - "thiserror", + "rustc_version 0.4.1", + "send_wrapper", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -7643,28 +10250,91 @@ dependencies = [ ] [[package]] -name = "xml-rs" -version = "0.8.16" +name = "xmlparser" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47430998a7b5d499ccee752b41567bc3afc57e1327dc855b1a2aa44ce29b5fa1" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "yoke" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] [[package]] -name = "yansi" -version = "1.0.0-rc.1" +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "synstructure", +] [[package]] name = "zeroize" -version = "1.6.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -7677,55 +10347,70 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.98", ] [[package]] -name = "zip" -version = "0.6.6" +name = "zerovec" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac 0.12.1", - "pbkdf2 0.11.0", - "sha1", - "time", - "zstd", + "yoke", + "zerofrom", + "zerovec-derive", ] [[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" +name = "zerovec-derive" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ - "zstd-safe", + "proc-macro2", + "quote", + "syn 2.0.98", ] [[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +name = "zip" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" dependencies = [ - "libc", - "zstd-sys", + "arbitrary", + "bzip2", + "crc32fast", + "crossbeam-utils", + "displaydoc", + "flate2", + "indexmap 2.7.1", + "memchr", + "thiserror 2.0.11", + "zopfli", ] [[package]] -name = "zstd-sys" -version = "2.0.8+zstd.1.5.5" +name = "zip-extract" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" dependencies = [ - "cc", - "libc", - "pkg-config", + "log", + "thiserror 1.0.69", + "zip", +] + +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", ] diff --git a/Cargo.toml b/Cargo.toml index 71828bf6c14a7..44e561c79a365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,104 +1,159 @@ [workspace] -members = ["crates/*"] +members = [ + "crates/anvil/", + "crates/anvil/core/", + "crates/anvil/rpc/", + "crates/anvil/server/", + "crates/cast/", + "crates/cheatcodes/", + "crates/cheatcodes/spec/", + "crates/chisel/", + "crates/cli/", + "crates/common/", + "crates/config/", + "crates/debugger/", + "crates/doc/", + "crates/evm/core/", + "crates/evm/coverage/", + "crates/evm/evm/", + "crates/evm/fuzz/", + "crates/evm/traces/", + "crates/fmt/", + "crates/forge/", + "crates/script-sequence/", + "crates/macros/", + "crates/test-utils/", +] resolver = "2" [workspace.package] -version = "0.2.0" +version = "1.0.0" edition = "2021" -rust-version = "1.71" +# Remember to update clippy.toml as well +rust-version = "1.83" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" repository = "https://github.com/foundry-rs/foundry" exclude = ["benches/", "tests/", "test-data/", "testdata/"] +[workspace.lints.clippy] +dbg-macro = "warn" +manual-string-new = "warn" +uninlined-format-args = "warn" +use-self = "warn" +redundant-clone = "warn" +octal-escapes = "allow" +# until is fixed +literal-string-with-formatting-args = "allow" + +[workspace.lints.rust] +rust-2018-idioms = "warn" +# unreachable-pub = "warn" +unused-must-use = "warn" +redundant-lifetimes = "warn" + +[workspace.lints.rustdoc] +all = "warn" + +# Speed up compilation time for dev builds by reducing emitted debug info. +# NOTE: Debuggers may provide less useful information with this setting. +# Uncomment this section if you're using a debugger. [profile.dev] -# Disabling debug info speeds up builds a bunch, -# and we don't rely on it for debugging that much -debug = 0 +# 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 = "none" +strip = "debuginfo" +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 = "full" +split-debuginfo = "unpacked" +strip = false + +[profile.bench] +inherits = "profiling" + +[profile.maxperf] +inherits = "release" +lto = "fat" +codegen-units = 1 -# Speed up tests and dev build +# Speed up tests and dev build. [profile.dev.package] -# evm -revm.opt-level = 3 -revm-primitives.opt-level = 3 +# Solc and artifacts. +foundry-compilers-artifacts-solc.opt-level = 3 +foundry-compilers-core.opt-level = 3 +foundry-compilers.opt-level = 3 +serde_json.opt-level = 3 +serde.opt-level = 3 + +solang-parser.opt-level = 3 +lalrpop-util.opt-level = 3 + +solar-ast.opt-level = 3 +solar-data-structures.opt-level = 3 +solar-interface.opt-level = 3 +solar-parse.opt-level = 3 + +# EVM. +alloy-dyn-abi.opt-level = 3 +alloy-json-abi.opt-level = 3 +alloy-primitives.opt-level = 3 +alloy-sol-type-parser.opt-level = 3 +alloy-sol-types.opt-level = 3 +hashbrown.opt-level = 3 +foldhash.opt-level = 3 +keccak.opt-level = 3 revm-interpreter.opt-level = 3 revm-precompile.opt-level = 3 -tiny-keccak.opt-level = 3 +revm-primitives.opt-level = 3 +revm.opt-level = 3 ruint.opt-level = 3 -primitive-types.opt-level = 3 +sha2.opt-level = 3 +sha3.opt-level = 3 +tiny-keccak.opt-level = 3 +bitvec.opt-level = 3 -# keystores -scrypt.opt-level = 3 +# Fuzzing. +proptest.opt-level = 3 +foundry-evm-fuzz.opt-level = 3 -# forking +# Forking. axum.opt-level = 3 -[profile.release] -opt-level = "s" -lto = "fat" -strip = true -panic = "abort" -codegen-units = 1 +# Keystores. +scrypt.opt-level = 3 + +# Misc. +rayon.opt-level = 3 +regex.opt-level = 3 +regex-syntax.opt-level = 3 +regex-automata.opt-level = 3 +# Override packages which aren't perf-sensitive for faster compilation speed and smaller binary size. [profile.release.package] -# Optimize all non-workspace packages for speed -"*".opt-level = 3 - -# Package overrides -foundry-evm.opt-level = 3 - -ethers-solc.opt-level = 1 -foundry-abi.opt-level = 1 -mdbook.opt-level = 1 -protobuf.opt-level = 1 -rusoto_core.opt-level = 1 -rusoto_credential.opt-level = 1 -rusoto_kms.opt-level = 1 -toml_edit.opt-level = 1 -trezor-client.opt-level = 1 - -# Given that the `"*"` above takes precedence over the defaults for build scripts and macros, we -# have to override all of them to reduce compile times -syn.opt-level = 0 -prettyplease.opt-level = 0 -lalrpop.opt-level = 0 - -ethers-contract-abigen.opt-level = 0 -ethers-contract-derive.opt-level = 0 -async-recursion.opt-level = 0 -miette-derive.opt-level = 0 -strum_macros.opt-level = 0 -enumn.opt-level = 0 -clap_derive.opt-level = 0 -serde_derive.opt-level = 0 -pear_codegen.opt-level = 0 -num_enum_derive.opt-level = 0 -scale-info-derive.opt-level = 0 -parity-scale-codec-derive.opt-level = 0 -time-macros.opt-level = 0 -phf_macros.opt-level = 0 -pin-project-internal.opt-level = 0 -auto_impl.opt-level = 0 -derive_more.opt-level = 0 -rlp-derive.opt-level = 0 -impl-trait-for-tuples.opt-level = 0 -async-trait.opt-level = 0 -tokio-macros.opt-level = 0 -tracing-attributes.opt-level = 0 -futures-macro.opt-level = 0 -thiserror-impl.opt-level = 0 -wasm-bindgen-macro-support.opt-level = 0 -wasm-bindgen-backend.opt-level = 0 - -# Local "release" mode, more optimized than dev but much faster to compile than release -[profile.local] -inherits = "release" -opt-level = 1 -lto = "none" -codegen-units = 16 -# Empty, clears `profile.release.package` -package = {} +alloy-sol-macro-expander.opt-level = "z" +figment.opt-level = "z" +foundry-compilers-artifacts-solc.opt-level = "z" +foundry-config.opt-level = "z" +html5ever.opt-level = "z" +mdbook.opt-level = "z" +prettyplease.opt-level = "z" +protobuf.opt-level = "z" +pulldown-cmark.opt-level = "z" +syn-solidity.opt-level = "z" +syn.opt-level = "z" +trezor-client.opt-level = "z" [workspace.dependencies] anvil = { path = "crates/anvil" } @@ -108,44 +163,190 @@ forge = { path = "crates/forge" } forge-doc = { path = "crates/doc" } forge-fmt = { path = "crates/fmt" } -foundry-abi = { path = "crates/abi" } -foundry-binder = { path = "crates/binder" } +forge-verify = { path = "crates/verify" } +forge-script = { path = "crates/script" } +forge-sol-macro-gen = { path = "crates/sol-macro-gen" } +forge-script-sequence = { path = "crates/script-sequence" } +foundry-cheatcodes = { path = "crates/cheatcodes" } +foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } foundry-cli = { path = "crates/cli" } foundry-common = { path = "crates/common" } +foundry-common-fmt = { path = "crates/common/fmt" } foundry-config = { path = "crates/config" } -foundry-evm = { path = "crates/evm" } +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" } +foundry-evm-traces = { path = "crates/evm/traces" } foundry-macros = { path = "crates/macros" } foundry-test-utils = { path = "crates/test-utils" } -foundry-utils = { path = "crates/utils" } -ui = { path = "crates/ui" } - -ethers = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-addressbook = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-contract = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-providers = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-signers = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-etherscan = { git = "https://github.com/gakonst/ethers-rs", default-features = false } -ethers-solc = { git = "https://github.com/gakonst/ethers-rs", default-features = false } - -chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } -hex = { package = "const-hex", version = "1.6", features = ["hex"] } -itertools = "0.11" -solang-parser = "=0.3.1" - -#[patch."https://github.com/gakonst/ethers-rs"] -#ethers = { path = "../ethers-rs/ethers" } -#ethers-addressbook = { path = "../ethers-rs/ethers-addressbook" } -#ethers-contract = { path = "../ethers-rs/ethers-contract" } -#ethers-contract-abigen = { path = "../ethers-rs/ethers-contract/ethers-contract-abigen" } -#ethers-core = { path = "../ethers-rs/ethers-core" } -#ethers-etherscan = { path = "../ethers-rs/ethers-etherscan" } -#ethers-middleware = { path = "../ethers-rs/ethers-middleware" } -#ethers-providers = { path = "../ethers-rs/ethers-providers" } -#ethers-signers = { path = "../ethers-rs/ethers-signers" } -#ethers-solc = { path = "../ethers-rs/ethers-solc" } +foundry-wallets = { path = "crates/wallets" } +foundry-linking = { path = "crates/linking" } + +# solc & compilation utilities +foundry-block-explorers = { version = "0.11.0", default-features = false } +foundry-compilers = { version = "0.13.3", default-features = false } +foundry-fork-db = "0.11.0" +solang-parser = "=0.3.3" +solar-parse = { version = "=0.1.1", default-features = false } + +## revm +revm = { version = "19.4.0", default-features = false } +revm-primitives = { version = "15.1.0", default-features = false } +revm-inspectors = { version = "0.15.0", features = ["serde"] } + +## alloy +alloy-consensus = { version = "0.11.1", default-features = false } +alloy-contract = { version = "0.11.1", default-features = false } +alloy-eips = { version = "0.11.1", default-features = false } +alloy-genesis = { version = "0.11.1", default-features = false } +alloy-json-rpc = { version = "0.11.1", default-features = false } +alloy-network = { version = "0.11.1", default-features = false } +alloy-provider = { version = "0.11.1", default-features = false } +alloy-pubsub = { version = "0.11.1", default-features = false } +alloy-rpc-client = { version = "0.11.1", default-features = false } +alloy-rpc-types = { version = "0.11.1", default-features = true } +alloy-serde = { version = "0.11.1", default-features = false } +alloy-signer = { version = "0.11.1", default-features = false } +alloy-signer-aws = { version = "0.11.1", default-features = false } +alloy-signer-gcp = { version = "0.11.1", default-features = false } +alloy-signer-ledger = { version = "0.11.1", default-features = false } +alloy-signer-local = { version = "0.11.1", default-features = false } +alloy-signer-trezor = { version = "0.11.1", default-features = false } +alloy-transport = { version = "0.11.1", default-features = false } +alloy-transport-http = { version = "0.11.1", default-features = false } +alloy-transport-ipc = { version = "0.11.1", default-features = false } +alloy-transport-ws = { version = "0.11.1", default-features = false } +alloy-node-bindings = { version = "0.11.1", default-features = false } +alloy-network-primitives = { version = "0.11.1", default-features = false } + +## alloy-core +alloy-dyn-abi = "0.8.18" +alloy-json-abi = "0.8.18" +alloy-primitives = { version = "0.8.18", features = [ + "getrandom", + "rand", + "map-fxhash", + "map-foldhash", +] } +alloy-sol-macro-expander = "0.8.18" +alloy-sol-macro-input = "0.8.18" +alloy-sol-types = "0.8.18" +syn-solidity = "0.8.18" + +alloy-chains = "0.1" +alloy-rlp = "0.3" +alloy-trie = "0.7.0" + +## op-alloy +op-alloy-consensus = "0.10.0" +op-alloy-rpc-types = "0.10.0" + +## cli +anstream = "0.6" +anstyle = "1.0" +terminal_size = "0.4" + +# macros +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0" +async-trait = "0.1" +derive_more = { version = "1.0", features = ["full"] } +thiserror = "2" + +# bench +divan = "0.1" + +# misc +auto_impl = "1" +bytes = "1.8" +walkdir = "2" +prettyplease = "0.2" +base64 = "0.22" +chrono = { version = "0.4", default-features = false, features = [ + "clock", + "std", +] } +axum = "0.7" +color-eyre = "0.6" +comfy-table = "7" +dirs = "6" +dunce = "1" +evm-disassembler = "0.5" +evmole = "0.6" +eyre = "0.6" +figment = "0.10" +futures = "0.3" +hyper = "1.5" +indexmap = "2.6" +itertools = "0.14" +jsonpath_lib = "0.3" +k256 = "0.13" +mesc = "0.3" +num-format = "0.4" +parking_lot = "0.12" +proptest = "1" +rand = "0.8" +rayon = "1" +regex = { version = "1", default-features = false } +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", + "rustls-tls-native-roots", +] } +semver = "1" +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", features = ["arbitrary_precision"] } +similar-asserts = "1.6" +soldeer-commands = "=0.5.2" +strum = "0.26" +tempfile = "3.13" +tikv-jemallocator = "0.6" +tokio = "1" +toml = "0.8" +tower = "0.5" +tower-http = "0.6" +tracing = "0.1" +tracing-subscriber = "0.3" +url = "2" +vergen = { version = "8", default-features = false } +yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } [patch.crates-io] -revm = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" } +## alloy-core +# alloy-dyn-abi = { path = "../../alloy-rs/core/crates/dyn-abi" } +# alloy-json-abi = { path = "../../alloy-rs/core/crates/json-abi" } +# alloy-primitives = { path = "../../alloy-rs/core/crates/primitives" } +# alloy-sol-macro = { path = "../../alloy-rs/core/crates/sol-macro" } +# alloy-sol-macro-expander = { path = "../../alloy-rs/core/crates/sol-macro-expander" } +# alloy-sol-macro-input = { path = "../../alloy-rs/core/crates/sol-macro-input" } +# alloy-sol-type-parser = { path = "../../alloy-rs/core/crates/sol-type-parser" } +# alloy-sol-types = { path = "../../alloy-rs/core/crates/sol-types" } +# syn-solidity = { path = "../../alloy-rs/core/crates/syn-solidity" } + +## alloy +# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-signer-gcp = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } diff --git a/Dockerfile b/Dockerfile index 4d048ab603fa3..08b997d369050 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.4 -FROM alpine:3.16 as build-environment +FROM alpine:3.21 as build-environment ARG TARGETARCH WORKDIR /opt @@ -15,8 +15,11 @@ RUN [[ "$TARGETARCH" = "arm64" ]] && echo "export CFLAGS=-mno-outline-atomics" > WORKDIR /opt/foundry COPY . . +# see +RUN git update-index --force-write-index + 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 \ + source $HOME/.profile && cargo build --release --features cast/aws-kms,forge/aws-kms \ && mkdir out \ && mv target/release/forge out/forge \ && mv target/release/cast out/cast \ @@ -27,9 +30,9 @@ RUN --mount=type=cache,target=/root/.cargo/registry --mount=type=cache,target=/r && strip out/chisel \ && strip out/anvil; -FROM docker.io/frolvlad/alpine-glibc:alpine-3.16_glibc-2.34 as foundry-client +FROM alpine:3.21 as foundry-client -RUN apk add --no-cache linux-headers git +RUN apk add --no-cache linux-headers git gcompat libstdc++ COPY --from=build-environment /opt/foundry/out/forge /usr/local/bin/forge COPY --from=build-environment /opt/foundry/out/cast /usr/local/bin/cast diff --git a/Dockerfile.cross b/Dockerfile.cross new file mode 100644 index 0000000000000..3efdde14ae7c0 --- /dev/null +++ b/Dockerfile.cross @@ -0,0 +1,28 @@ +# This image is meant to enable cross-architecture builds. +# It assumes the foundry binaries have already been compiled for `$TARGETPLATFORM` and are +# locatable in `./dist/bin/$TARGETARCH` +FROM ubuntu:22.04 + +# Filled by docker buildx +ARG TARGETARCH + +RUN apt update && apt install -y git + +COPY ./dist/bin/$TARGETARCH/* /usr/local/bin/ + +RUN groupadd -g 1000 foundry && \ + useradd -m -u 1000 -g foundry foundry + +USER foundry + +ENTRYPOINT ["/bin/sh", "-c"] + +LABEL org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="Foundry" \ + org.label-schema.description="Foundry" \ + org.label-schema.url="https://getfoundry.sh" \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vcs-url="https://github.com/foundry-rs/foundry.git" \ + org.label-schema.vendor="Foundry-rs" \ + org.label-schema.version=$VERSION \ + org.label-schema.schema-version="1.0" diff --git a/FUNDING.json b/FUNDING.json new file mode 100644 index 0000000000000..52baedf3bfc3c --- /dev/null +++ b/FUNDING.json @@ -0,0 +1,7 @@ +{ + "drips": { + "ethereum": { + "ownedBy": "0x86308c59a6005d012C51Eef104bBc21786aC5D2E" + } + } +} diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 3c4dfe081ce47..a7b8b5e6a6dc5 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -184,4 +184,4 @@ comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier - identification within third-party archives. + identification within third-party archives. \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT index 8dce0ba55e1c8..38bfa706247e8 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000..15c7798a2cefb --- /dev/null +++ b/Makefile @@ -0,0 +1,119 @@ +# Heavily inspired by: +# - Lighthouse: https://github.com/sigp/lighthouse/blob/693886b94176faa4cb450f024696cb69cda2fe58/Makefile +# - Reth: https://github.com/paradigmxyz/reth/blob/1f642353ca083b374851ab355b5d80207b36445c/Makefile +.DEFAULT_GOAL := help + +# Cargo profile for builds. +PROFILE ?= dev +# The docker image name +DOCKER_IMAGE_NAME ?= ghcr.io/foundry-rs/foundry:latest +BIN_DIR = dist/bin +CARGO_TARGET_DIR ?= target + +# List of features to use when building. Can be overridden via the environment. +# No jemalloc on Windows +ifeq ($(OS),Windows_NT) + FEATURES ?= aws-kms cli asm-keccak +else + FEATURES ?= jemalloc aws-kms cli asm-keccak +endif + +##@ Help + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Build + +.PHONY: build +build: ## Build the project. + cargo build --features "$(FEATURES)" --profile "$(PROFILE)" + +# The following commands use `cross` to build a cross-compile. +# +# These commands require that: +# +# - `cross` is installed (`cargo install cross`). +# - Docker is running. +# - The current user is in the `docker` group. +# +# The resulting binaries will be created in the `target/` directory. +build-%: + cross build --target $* --features "$(FEATURES)" --profile "$(PROFILE)" + +.PHONY: docker-build-push +docker-build-push: docker-build-prepare ## Build and push a cross-arch Docker image tagged with DOCKER_IMAGE_NAME. + $(MAKE) build-x86_64-unknown-linux-gnu + mkdir -p $(BIN_DIR)/amd64 + for bin in anvil cast chisel forge; do \ + cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/$$bin $(BIN_DIR)/amd64/; \ + done + + $(MAKE) build-aarch64-unknown-linux-gnu + mkdir -p $(BIN_DIR)/arm64 + for bin in anvil cast chisel forge; do \ + cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/$$bin $(BIN_DIR)/arm64/; \ + done + + docker buildx build --file ./Dockerfile.cross . \ + --platform linux/amd64,linux/arm64 \ + $(foreach tag,$(shell echo $(DOCKER_IMAGE_NAME) | tr ',' ' '),--tag $(tag)) \ + --provenance=false \ + --push + +.PHONY: docker-build-prepare +docker-build-prepare: ## Prepare the Docker build environment. + docker run --privileged --rm tonistiigi/binfmt:qemu-v7.0.0-28 --install amd64,arm64 + @if ! docker buildx inspect cross-builder &> /dev/null; then \ + echo "Creating a new buildx builder instance"; \ + docker buildx create --use --driver docker-container --name cross-builder; \ + else \ + echo "Using existing buildx builder instance"; \ + docker buildx use cross-builder; \ + fi + +##@ Other + +.PHONY: clean +clean: ## Clean the project. + cargo clean + +## Linting + +fmt: ## Run all formatters. + cargo +nightly fmt + ./.github/scripts/format.sh --check + +lint-foundry: + RUSTFLAGS="-Dwarnings" cargo clippy --workspace --all-targets --all-features + +lint-codespell: ensure-codespell + codespell --skip "*.json" + +ensure-codespell: + @if ! command -v codespell &> /dev/null; then \ + echo "codespell not found. Please install it by running the command `pip install codespell` or refer to the following link for more information: https://github.com/codespell-project/codespell" \ + exit 1; \ + fi + +lint: ## Run all linters. + make fmt && \ + make lint-foundry && \ + make lint-codespell + +## Testing + +test-foundry: + cargo nextest run -E 'kind(test) & !test(/\b(issue|ext_integration)/)' + +test-doc: + cargo test --doc --workspace + +test: ## Run all tests. + make test-foundry && \ + make test-doc + +pr: ## Run all tests and linters in preparation for a PR. + make lint && \ + make test diff --git a/README.md b/README.md index 8f205f13d9222..4fa5510481304 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,312 @@ -Foundry logo +
+ Foundry banner -## Foundry +  -![Github Actions][gha-badge] [![Telegram Chat][tg-badge]][tg-url] [![Telegram Support][tg-support-badge]][tg-support-url] +[![Github Actions][gha-badge]][gha-url] [![Telegram Chat][tg-badge]][tg-url] [![Telegram Support][tg-support-badge]][tg-support-url] +![Foundry](https://img.shields.io/badge/Foundry-grey?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAElElEQVR4nH1VUUhUaRg9984YdzBpkqR0Z210rIESIXSabEbcHgydrpNRRj00kWaztj0U1MOW0MOIbD300IvLMqBpMTGYxdoqyoRNDUESBDWwUuPugCSSsTM7u0Oj1/+efdiMcmnP2/fDd77D4f/OB6xCa2urQZbllVICYGtqanK1tLS4AdgAyAAgyzJaW1sNq/ulT4twOGw4fPiwAGDp7Ow8VV1d7bVarRWxWCw/k8mgsbExm0wmZ+Lx+M/Xr1//CcAsSVmSJH01McLhsAEAnE5nx+Tk5B/xeJxOp5N9fX2sqqqixWLhnTt36HA4GIvFGI1GU3V1df5Pe/9D1t7eHkgkEuzo6GBPT49WWloq7Ha7fujQITocDu7atUs3m83i6tWr2okTJ/jixQuePn265zPScDhskGUZe/fubXv8+DFv3rypbdiwQaxbt46RSIT79u3j0NAQb926RVVVOT4+TqvVyvz8fD0YDC5NTk6ysbHxlCRJ/5KSlAAURyKRTFNTkwAg7t69S5/Px76+Pq7GyMgI9+/fz9HRUQIQO3bsEKOjo38DsJCUJADw+/0BVVW7otHo8ps3b4yvXr3CxMQETCYTTCYTNE0DAOTl5SGXy0FRFOzZswdmsxkVFRXLNTU1xmg0+kNvb+/3AGAcGBiI7969Wwcg6urq+OTJE967d49btmzh9PT0R3WJRIKBQIDBYJBTU1NsaGggAGGz2fTe3t5fAeQZAWwuLi4uP3nypOT1emEwGFBeXo7a2losLCygoaEB/f39MJlMCIVCkCQJBw8ehNVqhcfjQXNzs1RSUiKtX7++DEAZqqqq3KFQiABYUFDAM2fOkCQXFxdJkvfv32dhYSG9Xi+vXbvG2dnZj4oDgQCLioqoKAqHhobodDq/Mc7NzUklJSUIBoOw2WzYtm0blpeXsWbNGkxMTODp06doa2vD4OAgNm7cCIvFApLQdR3nzp3Dzp078fLlSxQVFeHdu3cAgIpHjx69/zBUX5k+MDBAt9vNY8eOsbu7m6lUigcOHKDL5WImkyHJz9TGYrEcALsMIPn69esZTdMIgM+ePUNXVxdu376NsrIyuN1uXLp0CWazGcPDw3C5XFBVFWfPnkVNTQ18Pp+ezWY5MzPzO4DfAABHjhzpJslUKqVdvHiR4+PjbG9vZy6XI0kuLS0xmUxSCEGS9Pv9LC0tpdFoZGVlpSaEoM/nuwIAKx/7q5GRkb9CoZBQVVWcP3+ez58/J0mm02kODg7ywoULjMViTKfTtNvtXLt2LTdt2qTncrnlsbGxLICvSUqfrl5HJBLh1NTUkhBCJ8mFhQX29/dTVVUWFBTwwYMH1HWdly9fpqIoeiKRWJqfn2d1dXWnLMuf7zMAHD16tGd+fn7FZy2bzYrKykodAAFQVVV9cXFRkNTevn3Lubk5trS0XPnfxHE4HN8ODw+nV/yanp6mx+Ohx+P5aIMQgmNjY3/W1tZ+t5rsSwG7+fjx4/76+vrm7du32woLC00AkE6n38fj8ZmHDx/+cuPGjR8BJL8YsCtYdQIMALYqilKvKEo9APuHty+egH8A3GfFDJXmxmMAAAAASUVORK5CYII%3D&link=https%3A%2F%2Fbook.getfoundry.sh%2F) [gha-badge]: https://img.shields.io/github/actions/workflow/status/foundry-rs/foundry/test.yml?branch=master +[gha-url]: https://github.com/foundry-rs/foundry/actions [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_rs [tg-url]: https://t.me/foundry_rs [tg-support-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=support&style=flat-square&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Ffoundry_support [tg-support-url]: https://t.me/foundry_support -**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** +**[Install](https://book.getfoundry.sh/getting-started/installation)** +| [User Book][foundry-book] +| [Developer Docs](./docs/dev/README.md) +| [Contributing](./CONTRIBUTING.md) + +
+ +--- + +### Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust. Foundry consists of: -- [**Forge**](./crates/forge): Ethereum testing framework (like Truffle, Hardhat and DappTools). -- [**Cast**](./crates/cast): Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. -- [**Anvil**](./crates/anvil): Local Ethereum node, akin to Ganache, Hardhat Network. -- [**Chisel**](./crates/chisel): Fast, utilitarian, and verbose solidity REPL. +- [**Forge**](#forge): Build, test, fuzz, debug and deploy [Solidity][solidity] contracts, like Hardhat, Brownie, Ape. +- [**Cast**](#cast): A Swiss Army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- [**Anvil**](#anvil): Fast local Ethereum development node, akin to Hardhat Network, Tenderly. +- [**Chisel**](#chisel): Fast, utilitarian, and verbose Solidity REPL. + +**Need help getting started with Foundry? Read the [📖 Foundry Book][foundry-book]!** + +![Demo](.github/assets/demo.gif) + +## Features + +- **High-Performance Compilation** + + - **Fast and Flexible**: Automatically detects and installs the required Solidity compiler version. + - **Solidity and Vyper Support**: Fully supports both Solidity and Vyper out-of-the-box. + - **Incremental Compilation**: Re-compiles only changed files, saving time. + - **Parallelized Pipeline**: Leverages multi-core systems for ultra-fast builds. + - **Broad Compatibility**: Supports non-standard directory structures, including [Hardhat repos](https://twitter.com/gakonst/status/1461289225337421829). + +- **Advanced Testing** -**Need help getting started with Foundry? Read the [📖 Foundry Book][foundry-book] (WIP)!** + - **No Context Switching**: Write tests directly in Solidity. + - **Fuzz Testing**: Quickly identify edge cases with input shrinking and counter-example generation. + - **Invariant Testing**: Ensure complex system properties hold across a wide range of inputs. + - **Debugging Made Easy**: Use [forge-std](https://github.com/foundry-rs/forge-std)'s `console.sol` for flexible debug logging. + - **Interactive Debugger**: Step through your Solidity code with Foundry's interactive debugger, making it easy to pinpoint issues. -![Demo](.github/demo.gif) +- **Powerful Runtime Features** + + - **RPC Forking**: Fast and efficient remote RPC forking backed by [Alloy][alloy]. + - **Lightweight & Portable**: No dependency on Nix or other package managers for installation. + +- **Streamlined CI/CD** + + - **Optimized CI**: Accelerate builds, run tests and execute scripts using [Foundry's GitHub action][foundry-gha]. ## Installation -See the [installation guide](https://book.getfoundry.sh/getting-started/installation) in the book. +Getting started is very easy: + +Install `foundryup`: + +``` +curl -L https://foundry.paradigm.xyz | bash +``` + +Next, run `foundryup`. + +It will automatically install the latest version of the precompiled binaries: [`forge`](#forge), [`cast`](#cast), [`anvil`](#anvil), and [`chisel`](#chisel). + +``` +foundryup +``` + +**Done!** + +For additional details see the [installation guide](https://book.getfoundry.sh/getting-started/installation) in the [Foundry Book][foundry-book]. If you're experiencing any issues while installing, check out [Getting Help](#getting-help) and the [FAQ](https://book.getfoundry.sh/faq). +## How Fast? + +Forge is quite fast at both compiling (leveraging `solc` with [foundry-compilers]) and testing. + +See the benchmarks below. Older benchmarks against [DappTools][dapptools] can be found in the [v0.2.0 announcement post][benchmark-post] and in the [Convex Shutdown Simulation][convex] repository. + +### Testing Benchmarks + +| Project | Type | [Forge 1.0][foundry-1.0] | [Forge 0.2][foundry-0.2] | DappTools | Speedup | +| --------------------------------------------- | -------------------- | ------------------------ | ------------------------ | --------- | -------------- | +| [vectorized/solady][solady] | Unit / Fuzz | 0.9s | 2.3s | - | 2.6x | +| [morpho-org/morpho-blue][morpho-blue] | Invariant | 0.7s | 1m43s | - | 147.1x | +| [morpho-org/morpho-blue-oracles][morpho-blue] | Integration (Cold) | 6.1s | 6.3s | - | 1.04x | +| [morpho-org/morpho-blue-oracles][morpho-blue] | Integration (Cached) | 0.6s | 0.9s | - | 1.50x | +| [transmissions11/solmate][solmate] | Unit / Fuzz | 2.7s | 2.8s | 6m34s | 1.03x / 140.0x | +| [reflexer-labs/geb][geb] | Unit / Fuzz | 0.2s | 0.4s | 23s | 2.0x / 57.5x | + +_In the above benchmarks, compilation was always skipped_ + +**Takeaway: Forge dramatically outperforms the competition, delivering blazing-fast execution speeds while continuously expanding its robust feature set.** + +### Compilation Benchmarks + +
+ + + + + + + + + + +  + +
+ +**Takeaway: Forge compilation is consistently faster than Hardhat by a factor of `2.1x` to `5.2x`, depending on the amount of caching involved.** + ## Forge -### Features +Forge helps you build, test, fuzz, debug and deploy Solidity contracts. + +The best way to understand Forge is to simply try it (in less than 30 seconds!). + +First, let's initialize a new `counter` example repository: + +```sh +forge init counter +``` + +Next `cd` into `counter` and build : -- **Fast & flexible compilation pipeline** - - Automatic Solidity compiler version detection & installation (under `~/.svm`) - - **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]. +```sh +forge build +``` -### How Fast? +```console +[⠊] Compiling... +[⠔] Compiling 27 files with Solc 0.8.28 +[⠒] Solc 0.8.28 finished in 452.13ms +Compiler run successful! +``` -Forge is quite fast at both compiling (leveraging [ethers-solc][ethers-solc]) and testing. +Let's [test](https://book.getfoundry.sh/forge/tests#tests) our contracts: -See the benchmarks below. More benchmarks can be found in the [v0.2.0 announcement post][benchmark-post] and in the [Convex Shutdown Simulation][convex] repository. +```sh +forge test +``` -**Testing Benchmarks** +```console +[⠊] Compiling... +No files changed, compilation skipped -| Project | Forge | DappTools | Speedup | -| ---------------------------------- | ----- | --------- | ------- | -| [transmissions11/solmate][solmate] | 2.8s | 6m34s | 140x | -| [reflexer-labs/geb][geb] | 0.4s | 23s | 57.5x | -| [Rari-Capital/vaults][vaults] | 0.28s | 6.5s | 23x | +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 31121, ~: 31277) +[PASS] test_Increment() (gas: 31293) +Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.35ms (4.86ms CPU time) -_Note: In the above benchmarks, compilation was always skipped_ +Ran 1 test suite in 5.91ms (5.35ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests) +``` -**Compilation Benchmarks** +Finally, let's run our deployment script: -Compilation benchmarks +```sh +forge script script/Counter.s.sol +``` -**Takeaway: Forge compilation is consistently faster by a factor of 1.7-11.3x, depending on the amount of caching involved.** +```console +[⠊] Compiling... +No files changed, compilation skipped +Script ran successfully. +Gas used: 109037 + +If you wish to simulate on-chain transactions pass a RPC URL. +``` + +Run `forge --help` to explore the full list of available subcommands and their usage. + +More documentation can be found in the [forge][foundry-book-forge] section of the Foundry Book. ## Cast -Cast is a swiss army knife for interacting with Ethereum applications from the command line. +Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. -More documentation can be found in the [cast package](./crates/cast). +Here are a few examples of what you can do: -## Configuration +**Check the latest block on Ethereum Mainnet**: + +```sh +cast block-number --rpc-url https://eth.merkle.io +``` + +**Check the Ether balance of `vitalik.eth`** + +```sh +cast balance vitalik.eth --ether --rpc-url https://eth.merkle.io +``` + +**Replay and trace a transaction** + +```sh +cast run 0x9c32042f5e997e27e67f82583839548eb19dc78c4769ad6218657c17f2a5ed31 --rpc-url https://eth.merkle.io +``` + +Optionally, pass `--etherscan-api-key ` to decode transaction traces using verified source maps, providing more detailed and human-readable information. + +--- + +Run `cast --help` to explore the full list of available subcommands and their usage. + +More documentation can be found in the [cast][foundry-book-cast] section of the Foundry Book. + +## Anvil + +Anvil is a fast local Ethereum development node. + +Let's fork Ethereum mainnet at the latest block: + +```sh +anvil --fork-url https://eth.merkle.io +``` + +You can use those same `cast` subcommands against your `anvil` instance: -### Using `foundry.toml` +```sh +cast block-number +``` -Foundry is designed to be very configurable. You can configure Foundry using a file called [`foundry.toml`](./crates/config) in the root of your project, or any other parent directory. See [config package](./crates/config/README.md#all-options) for all available options. +--- -Configuration can be arbitrarily namespaced by profiles. The default profile is named `default` (see ["Default Profile"](./crates/config/README.md#default-profile)). +Run `anvil --help` to explore the full list of available features and their usage. -You can select another profile using the `FOUNDRY_PROFILE` environment variable. You can also override parts of your configuration using `FOUNDRY_` or `DAPP_` prefixed environment variables, like `FOUNDRY_SRC`. +More documentation can be found in the [anvil][foundry-book-anvil] section of the Foundry Book. -`forge init` creates a basic, extendable `foundry.toml` file. +## Chisel -To see your current configuration, run `forge config`. To see only basic options (as set with `forge init`), run `forge config --basic`. This can be used to create a new `foundry.toml` file with `forge config --basic > foundry.toml`. +Chisel is a fast, utilitarian, and verbose Solidity REPL. -By default `forge config` shows the currently selected foundry profile and its values. It also accepts the same arguments as `forge build`. +To use Chisel, simply type `chisel`. -### DappTools Compatibility +```sh +chisel +``` -You can re-use your `.dapprc` environment variables by running `source .dapprc` before using a Foundry tool. +From here, start writing Solidity code! Chisel will offer verbose feedback on each input. -### Additional Configuration +Create a variable `a` and query it: -You can find additional setup and configurations guides in the [Foundry Book][foundry-book]: +```console +➜ uint256 a = 123; +➜ a +Type: uint256 +├ Hex: 0x7b +├ Hex (full word): 0x000000000000000000000000000000000000000000000000000000000000007b +└ Decimal: 123 +``` -- [Setting up VSCode][vscode-setup] -- [Shell autocompletions][shell-setup] +Finally, run `!source` to see `a` was applied: + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Vm} from "forge-std/Vm.sol"; + +contract REPL { + Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + /// @notice REPL contract entry point + function run() public { + uint256 a = 123; + } +} +``` + +--- + +Run `chisel --help` to explore the full list of available features and their usage. + +More documentation can be found in the [chisel][foundry-book-chisel] section of the Foundry Book. + +## Configuration + +Foundry is highly configurable, allowing you to tailor it to your needs. Configuration is managed via a file called [`foundry.toml`](./crates/config) located in the root of your project or any parent directory. For a full list of configuration options, refer to the [config package documentation](./crates/config/README.md#all-options). + +**Profiles and Namespaces** + +- Configuration can be organized into **profiles**, which are arbitrarily namespaced for flexibility. +- The default profile is named `default`. Learn more in the [Default Profile section](./crates/config/README.md#default-profile). +- To select a different profile, set the `FOUNDRY_PROFILE` environment variable. +- Override specific settings using environment variables prefixed with `FOUNDRY_` (e.g., `FOUNDRY_SRC`). + +--- + +You can find additional [setup and configurations guides][foundry-book-config] in the [Foundry Book][foundry-book] and in the [config crate](./crates/config/README.md): + +- [Configuring with `foundry.toml`](https://book.getfoundry.sh/config/) +- [Setting up VSCode][vscode-setup] +- [Shell autocompletions][shell-setup] ## Contributing @@ -108,31 +314,54 @@ See our [contributing guidelines](./CONTRIBUTING.md). ## Getting Help -First, see if the answer to your question can be found in [book][foundry-book], or in the relevant crate. +First, see if the answer to your question can be found in the [Foundy Book][foundry-book], or in the relevant crate. If the answer is not there: -- Join the [support Telegram][tg-support-url] to get help, or -- Open a [discussion](https://github.com/foundry-rs/foundry/discussions/new) with your question, or -- Open an issue with [the bug](https://github.com/foundry-rs/foundry/issues/new) +- Join the [support Telegram][tg-support-url] to get help, or +- Open a [discussion](https://github.com/foundry-rs/foundry/discussions/new) with your question, or +- Open an issue with [the bug](https://github.com/foundry-rs/foundry/issues/new) If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/foundry_rs) to chat with us about the development of Foundry! +## License + +Licensed under either of [Apache License](./LICENSE-APACHE), Version +2.0 or [MIT License](./LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in these crates by you, as defined in the Apache-2.0 license, +shall be dual licensed as above, without any additional terms or conditions. + ## Acknowledgements -- Foundry is a clean-room rewrite of the testing framework [DappTools](https://github.com/dapphub/dapptools). None of this would have been possible without the DappHub team's work over the years. -- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc](https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. -- [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. -- [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. -- All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs) & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. +- Foundry is a clean-room rewrite of the testing framework [DappTools][dapptools]. None of this would have been possible without the DappHub team's work over the years. +- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] (now [foundry-compilers]) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. +- [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. +- [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. +- All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs), [alloy][alloy] & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. +[solidity]: https://soliditylang.org/ [foundry-book]: https://book.getfoundry.sh +[foundry-book-config]: https://book.getfoundry.sh/config/ +[foundry-book-forge]: https://book.getfoundry.sh/reference/forge/ +[foundry-book-anvil]: https://book.getfoundry.sh/reference/anvil/ +[foundry-book-cast]: https://book.getfoundry.sh/reference/cast/ +[foundry-book-chisel]: https://book.getfoundry.sh/reference/chisel/ [foundry-gha]: https://github.com/foundry-rs/foundry-toolchain +[foundry-compilers]: https://github.com/foundry-rs/compilers [ethers-solc]: https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/ +[solady]: https://github.com/Vectorized/solady +[openzeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/release-v5.1 +[morpho-blue]: https://github.com/morpho-org/morpho-blue +[foundry-compilers]: https://github.com/foundry-rs/compilers [solmate]: https://github.com/transmissions11/solmate/ [geb]: https://github.com/reflexer-labs/geb -[vaults]: https://github.com/rari-capital/vaults [benchmark-post]: https://www.paradigm.xyz/2022/03/foundry-02#blazing-fast-compilation--testing [convex]: https://github.com/mds1/convex-shutdown-simulation [vscode-setup]: https://book.getfoundry.sh/config/vscode.html [shell-setup]: https://book.getfoundry.sh/config/shell-autocompletion.html +[foundry-0.2]: https://github.com/foundry-rs/foundry/releases/tag/nightly-5b7e4cb3c882b28f3c32ba580de27ce7381f415a +[foundry-1.0]: https://github.com/foundry-rs/foundry/releases/tag/nightly-59f354c179f4e7f6d7292acb3d068815c79286d1 +[dapptools]: https://github.com/dapphub/dapptools +[alloy]: https://github.com/alloy-rs/alloy diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000..bea27ad1140c0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Contact [security@ithaca.xyz](mailto:security@ithaca.xyz). diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000000000..69dfa469ce10b --- /dev/null +++ b/clippy.toml @@ -0,0 +1,13 @@ +msrv = "1.83" + +# `bytes::Bytes` is included by default and `alloy_primitives::Bytes` is a wrapper around it, +# so it is safe to ignore it as well. +ignore-interior-mutability = ["bytes::Bytes", "alloy_primitives::Bytes"] + +disallowed-macros = [ + # See `foundry_common::shell`. + { path = "std::print", reason = "use `sh_print` or similar macros instead" }, + { path = "std::eprint", reason = "use `sh_eprint` or similar macros instead" }, + { path = "std::println", reason = "use `sh_println` or similar macros instead" }, + { path = "std::eprintln", reason = "use `sh_eprintln` or similar macros instead" }, +] diff --git a/crates/abi/Cargo.toml b/crates/abi/Cargo.toml deleted file mode 100644 index e6d53e9989aed..0000000000000 --- a/crates/abi/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "foundry-abi" -description = "Foundry's Solidity ABI bindings" -exclude = ["abi", "build.rs"] - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[build-dependencies] -ethers-contract-abigen.workspace = true -eyre = "0.6" -syn = "2.0" - -[dependencies] -foundry-macros.workspace = true - -ethers-core.workspace = true -ethers-contract = { workspace = true, features = ["abigen"] } -ethers-providers.workspace = true diff --git a/crates/abi/README.md b/crates/abi/README.md deleted file mode 100644 index 908d463f4a141..0000000000000 --- a/crates/abi/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# foundry-abi - -Contains automatically-generated Rust bindings from Solidity ABI. - -Additional bindings can be generated by doing the following: - -1. add an ABI file in the [`abi` directory](./abi/), using the [ethers-js ABI formats](https://docs.ethers.org/v5/api/utils/abi/formats); -2. update the [build script](./build.rs)'s `MultiAbigen::new` call; -3. build the crate once with `cargo build -p foundry-abi`, generating the bindings for the first time; -4. export the newly-generated bindings at the root of the crate, in [`lib.rs`](./src/lib.rs). - -New cheatcodes can be added by doing the following: - -1. add its Solidity definition(s) in [`HEVM.sol`](./abi/HEVM.sol), bindings should regenerate automatically; -2. implement it in [`foundry-evm`](../evm/src/executor/inspector/cheatcodes/); -3. update the [`Vm.sol`](../../testdata/cheats/Vm.sol) test interface; -4. add tests in [`testdata`](../../testdata/cheats/); -5. open a PR to [`forge-std`](https://github.com/foundry-rs/forge-std) to add it to the `Vm` interface. diff --git a/crates/abi/abi/Console.sol b/crates/abi/abi/Console.sol deleted file mode 100644 index 28b9f559210fb..0000000000000 --- a/crates/abi/abi/Console.sol +++ /dev/null @@ -1,22 +0,0 @@ -event log(string) -event logs (bytes) -event log_address (address) -event log_bytes32 (bytes32) -event log_int (int) -event log_uint (uint) -event log_bytes (bytes) -event log_string (string) -event log_array (uint256[] val) -event log_array (int256[] val) -event log_array (address[] val) -event log_named_address (string key, address val) -event log_named_bytes32 (string key, bytes32 val) -event log_named_decimal_int (string key, int val, uint decimals) -event log_named_decimal_uint (string key, uint val, uint decimals) -event log_named_int (string key, int val) -event log_named_uint (string key, uint val) -event log_named_bytes (string key, bytes val) -event log_named_string (string key, string val) -event log_named_array (string key, uint256[] val) -event log_named_array (string key, int256[] val) -event log_named_array (string key, address[] val) diff --git a/crates/abi/abi/HEVM.sol b/crates/abi/abi/HEVM.sol deleted file mode 100644 index 3e020e6640e49..0000000000000 --- a/crates/abi/abi/HEVM.sol +++ /dev/null @@ -1,230 +0,0 @@ -struct Log { bytes32[] topics; bytes data; } -struct Rpc { string name; string url; } -struct DirEntry { string errorMessage; string path; uint64 depth; bool isDir; bool isSymlink; } -struct FsMetadata { bool isDir; bool isSymlink; uint256 length; bool readOnly; uint256 modified; uint256 accessed; uint256 created; } -struct Wallet { address addr; uint256 publicKeyX; uint256 publicKeyY; uint256 privateKey; } - -allowCheatcodes(address) - -ffi(string[])(bytes) - -breakpoint(string) -breakpoint(string,bool) - -roll(uint256) -warp(uint256) -difficulty(uint256) -prevrandao(bytes32) -fee(uint256) -coinbase(address) -store(address,bytes32,bytes32) -load(address,bytes32)(bytes32) - -setEnv(string,string) -envBool(string)(bool) -envUint(string)(uint256) -envInt(string)(int256) -envAddress(string)(address) -envBytes32(string)(bytes32) -envString(string)(string) -envBytes(string)(bytes) -envBool(string,string)(bool[]) -envUint(string,string)(uint256[]) -envInt(string,string)(int256[]) -envAddress(string,string)(address[]) -envBytes32(string,string)(bytes32[]) -envString(string,string)(string[]) -envBytes(string,string)(bytes[]) -envOr(string,bool)(bool) -envOr(string,uint256)(uint256) -envOr(string,int256)(int256) -envOr(string,address)(address) -envOr(string,bytes32)(bytes32) -envOr(string,string)(string) -envOr(string,bytes)(bytes) -envOr(string,string,bool[])(bool[]) -envOr(string,string,uint256[])(uint256[]) -envOr(string,string,int256[])(int256[]) -envOr(string,string,address[])(address[]) -envOr(string,string,bytes32[])(bytes32[]) -envOr(string,string,string[])(string[]) -envOr(string,string,bytes[])(bytes[]) - -addr(uint256)(address) -sign(uint256,bytes32)(uint8,bytes32,bytes32) -deriveKey(string,uint32)(uint256) -deriveKey(string,string,uint32)(uint256) -deriveKey(string,uint32,string)(uint256) -deriveKey(string,string,uint32,string)(uint256) -rememberKey(uint256)(address) - -createWallet(string)(Wallet) -createWallet(uint256)(Wallet) -createWallet(uint256,string)(Wallet) -sign(Wallet,bytes32)(uint8,bytes32,bytes32) -getNonce(Wallet)(uint64) - -prank(address) -prank(address,address) -readCallers()(uint256,address,address) -startPrank(address) -startPrank(address,address) -stopPrank() - -deal(address,uint256) -etch(address,bytes) -expectRevert() -expectRevert(bytes) -expectRevert(bytes4) -record() -accesses(address)(bytes32[],bytes32[]) -skip(bool) - -recordLogs() -getRecordedLogs()(Log[]) - -expectEmit() -expectEmit(address) -expectEmit(bool,bool,bool,bool) -expectEmit(bool,bool,bool,bool,address) - -mockCall(address,bytes,bytes) -mockCall(address,uint256,bytes,bytes) -mockCallRevert(address,bytes,bytes) -mockCallRevert(address,uint256,bytes,bytes) -clearMockedCalls() - -expectCall(address,bytes) -expectCall(address,bytes,uint64) -expectCall(address,uint256,bytes) -expectCall(address,uint256,bytes,uint64) -expectCall(address,uint256,uint64,bytes) -expectCall(address,uint256,uint64,bytes,uint64) -expectCallMinGas(address,uint256,uint64,bytes) -expectCallMinGas(address,uint256,uint64,bytes,uint64) -expectSafeMemory(uint64,uint64) -expectSafeMemoryCall(uint64,uint64) - -getCode(string) -getDeployedCode(string) -label(address,string) -getLabel(address)(string) -assume(bool) -setNonce(address,uint64) -getNonce(address) -resetNonce(address) -setNonceUnsafe(address,uint64) -chainId(uint256) -txGasPrice(uint256) - -broadcast() -broadcast(address) -broadcast(uint256) -startBroadcast() -startBroadcast(address) -startBroadcast(uint256) -stopBroadcast() - -projectRoot()(string) -openFile(string) -readFile(string)(string) -readFileBinary(string)(bytes) -readLine(string)(string) -writeFile(string,string) -writeFileBinary(string,bytes) -writeLine(string,string) -copyFile(string,string) -closeFile(string) -removeFile(string) -createDir(string, bool) -removeDir(string, bool) -readDir(string)(DirEntry[]) -readDir(string, uint64)(DirEntry[]) -readDir(string, uint64, bool)(DirEntry[]) -readLink(string)(string) -fsMetadata(string)(FsMetadata) - -toString(bytes) -toString(address) -toString(uint256) -toString(int256) -toString(bytes32) -toString(bool) -parseBytes(string)(bytes) -parseAddress(string)(address) -parseUint(string)(uint256) -parseInt(string)(int256) -parseBytes32(string)(bytes32) -parseBool(string)(bool) - -snapshot()(uint256) -revertTo(uint256)(bool) -createFork(string,uint256)(uint256) -createFork(string,bytes32)(uint256) -createFork(string)(uint256) -createSelectFork(string,uint256)(uint256) -createSelectFork(string,bytes32)(uint256) -createSelectFork(string)(uint256) -selectFork(uint256) -activeFork()(uint256) -transact(bytes32) -transact(uint256,bytes32) -makePersistent(address) -makePersistent(address,address) -makePersistent(address,address,address) -makePersistent(address[]) -revokePersistent(address) -revokePersistent(address[]) -isPersistent(address)(bool) -rollFork(uint256) -rollFork(bytes32) -rollFork(uint256,uint256) -rollFork(uint256,bytes32) -rpcUrl(string)(string) -rpcUrls()(string[2][]) -rpcUrlStructs()(Rpc[]) - -writeJson(string, string) -writeJson(string, string, string) -parseJson(string)(bytes) -parseJson(string, string)(bytes) -parseJsonKeys(string, string)(string[]) -parseJsonUint(string, string)(uint256) -parseJsonUintArray(string, string)(uint256[]) -parseJsonInt(string, string)(int256) -parseJsonIntArray(string, string)(int256[]) -parseJsonString(string, string)(string) -parseJsonStringArray(string, string)(string[]) -parseJsonAddress(string, string)(address) -parseJsonAddressArray(string, string)(address[]) -parseJsonBool(string, string)(bool) -parseJsonBoolArray(string, string)(bool[]) -parseJsonBytes(string, string)(bytes) -parseJsonBytesArray(string, string)(bytes[]) -parseJsonBytes32(string, string)(bytes32) -parseJsonBytes32Array(string, string)(bytes32[]) -serializeBool(string,string,bool)(string) -serializeBool(string,string,bool[])(string) -serializeUint(string,string,uint256)(string) -serializeUint(string,string,uint256[])(string) -serializeInt(string,string,int256)(string) -serializeInt(string,string,int256[])(string) -serializeAddress(string,string,address)(string) -serializeAddress(string,string,address[])(string) -serializeBytes32(string,string,bytes32)(string) -serializeBytes32(string,string,bytes32[])(string) -serializeString(string,string,string)(string) -serializeString(string,string,string[])(string) -serializeBytes(string,string,bytes)(string) -serializeBytes(string,string,bytes[])(string) -keyExists(string,string)(bool) - -pauseGasMetering() -resumeGasMetering() -startMappingRecording() -stopMappingRecording() -getMappingLength(address,bytes32) -getMappingSlotAt(address,bytes32,uint256) -getMappingKeyAndParentOf(address,bytes32) - -sleep(uint256) diff --git a/crates/abi/abi/HardhatConsole.json b/crates/abi/abi/HardhatConsole.json deleted file mode 100644 index c1b1b46cf4fb4..0000000000000 --- a/crates/abi/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"}] diff --git a/crates/abi/build.rs b/crates/abi/build.rs deleted file mode 100644 index 9817d2deb30fe..0000000000000 --- a/crates/abi/build.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ethers_contract_abigen::MultiAbigen; - -/// Includes a JSON ABI as a string literal. -macro_rules! include_json_abi { - ($path:literal) => {{ - println!(concat!("cargo:rerun-if-changed=", $path)); - include_str!($path) - }}; -} - -/// Includes a human-readable ABI file as a string literal by wrapping it in brackets. -macro_rules! include_hr_abi { - ($path:literal) => {{ - println!(concat!("cargo:rerun-if-changed=", $path)); - concat!("[\n", include_str!($path), "\n]") - }}; -} - -fn main() -> eyre::Result<()> { - let mut multi = MultiAbigen::new([ - ("HardhatConsole", include_json_abi!("abi/HardhatConsole.json")), - ("Console", include_hr_abi!("abi/Console.sol")), - ("HEVM", include_hr_abi!("abi/HEVM.sol")), - ])?; - - // Add the ConsoleFmt derive to the HardhatConsole contract - multi[0].derives_mut().push(syn::parse_str("foundry_macros::ConsoleFmt")?); - - // Generate and write to the bindings module - let bindings = multi.build()?; - bindings.write_to_module("src/bindings/", false)?; - - Ok(()) -} diff --git a/crates/abi/src/bindings/console.rs b/crates/abi/src/bindings/console.rs deleted file mode 100644 index 4749b13ffc3be..0000000000000 --- a/crates/abi/src/bindings/console.rs +++ /dev/null @@ -1,1276 +0,0 @@ -pub use console::*; -/// This module was auto-generated with ethers-rs Abigen. -/// More information at: -#[allow( - clippy::enum_variant_names, - clippy::too_many_arguments, - clippy::upper_case_acronyms, - clippy::type_complexity, - dead_code, - non_camel_case_types, -)] -pub mod console { - #[allow(deprecated)] - fn __abi() -> ::ethers_core::abi::Abi { - ::ethers_core::abi::ethabi::Contract { - constructor: ::core::option::Option::None, - functions: ::std::collections::BTreeMap::new(), - events: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("log"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_address"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_address"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_array"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_array"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - indexed: false, - }, - ], - anonymous: false, - }, - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_array"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - indexed: false, - }, - ], - anonymous: false, - }, - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_array"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_bytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_bytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_bytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_bytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_int"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_int"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_address"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_address"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_array"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_array"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - indexed: false, - }, - ], - anonymous: false, - }, - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_array"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - indexed: false, - }, - ], - anonymous: false, - }, - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_array"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_bytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_bytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_bytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_bytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_decimal_int"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned( - "log_named_decimal_int", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("decimals"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_decimal_uint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned( - "log_named_decimal_uint", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("decimals"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_int"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_int"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_string"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_string"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_named_uint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_named_uint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("key"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ::ethers_core::abi::ethabi::EventParam { - name: ::std::borrow::ToOwned::to_owned("val"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_string"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_string"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("log_uint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("log_uint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logs"), - ::std::vec![ - ::ethers_core::abi::ethabi::Event { - name: ::std::borrow::ToOwned::to_owned("logs"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::EventParam { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - indexed: false, - }, - ], - anonymous: false, - }, - ], - ), - ]), - errors: ::std::collections::BTreeMap::new(), - receive: false, - fallback: false, - } - } - ///The parsed human-readable ABI of the contract. - pub static CONSOLE_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( - __abi, - ); - pub struct Console(::ethers_contract::Contract); - impl ::core::clone::Clone for Console { - fn clone(&self) -> Self { - Self(::core::clone::Clone::clone(&self.0)) - } - } - impl ::core::ops::Deref for Console { - type Target = ::ethers_contract::Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl ::core::ops::DerefMut for Console { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - impl ::core::fmt::Debug for Console { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(Console)).field(&self.address()).finish() - } - } - impl Console { - /// Creates a new contract instance with the specified `ethers` client at - /// `address`. The contract derefs to a `ethers::Contract` object. - pub fn new>( - address: T, - client: ::std::sync::Arc, - ) -> Self { - Self( - ::ethers_contract::Contract::new( - address.into(), - CONSOLE_ABI.clone(), - client, - ), - ) - } - ///Gets the contract's `log` event - pub fn log_filter( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, LogFilter> { - self.0.event() - } - ///Gets the contract's `log_address` event - pub fn log_address_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogAddressFilter, - > { - self.0.event() - } - ///Gets the contract's `log_array` event - pub fn log_array_1_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogArray1Filter, - > { - self.0.event() - } - ///Gets the contract's `log_array` event - pub fn log_array_2_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogArray2Filter, - > { - self.0.event() - } - ///Gets the contract's `log_array` event - pub fn log_array_3_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogArray3Filter, - > { - self.0.event() - } - ///Gets the contract's `log_bytes` event - pub fn log_bytes_filter( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, LogBytesFilter> { - self.0.event() - } - ///Gets the contract's `log_bytes32` event - pub fn log_bytes_32_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogBytes32Filter, - > { - self.0.event() - } - ///Gets the contract's `log_int` event - pub fn log_int_filter( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, LogIntFilter> { - self.0.event() - } - ///Gets the contract's `log_named_address` event - pub fn log_named_address_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedAddressFilter, - > { - self.0.event() - } - ///Gets the contract's `log_named_array` event - pub fn log_named_array_1_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedArray1Filter, - > { - self.0.event() - } - ///Gets the contract's `log_named_array` event - pub fn log_named_array_2_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedArray2Filter, - > { - self.0.event() - } - ///Gets the contract's `log_named_array` event - pub fn log_named_array_3_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedArray3Filter, - > { - self.0.event() - } - ///Gets the contract's `log_named_bytes` event - pub fn log_named_bytes_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedBytesFilter, - > { - self.0.event() - } - ///Gets the contract's `log_named_bytes32` event - pub fn log_named_bytes_32_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedBytes32Filter, - > { - self.0.event() - } - ///Gets the contract's `log_named_decimal_int` event - pub fn log_named_decimal_int_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedDecimalIntFilter, - > { - self.0.event() - } - ///Gets the contract's `log_named_decimal_uint` event - pub fn log_named_decimal_uint_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedDecimalUintFilter, - > { - self.0.event() - } - ///Gets the contract's `log_named_int` event - pub fn log_named_int_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedIntFilter, - > { - self.0.event() - } - ///Gets the contract's `log_named_string` event - pub fn log_named_string_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedStringFilter, - > { - self.0.event() - } - ///Gets the contract's `log_named_uint` event - pub fn log_named_uint_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogNamedUintFilter, - > { - self.0.event() - } - ///Gets the contract's `log_string` event - pub fn log_string_filter( - &self, - ) -> ::ethers_contract::builders::Event< - ::std::sync::Arc, - M, - LogStringFilter, - > { - self.0.event() - } - ///Gets the contract's `log_uint` event - pub fn log_uint_filter( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, LogUintFilter> { - self.0.event() - } - ///Gets the contract's `logs` event - pub fn logs_filter( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, LogsFilter> { - self.0.event() - } - /// Returns an `Event` builder for all the events of this contract. - pub fn events( - &self, - ) -> ::ethers_contract::builders::Event<::std::sync::Arc, M, ConsoleEvents> { - self.0.event_with_filter(::core::default::Default::default()) - } - } - impl From<::ethers_contract::Contract> - for Console { - fn from(contract: ::ethers_contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) - } - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log", abi = "log(string)")] - pub struct LogFilter(pub ::std::string::String); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_address", abi = "log_address(address)")] - pub struct LogAddressFilter(pub ::ethers_core::types::Address); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_array", abi = "log_array(uint256[])")] - pub struct LogArray1Filter { - pub val: ::std::vec::Vec<::ethers_core::types::U256>, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_array", abi = "log_array(int256[])")] - pub struct LogArray2Filter { - pub val: ::std::vec::Vec<::ethers_core::types::I256>, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_array", abi = "log_array(address[])")] - pub struct LogArray3Filter { - pub val: ::std::vec::Vec<::ethers_core::types::Address>, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_bytes", abi = "log_bytes(bytes)")] - pub struct LogBytesFilter(pub ::ethers_core::types::Bytes); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_bytes32", abi = "log_bytes32(bytes32)")] - pub struct LogBytes32Filter(pub [u8; 32]); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_int", abi = "log_int(int256)")] - pub struct LogIntFilter(pub ::ethers_core::types::I256); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_address", abi = "log_named_address(string,address)")] - pub struct LogNamedAddressFilter { - pub key: ::std::string::String, - pub val: ::ethers_core::types::Address, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_array", abi = "log_named_array(string,uint256[])")] - pub struct LogNamedArray1Filter { - pub key: ::std::string::String, - pub val: ::std::vec::Vec<::ethers_core::types::U256>, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_array", abi = "log_named_array(string,int256[])")] - pub struct LogNamedArray2Filter { - pub key: ::std::string::String, - pub val: ::std::vec::Vec<::ethers_core::types::I256>, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_array", abi = "log_named_array(string,address[])")] - pub struct LogNamedArray3Filter { - pub key: ::std::string::String, - pub val: ::std::vec::Vec<::ethers_core::types::Address>, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_bytes", abi = "log_named_bytes(string,bytes)")] - pub struct LogNamedBytesFilter { - pub key: ::std::string::String, - pub val: ::ethers_core::types::Bytes, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_bytes32", abi = "log_named_bytes32(string,bytes32)")] - pub struct LogNamedBytes32Filter { - pub key: ::std::string::String, - pub val: [u8; 32], - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent( - name = "log_named_decimal_int", - abi = "log_named_decimal_int(string,int256,uint256)" - )] - pub struct LogNamedDecimalIntFilter { - pub key: ::std::string::String, - pub val: ::ethers_core::types::I256, - pub decimals: ::ethers_core::types::U256, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent( - name = "log_named_decimal_uint", - abi = "log_named_decimal_uint(string,uint256,uint256)" - )] - pub struct LogNamedDecimalUintFilter { - pub key: ::std::string::String, - pub val: ::ethers_core::types::U256, - pub decimals: ::ethers_core::types::U256, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_int", abi = "log_named_int(string,int256)")] - pub struct LogNamedIntFilter { - pub key: ::std::string::String, - pub val: ::ethers_core::types::I256, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_string", abi = "log_named_string(string,string)")] - pub struct LogNamedStringFilter { - pub key: ::std::string::String, - pub val: ::std::string::String, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_named_uint", abi = "log_named_uint(string,uint256)")] - pub struct LogNamedUintFilter { - pub key: ::std::string::String, - pub val: ::ethers_core::types::U256, - } - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_string", abi = "log_string(string)")] - pub struct LogStringFilter(pub ::std::string::String); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "log_uint", abi = "log_uint(uint256)")] - pub struct LogUintFilter(pub ::ethers_core::types::U256); - #[derive( - Clone, - ::ethers_contract::EthEvent, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethevent(name = "logs", abi = "logs(bytes)")] - pub struct LogsFilter(pub ::ethers_core::types::Bytes); - ///Container type for all of the contract's events - #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum ConsoleEvents { - LogFilter(LogFilter), - LogAddressFilter(LogAddressFilter), - LogArray1Filter(LogArray1Filter), - LogArray2Filter(LogArray2Filter), - LogArray3Filter(LogArray3Filter), - LogBytesFilter(LogBytesFilter), - LogBytes32Filter(LogBytes32Filter), - LogIntFilter(LogIntFilter), - LogNamedAddressFilter(LogNamedAddressFilter), - LogNamedArray1Filter(LogNamedArray1Filter), - LogNamedArray2Filter(LogNamedArray2Filter), - LogNamedArray3Filter(LogNamedArray3Filter), - LogNamedBytesFilter(LogNamedBytesFilter), - LogNamedBytes32Filter(LogNamedBytes32Filter), - LogNamedDecimalIntFilter(LogNamedDecimalIntFilter), - LogNamedDecimalUintFilter(LogNamedDecimalUintFilter), - LogNamedIntFilter(LogNamedIntFilter), - LogNamedStringFilter(LogNamedStringFilter), - LogNamedUintFilter(LogNamedUintFilter), - LogStringFilter(LogStringFilter), - LogUintFilter(LogUintFilter), - LogsFilter(LogsFilter), - } - impl ::ethers_contract::EthLogDecode for ConsoleEvents { - fn decode_log( - log: &::ethers_core::abi::RawLog, - ) -> ::core::result::Result { - if let Ok(decoded) = LogFilter::decode_log(log) { - return Ok(ConsoleEvents::LogFilter(decoded)); - } - if let Ok(decoded) = LogAddressFilter::decode_log(log) { - return Ok(ConsoleEvents::LogAddressFilter(decoded)); - } - if let Ok(decoded) = LogArray1Filter::decode_log(log) { - return Ok(ConsoleEvents::LogArray1Filter(decoded)); - } - if let Ok(decoded) = LogArray2Filter::decode_log(log) { - return Ok(ConsoleEvents::LogArray2Filter(decoded)); - } - if let Ok(decoded) = LogArray3Filter::decode_log(log) { - return Ok(ConsoleEvents::LogArray3Filter(decoded)); - } - if let Ok(decoded) = LogBytesFilter::decode_log(log) { - return Ok(ConsoleEvents::LogBytesFilter(decoded)); - } - if let Ok(decoded) = LogBytes32Filter::decode_log(log) { - return Ok(ConsoleEvents::LogBytes32Filter(decoded)); - } - if let Ok(decoded) = LogIntFilter::decode_log(log) { - return Ok(ConsoleEvents::LogIntFilter(decoded)); - } - if let Ok(decoded) = LogNamedAddressFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedAddressFilter(decoded)); - } - if let Ok(decoded) = LogNamedArray1Filter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedArray1Filter(decoded)); - } - if let Ok(decoded) = LogNamedArray2Filter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedArray2Filter(decoded)); - } - if let Ok(decoded) = LogNamedArray3Filter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedArray3Filter(decoded)); - } - if let Ok(decoded) = LogNamedBytesFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedBytesFilter(decoded)); - } - if let Ok(decoded) = LogNamedBytes32Filter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedBytes32Filter(decoded)); - } - if let Ok(decoded) = LogNamedDecimalIntFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedDecimalIntFilter(decoded)); - } - if let Ok(decoded) = LogNamedDecimalUintFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedDecimalUintFilter(decoded)); - } - if let Ok(decoded) = LogNamedIntFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedIntFilter(decoded)); - } - if let Ok(decoded) = LogNamedStringFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedStringFilter(decoded)); - } - if let Ok(decoded) = LogNamedUintFilter::decode_log(log) { - return Ok(ConsoleEvents::LogNamedUintFilter(decoded)); - } - if let Ok(decoded) = LogStringFilter::decode_log(log) { - return Ok(ConsoleEvents::LogStringFilter(decoded)); - } - if let Ok(decoded) = LogUintFilter::decode_log(log) { - return Ok(ConsoleEvents::LogUintFilter(decoded)); - } - if let Ok(decoded) = LogsFilter::decode_log(log) { - return Ok(ConsoleEvents::LogsFilter(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData) - } - } - impl ::core::fmt::Display for ConsoleEvents { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::LogFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogAddressFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogArray1Filter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogArray2Filter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogArray3Filter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytesFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes32Filter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogIntFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogNamedAddressFilter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedArray1Filter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedArray2Filter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedArray3Filter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedBytesFilter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedBytes32Filter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedDecimalIntFilter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedDecimalUintFilter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedIntFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogNamedStringFilter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogNamedUintFilter(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::LogStringFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogUintFilter(element) => ::core::fmt::Display::fmt(element, f), - Self::LogsFilter(element) => ::core::fmt::Display::fmt(element, f), - } - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogFilter) -> Self { - Self::LogFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogAddressFilter) -> Self { - Self::LogAddressFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogArray1Filter) -> Self { - Self::LogArray1Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogArray2Filter) -> Self { - Self::LogArray2Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogArray3Filter) -> Self { - Self::LogArray3Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogBytesFilter) -> Self { - Self::LogBytesFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogBytes32Filter) -> Self { - Self::LogBytes32Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogIntFilter) -> Self { - Self::LogIntFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedAddressFilter) -> Self { - Self::LogNamedAddressFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedArray1Filter) -> Self { - Self::LogNamedArray1Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedArray2Filter) -> Self { - Self::LogNamedArray2Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedArray3Filter) -> Self { - Self::LogNamedArray3Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedBytesFilter) -> Self { - Self::LogNamedBytesFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedBytes32Filter) -> Self { - Self::LogNamedBytes32Filter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedDecimalIntFilter) -> Self { - Self::LogNamedDecimalIntFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedDecimalUintFilter) -> Self { - Self::LogNamedDecimalUintFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedIntFilter) -> Self { - Self::LogNamedIntFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedStringFilter) -> Self { - Self::LogNamedStringFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogNamedUintFilter) -> Self { - Self::LogNamedUintFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogStringFilter) -> Self { - Self::LogStringFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogUintFilter) -> Self { - Self::LogUintFilter(value) - } - } - impl ::core::convert::From for ConsoleEvents { - fn from(value: LogsFilter) -> Self { - Self::LogsFilter(value) - } - } -} diff --git a/crates/abi/src/bindings/hardhat_console.rs b/crates/abi/src/bindings/hardhat_console.rs deleted file mode 100644 index 01cefc866dec9..0000000000000 --- a/crates/abi/src/bindings/hardhat_console.rs +++ /dev/null @@ -1,28450 +0,0 @@ -pub use hardhat_console::*; -/// This module was auto-generated with ethers-rs Abigen. -/// More information at: -#[allow( - clippy::enum_variant_names, - clippy::too_many_arguments, - clippy::upper_case_acronyms, - clippy::type_complexity, - dead_code, - non_camel_case_types, -)] -pub mod hardhat_console { - #[allow(deprecated)] - fn __abi() -> ::ethers_core::abi::Abi { - ::ethers_core::abi::ethabi::Contract { - constructor: ::core::option::Option::None, - functions: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("log"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p2"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p3"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("int256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("log"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p1"), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("int256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logAddress"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBool"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes1"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes1"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 1usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes1"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes10"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes10"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 10usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes10"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes11"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes11"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 11usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes11"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes12"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes12"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 12usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes12"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes13"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes13"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 13usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes13"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes14"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes14"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 14usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes14"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes15"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes15"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 15usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes15"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes16"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes16"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 16usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes16"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes17"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes17"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 17usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes17"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes18"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes18"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 18usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes18"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes19"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes19"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 19usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes19"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes2"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes2"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 2usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes2"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes20"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes20"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 20usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes20"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes21"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes21"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 21usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes21"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes22"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes22"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 22usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes22"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes23"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes23"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 23usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes23"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes24"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes24"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 24usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes24"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes25"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes25"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 25usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes25"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes26"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes26"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 26usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes26"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes27"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes27"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 27usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes27"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes28"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes28"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 28usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes28"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes29"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes29"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 29usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes29"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes3"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes3"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 3usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes3"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes30"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes30"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 30usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes30"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes31"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes31"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 31usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes31"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes4"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes4"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 4usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes4"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes5"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes5"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 5usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes5"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes6"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes6"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 6usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes6"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes7"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes7"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 7usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes7"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes8"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes8"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 8usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes8"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logBytes9"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logBytes9"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 9usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes9"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logInt"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("int256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logString"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("logUint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("logUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("p0"), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::View, - }, - ], - ), - ]), - events: ::std::collections::BTreeMap::new(), - errors: ::std::collections::BTreeMap::new(), - receive: false, - fallback: false, - } - } - ///The parsed JSON ABI of the contract. - pub static HARDHATCONSOLE_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( - __abi, - ); - pub struct HardhatConsole(::ethers_contract::Contract); - impl ::core::clone::Clone for HardhatConsole { - fn clone(&self) -> Self { - Self(::core::clone::Clone::clone(&self.0)) - } - } - impl ::core::ops::Deref for HardhatConsole { - type Target = ::ethers_contract::Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl ::core::ops::DerefMut for HardhatConsole { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - impl ::core::fmt::Debug for HardhatConsole { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(HardhatConsole)) - .field(&self.address()) - .finish() - } - } - impl HardhatConsole { - /// Creates a new contract instance with the specified `ethers` client at - /// `address`. The contract derefs to a `ethers::Contract` object. - pub fn new>( - address: T, - client: ::std::sync::Arc, - ) -> Self { - Self( - ::ethers_contract::Contract::new( - address.into(), - HARDHATCONSOLE_ABI.clone(), - client, - ), - ) - } - ///Calls the contract's `log` (0x007150be) function - pub fn log_23( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([0, 113, 80, 190], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x00dd87b9) function - pub fn log_87( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([0, 221, 135, 185], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x018c84c2) function - pub fn log_24( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([1, 140, 132, 194], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x031c6f73) function - pub fn log_88( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([3, 28, 111, 115], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0454c079) function - pub fn log_89( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([4, 84, 192, 121], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x078287f5) function - pub fn log_90( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([7, 130, 135, 245], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x07831502) function - pub fn log_91( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([7, 131, 21, 2], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x088ef9d2) function - pub fn log_25( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([8, 142, 249, 210], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x091ffaf5) function - pub fn log_92( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([9, 31, 250, 245], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0aa6cfad) function - pub fn log_93( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([10, 166, 207, 173], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0bb00eab) function - pub fn log_94( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([11, 176, 14, 171], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0c66d1be) function - pub fn log_95( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([12, 102, 209, 190], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0c9cd9c1) function - pub fn log_96( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([12, 156, 217, 193], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0d26b925) function - pub fn log_26( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([13, 38, 185, 37], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0d36fa20) function - pub fn log_97( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([13, 54, 250, 32], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0df12b76) function - pub fn log_98( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([13, 241, 43, 118], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0e378994) function - pub fn log_99( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([14, 55, 137, 148], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x0ef7e050) function - pub fn log_100( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([14, 247, 224, 80], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x100f650e) function - pub fn log_101( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([16, 15, 101, 14], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1023f7b2) function - pub fn log_102( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([16, 35, 247, 178], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1078f68d) function - pub fn log_27( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([16, 120, 246, 141], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1093ee11) function - pub fn log_28( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([16, 147, 238, 17], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x12d6c788) function - pub fn log_103( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([18, 214, 199, 136], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x12f21602) function - pub fn log_29( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([18, 242, 22, 2], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x136b05dd) function - pub fn log_104( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([19, 107, 5, 221], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1537dc87) function - pub fn log_105( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([21, 55, 220, 135], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1596a1ce) function - pub fn log_106( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([21, 150, 161, 206], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x159f8927) function - pub fn log_107( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([21, 159, 137, 39], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x15c127b5) function - pub fn log_108( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([21, 193, 39, 181], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x15cac476) function - pub fn log_109( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([21, 202, 196, 118], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1606a393) function - pub fn log_110( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([22, 6, 163, 147], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1762e32a) function - pub fn log_111( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([23, 98, 227, 42], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x17fe6185) function - pub fn log_30( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([23, 254, 97, 133], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x18c9c746) function - pub fn log_31( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([24, 201, 199, 70], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x193fb800) function - pub fn log_112( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([25, 63, 184, 0], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x19fd4956) function - pub fn log_113( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([25, 253, 73, 86], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1ad96de6) function - pub fn log_114( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([26, 217, 109, 230], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1bb3b09a) function - pub fn log_115( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([27, 179, 176, 154], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1c41a336) function - pub fn log_116( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([28, 65, 163, 54], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1c7ec448) function - pub fn log_32( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([28, 126, 196, 72], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1c9d7eb3) function - pub fn log_6( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([28, 157, 126, 179], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1d14d001) function - pub fn log_117( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([29, 20, 208, 1], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1da986ea) function - pub fn log_118( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([29, 169, 134, 234], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1dc8e1b8) function - pub fn log_119( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([29, 200, 225, 184], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x1e4b87e5) function - pub fn log_120( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([30, 75, 135, 229], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x20098014) function - pub fn log_33( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([32, 9, 128, 20], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x205871c2) function - pub fn log_121( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([32, 88, 113, 194], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x20718650) function - pub fn log_34( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([32, 113, 134, 80], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x20e3984d) function - pub fn log_122( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([32, 227, 152, 77], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x212255cc) function - pub fn log_35( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([33, 34, 85, 204], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x21ad0683) function - pub fn log_123( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([33, 173, 6, 131], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x21bdaf25) function - pub fn log_124( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([33, 189, 175, 37], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x223603bd) function - pub fn log_125( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([34, 54, 3, 189], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x22f6b999) function - pub fn log_126( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([34, 246, 185, 153], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x245986f2) function - pub fn log_127( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([36, 89, 134, 242], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2488b414) function - pub fn log_128( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([36, 136, 180, 20], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x24f91465) function - pub fn log_129( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([36, 249, 20, 101], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2555fa46) function - pub fn log_36( - &self, - p_0: bool, - p_1: bool, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([37, 85, 250, 70], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x26f560a8) function - pub fn log_130( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([38, 245, 96, 168], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x27d8afd2) function - pub fn log_131( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([39, 216, 175, 210], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x28863fcb) function - pub fn log_132( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([40, 134, 63, 203], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2a110e83) function - pub fn log_7( - &self, - p_0: bool, - p_1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([42, 17, 14, 131], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2ae408d4) function - pub fn log_133( - &self, - p_0: bool, - p_1: bool, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([42, 228, 8, 212], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2b2b18dc) function - pub fn log_134( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([43, 43, 24, 220], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2c1754ed) function - pub fn log_135( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([44, 23, 84, 237], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2c1d0746) function - pub fn log_136( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([44, 29, 7, 70], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2c2ecbc2) function - pub fn log_1( - &self, - p_0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([44, 46, 203, 194], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2cd4134a) function - pub fn log_137( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([44, 212, 19, 74], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2ced7cef) function - pub fn log_37( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([44, 237, 124, 239], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2d8e33a4) function - pub fn log_138( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([45, 142, 51, 164], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2dd778e6) function - pub fn log_139( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([45, 215, 120, 230], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x319af333) function - pub fn log_8( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([49, 154, 243, 51], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x32458eed) function - pub fn log_2( - &self, - p_0: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([50, 69, 142, 237], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x33e9dd1d) function - pub fn log_140( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([51, 233, 221, 29], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x34f0e636) function - pub fn log_141( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([52, 240, 230, 54], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x35085f7b) function - pub fn log_38( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([53, 8, 95, 123], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x354c36d6) function - pub fn log_142( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([53, 76, 54, 214], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x35a5071f) function - pub fn log_143( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([53, 165, 7, 31], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x37103367) function - pub fn log_39( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([55, 16, 51, 103], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x374bb4b2) function - pub fn log_144( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([55, 75, 180, 178], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x37aa7d4c) function - pub fn log_40( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([55, 170, 125, 76], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x386ff5f4) function - pub fn log_145( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([56, 111, 245, 244], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3971e78c) function - pub fn log_146( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([57, 113, 231, 140], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x399174d3) function - pub fn log_9( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([57, 145, 116, 211], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3b2279b4) function - pub fn log_147( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([59, 34, 121, 180], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3b2a5ce0) function - pub fn log_148( - &self, - p_0: bool, - p_1: bool, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([59, 42, 92, 224], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3bf5e537) function - pub fn log_149( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([59, 245, 229, 55], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3e128ca3) function - pub fn log_150( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([62, 18, 140, 163], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3e9f866a) function - pub fn log_151( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([62, 159, 134, 106], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3f8a701d) function - pub fn log_152( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([63, 138, 112, 29], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x40785869) function - pub fn log_153( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([64, 120, 88, 105], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x41304fac) function - pub fn log_3( - &self, - p_0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([65, 48, 79, 172], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x42d21db7) function - pub fn log_154( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([66, 210, 29, 183], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x439c7bef) function - pub fn log_155( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([67, 156, 123, 239], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x448830a8) function - pub fn log_156( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([68, 136, 48, 168], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x454d54a5) function - pub fn log_157( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([69, 77, 84, 165], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x457fe3cf) function - pub fn log_158( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([69, 127, 227, 207], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x46600be0) function - pub fn log_159( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([70, 96, 11, 224], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x46826b5d) function - pub fn log_160( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([70, 130, 107, 93], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x475c5c33) function - pub fn log_161( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([71, 92, 92, 51], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4766da72) function - pub fn log_41( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([71, 102, 218, 114], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x478d1c62) function - pub fn log_162( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([71, 141, 28, 98], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x483d0416) function - pub fn log_163( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([72, 61, 4, 22], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4a28c017) function - pub fn log_164( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([74, 40, 192, 23], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4a66cb34) function - pub fn log_165( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([74, 102, 203, 52], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4b5c4277) function - pub fn log_10( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([75, 92, 66, 119], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4c123d57) function - pub fn log_166( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([76, 18, 61, 87], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4ceda75a) function - pub fn log_42( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([76, 237, 167, 90], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x4f04fdc6) function - pub fn log_167( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([79, 4, 253, 198], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x50709698) function - pub fn log_43( - &self, - p_0: bool, - p_1: bool, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([80, 112, 150, 152], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x50ad461d) function - pub fn log_168( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([80, 173, 70, 29], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x515e38b6) function - pub fn log_169( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([81, 94, 56, 182], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x51973ec9) function - pub fn log_0(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([81, 151, 62, 201], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x51f09ff8) function - pub fn log_170( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([81, 240, 159, 248], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x538e06ab) function - pub fn log_171( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([83, 142, 6, 171], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x54a7a9a0) function - pub fn log_172( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([84, 167, 169, 160], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x56a5d1b1) function - pub fn log_173( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([86, 165, 209, 177], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5821efa1) function - pub fn log_44( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([88, 33, 239, 161], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5970e089) function - pub fn log_45( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([89, 112, 224, 137], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x59cfcbe3) function - pub fn log_174( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([89, 207, 203, 227], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5a477632) function - pub fn log_175( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([90, 71, 118, 50], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5a9b5ed5) function - pub fn log_46( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([90, 155, 94, 213], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5ab84e1f) function - pub fn log_176( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([90, 184, 78, 31], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5abd992a) function - pub fn log_177( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([90, 189, 153, 42], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5c430d47) function - pub fn log_178( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([92, 67, 13, 71], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5c96b331) function - pub fn log_47( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([92, 150, 179, 49], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5ccd4e37) function - pub fn log_179( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([92, 205, 78, 55], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5d02c50b) function - pub fn log_180( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([93, 2, 197, 11], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5d08bb05) function - pub fn log_181( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([93, 8, 187, 5], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5d1a971a) function - pub fn log_182( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([93, 26, 151, 26], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5da297eb) function - pub fn log_183( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([93, 162, 151, 235], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5e84b0ea) function - pub fn log_184( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([94, 132, 176, 234], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5ea2b7ae) function - pub fn log_185( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([94, 162, 183, 174], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5f15d28c) function - pub fn log_186( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([95, 21, 210, 140], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5f1d5c9f) function - pub fn log_187( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([95, 29, 92, 159], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5f743a7c) function - pub fn log_188( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([95, 116, 58, 124], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x5f7b9afb) function - pub fn log_48( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([95, 123, 154, 251], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6168ed61) function - pub fn log_189( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([97, 104, 237, 97], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x619e4d0e) function - pub fn log_190( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([97, 158, 77, 14], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x63183678) function - pub fn log_191( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([99, 24, 54, 120], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x63cb41f9) function - pub fn log_49( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([99, 203, 65, 249], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x63fb8bc5) function - pub fn log_192( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([99, 251, 139, 197], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x643fd0df) function - pub fn log_11( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([100, 63, 208, 223], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x64b5bb67) function - pub fn log_193( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([100, 181, 187, 103], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x660375dd) function - pub fn log_194( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([102, 3, 117, 221], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x665bf134) function - pub fn log_195( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([102, 91, 241, 52], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x66f1bc67) function - pub fn log_196( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([102, 241, 188, 103], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x678209a8) function - pub fn log_50( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([103, 130, 9, 168], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x67dd6ff1) function - pub fn log_51( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([103, 221, 111, 241], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x68c8b8bd) function - pub fn log_197( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([104, 200, 184, 189], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x691a8f74) function - pub fn log_198( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([105, 26, 143, 116], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x69276c86) function - pub fn log_12( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([105, 39, 108, 134], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x69640b59) function - pub fn log_199( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([105, 100, 11, 89], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6a1199e2) function - pub fn log_200( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([106, 17, 153, 226], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6a9c478b) function - pub fn log_201( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([106, 156, 71, 139], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6b0e5d53) function - pub fn log_202( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([107, 14, 93, 83], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6cde40b8) function - pub fn log_203( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([108, 222, 64, 184], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6d1e8751) function - pub fn log_204( - &self, - p_0: bool, - p_1: bool, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([109, 30, 135, 81], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6d572f44) function - pub fn log_205( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([109, 87, 47, 68], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6d7045c1) function - pub fn log_206( - &self, - p_0: bool, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([109, 112, 69, 193], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6dd434ca) function - pub fn log_207( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([109, 212, 52, 202], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6f1a594e) function - pub fn log_208( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([111, 26, 89, 78], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x6f7c603e) function - pub fn log_209( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([111, 124, 96, 62], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7190a529) function - pub fn log_210( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([113, 144, 165, 41], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x71d04af2) function - pub fn log_52( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([113, 208, 74, 242], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x736efbb6) function - pub fn log_211( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([115, 110, 251, 182], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x742d6ee7) function - pub fn log_212( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([116, 45, 110, 231], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7464ce23) function - pub fn log_213( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([116, 100, 206, 35], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x759f86bb) function - pub fn log_13( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([117, 159, 134, 187], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x75b605d3) function - pub fn log_14( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([117, 182, 5, 211], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7626db92) function - pub fn log_214( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([118, 38, 219, 146], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x79884c2b) function - pub fn log_215( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([121, 136, 76, 43], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7af6ab25) function - pub fn log_216( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([122, 246, 171, 37], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7afac959) function - pub fn log_53( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([122, 250, 201, 89], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7bc0d848) function - pub fn log_54( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([123, 192, 216, 72], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7be0c3eb) function - pub fn log_217( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([123, 224, 195, 235], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7bf181a1) function - pub fn log_218( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([123, 241, 129, 161], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7c4632a4) function - pub fn log_219( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([124, 70, 50, 164], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7cc3c607) function - pub fn log_220( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([124, 195, 198, 7], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7d24491d) function - pub fn log_221( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([125, 36, 73, 29], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7dd4d0e0) function - pub fn log_222( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([125, 212, 208, 224], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x7f9bbca2) function - pub fn log_223( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([127, 155, 188, 162], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x800a1c67) function - pub fn log_224( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([128, 10, 28, 103], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x80e6a20b) function - pub fn log_225( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([128, 230, 162, 11], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x82112a42) function - pub fn log_226( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([130, 17, 42, 66], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x82c25b74) function - pub fn log_227( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([130, 194, 91, 116], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8309e8a8) function - pub fn log_15( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([131, 9, 232, 168], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x850b7ad6) function - pub fn log_55( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([133, 11, 122, 214], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x853c4849) function - pub fn log_16( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([133, 60, 72, 73], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x854b3496) function - pub fn log_228( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([133, 75, 52, 150], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x85775021) function - pub fn log_56( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([133, 119, 80, 33], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x88a8c406) function - pub fn log_229( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([136, 168, 196, 6], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x88cb6041) function - pub fn log_230( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([136, 203, 96, 65], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x88f6e4b2) function - pub fn log_231( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([136, 246, 228, 178], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x895af8c5) function - pub fn log_232( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([137, 90, 248, 197], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8af7cf8a) function - pub fn log_233( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([138, 247, 207, 138], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8c329b1a) function - pub fn log_234( - &self, - p_0: bool, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([140, 50, 155, 26], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8c4e5de6) function - pub fn log_235( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([140, 78, 93, 230], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8da6def5) function - pub fn log_236( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([141, 166, 222, 245], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8e3f78a9) function - pub fn log_237( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([142, 63, 120, 169], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8e69fb5d) function - pub fn log_238( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([142, 105, 251, 93], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8eafb02b) function - pub fn log_239( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([142, 175, 176, 43], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8ef3f399) function - pub fn log_240( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([142, 243, 243, 153], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8f736d16) function - pub fn log_241( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([143, 115, 109, 22], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x8feac525) function - pub fn log_17( - &self, - p_0: bool, - p_1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([143, 234, 197, 37], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x90c30a56) function - pub fn log_242( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([144, 195, 10, 86], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x90fb06aa) function - pub fn log_243( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([144, 251, 6, 170], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9143dbb1) function - pub fn log_244( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([145, 67, 219, 177], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x91a02e2a) function - pub fn log_245( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([145, 160, 46, 42], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x91d1112e) function - pub fn log_246( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([145, 209, 17, 46], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x932bbb38) function - pub fn log_57( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([147, 43, 187, 56], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x935e09bf) function - pub fn log_247( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([147, 94, 9, 191], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x94250d77) function - pub fn log_248( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([148, 37, 13, 119], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x958c28c6) function - pub fn log_249( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([149, 140, 40, 198], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9591b953) function - pub fn log_58( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([149, 145, 185, 83], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x95ed0195) function - pub fn log_59( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([149, 237, 1, 149], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x97d394d8) function - pub fn log_250( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([151, 211, 148, 216], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9a816a83) function - pub fn log_251( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([154, 129, 106, 131], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9acd3616) function - pub fn log_252( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([154, 205, 54, 22], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9b4254e2) function - pub fn log_253( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([155, 66, 84, 226], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9b6ec042) function - pub fn log_60( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([155, 110, 192, 66], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9c3adfa1) function - pub fn log_254( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([156, 58, 223, 161], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9c4f99fb) function - pub fn log_61( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([156, 79, 153, 251], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9cba8fff) function - pub fn log_255( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([156, 186, 143, 255], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9d22d5dd) function - pub fn log_256( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([157, 34, 213, 221], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9f1bc36e) function - pub fn log_257( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([159, 27, 195, 110], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x9ffb2f93) function - pub fn log_258( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([159, 251, 47, 147], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa04e2f87) function - pub fn log_259( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([160, 78, 47, 135], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa0a47963) function - pub fn log_260( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([160, 164, 121, 99], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa1bcc9b3) function - pub fn log_261( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([161, 188, 201, 179], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa1ef4cbb) function - pub fn log_262( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([161, 239, 76, 187], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa1f2e8aa) function - pub fn log_62( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([161, 242, 232, 170], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa31bfdcc) function - pub fn log_263( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([163, 27, 253, 204], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa5b4fc99) function - pub fn log_264( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([165, 180, 252, 153], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa5cada94) function - pub fn log_265( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([165, 202, 218, 148], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa6f50b0f) function - pub fn log_266( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([166, 245, 11, 15], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa73c1db6) function - pub fn log_267( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([167, 60, 29, 182], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa75c59de) function - pub fn log_268( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([167, 92, 89, 222], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa7a87853) function - pub fn log_269( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([167, 168, 120, 83], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xa826caeb) function - pub fn log_270( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([168, 38, 202, 235], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xaa6540c8) function - pub fn log_271( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([170, 101, 64, 200], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xaabc9a31) function - pub fn log_272( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([170, 188, 154, 49], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xab085ae6) function - pub fn log_273( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([171, 8, 90, 230], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xabf73a98) function - pub fn log_274( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([171, 247, 58, 152], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xade052c7) function - pub fn log_275( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([173, 224, 82, 199], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xae2ec581) function - pub fn log_276( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([174, 46, 197, 129], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb028c9bd) function - pub fn log_277( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([176, 40, 201, 189], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb076847f) function - pub fn log_63( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([176, 118, 132, 127], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb0e0f9b5) function - pub fn log_64( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([176, 224, 249, 181], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb115611f) function - pub fn log_65( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([177, 21, 97, 31], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb3a6b6bd) function - pub fn log_278( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([179, 166, 182, 189], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb4c314ff) function - pub fn log_279( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([180, 195, 20, 255], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb59dbd60) function - pub fn log_280( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([181, 157, 189, 96], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb60e72cc) function - pub fn log_18( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([182, 14, 114, 204], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb69bcaf6) function - pub fn log_66( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([182, 155, 202, 246], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb6f577a1) function - pub fn log_281( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([182, 245, 119, 161], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb7b914ca) function - pub fn log_282( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([183, 185, 20, 202], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xb857163a) function - pub fn log_283( - &self, - p_0: bool, - p_1: bool, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([184, 87, 22, 58], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xba535d9c) function - pub fn log_284( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([186, 83, 93, 156], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xbc0b61fe) function - pub fn log_285( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([188, 11, 97, 254], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xbcfd9be0) function - pub fn log_67( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([188, 253, 155, 224], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xbe553481) function - pub fn log_286( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([190, 85, 52, 129], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xbe984353) function - pub fn log_287( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([190, 152, 67, 83], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xbf01f891) function - pub fn log_288( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([191, 1, 248, 145], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc0a302d8) function - pub fn log_289( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([192, 163, 2, 216], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc21f64c7) function - pub fn log_290( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([194, 31, 100, 199], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc371c7db) function - pub fn log_291( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([195, 113, 199, 219], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc3a8a654) function - pub fn log_292( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([195, 168, 166, 84], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc3b55635) function - pub fn log_19( - &self, - p_0: ::std::string::String, - p_1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([195, 181, 86, 53], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc3fc3970) function - pub fn log_68( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([195, 252, 57, 112], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc4643e20) function - pub fn log_293( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([196, 100, 62, 32], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc598d185) function - pub fn log_294( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([197, 152, 209, 133], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc5ad85f9) function - pub fn log_295( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([197, 173, 133, 249], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc67ea9d1) function - pub fn log_296( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([198, 126, 169, 209], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc6acc7a8) function - pub fn log_297( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([198, 172, 199, 168], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc91d5ed4) function - pub fn log_69( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([201, 29, 94, 212], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xc95958d6) function - pub fn log_70( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([201, 89, 88, 214], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xca47c4eb) function - pub fn log_71( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([202, 71, 196, 235], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xca7733b1) function - pub fn log_72( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([202, 119, 51, 177], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xcac43479) function - pub fn log_298( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([202, 196, 52, 121], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xcc32ab07) function - pub fn log_299( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([204, 50, 171, 7], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xccf790a1) function - pub fn log_300( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([204, 247, 144, 161], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xceb5f4d7) function - pub fn log_301( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([206, 181, 244, 215], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xcf009880) function - pub fn log_302( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([207, 0, 152, 128], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xcf020fb1) function - pub fn log_73( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([207, 2, 15, 177], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xcf18105c) function - pub fn log_303( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([207, 24, 16, 92], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xcf394485) function - pub fn log_304( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([207, 57, 68, 133], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd1ed7a3c) function - pub fn log_74( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([209, 237, 122, 60], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd2763667) function - pub fn log_75( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([210, 118, 54, 103], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd2d423cd) function - pub fn log_305( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([210, 212, 35, 205], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd583c602) function - pub fn log_306( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([213, 131, 198, 2], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd6019f1c) function - pub fn log_307( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([214, 1, 159, 28], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd6aefad2) function - pub fn log_308( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([214, 174, 250, 210], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xd812a167) function - pub fn log_309( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([216, 18, 161, 103], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xdaf0d4aa) function - pub fn log_20( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([218, 240, 212, 170], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xdbb4c247) function - pub fn log_76( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([219, 180, 194, 71], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xdc5e935b) function - pub fn log_310( - &self, - p_0: bool, - p_1: ::std::string::String, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([220, 94, 147, 91], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xddb06521) function - pub fn log_311( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([221, 176, 101, 33], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xdddb9561) function - pub fn log_312( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([221, 219, 149, 97], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xde03e774) function - pub fn log_313( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([222, 3, 231, 116], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xde68f20a) function - pub fn log_314( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([222, 104, 242, 10], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xde9a9270) function - pub fn log_77( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([222, 154, 146, 112], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xdfc4a2e8) function - pub fn log_315( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: bool, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([223, 196, 162, 232], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe0625b29) function - pub fn log_316( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([224, 98, 91, 41], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe0e95b98) function - pub fn log_317( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([224, 233, 91, 152], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe0e9ad4f) function - pub fn log_78( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([224, 233, 173, 79], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe21de278) function - pub fn log_318( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([226, 29, 226, 120], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe298f47d) function - pub fn log_79( - &self, - p_0: ::std::string::String, - p_1: bool, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([226, 152, 244, 125], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe2bfd60b) function - pub fn log_319( - &self, - p_0: bool, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([226, 191, 214, 11], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe351140f) function - pub fn log_320( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([227, 81, 20, 15], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe3a9ca2f) function - pub fn log_321( - &self, - p_0: bool, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([227, 169, 202, 47], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe41b6f6f) function - pub fn log_322( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([228, 27, 111, 111], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe5e70b2b) function - pub fn log_323( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([229, 231, 11, 43], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe8d3018d) function - pub fn log_324( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([232, 211, 1, 141], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xe8defba9) function - pub fn log_80( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([232, 222, 251, 169], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xeb1bff80) function - pub fn log_325( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([235, 27, 255, 128], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xeb7f6fd2) function - pub fn log_326( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: bool, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([235, 127, 111, 210], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xeb830c92) function - pub fn log_81( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([235, 131, 12, 146], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xeb928d7f) function - pub fn log_327( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::std::string::String, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([235, 146, 141, 127], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xed8f28f6) function - pub fn log_328( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([237, 143, 40, 246], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xef1cefe7) function - pub fn log_329( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::std::string::String, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([239, 28, 239, 231], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xef529018) function - pub fn log_330( - &self, - p_0: ::ethers_core::types::U256, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([239, 82, 144, 24], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xef72c513) function - pub fn log_331( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::Address, - p_2: bool, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([239, 114, 197, 19], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf08744e8) function - pub fn log_82( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([240, 135, 68, 232], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf11699ed) function - pub fn log_83( - &self, - p_0: ::ethers_core::types::Address, - p_1: bool, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([241, 22, 153, 237], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf2a66286) function - pub fn log_84( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([242, 166, 98, 134], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf45d7d2c) function - pub fn log_332( - &self, - p_0: ::std::string::String, - p_1: ::std::string::String, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([244, 93, 125, 44], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf4880ea4) function - pub fn log_333( - &self, - p_0: bool, - p_1: bool, - p_2: ::ethers_core::types::Address, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([244, 136, 14, 164], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf5bc2249) function - pub fn log_334( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([245, 188, 34, 73], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf666715a) function - pub fn log_21( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([246, 102, 113, 90], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf7e36245) function - pub fn log_335( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([247, 227, 98, 69], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf808da20) function - pub fn log_336( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([248, 8, 218, 32], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf82c50f1) function - pub fn log_4( - &self, - p_0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([248, 44, 80, 241], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf8f51b1e) function - pub fn log_337( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([248, 245, 27, 30], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xf9ad2b89) function - pub fn log_338( - &self, - p_0: bool, - p_1: bool, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([249, 173, 43, 137], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xfa8185af) function - pub fn log_339( - &self, - p_0: ::ethers_core::types::U256, - p_1: ::ethers_core::types::U256, - p_2: ::ethers_core::types::U256, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([250, 129, 133, 175], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xfb772265) function - pub fn log_85( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::std::string::String, - p_2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([251, 119, 34, 101], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xfc4845f0) function - pub fn log_340( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([252, 72, 69, 240], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xfcec75e0) function - pub fn log_86( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([252, 236, 117, 224], (p_0, p_1, p_2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xfdb4f990) function - pub fn log_341( - &self, - p_0: ::ethers_core::types::Address, - p_1: ::ethers_core::types::Address, - p_2: ::ethers_core::types::U256, - p_3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([253, 180, 249, 144], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0xfedd1fff) function - pub fn log_342( - &self, - p_0: bool, - p_1: ::ethers_core::types::U256, - p_2: ::std::string::String, - p_3: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([254, 221, 31, 255], (p_0, p_1, p_2, p_3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x2d5b6cb9) function - pub fn log_5( - &self, - p_0: ::ethers_core::types::I256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([45, 91, 108, 185], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `log` (0x3ca6268e) function - pub fn log_22( - &self, - p_0: ::std::string::String, - p_1: ::ethers_core::types::I256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([60, 166, 38, 142], (p_0, p_1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logAddress` (0x5f91b0af) function - pub fn log_address( - &self, - p_0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([95, 145, 176, 175], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBool` (0xba7ab84e) function - pub fn log_bool( - &self, - p_0: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([186, 122, 184, 78], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes` (0xe17bf956) function - pub fn log_bytes( - &self, - p_0: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([225, 123, 249, 86], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes1` (0x6f4171c9) function - pub fn log_bytes_1( - &self, - p_0: [u8; 1], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([111, 65, 113, 201], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes10` (0x9dc2a897) function - pub fn log_bytes_10( - &self, - p_0: [u8; 10], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([157, 194, 168, 151], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes11` (0xdc08b6a7) function - pub fn log_bytes_11( - &self, - p_0: [u8; 11], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([220, 8, 182, 167], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes12` (0x7656d6c7) function - pub fn log_bytes_12( - &self, - p_0: [u8; 12], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([118, 86, 214, 199], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes13` (0x34c1d81b) function - pub fn log_bytes_13( - &self, - p_0: [u8; 13], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([52, 193, 216, 27], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes14` (0x3ceaba65) function - pub fn log_bytes_14( - &self, - p_0: [u8; 14], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([60, 234, 186, 101], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes15` (0x591a3da2) function - pub fn log_bytes_15( - &self, - p_0: [u8; 15], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([89, 26, 61, 162], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes16` (0x1f8d7312) function - pub fn log_bytes_16( - &self, - p_0: [u8; 16], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([31, 141, 115, 18], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes17` (0xf89a532f) function - pub fn log_bytes_17( - &self, - p_0: [u8; 17], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([248, 154, 83, 47], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes18` (0xd8652642) function - pub fn log_bytes_18( - &self, - p_0: [u8; 18], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([216, 101, 38, 66], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes19` (0x00f56bc9) function - pub fn log_bytes_19( - &self, - p_0: [u8; 19], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([0, 245, 107, 201], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes2` (0x9b5e943e) function - pub fn log_bytes_2( - &self, - p_0: [u8; 2], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([155, 94, 148, 62], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes20` (0xecb8567e) function - pub fn log_bytes_20( - &self, - p_0: [u8; 20], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([236, 184, 86, 126], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes21` (0x3052c08f) function - pub fn log_bytes_21( - &self, - p_0: [u8; 21], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([48, 82, 192, 143], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes22` (0x807ab434) function - pub fn log_bytes_22( - &self, - p_0: [u8; 22], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([128, 122, 180, 52], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes23` (0x4979b037) function - pub fn log_bytes_23( - &self, - p_0: [u8; 23], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([73, 121, 176, 55], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes24` (0x0977aefc) function - pub fn log_bytes_24( - &self, - p_0: [u8; 24], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([9, 119, 174, 252], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes25` (0xaea9963f) function - pub fn log_bytes_25( - &self, - p_0: [u8; 25], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([174, 169, 150, 63], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes26` (0xd3635628) function - pub fn log_bytes_26( - &self, - p_0: [u8; 26], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([211, 99, 86, 40], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes27` (0xfc372f9f) function - pub fn log_bytes_27( - &self, - p_0: [u8; 27], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([252, 55, 47, 159], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes28` (0x382f9a34) function - pub fn log_bytes_28( - &self, - p_0: [u8; 28], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([56, 47, 154, 52], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes29` (0x7a187641) function - pub fn log_bytes_29( - &self, - p_0: [u8; 29], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([122, 24, 118, 65], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes3` (0x7782fa2d) function - pub fn log_bytes_3( - &self, - p_0: [u8; 3], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([119, 130, 250, 45], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes30` (0xc4340ef6) function - pub fn log_bytes_30( - &self, - p_0: [u8; 30], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([196, 52, 14, 246], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes31` (0x81fc8648) function - pub fn log_bytes_31( - &self, - p_0: [u8; 31], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([129, 252, 134, 72], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes32` (0x2d21d6f7) function - pub fn log_bytes_32( - &self, - p_0: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([45, 33, 214, 247], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes4` (0xfba3ad39) function - pub fn log_bytes_4( - &self, - p_0: [u8; 4], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([251, 163, 173, 57], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes5` (0x5583be2e) function - pub fn log_bytes_5( - &self, - p_0: [u8; 5], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([85, 131, 190, 46], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes6` (0x4942adc6) function - pub fn log_bytes_6( - &self, - p_0: [u8; 6], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([73, 66, 173, 198], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes7` (0x4574afab) function - pub fn log_bytes_7( - &self, - p_0: [u8; 7], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([69, 116, 175, 171], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes8` (0x9902e47f) function - pub fn log_bytes_8( - &self, - p_0: [u8; 8], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([153, 2, 228, 127], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logBytes9` (0x50a138df) function - pub fn log_bytes_9( - &self, - p_0: [u8; 9], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([80, 161, 56, 223], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logInt` (0x6525b5f5) function - pub fn log_int( - &self, - p_0: ::ethers_core::types::I256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([101, 37, 181, 245], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logString` (0x0bb563d6) function - pub fn log_string( - &self, - p_0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([11, 181, 99, 214], p_0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `logUint` (0x9905b744) function - pub fn log_uint( - &self, - p_0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([153, 5, 183, 68], p_0) - .expect("method not found (this should never happen)") - } - } - impl From<::ethers_contract::Contract> - for HardhatConsole { - fn from(contract: ::ethers_contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) - } - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,string)` and selector `0x007150be` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,string)")] - pub struct Log23Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,uint256,address)` and selector `0x00dd87b9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,uint256,address)")] - pub struct Log87Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,address)` and selector `0x018c84c2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,address)")] - pub struct Log24Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,address,string)` and selector `0x031c6f73` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,address,string)")] - pub struct Log88Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,bool,string)` and selector `0x0454c079` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,bool,string)")] - pub struct Log89Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,address,uint256)` and selector `0x078287f5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,address,uint256)")] - pub struct Log90Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,bool,uint256)` and selector `0x07831502` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,bool,uint256)")] - pub struct Log91Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,address)` and selector `0x088ef9d2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,address)")] - pub struct Log25Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,address,bool)` and selector `0x091ffaf5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,address,bool)")] - pub struct Log92Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,uint256,string)` and selector `0x0aa6cfad` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,uint256,string)")] - pub struct Log93Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,uint256,uint256)` and selector `0x0bb00eab` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,uint256,uint256)")] - pub struct Log94Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,address,uint256)` and selector `0x0c66d1be` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,address,uint256)")] - pub struct Log95Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,uint256,uint256)` and selector `0x0c9cd9c1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,uint256,uint256)")] - pub struct Log96Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,uint256)` and selector `0x0d26b925` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,uint256)")] - pub struct Log26Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,address,address)` and selector `0x0d36fa20` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,address,address)")] - pub struct Log97Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,address,bool)` and selector `0x0df12b76` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,address,bool)")] - pub struct Log98Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,address,bool)` and selector `0x0e378994` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,address,bool)")] - pub struct Log99Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,uint256,bool)` and selector `0x0ef7e050` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,uint256,bool)")] - pub struct Log100Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,address,uint256)` and selector `0x100f650e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,address,uint256)")] - pub struct Log101Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,uint256,address)` and selector `0x1023f7b2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,uint256,address)")] - pub struct Log102Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,address)` and selector `0x1078f68d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,address)")] - pub struct Log27Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,uint256)` and selector `0x1093ee11` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,uint256)")] - pub struct Log28Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,address,string)` and selector `0x12d6c788` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,address,string)")] - pub struct Log103Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,uint256)` and selector `0x12f21602` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,uint256)")] - pub struct Log29Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,uint256,address)` and selector `0x136b05dd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,uint256,address)")] - pub struct Log104Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,address,uint256)` and selector `0x1537dc87` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,address,uint256)")] - pub struct Log105Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,uint256,address)` and selector `0x1596a1ce` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,uint256,address)")] - pub struct Log106Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,string,uint256)` and selector `0x159f8927` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,string,uint256)")] - pub struct Log107Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,uint256,address)` and selector `0x15c127b5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,uint256,address)")] - pub struct Log108Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,address,bool)` and selector `0x15cac476` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,address,bool)")] - pub struct Log109Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,bool,uint256)` and selector `0x1606a393` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,bool,uint256)")] - pub struct Log110Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,string,string)` and selector `0x1762e32a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,string,string)")] - pub struct Log111Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,uint256)` and selector `0x17fe6185` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,uint256)")] - pub struct Log30Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,bool)` and selector `0x18c9c746` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,bool)")] - pub struct Log31Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,uint256,uint256)` and selector `0x193fb800` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,uint256,uint256)")] - pub struct Log112Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,string,address)` and selector `0x19fd4956` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,string,address)")] - pub struct Log113Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,uint256,string)` and selector `0x1ad96de6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,uint256,string)")] - pub struct Log114Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,address,string)` and selector `0x1bb3b09a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,address,string)")] - pub struct Log115Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,bool,address)` and selector `0x1c41a336` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,bool,address)")] - pub struct Log116Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,address)` and selector `0x1c7ec448` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,address)")] - pub struct Log32Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool)` and selector `0x1c9d7eb3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool)")] - pub struct Log6Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,address,address)` and selector `0x1d14d001` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,address,address)")] - pub struct Log117Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,address,string)` and selector `0x1da986ea` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,address,string)")] - pub struct Log118Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,uint256,uint256)` and selector `0x1dc8e1b8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,uint256,uint256)")] - pub struct Log119Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,string,bool)` and selector `0x1e4b87e5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,string,bool)")] - pub struct Log120Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,uint256)` and selector `0x20098014` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,uint256)")] - pub struct Log33Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,bool,address)` and selector `0x205871c2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,bool,address)")] - pub struct Log121Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,bool)` and selector `0x20718650` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,bool)")] - pub struct Log34Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,uint256,address)` and selector `0x20e3984d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,uint256,address)")] - pub struct Log122Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,string)` and selector `0x212255cc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,string)")] - pub struct Log35Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,string,string)` and selector `0x21ad0683` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,string,string)")] - pub struct Log123Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,string,string)` and selector `0x21bdaf25` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,string,string)")] - pub struct Log124Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,bool,address)` and selector `0x223603bd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,bool,address)")] - pub struct Log125Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,bool,uint256)` and selector `0x22f6b999` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,bool,uint256)")] - pub struct Log126Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,string,string)` and selector `0x245986f2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,string,string)")] - pub struct Log127Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,address,address)` and selector `0x2488b414` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,address,address)")] - pub struct Log128Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,string,uint256)` and selector `0x24f91465` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,string,uint256)")] - pub struct Log129Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,string)` and selector `0x2555fa46` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,string)")] - pub struct Log36Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,address,address)` and selector `0x26f560a8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,address,address)")] - pub struct Log130Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,string,string)` and selector `0x27d8afd2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,string,string)")] - pub struct Log131Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,uint256,uint256)` and selector `0x28863fcb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,uint256,uint256)")] - pub struct Log132Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool)` and selector `0x2a110e83` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool)")] - pub struct Log7Call { - pub p_0: bool, - pub p_1: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,bool,string)` and selector `0x2ae408d4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,bool,string)")] - pub struct Log133Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,address,address)` and selector `0x2b2b18dc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,address,address)")] - pub struct Log134Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,string,bool)` and selector `0x2c1754ed` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,string,bool)")] - pub struct Log135Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,string,uint256)` and selector `0x2c1d0746` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,string,uint256)")] - pub struct Log136Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address)` and selector `0x2c2ecbc2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address)")] - pub struct Log1Call { - pub p_0: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,bool,bool)` and selector `0x2cd4134a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,bool,bool)")] - pub struct Log137Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,string)` and selector `0x2ced7cef` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,string)")] - pub struct Log37Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,address,string)` and selector `0x2d8e33a4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,address,string)")] - pub struct Log138Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,address,string)` and selector `0x2dd778e6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,address,string)")] - pub struct Log139Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address)` and selector `0x319af333` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address)")] - pub struct Log8Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool)` and selector `0x32458eed` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool)")] - pub struct Log2Call { - pub p_0: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,address,address)` and selector `0x33e9dd1d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,address,address)")] - pub struct Log140Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,uint256,uint256)` and selector `0x34f0e636` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,uint256,uint256)")] - pub struct Log141Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,address)` and selector `0x35085f7b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,address)")] - pub struct Log38Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,bool,bool)` and selector `0x354c36d6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,bool,bool)")] - pub struct Log142Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,string,bool)` and selector `0x35a5071f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,string,bool)")] - pub struct Log143Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,uint256)` and selector `0x37103367` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,uint256)")] - pub struct Log39Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,uint256,uint256)` and selector `0x374bb4b2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,uint256,uint256)")] - pub struct Log144Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,uint256)` and selector `0x37aa7d4c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,uint256)")] - pub struct Log40Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,uint256,uint256)` and selector `0x386ff5f4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,uint256,uint256)")] - pub struct Log145Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,bool,uint256)` and selector `0x3971e78c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,bool,uint256)")] - pub struct Log146Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256)` and selector `0x399174d3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256)")] - pub struct Log9Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,uint256,address)` and selector `0x3b2279b4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,uint256,address)")] - pub struct Log147Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,bool,bool)` and selector `0x3b2a5ce0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,bool,bool)")] - pub struct Log148Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,bool,bool)` and selector `0x3bf5e537` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,bool,bool)")] - pub struct Log149Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,string,string)` and selector `0x3e128ca3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,string,string)")] - pub struct Log150Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,bool,uint256)` and selector `0x3e9f866a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,bool,uint256)")] - pub struct Log151Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,string,bool)` and selector `0x3f8a701d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,string,bool)")] - pub struct Log152Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,bool,bool)` and selector `0x40785869` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,bool,bool)")] - pub struct Log153Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string)` and selector `0x41304fac` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string)")] - pub struct Log3Call { - pub p_0: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,string,address)` and selector `0x42d21db7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,string,address)")] - pub struct Log154Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,address,address)` and selector `0x439c7bef` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,address,address)")] - pub struct Log155Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,uint256,string)` and selector `0x448830a8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,uint256,string)")] - pub struct Log156Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,address,bool)` and selector `0x454d54a5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,address,bool)")] - pub struct Log157Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,address,uint256)` and selector `0x457fe3cf` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,address,uint256)")] - pub struct Log158Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,address,bool)` and selector `0x46600be0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,address,bool)")] - pub struct Log159Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,string,uint256)` and selector `0x46826b5d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,string,uint256)")] - pub struct Log160Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,string,string)` and selector `0x475c5c33` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,string,string)")] - pub struct Log161Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,bool)` and selector `0x4766da72` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,bool)")] - pub struct Log41Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,address,address)` and selector `0x478d1c62` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,address,address)")] - pub struct Log162Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,bool,string)` and selector `0x483d0416` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,bool,string)")] - pub struct Log163Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,uint256,string)` and selector `0x4a28c017` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,uint256,string)")] - pub struct Log164Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,bool,string)` and selector `0x4a66cb34` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,bool,string)")] - pub struct Log165Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string)` and selector `0x4b5c4277` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string)")] - pub struct Log10Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,address,uint256)` and selector `0x4c123d57` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,address,uint256)")] - pub struct Log166Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,bool)` and selector `0x4ceda75a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,bool)")] - pub struct Log42Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,address,uint256)` and selector `0x4f04fdc6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,address,uint256)")] - pub struct Log167Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,bool)` and selector `0x50709698` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,bool)")] - pub struct Log43Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,string,bool)` and selector `0x50ad461d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,string,bool)")] - pub struct Log168Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,bool,uint256)` and selector `0x515e38b6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,bool,uint256)")] - pub struct Log169Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log()` and selector `0x51973ec9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log()")] - pub struct Log0Call; - ///Container type for all input parameters for the `log` function with signature `log(bool,address,uint256,string)` and selector `0x51f09ff8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,uint256,string)")] - pub struct Log170Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,bool,address)` and selector `0x538e06ab` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,bool,address)")] - pub struct Log171Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,uint256,address)` and selector `0x54a7a9a0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,uint256,address)")] - pub struct Log172Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,address,address)` and selector `0x56a5d1b1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,address,address)")] - pub struct Log173Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,uint256)` and selector `0x5821efa1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,uint256)")] - pub struct Log44Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,string)` and selector `0x5970e089` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,string)")] - pub struct Log45Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,uint256,string)` and selector `0x59cfcbe3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,uint256,string)")] - pub struct Log174Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,uint256,string)` and selector `0x5a477632` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,uint256,string)")] - pub struct Log175Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,uint256)` and selector `0x5a9b5ed5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,uint256)")] - pub struct Log46Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,string,string)` and selector `0x5ab84e1f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,string,string)")] - pub struct Log176Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,bool,uint256)` and selector `0x5abd992a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,bool,uint256)")] - pub struct Log177Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,string,address)` and selector `0x5c430d47` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,string,address)")] - pub struct Log178Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,address)` and selector `0x5c96b331` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,address)")] - pub struct Log47Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,address,bool)` and selector `0x5ccd4e37` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,address,bool)")] - pub struct Log179Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,string,string)` and selector `0x5d02c50b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,string,string)")] - pub struct Log180Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,address,uint256)` and selector `0x5d08bb05` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,address,uint256)")] - pub struct Log181Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,uint256,string)` and selector `0x5d1a971a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,uint256,string)")] - pub struct Log182Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,string,uint256)` and selector `0x5da297eb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,string,uint256)")] - pub struct Log183Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,bool,string)` and selector `0x5e84b0ea` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,bool,string)")] - pub struct Log184Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,address,address)` and selector `0x5ea2b7ae` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,address,address)")] - pub struct Log185Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,string,bool)` and selector `0x5f15d28c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,string,bool)")] - pub struct Log186Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,bool,bool)` and selector `0x5f1d5c9f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,bool,bool)")] - pub struct Log187Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,uint256,bool)` and selector `0x5f743a7c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,uint256,bool)")] - pub struct Log188Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,uint256)` and selector `0x5f7b9afb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,uint256)")] - pub struct Log48Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,address,address)` and selector `0x6168ed61` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,address,address)")] - pub struct Log189Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,uint256,bool)` and selector `0x619e4d0e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,uint256,bool)")] - pub struct Log190Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,uint256,address)` and selector `0x63183678` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,uint256,address)")] - pub struct Log191Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,string)` and selector `0x63cb41f9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,string)")] - pub struct Log49Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,uint256,address)` and selector `0x63fb8bc5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,uint256,address)")] - pub struct Log192Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string)` and selector `0x643fd0df` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string)")] - pub struct Log11Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,uint256,uint256)` and selector `0x64b5bb67` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,uint256,uint256)")] - pub struct Log193Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,address,address)` and selector `0x660375dd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,address,address)")] - pub struct Log194Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,address,address)` and selector `0x665bf134` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,address,address)")] - pub struct Log195Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,uint256,bool)` and selector `0x66f1bc67` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,uint256,bool)")] - pub struct Log196Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,bool)` and selector `0x678209a8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,bool)")] - pub struct Log50Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,uint256)` and selector `0x67dd6ff1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,uint256)")] - pub struct Log51Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,string,string)` and selector `0x68c8b8bd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,string,string)")] - pub struct Log197Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,uint256,bool)` and selector `0x691a8f74` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,uint256,bool)")] - pub struct Log198Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address)` and selector `0x69276c86` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address)")] - pub struct Log12Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,bool,address)` and selector `0x69640b59` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,bool,address)")] - pub struct Log199Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,string,uint256)` and selector `0x6a1199e2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,string,uint256)")] - pub struct Log200Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,bool,bool)` and selector `0x6a9c478b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,bool,bool)")] - pub struct Log201Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,uint256,bool)` and selector `0x6b0e5d53` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,uint256,bool)")] - pub struct Log202Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,address,string)` and selector `0x6cde40b8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,address,string)")] - pub struct Log203Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,string,string)` and selector `0x6d1e8751` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,string,string)")] - pub struct Log204Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,string,address)` and selector `0x6d572f44` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,string,address)")] - pub struct Log205Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,bool,uint256)` and selector `0x6d7045c1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,bool,uint256)")] - pub struct Log206Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,address,bool)` and selector `0x6dd434ca` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,address,bool)")] - pub struct Log207Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,string,bool)` and selector `0x6f1a594e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,string,bool)")] - pub struct Log208Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,string,address)` and selector `0x6f7c603e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,string,address)")] - pub struct Log209Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,bool,address)` and selector `0x7190a529` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,bool,address)")] - pub struct Log210Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,string)` and selector `0x71d04af2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,string)")] - pub struct Log52Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,address,uint256)` and selector `0x736efbb6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,address,uint256)")] - pub struct Log211Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,uint256,string)` and selector `0x742d6ee7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,uint256,string)")] - pub struct Log212Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,bool,uint256)` and selector `0x7464ce23` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,bool,uint256)")] - pub struct Log213Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string)` and selector `0x759f86bb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string)")] - pub struct Log13Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool)` and selector `0x75b605d3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool)")] - pub struct Log14Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,uint256,bool)` and selector `0x7626db92` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,uint256,bool)")] - pub struct Log214Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,bool,bool)` and selector `0x79884c2b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,bool,bool)")] - pub struct Log215Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,string,bool)` and selector `0x7af6ab25` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,string,bool)")] - pub struct Log216Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,address)` and selector `0x7afac959` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,address)")] - pub struct Log53Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,address)` and selector `0x7bc0d848` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,address)")] - pub struct Log54Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,string,uint256)` and selector `0x7be0c3eb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,string,uint256)")] - pub struct Log217Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,uint256,uint256)` and selector `0x7bf181a1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,uint256,uint256)")] - pub struct Log218Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,string,address)` and selector `0x7c4632a4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,string,address)")] - pub struct Log219Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,address,uint256)` and selector `0x7cc3c607` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,address,uint256)")] - pub struct Log220Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,string,bool)` and selector `0x7d24491d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,string,bool)")] - pub struct Log221Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,uint256,string)` and selector `0x7dd4d0e0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,uint256,string)")] - pub struct Log222Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,bool,uint256)` and selector `0x7f9bbca2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,bool,uint256)")] - pub struct Log223Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,address,string)` and selector `0x800a1c67` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,address,string)")] - pub struct Log224Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,string,uint256)` and selector `0x80e6a20b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,string,uint256)")] - pub struct Log225Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,address,bool)` and selector `0x82112a42` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,address,bool)")] - pub struct Log226Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,uint256,uint256)` and selector `0x82c25b74` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,uint256,uint256)")] - pub struct Log227Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256)` and selector `0x8309e8a8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256)")] - pub struct Log15Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,bool)` and selector `0x850b7ad6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,bool)")] - pub struct Log55Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address)` and selector `0x853c4849` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address)")] - pub struct Log16Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,uint256,string)` and selector `0x854b3496` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,uint256,string)")] - pub struct Log228Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,string)` and selector `0x85775021` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,string)")] - pub struct Log56Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,string,string)` and selector `0x88a8c406` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,string,string)")] - pub struct Log229Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,uint256,address)` and selector `0x88cb6041` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,uint256,address)")] - pub struct Log230Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,address,uint256)` and selector `0x88f6e4b2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,address,uint256)")] - pub struct Log231Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,bool,bool)` and selector `0x895af8c5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,bool,bool)")] - pub struct Log232Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,uint256,bool)` and selector `0x8af7cf8a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,uint256,bool)")] - pub struct Log233Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,bool,address)` and selector `0x8c329b1a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,bool,address)")] - pub struct Log234Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,bool,uint256)` and selector `0x8c4e5de6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,bool,uint256)")] - pub struct Log235Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,uint256,address)` and selector `0x8da6def5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,uint256,address)")] - pub struct Log236Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,bool,uint256)` and selector `0x8e3f78a9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,bool,uint256)")] - pub struct Log237Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,uint256,string)` and selector `0x8e69fb5d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,uint256,string)")] - pub struct Log238Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,string,uint256)` and selector `0x8eafb02b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,string,uint256)")] - pub struct Log239Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,address,uint256)` and selector `0x8ef3f399` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,address,uint256)")] - pub struct Log240Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,string,address)` and selector `0x8f736d16` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,string,address)")] - pub struct Log241Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string)` and selector `0x8feac525` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string)")] - pub struct Log17Call { - pub p_0: bool, - pub p_1: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,address,bool)` and selector `0x90c30a56` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,address,bool)")] - pub struct Log242Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,bool,string)` and selector `0x90fb06aa` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,bool,string)")] - pub struct Log243Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,bool,string)` and selector `0x9143dbb1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,bool,string)")] - pub struct Log244Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,uint256,bool)` and selector `0x91a02e2a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,uint256,bool)")] - pub struct Log245Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,string,uint256)` and selector `0x91d1112e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,string,uint256)")] - pub struct Log246Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,address)` and selector `0x932bbb38` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,address)")] - pub struct Log57Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,uint256,address)` and selector `0x935e09bf` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,uint256,address)")] - pub struct Log247Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,address,uint256)` and selector `0x94250d77` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,address,uint256)")] - pub struct Log248Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,address,bool)` and selector `0x958c28c6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,address,bool)")] - pub struct Log249Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,address)` and selector `0x9591b953` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,address)")] - pub struct Log58Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,address)` and selector `0x95ed0195` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,address)")] - pub struct Log59Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,string,address)` and selector `0x97d394d8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,string,address)")] - pub struct Log250Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,bool,address)` and selector `0x9a816a83` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,bool,address)")] - pub struct Log251Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,bool,address)` and selector `0x9acd3616` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,bool,address)")] - pub struct Log252Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,uint256,bool)` and selector `0x9b4254e2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,uint256,bool)")] - pub struct Log253Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,bool)` and selector `0x9b6ec042` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,bool)")] - pub struct Log60Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,address,string)` and selector `0x9c3adfa1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,address,string)")] - pub struct Log254Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,uint256)` and selector `0x9c4f99fb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,uint256)")] - pub struct Log61Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,string,address)` and selector `0x9cba8fff` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,string,address)")] - pub struct Log255Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,bool,string)` and selector `0x9d22d5dd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,bool,string)")] - pub struct Log256Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,bool,address)` and selector `0x9f1bc36e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,bool,address)")] - pub struct Log257Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,address,string)` and selector `0x9ffb2f93` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,address,string)")] - pub struct Log258Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,string,address)` and selector `0xa04e2f87` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,string,address)")] - pub struct Log259Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,address,string)` and selector `0xa0a47963` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,address,string)")] - pub struct Log260Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,address,bool)` and selector `0xa1bcc9b3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,address,bool)")] - pub struct Log261Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,address,address)` and selector `0xa1ef4cbb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,address,address)")] - pub struct Log262Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,string)` and selector `0xa1f2e8aa` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,string)")] - pub struct Log62Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,bool,address)` and selector `0xa31bfdcc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,bool,address)")] - pub struct Log263Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,bool,string)` and selector `0xa5b4fc99` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,bool,string)")] - pub struct Log264Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,address,uint256)` and selector `0xa5cada94` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,address,uint256)")] - pub struct Log265Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,address,bool)` and selector `0xa6f50b0f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,address,bool)")] - pub struct Log266Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,string,string)` and selector `0xa73c1db6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,string,string)")] - pub struct Log267Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,address,uint256)` and selector `0xa75c59de` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,address,uint256)")] - pub struct Log268Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,uint256,uint256)` and selector `0xa7a87853` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,uint256,uint256)")] - pub struct Log269Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,string,string)` and selector `0xa826caeb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,string,string)")] - pub struct Log270Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,bool,string)` and selector `0xaa6540c8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,bool,string)")] - pub struct Log271Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,string,address)` and selector `0xaabc9a31` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,string,address)")] - pub struct Log272Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,bool,bool)` and selector `0xab085ae6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,bool,bool)")] - pub struct Log273Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,bool,string)` and selector `0xabf73a98` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,bool,string)")] - pub struct Log274Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,address,string)` and selector `0xade052c7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,address,string)")] - pub struct Log275Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,bool,address)` and selector `0xae2ec581` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,bool,address)")] - pub struct Log276Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,string,uint256)` and selector `0xb028c9bd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,string,uint256)")] - pub struct Log277Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,string)` and selector `0xb076847f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,string)")] - pub struct Log63Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,bool)` and selector `0xb0e0f9b5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,bool)")] - pub struct Log64Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,string)` and selector `0xb115611f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,string)")] - pub struct Log65Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,string,bool)` and selector `0xb3a6b6bd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,string,bool)")] - pub struct Log278Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,address,bool)` and selector `0xb4c314ff` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,address,bool)")] - pub struct Log279Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,address,bool)` and selector `0xb59dbd60` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,address,bool)")] - pub struct Log280Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256)` and selector `0xb60e72cc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256)")] - pub struct Log18Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,uint256)` and selector `0xb69bcaf6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,uint256)")] - pub struct Log66Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,bool,bool)` and selector `0xb6f577a1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,bool,bool)")] - pub struct Log281Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,uint256,string)` and selector `0xb7b914ca` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,uint256,string)")] - pub struct Log282Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,string,bool)` and selector `0xb857163a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,string,bool)")] - pub struct Log283Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,bool,bool)` and selector `0xba535d9c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,bool,bool)")] - pub struct Log284Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,bool,string)` and selector `0xbc0b61fe` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,bool,string)")] - pub struct Log285Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,address)` and selector `0xbcfd9be0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,address)")] - pub struct Log67Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,uint256,uint256)` and selector `0xbe553481` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,uint256,uint256)")] - pub struct Log286Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,uint256,bool)` and selector `0xbe984353` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,uint256,bool)")] - pub struct Log287Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,string,uint256)` and selector `0xbf01f891` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,string,uint256)")] - pub struct Log288Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,address,bool)` and selector `0xc0a302d8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,address,bool)")] - pub struct Log289Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,string,uint256)` and selector `0xc21f64c7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,string,uint256)")] - pub struct Log290Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,bool,address)` and selector `0xc371c7db` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,bool,address)")] - pub struct Log291Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,uint256,bool)` and selector `0xc3a8a654` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,uint256,bool)")] - pub struct Log292Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool)` and selector `0xc3b55635` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool)")] - pub struct Log19Call { - pub p_0: ::std::string::String, - pub p_1: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,string)` and selector `0xc3fc3970` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,string)")] - pub struct Log68Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,uint256,bool)` and selector `0xc4643e20` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,uint256,bool)")] - pub struct Log293Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,uint256,bool)` and selector `0xc598d185` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,uint256,bool)")] - pub struct Log294Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,bool,string)` and selector `0xc5ad85f9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,bool,string)")] - pub struct Log295Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,string,uint256)` and selector `0xc67ea9d1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,string,uint256)")] - pub struct Log296Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,uint256,uint256)` and selector `0xc6acc7a8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,uint256,uint256)")] - pub struct Log297Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,bool)` and selector `0xc91d5ed4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,bool)")] - pub struct Log69Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,uint256)` and selector `0xc95958d6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,uint256)")] - pub struct Log70Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,uint256)` and selector `0xca47c4eb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,uint256)")] - pub struct Log71Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,bool)` and selector `0xca7733b1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,bool)")] - pub struct Log72Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,bool,bool)` and selector `0xcac43479` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,bool,bool)")] - pub struct Log298Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,string,bool)` and selector `0xcc32ab07` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,string,bool)")] - pub struct Log299Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,uint256,address)` and selector `0xccf790a1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,uint256,address)")] - pub struct Log300Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,bool,bool)` and selector `0xceb5f4d7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,bool,bool)")] - pub struct Log301Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,bool,uint256)` and selector `0xcf009880` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,bool,uint256)")] - pub struct Log302Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,bool)` and selector `0xcf020fb1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,bool)")] - pub struct Log73Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,uint256,string,bool)` and selector `0xcf18105c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,uint256,string,bool)")] - pub struct Log303Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,bool,address)` and selector `0xcf394485` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,bool,address)")] - pub struct Log304Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,uint256)` and selector `0xd1ed7a3c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,uint256)")] - pub struct Log74Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,address)` and selector `0xd2763667` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,address)")] - pub struct Log75Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,bool,string)` and selector `0xd2d423cd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,bool,string)")] - pub struct Log305Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,string,address)` and selector `0xd583c602` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,string,address)")] - pub struct Log306Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,uint256,bool)` and selector `0xd6019f1c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,uint256,bool)")] - pub struct Log307Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,bool,uint256)` and selector `0xd6aefad2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,bool,uint256)")] - pub struct Log308Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,address,string)` and selector `0xd812a167` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,address,string)")] - pub struct Log309Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address)` and selector `0xdaf0d4aa` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address)")] - pub struct Log20Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,bool)` and selector `0xdbb4c247` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,bool)")] - pub struct Log76Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,string,bool,bool)` and selector `0xdc5e935b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,string,bool,bool)")] - pub struct Log310Call { - pub p_0: bool, - pub p_1: ::std::string::String, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,uint256,string)` and selector `0xddb06521` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,uint256,string)")] - pub struct Log311Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,bool,string)` and selector `0xdddb9561` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,bool,string)")] - pub struct Log312Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,uint256,string)` and selector `0xde03e774` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,uint256,string)")] - pub struct Log313Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,string,string)` and selector `0xde68f20a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,string,string)")] - pub struct Log314Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,string)` and selector `0xde9a9270` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,string)")] - pub struct Log77Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,bool,string)` and selector `0xdfc4a2e8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,bool,string)")] - pub struct Log315Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: bool, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,string,address)` and selector `0xe0625b29` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,string,address)")] - pub struct Log316Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,bool,address)` and selector `0xe0e95b98` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,bool,address)")] - pub struct Log317Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,string)` and selector `0xe0e9ad4f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,string)")] - pub struct Log78Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,uint256,address)` and selector `0xe21de278` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,uint256,address)")] - pub struct Log318Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(string,bool,string)` and selector `0xe298f47d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,bool,string)")] - pub struct Log79Call { - pub p_0: ::std::string::String, - pub p_1: bool, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,address,string,bool)` and selector `0xe2bfd60b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,address,string,bool)")] - pub struct Log319Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,bool,bool)` and selector `0xe351140f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,bool,bool)")] - pub struct Log320Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,string,uint256)` and selector `0xe3a9ca2f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,string,uint256)")] - pub struct Log321Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,uint256,bool,uint256)` and selector `0xe41b6f6f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,uint256,bool,uint256)")] - pub struct Log322Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,string,bool)` and selector `0xe5e70b2b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,string,bool)")] - pub struct Log323Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,string,address,uint256)` and selector `0xe8d3018d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,string,address,uint256)")] - pub struct Log324Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,bool)` and selector `0xe8defba9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,bool)")] - pub struct Log80Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,address,string)` and selector `0xeb1bff80` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,address,string)")] - pub struct Log325Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,bool,uint256)` and selector `0xeb7f6fd2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,bool,uint256)")] - pub struct Log326Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: bool, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,bool)` and selector `0xeb830c92` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,bool)")] - pub struct Log81Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,string,bool)` and selector `0xeb928d7f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,string,bool)")] - pub struct Log327Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,address,address)` and selector `0xed8f28f6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,address,address)")] - pub struct Log328Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,string,uint256)` and selector `0xef1cefe7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,string,uint256)")] - pub struct Log329Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,bool,string,address)` and selector `0xef529018` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,bool,string,address)")] - pub struct Log330Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,address,bool,address)` and selector `0xef72c513` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,address,bool,address)")] - pub struct Log331Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,address)` and selector `0xf08744e8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,address)")] - pub struct Log82Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,bool,address)` and selector `0xf11699ed` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,bool,address)")] - pub struct Log83Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,bool)` and selector `0xf2a66286` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,bool)")] - pub struct Log84Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,string,uint256,uint256)` and selector `0xf45d7d2c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,string,uint256,uint256)")] - pub struct Log332Call { - pub p_0: ::std::string::String, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,address,address)` and selector `0xf4880ea4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,address,address)")] - pub struct Log333Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,string,string)` and selector `0xf5bc2249` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,string,string)")] - pub struct Log334Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256)` and selector `0xf666715a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256)")] - pub struct Log21Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,address,string)` and selector `0xf7e36245` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,address,string)")] - pub struct Log335Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,address,string)` and selector `0xf808da20` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,address,string)")] - pub struct Log336Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256)` and selector `0xf82c50f1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256)")] - pub struct Log4Call { - pub p_0: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,uint256,uint256)` and selector `0xf8f51b1e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,uint256,uint256)")] - pub struct Log337Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::U256, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,bool,string,address)` and selector `0xf9ad2b89` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,bool,string,address)")] - pub struct Log338Call { - pub p_0: bool, - pub p_1: bool, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(uint256,uint256,uint256,address)` and selector `0xfa8185af` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(uint256,uint256,uint256,address)")] - pub struct Log339Call { - pub p_0: ::ethers_core::types::U256, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,string,string)` and selector `0xfb772265` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,string,string)")] - pub struct Log85Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::std::string::String, - pub p_2: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,uint256,bool)` and selector `0xfc4845f0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,uint256,bool)")] - pub struct Log340Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: bool, - } - ///Container type for all input parameters for the `log` function with signature `log(string,address,address)` and selector `0xfcec75e0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,address,address)")] - pub struct Log86Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(address,address,uint256,string)` and selector `0xfdb4f990` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(address,address,uint256,string)")] - pub struct Log341Call { - pub p_0: ::ethers_core::types::Address, - pub p_1: ::ethers_core::types::Address, - pub p_2: ::ethers_core::types::U256, - pub p_3: ::std::string::String, - } - ///Container type for all input parameters for the `log` function with signature `log(bool,uint256,string,address)` and selector `0xfedd1fff` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(bool,uint256,string,address)")] - pub struct Log342Call { - pub p_0: bool, - pub p_1: ::ethers_core::types::U256, - pub p_2: ::std::string::String, - pub p_3: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `log` function with signature `log(int256)` and selector `0x2d5b6cb9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(int256)")] - pub struct Log5Call { - pub p_0: ::ethers_core::types::I256, - } - ///Container type for all input parameters for the `log` function with signature `log(string,int256)` and selector `0x3ca6268e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "log", abi = "log(string,int256)")] - pub struct Log22Call { - pub p_0: ::std::string::String, - pub p_1: ::ethers_core::types::I256, - } - ///Container type for all input parameters for the `logAddress` function with signature `logAddress(address)` and selector `0x5f91b0af` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logAddress", abi = "logAddress(address)")] - pub struct LogAddressCall { - pub p_0: ::ethers_core::types::Address, - } - ///Container type for all input parameters for the `logBool` function with signature `logBool(bool)` and selector `0xba7ab84e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBool", abi = "logBool(bool)")] - pub struct LogBoolCall { - pub p_0: bool, - } - ///Container type for all input parameters for the `logBytes` function with signature `logBytes(bytes)` and selector `0xe17bf956` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes", abi = "logBytes(bytes)")] - pub struct LogBytesCall { - pub p_0: ::ethers_core::types::Bytes, - } - ///Container type for all input parameters for the `logBytes1` function with signature `logBytes1(bytes1)` and selector `0x6f4171c9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes1", abi = "logBytes1(bytes1)")] - pub struct LogBytes1Call { - pub p_0: [u8; 1], - } - ///Container type for all input parameters for the `logBytes10` function with signature `logBytes10(bytes10)` and selector `0x9dc2a897` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes10", abi = "logBytes10(bytes10)")] - pub struct LogBytes10Call { - pub p_0: [u8; 10], - } - ///Container type for all input parameters for the `logBytes11` function with signature `logBytes11(bytes11)` and selector `0xdc08b6a7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes11", abi = "logBytes11(bytes11)")] - pub struct LogBytes11Call { - pub p_0: [u8; 11], - } - ///Container type for all input parameters for the `logBytes12` function with signature `logBytes12(bytes12)` and selector `0x7656d6c7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes12", abi = "logBytes12(bytes12)")] - pub struct LogBytes12Call { - pub p_0: [u8; 12], - } - ///Container type for all input parameters for the `logBytes13` function with signature `logBytes13(bytes13)` and selector `0x34c1d81b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes13", abi = "logBytes13(bytes13)")] - pub struct LogBytes13Call { - pub p_0: [u8; 13], - } - ///Container type for all input parameters for the `logBytes14` function with signature `logBytes14(bytes14)` and selector `0x3ceaba65` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes14", abi = "logBytes14(bytes14)")] - pub struct LogBytes14Call { - pub p_0: [u8; 14], - } - ///Container type for all input parameters for the `logBytes15` function with signature `logBytes15(bytes15)` and selector `0x591a3da2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes15", abi = "logBytes15(bytes15)")] - pub struct LogBytes15Call { - pub p_0: [u8; 15], - } - ///Container type for all input parameters for the `logBytes16` function with signature `logBytes16(bytes16)` and selector `0x1f8d7312` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes16", abi = "logBytes16(bytes16)")] - pub struct LogBytes16Call { - pub p_0: [u8; 16], - } - ///Container type for all input parameters for the `logBytes17` function with signature `logBytes17(bytes17)` and selector `0xf89a532f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes17", abi = "logBytes17(bytes17)")] - pub struct LogBytes17Call { - pub p_0: [u8; 17], - } - ///Container type for all input parameters for the `logBytes18` function with signature `logBytes18(bytes18)` and selector `0xd8652642` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes18", abi = "logBytes18(bytes18)")] - pub struct LogBytes18Call { - pub p_0: [u8; 18], - } - ///Container type for all input parameters for the `logBytes19` function with signature `logBytes19(bytes19)` and selector `0x00f56bc9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes19", abi = "logBytes19(bytes19)")] - pub struct LogBytes19Call { - pub p_0: [u8; 19], - } - ///Container type for all input parameters for the `logBytes2` function with signature `logBytes2(bytes2)` and selector `0x9b5e943e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes2", abi = "logBytes2(bytes2)")] - pub struct LogBytes2Call { - pub p_0: [u8; 2], - } - ///Container type for all input parameters for the `logBytes20` function with signature `logBytes20(bytes20)` and selector `0xecb8567e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes20", abi = "logBytes20(bytes20)")] - pub struct LogBytes20Call { - pub p_0: [u8; 20], - } - ///Container type for all input parameters for the `logBytes21` function with signature `logBytes21(bytes21)` and selector `0x3052c08f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes21", abi = "logBytes21(bytes21)")] - pub struct LogBytes21Call { - pub p_0: [u8; 21], - } - ///Container type for all input parameters for the `logBytes22` function with signature `logBytes22(bytes22)` and selector `0x807ab434` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes22", abi = "logBytes22(bytes22)")] - pub struct LogBytes22Call { - pub p_0: [u8; 22], - } - ///Container type for all input parameters for the `logBytes23` function with signature `logBytes23(bytes23)` and selector `0x4979b037` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes23", abi = "logBytes23(bytes23)")] - pub struct LogBytes23Call { - pub p_0: [u8; 23], - } - ///Container type for all input parameters for the `logBytes24` function with signature `logBytes24(bytes24)` and selector `0x0977aefc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes24", abi = "logBytes24(bytes24)")] - pub struct LogBytes24Call { - pub p_0: [u8; 24], - } - ///Container type for all input parameters for the `logBytes25` function with signature `logBytes25(bytes25)` and selector `0xaea9963f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes25", abi = "logBytes25(bytes25)")] - pub struct LogBytes25Call { - pub p_0: [u8; 25], - } - ///Container type for all input parameters for the `logBytes26` function with signature `logBytes26(bytes26)` and selector `0xd3635628` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes26", abi = "logBytes26(bytes26)")] - pub struct LogBytes26Call { - pub p_0: [u8; 26], - } - ///Container type for all input parameters for the `logBytes27` function with signature `logBytes27(bytes27)` and selector `0xfc372f9f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes27", abi = "logBytes27(bytes27)")] - pub struct LogBytes27Call { - pub p_0: [u8; 27], - } - ///Container type for all input parameters for the `logBytes28` function with signature `logBytes28(bytes28)` and selector `0x382f9a34` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes28", abi = "logBytes28(bytes28)")] - pub struct LogBytes28Call { - pub p_0: [u8; 28], - } - ///Container type for all input parameters for the `logBytes29` function with signature `logBytes29(bytes29)` and selector `0x7a187641` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes29", abi = "logBytes29(bytes29)")] - pub struct LogBytes29Call { - pub p_0: [u8; 29], - } - ///Container type for all input parameters for the `logBytes3` function with signature `logBytes3(bytes3)` and selector `0x7782fa2d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes3", abi = "logBytes3(bytes3)")] - pub struct LogBytes3Call { - pub p_0: [u8; 3], - } - ///Container type for all input parameters for the `logBytes30` function with signature `logBytes30(bytes30)` and selector `0xc4340ef6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes30", abi = "logBytes30(bytes30)")] - pub struct LogBytes30Call { - pub p_0: [u8; 30], - } - ///Container type for all input parameters for the `logBytes31` function with signature `logBytes31(bytes31)` and selector `0x81fc8648` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes31", abi = "logBytes31(bytes31)")] - pub struct LogBytes31Call { - pub p_0: [u8; 31], - } - ///Container type for all input parameters for the `logBytes32` function with signature `logBytes32(bytes32)` and selector `0x2d21d6f7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes32", abi = "logBytes32(bytes32)")] - pub struct LogBytes32Call { - pub p_0: [u8; 32], - } - ///Container type for all input parameters for the `logBytes4` function with signature `logBytes4(bytes4)` and selector `0xfba3ad39` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes4", abi = "logBytes4(bytes4)")] - pub struct LogBytes4Call { - pub p_0: [u8; 4], - } - ///Container type for all input parameters for the `logBytes5` function with signature `logBytes5(bytes5)` and selector `0x5583be2e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes5", abi = "logBytes5(bytes5)")] - pub struct LogBytes5Call { - pub p_0: [u8; 5], - } - ///Container type for all input parameters for the `logBytes6` function with signature `logBytes6(bytes6)` and selector `0x4942adc6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes6", abi = "logBytes6(bytes6)")] - pub struct LogBytes6Call { - pub p_0: [u8; 6], - } - ///Container type for all input parameters for the `logBytes7` function with signature `logBytes7(bytes7)` and selector `0x4574afab` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes7", abi = "logBytes7(bytes7)")] - pub struct LogBytes7Call { - pub p_0: [u8; 7], - } - ///Container type for all input parameters for the `logBytes8` function with signature `logBytes8(bytes8)` and selector `0x9902e47f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes8", abi = "logBytes8(bytes8)")] - pub struct LogBytes8Call { - pub p_0: [u8; 8], - } - ///Container type for all input parameters for the `logBytes9` function with signature `logBytes9(bytes9)` and selector `0x50a138df` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logBytes9", abi = "logBytes9(bytes9)")] - pub struct LogBytes9Call { - pub p_0: [u8; 9], - } - ///Container type for all input parameters for the `logInt` function with signature `logInt(int256)` and selector `0x6525b5f5` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logInt", abi = "logInt(int256)")] - pub struct LogIntCall { - pub p_0: ::ethers_core::types::I256, - } - ///Container type for all input parameters for the `logString` function with signature `logString(string)` and selector `0x0bb563d6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logString", abi = "logString(string)")] - pub struct LogStringCall { - pub p_0: ::std::string::String, - } - ///Container type for all input parameters for the `logUint` function with signature `logUint(uint256)` and selector `0x9905b744` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - foundry_macros::ConsoleFmt, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "logUint", abi = "logUint(uint256)")] - pub struct LogUintCall { - pub p_0: ::ethers_core::types::U256, - } - ///Container type for all of the contract's call - #[derive( - Clone, - ::ethers_contract::EthAbiType, - foundry_macros::ConsoleFmt, - Debug, - PartialEq, - Eq, - Hash - )] - pub enum HardhatConsoleCalls { - Log23(Log23Call), - Log87(Log87Call), - Log24(Log24Call), - Log88(Log88Call), - Log89(Log89Call), - Log90(Log90Call), - Log91(Log91Call), - Log25(Log25Call), - Log92(Log92Call), - Log93(Log93Call), - Log94(Log94Call), - Log95(Log95Call), - Log96(Log96Call), - Log26(Log26Call), - Log97(Log97Call), - Log98(Log98Call), - Log99(Log99Call), - Log100(Log100Call), - Log101(Log101Call), - Log102(Log102Call), - Log27(Log27Call), - Log28(Log28Call), - Log103(Log103Call), - Log29(Log29Call), - Log104(Log104Call), - Log105(Log105Call), - Log106(Log106Call), - Log107(Log107Call), - Log108(Log108Call), - Log109(Log109Call), - Log110(Log110Call), - Log111(Log111Call), - Log30(Log30Call), - Log31(Log31Call), - Log112(Log112Call), - Log113(Log113Call), - Log114(Log114Call), - Log115(Log115Call), - Log116(Log116Call), - Log32(Log32Call), - Log6(Log6Call), - Log117(Log117Call), - Log118(Log118Call), - Log119(Log119Call), - Log120(Log120Call), - Log33(Log33Call), - Log121(Log121Call), - Log34(Log34Call), - Log122(Log122Call), - Log35(Log35Call), - Log123(Log123Call), - Log124(Log124Call), - Log125(Log125Call), - Log126(Log126Call), - Log127(Log127Call), - Log128(Log128Call), - Log129(Log129Call), - Log36(Log36Call), - Log130(Log130Call), - Log131(Log131Call), - Log132(Log132Call), - Log7(Log7Call), - Log133(Log133Call), - Log134(Log134Call), - Log135(Log135Call), - Log136(Log136Call), - Log1(Log1Call), - Log137(Log137Call), - Log37(Log37Call), - Log138(Log138Call), - Log139(Log139Call), - Log8(Log8Call), - Log2(Log2Call), - Log140(Log140Call), - Log141(Log141Call), - Log38(Log38Call), - Log142(Log142Call), - Log143(Log143Call), - Log39(Log39Call), - Log144(Log144Call), - Log40(Log40Call), - Log145(Log145Call), - Log146(Log146Call), - Log9(Log9Call), - Log147(Log147Call), - Log148(Log148Call), - Log149(Log149Call), - Log150(Log150Call), - Log151(Log151Call), - Log152(Log152Call), - Log153(Log153Call), - Log3(Log3Call), - Log154(Log154Call), - Log155(Log155Call), - Log156(Log156Call), - Log157(Log157Call), - Log158(Log158Call), - Log159(Log159Call), - Log160(Log160Call), - Log161(Log161Call), - Log41(Log41Call), - Log162(Log162Call), - Log163(Log163Call), - Log164(Log164Call), - Log165(Log165Call), - Log10(Log10Call), - Log166(Log166Call), - Log42(Log42Call), - Log167(Log167Call), - Log43(Log43Call), - Log168(Log168Call), - Log169(Log169Call), - Log0(Log0Call), - Log170(Log170Call), - Log171(Log171Call), - Log172(Log172Call), - Log173(Log173Call), - Log44(Log44Call), - Log45(Log45Call), - Log174(Log174Call), - Log175(Log175Call), - Log46(Log46Call), - Log176(Log176Call), - Log177(Log177Call), - Log178(Log178Call), - Log47(Log47Call), - Log179(Log179Call), - Log180(Log180Call), - Log181(Log181Call), - Log182(Log182Call), - Log183(Log183Call), - Log184(Log184Call), - Log185(Log185Call), - Log186(Log186Call), - Log187(Log187Call), - Log188(Log188Call), - Log48(Log48Call), - Log189(Log189Call), - Log190(Log190Call), - Log191(Log191Call), - Log49(Log49Call), - Log192(Log192Call), - Log11(Log11Call), - Log193(Log193Call), - Log194(Log194Call), - Log195(Log195Call), - Log196(Log196Call), - Log50(Log50Call), - Log51(Log51Call), - Log197(Log197Call), - Log198(Log198Call), - Log12(Log12Call), - Log199(Log199Call), - Log200(Log200Call), - Log201(Log201Call), - Log202(Log202Call), - Log203(Log203Call), - Log204(Log204Call), - Log205(Log205Call), - Log206(Log206Call), - Log207(Log207Call), - Log208(Log208Call), - Log209(Log209Call), - Log210(Log210Call), - Log52(Log52Call), - Log211(Log211Call), - Log212(Log212Call), - Log213(Log213Call), - Log13(Log13Call), - Log14(Log14Call), - Log214(Log214Call), - Log215(Log215Call), - Log216(Log216Call), - Log53(Log53Call), - Log54(Log54Call), - Log217(Log217Call), - Log218(Log218Call), - Log219(Log219Call), - Log220(Log220Call), - Log221(Log221Call), - Log222(Log222Call), - Log223(Log223Call), - Log224(Log224Call), - Log225(Log225Call), - Log226(Log226Call), - Log227(Log227Call), - Log15(Log15Call), - Log55(Log55Call), - Log16(Log16Call), - Log228(Log228Call), - Log56(Log56Call), - Log229(Log229Call), - Log230(Log230Call), - Log231(Log231Call), - Log232(Log232Call), - Log233(Log233Call), - Log234(Log234Call), - Log235(Log235Call), - Log236(Log236Call), - Log237(Log237Call), - Log238(Log238Call), - Log239(Log239Call), - Log240(Log240Call), - Log241(Log241Call), - Log17(Log17Call), - Log242(Log242Call), - Log243(Log243Call), - Log244(Log244Call), - Log245(Log245Call), - Log246(Log246Call), - Log57(Log57Call), - Log247(Log247Call), - Log248(Log248Call), - Log249(Log249Call), - Log58(Log58Call), - Log59(Log59Call), - Log250(Log250Call), - Log251(Log251Call), - Log252(Log252Call), - Log253(Log253Call), - Log60(Log60Call), - Log254(Log254Call), - Log61(Log61Call), - Log255(Log255Call), - Log256(Log256Call), - Log257(Log257Call), - Log258(Log258Call), - Log259(Log259Call), - Log260(Log260Call), - Log261(Log261Call), - Log262(Log262Call), - Log62(Log62Call), - Log263(Log263Call), - Log264(Log264Call), - Log265(Log265Call), - Log266(Log266Call), - Log267(Log267Call), - Log268(Log268Call), - Log269(Log269Call), - Log270(Log270Call), - Log271(Log271Call), - Log272(Log272Call), - Log273(Log273Call), - Log274(Log274Call), - Log275(Log275Call), - Log276(Log276Call), - Log277(Log277Call), - Log63(Log63Call), - Log64(Log64Call), - Log65(Log65Call), - Log278(Log278Call), - Log279(Log279Call), - Log280(Log280Call), - Log18(Log18Call), - Log66(Log66Call), - Log281(Log281Call), - Log282(Log282Call), - Log283(Log283Call), - Log284(Log284Call), - Log285(Log285Call), - Log67(Log67Call), - Log286(Log286Call), - Log287(Log287Call), - Log288(Log288Call), - Log289(Log289Call), - Log290(Log290Call), - Log291(Log291Call), - Log292(Log292Call), - Log19(Log19Call), - Log68(Log68Call), - Log293(Log293Call), - Log294(Log294Call), - Log295(Log295Call), - Log296(Log296Call), - Log297(Log297Call), - Log69(Log69Call), - Log70(Log70Call), - Log71(Log71Call), - Log72(Log72Call), - Log298(Log298Call), - Log299(Log299Call), - Log300(Log300Call), - Log301(Log301Call), - Log302(Log302Call), - Log73(Log73Call), - Log303(Log303Call), - Log304(Log304Call), - Log74(Log74Call), - Log75(Log75Call), - Log305(Log305Call), - Log306(Log306Call), - Log307(Log307Call), - Log308(Log308Call), - Log309(Log309Call), - Log20(Log20Call), - Log76(Log76Call), - Log310(Log310Call), - Log311(Log311Call), - Log312(Log312Call), - Log313(Log313Call), - Log314(Log314Call), - Log77(Log77Call), - Log315(Log315Call), - Log316(Log316Call), - Log317(Log317Call), - Log78(Log78Call), - Log318(Log318Call), - Log79(Log79Call), - Log319(Log319Call), - Log320(Log320Call), - Log321(Log321Call), - Log322(Log322Call), - Log323(Log323Call), - Log324(Log324Call), - Log80(Log80Call), - Log325(Log325Call), - Log326(Log326Call), - Log81(Log81Call), - Log327(Log327Call), - Log328(Log328Call), - Log329(Log329Call), - Log330(Log330Call), - Log331(Log331Call), - Log82(Log82Call), - Log83(Log83Call), - Log84(Log84Call), - Log332(Log332Call), - Log333(Log333Call), - Log334(Log334Call), - Log21(Log21Call), - Log335(Log335Call), - Log336(Log336Call), - Log4(Log4Call), - Log337(Log337Call), - Log338(Log338Call), - Log339(Log339Call), - Log85(Log85Call), - Log340(Log340Call), - Log86(Log86Call), - Log341(Log341Call), - Log342(Log342Call), - Log5(Log5Call), - Log22(Log22Call), - LogAddress(LogAddressCall), - LogBool(LogBoolCall), - LogBytes(LogBytesCall), - LogBytes1(LogBytes1Call), - LogBytes10(LogBytes10Call), - LogBytes11(LogBytes11Call), - LogBytes12(LogBytes12Call), - LogBytes13(LogBytes13Call), - LogBytes14(LogBytes14Call), - LogBytes15(LogBytes15Call), - LogBytes16(LogBytes16Call), - LogBytes17(LogBytes17Call), - LogBytes18(LogBytes18Call), - LogBytes19(LogBytes19Call), - LogBytes2(LogBytes2Call), - LogBytes20(LogBytes20Call), - LogBytes21(LogBytes21Call), - LogBytes22(LogBytes22Call), - LogBytes23(LogBytes23Call), - LogBytes24(LogBytes24Call), - LogBytes25(LogBytes25Call), - LogBytes26(LogBytes26Call), - LogBytes27(LogBytes27Call), - LogBytes28(LogBytes28Call), - LogBytes29(LogBytes29Call), - LogBytes3(LogBytes3Call), - LogBytes30(LogBytes30Call), - LogBytes31(LogBytes31Call), - LogBytes32(LogBytes32Call), - LogBytes4(LogBytes4Call), - LogBytes5(LogBytes5Call), - LogBytes6(LogBytes6Call), - LogBytes7(LogBytes7Call), - LogBytes8(LogBytes8Call), - LogBytes9(LogBytes9Call), - LogInt(LogIntCall), - LogString(LogStringCall), - LogUint(LogUintCall), - } - impl ::ethers_core::abi::AbiDecode for HardhatConsoleCalls { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log23(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log87(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log24(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log88(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log89(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log90(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log91(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log25(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log92(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log93(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log94(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log95(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log96(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log26(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log97(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log98(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log99(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log100(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log101(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log102(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log27(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log28(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log103(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log29(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log104(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log105(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log106(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log107(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log108(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log109(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log110(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log111(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log30(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log31(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log112(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log113(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log114(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log115(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log116(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log32(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log6(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log117(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log118(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log119(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log120(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log33(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log121(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log34(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log122(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log35(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log123(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log124(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log125(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log126(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log127(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log128(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log129(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log36(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log130(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log131(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log132(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log7(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log133(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log134(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log135(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log136(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log137(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log37(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log138(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log139(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log8(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log140(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log141(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log38(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log142(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log143(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log39(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log144(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log40(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log145(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log146(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log9(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log147(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log148(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log149(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log150(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log151(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log152(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log153(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log154(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log155(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log156(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log157(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log158(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log159(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log160(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log161(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log41(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log162(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log163(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log164(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log165(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log10(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log166(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log42(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log167(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log43(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log168(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log169(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log170(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log171(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log172(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log173(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log44(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log45(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log174(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log175(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log46(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log176(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log177(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log178(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log47(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log179(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log180(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log181(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log182(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log183(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log184(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log185(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log186(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log187(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log188(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log48(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log189(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log190(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log191(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log49(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log192(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log11(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log193(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log194(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log195(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log196(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log50(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log51(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log197(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log198(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log12(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log199(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log200(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log201(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log202(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log203(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log204(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log205(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log206(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log207(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log208(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log209(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log210(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log52(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log211(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log212(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log213(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log13(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log14(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log214(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log215(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log216(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log53(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log54(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log217(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log218(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log219(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log220(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log221(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log222(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log223(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log224(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log225(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log226(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log227(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log15(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log55(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log16(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log228(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log56(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log229(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log230(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log231(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log232(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log233(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log234(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log235(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log236(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log237(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log238(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log239(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log240(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log241(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log17(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log242(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log243(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log244(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log245(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log246(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log57(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log247(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log248(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log249(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log58(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log59(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log250(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log251(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log252(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log253(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log60(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log254(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log61(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log255(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log256(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log257(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log258(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log259(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log260(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log261(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log262(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log62(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log263(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log264(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log265(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log266(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log267(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log268(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log269(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log270(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log271(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log272(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log273(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log274(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log275(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log276(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log277(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log63(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log64(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log65(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log278(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log279(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log280(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log18(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log66(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log281(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log282(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log283(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log284(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log285(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log67(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log286(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log287(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log288(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log289(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log290(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log291(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log292(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log19(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log68(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log293(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log294(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log295(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log296(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log297(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log69(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log70(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log71(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log72(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log298(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log299(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log300(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log301(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log302(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log73(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log303(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log304(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log74(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log75(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log305(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log306(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log307(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log308(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log309(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log20(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log76(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log310(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log311(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log312(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log313(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log314(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log77(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log315(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log316(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log317(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log78(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log318(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log79(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log319(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log320(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log321(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log322(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log323(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log324(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log80(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log325(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log326(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log81(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log327(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log328(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log329(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log330(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log331(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log82(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log83(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log84(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log332(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log333(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log334(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log21(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log335(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log336(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log4(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log337(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log338(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log339(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log85(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log340(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log86(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log341(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log342(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log5(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Log22(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogAddress(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBool(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes10(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes11(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes12(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes13(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes14(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes15(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes16(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes17(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes18(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes19(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes20(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes21(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes22(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes23(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes24(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes25(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes26(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes27(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes28(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes29(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes30(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes31(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes32(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes4(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes5(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes6(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes7(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes8(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogBytes9(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogInt(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogString(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::LogUint(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData.into()) - } - } - impl ::ethers_core::abi::AbiEncode for HardhatConsoleCalls { - fn encode(self) -> Vec { - match self { - Self::Log23(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log87(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log24(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log88(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log89(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log90(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log91(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log25(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log92(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log93(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log94(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log95(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log96(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log26(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log97(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log98(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log99(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log100(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log101(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log102(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log27(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log28(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log103(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log29(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log104(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log105(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log106(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log107(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log108(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log109(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log110(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log111(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log30(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log31(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log112(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log113(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log114(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log115(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log116(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log32(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log6(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log117(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log118(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log119(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log120(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log33(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log121(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log34(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log122(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log35(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log123(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log124(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log125(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log126(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log127(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log128(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log129(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log36(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log130(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log131(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log132(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log7(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log133(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log134(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log135(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log136(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log137(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log37(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log138(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log139(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log8(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log2(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log140(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log141(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log38(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log142(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log143(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log39(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log144(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log40(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log145(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log146(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log9(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log147(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log148(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log149(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log150(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log151(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log152(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log153(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log3(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log154(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log155(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log156(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log157(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log158(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log159(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log160(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log161(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log41(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log162(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log163(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log164(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log165(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log10(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log166(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log42(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log167(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log43(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log168(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log169(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log170(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log171(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log172(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log173(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log44(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log45(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log174(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log175(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log46(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log176(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log177(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log178(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log47(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log179(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log180(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log181(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log182(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log183(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log184(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log185(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log186(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log187(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log188(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log48(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log189(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log190(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log191(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log49(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log192(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log11(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log193(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log194(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log195(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log196(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log50(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log51(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log197(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log198(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log12(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log199(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log200(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log201(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log202(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log203(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log204(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log205(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log206(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log207(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log208(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log209(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log210(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log52(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log211(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log212(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log213(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log13(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log14(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log214(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log215(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log216(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log53(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log54(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log217(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log218(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log219(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log220(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log221(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log222(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log223(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log224(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log225(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log226(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log227(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log15(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log55(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log16(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log228(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log56(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log229(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log230(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log231(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log232(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log233(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log234(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log235(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log236(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log237(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log238(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log239(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log240(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log241(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log17(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log242(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log243(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log244(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log245(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log246(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log57(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log247(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log248(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log249(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log58(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log59(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log250(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log251(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log252(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log253(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log60(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log254(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log61(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log255(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log256(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log257(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log258(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log259(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log260(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log261(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log262(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log62(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log263(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log264(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log265(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log266(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log267(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log268(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log269(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log270(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log271(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log272(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log273(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log274(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log275(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log276(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log277(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log63(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log64(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log65(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log278(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log279(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log280(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log18(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log66(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log281(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log282(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log283(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log284(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log285(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log67(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log286(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log287(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log288(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log289(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log290(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log291(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log292(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log19(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log68(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log293(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log294(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log295(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log296(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log297(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log69(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log70(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log71(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log72(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log298(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log299(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log300(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log301(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log302(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log73(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log303(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log304(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log74(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log75(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log305(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log306(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log307(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log308(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log309(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log20(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log76(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log310(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log311(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log312(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log313(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log314(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log77(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log315(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log316(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log317(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log78(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log318(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log79(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log319(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log320(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log321(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log322(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log323(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log324(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log80(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log325(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log326(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log81(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log327(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log328(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log329(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log330(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log331(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log82(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log83(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log84(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log332(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log333(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log334(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log21(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log335(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log336(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log4(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log337(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log338(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log339(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log85(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log340(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log86(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log341(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log342(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log5(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Log22(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::LogAddress(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBool(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::LogBytes(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::LogBytes1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes10(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes11(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes12(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes13(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes14(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes15(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes16(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes17(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes18(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes19(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes20(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes21(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes22(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes23(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes24(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes25(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes26(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes27(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes28(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes29(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes30(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes31(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes32(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes4(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes5(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes6(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes7(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes8(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogBytes9(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogInt(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::LogString(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::LogUint(element) => ::ethers_core::abi::AbiEncode::encode(element), - } - } - } - impl ::core::fmt::Display for HardhatConsoleCalls { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::Log23(element) => ::core::fmt::Display::fmt(element, f), - Self::Log87(element) => ::core::fmt::Display::fmt(element, f), - Self::Log24(element) => ::core::fmt::Display::fmt(element, f), - Self::Log88(element) => ::core::fmt::Display::fmt(element, f), - Self::Log89(element) => ::core::fmt::Display::fmt(element, f), - Self::Log90(element) => ::core::fmt::Display::fmt(element, f), - Self::Log91(element) => ::core::fmt::Display::fmt(element, f), - Self::Log25(element) => ::core::fmt::Display::fmt(element, f), - Self::Log92(element) => ::core::fmt::Display::fmt(element, f), - Self::Log93(element) => ::core::fmt::Display::fmt(element, f), - Self::Log94(element) => ::core::fmt::Display::fmt(element, f), - Self::Log95(element) => ::core::fmt::Display::fmt(element, f), - Self::Log96(element) => ::core::fmt::Display::fmt(element, f), - Self::Log26(element) => ::core::fmt::Display::fmt(element, f), - Self::Log97(element) => ::core::fmt::Display::fmt(element, f), - Self::Log98(element) => ::core::fmt::Display::fmt(element, f), - Self::Log99(element) => ::core::fmt::Display::fmt(element, f), - Self::Log100(element) => ::core::fmt::Display::fmt(element, f), - Self::Log101(element) => ::core::fmt::Display::fmt(element, f), - Self::Log102(element) => ::core::fmt::Display::fmt(element, f), - Self::Log27(element) => ::core::fmt::Display::fmt(element, f), - Self::Log28(element) => ::core::fmt::Display::fmt(element, f), - Self::Log103(element) => ::core::fmt::Display::fmt(element, f), - Self::Log29(element) => ::core::fmt::Display::fmt(element, f), - Self::Log104(element) => ::core::fmt::Display::fmt(element, f), - Self::Log105(element) => ::core::fmt::Display::fmt(element, f), - Self::Log106(element) => ::core::fmt::Display::fmt(element, f), - Self::Log107(element) => ::core::fmt::Display::fmt(element, f), - Self::Log108(element) => ::core::fmt::Display::fmt(element, f), - Self::Log109(element) => ::core::fmt::Display::fmt(element, f), - Self::Log110(element) => ::core::fmt::Display::fmt(element, f), - Self::Log111(element) => ::core::fmt::Display::fmt(element, f), - Self::Log30(element) => ::core::fmt::Display::fmt(element, f), - Self::Log31(element) => ::core::fmt::Display::fmt(element, f), - Self::Log112(element) => ::core::fmt::Display::fmt(element, f), - Self::Log113(element) => ::core::fmt::Display::fmt(element, f), - Self::Log114(element) => ::core::fmt::Display::fmt(element, f), - Self::Log115(element) => ::core::fmt::Display::fmt(element, f), - Self::Log116(element) => ::core::fmt::Display::fmt(element, f), - Self::Log32(element) => ::core::fmt::Display::fmt(element, f), - Self::Log6(element) => ::core::fmt::Display::fmt(element, f), - Self::Log117(element) => ::core::fmt::Display::fmt(element, f), - Self::Log118(element) => ::core::fmt::Display::fmt(element, f), - Self::Log119(element) => ::core::fmt::Display::fmt(element, f), - Self::Log120(element) => ::core::fmt::Display::fmt(element, f), - Self::Log33(element) => ::core::fmt::Display::fmt(element, f), - Self::Log121(element) => ::core::fmt::Display::fmt(element, f), - Self::Log34(element) => ::core::fmt::Display::fmt(element, f), - Self::Log122(element) => ::core::fmt::Display::fmt(element, f), - Self::Log35(element) => ::core::fmt::Display::fmt(element, f), - Self::Log123(element) => ::core::fmt::Display::fmt(element, f), - Self::Log124(element) => ::core::fmt::Display::fmt(element, f), - Self::Log125(element) => ::core::fmt::Display::fmt(element, f), - Self::Log126(element) => ::core::fmt::Display::fmt(element, f), - Self::Log127(element) => ::core::fmt::Display::fmt(element, f), - Self::Log128(element) => ::core::fmt::Display::fmt(element, f), - Self::Log129(element) => ::core::fmt::Display::fmt(element, f), - Self::Log36(element) => ::core::fmt::Display::fmt(element, f), - Self::Log130(element) => ::core::fmt::Display::fmt(element, f), - Self::Log131(element) => ::core::fmt::Display::fmt(element, f), - Self::Log132(element) => ::core::fmt::Display::fmt(element, f), - Self::Log7(element) => ::core::fmt::Display::fmt(element, f), - Self::Log133(element) => ::core::fmt::Display::fmt(element, f), - Self::Log134(element) => ::core::fmt::Display::fmt(element, f), - Self::Log135(element) => ::core::fmt::Display::fmt(element, f), - Self::Log136(element) => ::core::fmt::Display::fmt(element, f), - Self::Log1(element) => ::core::fmt::Display::fmt(element, f), - Self::Log137(element) => ::core::fmt::Display::fmt(element, f), - Self::Log37(element) => ::core::fmt::Display::fmt(element, f), - Self::Log138(element) => ::core::fmt::Display::fmt(element, f), - Self::Log139(element) => ::core::fmt::Display::fmt(element, f), - Self::Log8(element) => ::core::fmt::Display::fmt(element, f), - Self::Log2(element) => ::core::fmt::Display::fmt(element, f), - Self::Log140(element) => ::core::fmt::Display::fmt(element, f), - Self::Log141(element) => ::core::fmt::Display::fmt(element, f), - Self::Log38(element) => ::core::fmt::Display::fmt(element, f), - Self::Log142(element) => ::core::fmt::Display::fmt(element, f), - Self::Log143(element) => ::core::fmt::Display::fmt(element, f), - Self::Log39(element) => ::core::fmt::Display::fmt(element, f), - Self::Log144(element) => ::core::fmt::Display::fmt(element, f), - Self::Log40(element) => ::core::fmt::Display::fmt(element, f), - Self::Log145(element) => ::core::fmt::Display::fmt(element, f), - Self::Log146(element) => ::core::fmt::Display::fmt(element, f), - Self::Log9(element) => ::core::fmt::Display::fmt(element, f), - Self::Log147(element) => ::core::fmt::Display::fmt(element, f), - Self::Log148(element) => ::core::fmt::Display::fmt(element, f), - Self::Log149(element) => ::core::fmt::Display::fmt(element, f), - Self::Log150(element) => ::core::fmt::Display::fmt(element, f), - Self::Log151(element) => ::core::fmt::Display::fmt(element, f), - Self::Log152(element) => ::core::fmt::Display::fmt(element, f), - Self::Log153(element) => ::core::fmt::Display::fmt(element, f), - Self::Log3(element) => ::core::fmt::Display::fmt(element, f), - Self::Log154(element) => ::core::fmt::Display::fmt(element, f), - Self::Log155(element) => ::core::fmt::Display::fmt(element, f), - Self::Log156(element) => ::core::fmt::Display::fmt(element, f), - Self::Log157(element) => ::core::fmt::Display::fmt(element, f), - Self::Log158(element) => ::core::fmt::Display::fmt(element, f), - Self::Log159(element) => ::core::fmt::Display::fmt(element, f), - Self::Log160(element) => ::core::fmt::Display::fmt(element, f), - Self::Log161(element) => ::core::fmt::Display::fmt(element, f), - Self::Log41(element) => ::core::fmt::Display::fmt(element, f), - Self::Log162(element) => ::core::fmt::Display::fmt(element, f), - Self::Log163(element) => ::core::fmt::Display::fmt(element, f), - Self::Log164(element) => ::core::fmt::Display::fmt(element, f), - Self::Log165(element) => ::core::fmt::Display::fmt(element, f), - Self::Log10(element) => ::core::fmt::Display::fmt(element, f), - Self::Log166(element) => ::core::fmt::Display::fmt(element, f), - Self::Log42(element) => ::core::fmt::Display::fmt(element, f), - Self::Log167(element) => ::core::fmt::Display::fmt(element, f), - Self::Log43(element) => ::core::fmt::Display::fmt(element, f), - Self::Log168(element) => ::core::fmt::Display::fmt(element, f), - Self::Log169(element) => ::core::fmt::Display::fmt(element, f), - Self::Log0(element) => ::core::fmt::Display::fmt(element, f), - Self::Log170(element) => ::core::fmt::Display::fmt(element, f), - Self::Log171(element) => ::core::fmt::Display::fmt(element, f), - Self::Log172(element) => ::core::fmt::Display::fmt(element, f), - Self::Log173(element) => ::core::fmt::Display::fmt(element, f), - Self::Log44(element) => ::core::fmt::Display::fmt(element, f), - Self::Log45(element) => ::core::fmt::Display::fmt(element, f), - Self::Log174(element) => ::core::fmt::Display::fmt(element, f), - Self::Log175(element) => ::core::fmt::Display::fmt(element, f), - Self::Log46(element) => ::core::fmt::Display::fmt(element, f), - Self::Log176(element) => ::core::fmt::Display::fmt(element, f), - Self::Log177(element) => ::core::fmt::Display::fmt(element, f), - Self::Log178(element) => ::core::fmt::Display::fmt(element, f), - Self::Log47(element) => ::core::fmt::Display::fmt(element, f), - Self::Log179(element) => ::core::fmt::Display::fmt(element, f), - Self::Log180(element) => ::core::fmt::Display::fmt(element, f), - Self::Log181(element) => ::core::fmt::Display::fmt(element, f), - Self::Log182(element) => ::core::fmt::Display::fmt(element, f), - Self::Log183(element) => ::core::fmt::Display::fmt(element, f), - Self::Log184(element) => ::core::fmt::Display::fmt(element, f), - Self::Log185(element) => ::core::fmt::Display::fmt(element, f), - Self::Log186(element) => ::core::fmt::Display::fmt(element, f), - Self::Log187(element) => ::core::fmt::Display::fmt(element, f), - Self::Log188(element) => ::core::fmt::Display::fmt(element, f), - Self::Log48(element) => ::core::fmt::Display::fmt(element, f), - Self::Log189(element) => ::core::fmt::Display::fmt(element, f), - Self::Log190(element) => ::core::fmt::Display::fmt(element, f), - Self::Log191(element) => ::core::fmt::Display::fmt(element, f), - Self::Log49(element) => ::core::fmt::Display::fmt(element, f), - Self::Log192(element) => ::core::fmt::Display::fmt(element, f), - Self::Log11(element) => ::core::fmt::Display::fmt(element, f), - Self::Log193(element) => ::core::fmt::Display::fmt(element, f), - Self::Log194(element) => ::core::fmt::Display::fmt(element, f), - Self::Log195(element) => ::core::fmt::Display::fmt(element, f), - Self::Log196(element) => ::core::fmt::Display::fmt(element, f), - Self::Log50(element) => ::core::fmt::Display::fmt(element, f), - Self::Log51(element) => ::core::fmt::Display::fmt(element, f), - Self::Log197(element) => ::core::fmt::Display::fmt(element, f), - Self::Log198(element) => ::core::fmt::Display::fmt(element, f), - Self::Log12(element) => ::core::fmt::Display::fmt(element, f), - Self::Log199(element) => ::core::fmt::Display::fmt(element, f), - Self::Log200(element) => ::core::fmt::Display::fmt(element, f), - Self::Log201(element) => ::core::fmt::Display::fmt(element, f), - Self::Log202(element) => ::core::fmt::Display::fmt(element, f), - Self::Log203(element) => ::core::fmt::Display::fmt(element, f), - Self::Log204(element) => ::core::fmt::Display::fmt(element, f), - Self::Log205(element) => ::core::fmt::Display::fmt(element, f), - Self::Log206(element) => ::core::fmt::Display::fmt(element, f), - Self::Log207(element) => ::core::fmt::Display::fmt(element, f), - Self::Log208(element) => ::core::fmt::Display::fmt(element, f), - Self::Log209(element) => ::core::fmt::Display::fmt(element, f), - Self::Log210(element) => ::core::fmt::Display::fmt(element, f), - Self::Log52(element) => ::core::fmt::Display::fmt(element, f), - Self::Log211(element) => ::core::fmt::Display::fmt(element, f), - Self::Log212(element) => ::core::fmt::Display::fmt(element, f), - Self::Log213(element) => ::core::fmt::Display::fmt(element, f), - Self::Log13(element) => ::core::fmt::Display::fmt(element, f), - Self::Log14(element) => ::core::fmt::Display::fmt(element, f), - Self::Log214(element) => ::core::fmt::Display::fmt(element, f), - Self::Log215(element) => ::core::fmt::Display::fmt(element, f), - Self::Log216(element) => ::core::fmt::Display::fmt(element, f), - Self::Log53(element) => ::core::fmt::Display::fmt(element, f), - Self::Log54(element) => ::core::fmt::Display::fmt(element, f), - Self::Log217(element) => ::core::fmt::Display::fmt(element, f), - Self::Log218(element) => ::core::fmt::Display::fmt(element, f), - Self::Log219(element) => ::core::fmt::Display::fmt(element, f), - Self::Log220(element) => ::core::fmt::Display::fmt(element, f), - Self::Log221(element) => ::core::fmt::Display::fmt(element, f), - Self::Log222(element) => ::core::fmt::Display::fmt(element, f), - Self::Log223(element) => ::core::fmt::Display::fmt(element, f), - Self::Log224(element) => ::core::fmt::Display::fmt(element, f), - Self::Log225(element) => ::core::fmt::Display::fmt(element, f), - Self::Log226(element) => ::core::fmt::Display::fmt(element, f), - Self::Log227(element) => ::core::fmt::Display::fmt(element, f), - Self::Log15(element) => ::core::fmt::Display::fmt(element, f), - Self::Log55(element) => ::core::fmt::Display::fmt(element, f), - Self::Log16(element) => ::core::fmt::Display::fmt(element, f), - Self::Log228(element) => ::core::fmt::Display::fmt(element, f), - Self::Log56(element) => ::core::fmt::Display::fmt(element, f), - Self::Log229(element) => ::core::fmt::Display::fmt(element, f), - Self::Log230(element) => ::core::fmt::Display::fmt(element, f), - Self::Log231(element) => ::core::fmt::Display::fmt(element, f), - Self::Log232(element) => ::core::fmt::Display::fmt(element, f), - Self::Log233(element) => ::core::fmt::Display::fmt(element, f), - Self::Log234(element) => ::core::fmt::Display::fmt(element, f), - Self::Log235(element) => ::core::fmt::Display::fmt(element, f), - Self::Log236(element) => ::core::fmt::Display::fmt(element, f), - Self::Log237(element) => ::core::fmt::Display::fmt(element, f), - Self::Log238(element) => ::core::fmt::Display::fmt(element, f), - Self::Log239(element) => ::core::fmt::Display::fmt(element, f), - Self::Log240(element) => ::core::fmt::Display::fmt(element, f), - Self::Log241(element) => ::core::fmt::Display::fmt(element, f), - Self::Log17(element) => ::core::fmt::Display::fmt(element, f), - Self::Log242(element) => ::core::fmt::Display::fmt(element, f), - Self::Log243(element) => ::core::fmt::Display::fmt(element, f), - Self::Log244(element) => ::core::fmt::Display::fmt(element, f), - Self::Log245(element) => ::core::fmt::Display::fmt(element, f), - Self::Log246(element) => ::core::fmt::Display::fmt(element, f), - Self::Log57(element) => ::core::fmt::Display::fmt(element, f), - Self::Log247(element) => ::core::fmt::Display::fmt(element, f), - Self::Log248(element) => ::core::fmt::Display::fmt(element, f), - Self::Log249(element) => ::core::fmt::Display::fmt(element, f), - Self::Log58(element) => ::core::fmt::Display::fmt(element, f), - Self::Log59(element) => ::core::fmt::Display::fmt(element, f), - Self::Log250(element) => ::core::fmt::Display::fmt(element, f), - Self::Log251(element) => ::core::fmt::Display::fmt(element, f), - Self::Log252(element) => ::core::fmt::Display::fmt(element, f), - Self::Log253(element) => ::core::fmt::Display::fmt(element, f), - Self::Log60(element) => ::core::fmt::Display::fmt(element, f), - Self::Log254(element) => ::core::fmt::Display::fmt(element, f), - Self::Log61(element) => ::core::fmt::Display::fmt(element, f), - Self::Log255(element) => ::core::fmt::Display::fmt(element, f), - Self::Log256(element) => ::core::fmt::Display::fmt(element, f), - Self::Log257(element) => ::core::fmt::Display::fmt(element, f), - Self::Log258(element) => ::core::fmt::Display::fmt(element, f), - Self::Log259(element) => ::core::fmt::Display::fmt(element, f), - Self::Log260(element) => ::core::fmt::Display::fmt(element, f), - Self::Log261(element) => ::core::fmt::Display::fmt(element, f), - Self::Log262(element) => ::core::fmt::Display::fmt(element, f), - Self::Log62(element) => ::core::fmt::Display::fmt(element, f), - Self::Log263(element) => ::core::fmt::Display::fmt(element, f), - Self::Log264(element) => ::core::fmt::Display::fmt(element, f), - Self::Log265(element) => ::core::fmt::Display::fmt(element, f), - Self::Log266(element) => ::core::fmt::Display::fmt(element, f), - Self::Log267(element) => ::core::fmt::Display::fmt(element, f), - Self::Log268(element) => ::core::fmt::Display::fmt(element, f), - Self::Log269(element) => ::core::fmt::Display::fmt(element, f), - Self::Log270(element) => ::core::fmt::Display::fmt(element, f), - Self::Log271(element) => ::core::fmt::Display::fmt(element, f), - Self::Log272(element) => ::core::fmt::Display::fmt(element, f), - Self::Log273(element) => ::core::fmt::Display::fmt(element, f), - Self::Log274(element) => ::core::fmt::Display::fmt(element, f), - Self::Log275(element) => ::core::fmt::Display::fmt(element, f), - Self::Log276(element) => ::core::fmt::Display::fmt(element, f), - Self::Log277(element) => ::core::fmt::Display::fmt(element, f), - Self::Log63(element) => ::core::fmt::Display::fmt(element, f), - Self::Log64(element) => ::core::fmt::Display::fmt(element, f), - Self::Log65(element) => ::core::fmt::Display::fmt(element, f), - Self::Log278(element) => ::core::fmt::Display::fmt(element, f), - Self::Log279(element) => ::core::fmt::Display::fmt(element, f), - Self::Log280(element) => ::core::fmt::Display::fmt(element, f), - Self::Log18(element) => ::core::fmt::Display::fmt(element, f), - Self::Log66(element) => ::core::fmt::Display::fmt(element, f), - Self::Log281(element) => ::core::fmt::Display::fmt(element, f), - Self::Log282(element) => ::core::fmt::Display::fmt(element, f), - Self::Log283(element) => ::core::fmt::Display::fmt(element, f), - Self::Log284(element) => ::core::fmt::Display::fmt(element, f), - Self::Log285(element) => ::core::fmt::Display::fmt(element, f), - Self::Log67(element) => ::core::fmt::Display::fmt(element, f), - Self::Log286(element) => ::core::fmt::Display::fmt(element, f), - Self::Log287(element) => ::core::fmt::Display::fmt(element, f), - Self::Log288(element) => ::core::fmt::Display::fmt(element, f), - Self::Log289(element) => ::core::fmt::Display::fmt(element, f), - Self::Log290(element) => ::core::fmt::Display::fmt(element, f), - Self::Log291(element) => ::core::fmt::Display::fmt(element, f), - Self::Log292(element) => ::core::fmt::Display::fmt(element, f), - Self::Log19(element) => ::core::fmt::Display::fmt(element, f), - Self::Log68(element) => ::core::fmt::Display::fmt(element, f), - Self::Log293(element) => ::core::fmt::Display::fmt(element, f), - Self::Log294(element) => ::core::fmt::Display::fmt(element, f), - Self::Log295(element) => ::core::fmt::Display::fmt(element, f), - Self::Log296(element) => ::core::fmt::Display::fmt(element, f), - Self::Log297(element) => ::core::fmt::Display::fmt(element, f), - Self::Log69(element) => ::core::fmt::Display::fmt(element, f), - Self::Log70(element) => ::core::fmt::Display::fmt(element, f), - Self::Log71(element) => ::core::fmt::Display::fmt(element, f), - Self::Log72(element) => ::core::fmt::Display::fmt(element, f), - Self::Log298(element) => ::core::fmt::Display::fmt(element, f), - Self::Log299(element) => ::core::fmt::Display::fmt(element, f), - Self::Log300(element) => ::core::fmt::Display::fmt(element, f), - Self::Log301(element) => ::core::fmt::Display::fmt(element, f), - Self::Log302(element) => ::core::fmt::Display::fmt(element, f), - Self::Log73(element) => ::core::fmt::Display::fmt(element, f), - Self::Log303(element) => ::core::fmt::Display::fmt(element, f), - Self::Log304(element) => ::core::fmt::Display::fmt(element, f), - Self::Log74(element) => ::core::fmt::Display::fmt(element, f), - Self::Log75(element) => ::core::fmt::Display::fmt(element, f), - Self::Log305(element) => ::core::fmt::Display::fmt(element, f), - Self::Log306(element) => ::core::fmt::Display::fmt(element, f), - Self::Log307(element) => ::core::fmt::Display::fmt(element, f), - Self::Log308(element) => ::core::fmt::Display::fmt(element, f), - Self::Log309(element) => ::core::fmt::Display::fmt(element, f), - Self::Log20(element) => ::core::fmt::Display::fmt(element, f), - Self::Log76(element) => ::core::fmt::Display::fmt(element, f), - Self::Log310(element) => ::core::fmt::Display::fmt(element, f), - Self::Log311(element) => ::core::fmt::Display::fmt(element, f), - Self::Log312(element) => ::core::fmt::Display::fmt(element, f), - Self::Log313(element) => ::core::fmt::Display::fmt(element, f), - Self::Log314(element) => ::core::fmt::Display::fmt(element, f), - Self::Log77(element) => ::core::fmt::Display::fmt(element, f), - Self::Log315(element) => ::core::fmt::Display::fmt(element, f), - Self::Log316(element) => ::core::fmt::Display::fmt(element, f), - Self::Log317(element) => ::core::fmt::Display::fmt(element, f), - Self::Log78(element) => ::core::fmt::Display::fmt(element, f), - Self::Log318(element) => ::core::fmt::Display::fmt(element, f), - Self::Log79(element) => ::core::fmt::Display::fmt(element, f), - Self::Log319(element) => ::core::fmt::Display::fmt(element, f), - Self::Log320(element) => ::core::fmt::Display::fmt(element, f), - Self::Log321(element) => ::core::fmt::Display::fmt(element, f), - Self::Log322(element) => ::core::fmt::Display::fmt(element, f), - Self::Log323(element) => ::core::fmt::Display::fmt(element, f), - Self::Log324(element) => ::core::fmt::Display::fmt(element, f), - Self::Log80(element) => ::core::fmt::Display::fmt(element, f), - Self::Log325(element) => ::core::fmt::Display::fmt(element, f), - Self::Log326(element) => ::core::fmt::Display::fmt(element, f), - Self::Log81(element) => ::core::fmt::Display::fmt(element, f), - Self::Log327(element) => ::core::fmt::Display::fmt(element, f), - Self::Log328(element) => ::core::fmt::Display::fmt(element, f), - Self::Log329(element) => ::core::fmt::Display::fmt(element, f), - Self::Log330(element) => ::core::fmt::Display::fmt(element, f), - Self::Log331(element) => ::core::fmt::Display::fmt(element, f), - Self::Log82(element) => ::core::fmt::Display::fmt(element, f), - Self::Log83(element) => ::core::fmt::Display::fmt(element, f), - Self::Log84(element) => ::core::fmt::Display::fmt(element, f), - Self::Log332(element) => ::core::fmt::Display::fmt(element, f), - Self::Log333(element) => ::core::fmt::Display::fmt(element, f), - Self::Log334(element) => ::core::fmt::Display::fmt(element, f), - Self::Log21(element) => ::core::fmt::Display::fmt(element, f), - Self::Log335(element) => ::core::fmt::Display::fmt(element, f), - Self::Log336(element) => ::core::fmt::Display::fmt(element, f), - Self::Log4(element) => ::core::fmt::Display::fmt(element, f), - Self::Log337(element) => ::core::fmt::Display::fmt(element, f), - Self::Log338(element) => ::core::fmt::Display::fmt(element, f), - Self::Log339(element) => ::core::fmt::Display::fmt(element, f), - Self::Log85(element) => ::core::fmt::Display::fmt(element, f), - Self::Log340(element) => ::core::fmt::Display::fmt(element, f), - Self::Log86(element) => ::core::fmt::Display::fmt(element, f), - Self::Log341(element) => ::core::fmt::Display::fmt(element, f), - Self::Log342(element) => ::core::fmt::Display::fmt(element, f), - Self::Log5(element) => ::core::fmt::Display::fmt(element, f), - Self::Log22(element) => ::core::fmt::Display::fmt(element, f), - Self::LogAddress(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBool(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes1(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes10(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes11(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes12(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes13(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes14(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes15(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes16(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes17(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes18(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes19(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes2(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes20(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes21(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes22(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes23(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes24(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes25(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes26(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes27(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes28(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes29(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes3(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes30(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes31(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes32(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes4(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes5(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes6(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes7(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes8(element) => ::core::fmt::Display::fmt(element, f), - Self::LogBytes9(element) => ::core::fmt::Display::fmt(element, f), - Self::LogInt(element) => ::core::fmt::Display::fmt(element, f), - Self::LogString(element) => ::core::fmt::Display::fmt(element, f), - Self::LogUint(element) => ::core::fmt::Display::fmt(element, f), - } - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log23Call) -> Self { - Self::Log23(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log87Call) -> Self { - Self::Log87(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log24Call) -> Self { - Self::Log24(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log88Call) -> Self { - Self::Log88(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log89Call) -> Self { - Self::Log89(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log90Call) -> Self { - Self::Log90(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log91Call) -> Self { - Self::Log91(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log25Call) -> Self { - Self::Log25(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log92Call) -> Self { - Self::Log92(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log93Call) -> Self { - Self::Log93(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log94Call) -> Self { - Self::Log94(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log95Call) -> Self { - Self::Log95(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log96Call) -> Self { - Self::Log96(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log26Call) -> Self { - Self::Log26(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log97Call) -> Self { - Self::Log97(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log98Call) -> Self { - Self::Log98(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log99Call) -> Self { - Self::Log99(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log100Call) -> Self { - Self::Log100(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log101Call) -> Self { - Self::Log101(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log102Call) -> Self { - Self::Log102(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log27Call) -> Self { - Self::Log27(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log28Call) -> Self { - Self::Log28(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log103Call) -> Self { - Self::Log103(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log29Call) -> Self { - Self::Log29(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log104Call) -> Self { - Self::Log104(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log105Call) -> Self { - Self::Log105(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log106Call) -> Self { - Self::Log106(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log107Call) -> Self { - Self::Log107(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log108Call) -> Self { - Self::Log108(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log109Call) -> Self { - Self::Log109(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log110Call) -> Self { - Self::Log110(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log111Call) -> Self { - Self::Log111(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log30Call) -> Self { - Self::Log30(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log31Call) -> Self { - Self::Log31(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log112Call) -> Self { - Self::Log112(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log113Call) -> Self { - Self::Log113(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log114Call) -> Self { - Self::Log114(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log115Call) -> Self { - Self::Log115(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log116Call) -> Self { - Self::Log116(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log32Call) -> Self { - Self::Log32(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log6Call) -> Self { - Self::Log6(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log117Call) -> Self { - Self::Log117(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log118Call) -> Self { - Self::Log118(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log119Call) -> Self { - Self::Log119(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log120Call) -> Self { - Self::Log120(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log33Call) -> Self { - Self::Log33(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log121Call) -> Self { - Self::Log121(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log34Call) -> Self { - Self::Log34(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log122Call) -> Self { - Self::Log122(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log35Call) -> Self { - Self::Log35(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log123Call) -> Self { - Self::Log123(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log124Call) -> Self { - Self::Log124(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log125Call) -> Self { - Self::Log125(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log126Call) -> Self { - Self::Log126(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log127Call) -> Self { - Self::Log127(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log128Call) -> Self { - Self::Log128(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log129Call) -> Self { - Self::Log129(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log36Call) -> Self { - Self::Log36(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log130Call) -> Self { - Self::Log130(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log131Call) -> Self { - Self::Log131(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log132Call) -> Self { - Self::Log132(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log7Call) -> Self { - Self::Log7(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log133Call) -> Self { - Self::Log133(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log134Call) -> Self { - Self::Log134(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log135Call) -> Self { - Self::Log135(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log136Call) -> Self { - Self::Log136(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log1Call) -> Self { - Self::Log1(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log137Call) -> Self { - Self::Log137(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log37Call) -> Self { - Self::Log37(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log138Call) -> Self { - Self::Log138(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log139Call) -> Self { - Self::Log139(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log8Call) -> Self { - Self::Log8(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log2Call) -> Self { - Self::Log2(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log140Call) -> Self { - Self::Log140(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log141Call) -> Self { - Self::Log141(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log38Call) -> Self { - Self::Log38(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log142Call) -> Self { - Self::Log142(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log143Call) -> Self { - Self::Log143(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log39Call) -> Self { - Self::Log39(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log144Call) -> Self { - Self::Log144(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log40Call) -> Self { - Self::Log40(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log145Call) -> Self { - Self::Log145(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log146Call) -> Self { - Self::Log146(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log9Call) -> Self { - Self::Log9(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log147Call) -> Self { - Self::Log147(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log148Call) -> Self { - Self::Log148(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log149Call) -> Self { - Self::Log149(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log150Call) -> Self { - Self::Log150(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log151Call) -> Self { - Self::Log151(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log152Call) -> Self { - Self::Log152(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log153Call) -> Self { - Self::Log153(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log3Call) -> Self { - Self::Log3(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log154Call) -> Self { - Self::Log154(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log155Call) -> Self { - Self::Log155(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log156Call) -> Self { - Self::Log156(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log157Call) -> Self { - Self::Log157(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log158Call) -> Self { - Self::Log158(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log159Call) -> Self { - Self::Log159(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log160Call) -> Self { - Self::Log160(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log161Call) -> Self { - Self::Log161(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log41Call) -> Self { - Self::Log41(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log162Call) -> Self { - Self::Log162(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log163Call) -> Self { - Self::Log163(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log164Call) -> Self { - Self::Log164(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log165Call) -> Self { - Self::Log165(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log10Call) -> Self { - Self::Log10(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log166Call) -> Self { - Self::Log166(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log42Call) -> Self { - Self::Log42(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log167Call) -> Self { - Self::Log167(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log43Call) -> Self { - Self::Log43(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log168Call) -> Self { - Self::Log168(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log169Call) -> Self { - Self::Log169(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log0Call) -> Self { - Self::Log0(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log170Call) -> Self { - Self::Log170(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log171Call) -> Self { - Self::Log171(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log172Call) -> Self { - Self::Log172(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log173Call) -> Self { - Self::Log173(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log44Call) -> Self { - Self::Log44(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log45Call) -> Self { - Self::Log45(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log174Call) -> Self { - Self::Log174(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log175Call) -> Self { - Self::Log175(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log46Call) -> Self { - Self::Log46(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log176Call) -> Self { - Self::Log176(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log177Call) -> Self { - Self::Log177(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log178Call) -> Self { - Self::Log178(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log47Call) -> Self { - Self::Log47(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log179Call) -> Self { - Self::Log179(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log180Call) -> Self { - Self::Log180(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log181Call) -> Self { - Self::Log181(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log182Call) -> Self { - Self::Log182(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log183Call) -> Self { - Self::Log183(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log184Call) -> Self { - Self::Log184(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log185Call) -> Self { - Self::Log185(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log186Call) -> Self { - Self::Log186(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log187Call) -> Self { - Self::Log187(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log188Call) -> Self { - Self::Log188(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log48Call) -> Self { - Self::Log48(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log189Call) -> Self { - Self::Log189(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log190Call) -> Self { - Self::Log190(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log191Call) -> Self { - Self::Log191(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log49Call) -> Self { - Self::Log49(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log192Call) -> Self { - Self::Log192(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log11Call) -> Self { - Self::Log11(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log193Call) -> Self { - Self::Log193(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log194Call) -> Self { - Self::Log194(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log195Call) -> Self { - Self::Log195(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log196Call) -> Self { - Self::Log196(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log50Call) -> Self { - Self::Log50(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log51Call) -> Self { - Self::Log51(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log197Call) -> Self { - Self::Log197(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log198Call) -> Self { - Self::Log198(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log12Call) -> Self { - Self::Log12(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log199Call) -> Self { - Self::Log199(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log200Call) -> Self { - Self::Log200(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log201Call) -> Self { - Self::Log201(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log202Call) -> Self { - Self::Log202(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log203Call) -> Self { - Self::Log203(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log204Call) -> Self { - Self::Log204(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log205Call) -> Self { - Self::Log205(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log206Call) -> Self { - Self::Log206(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log207Call) -> Self { - Self::Log207(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log208Call) -> Self { - Self::Log208(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log209Call) -> Self { - Self::Log209(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log210Call) -> Self { - Self::Log210(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log52Call) -> Self { - Self::Log52(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log211Call) -> Self { - Self::Log211(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log212Call) -> Self { - Self::Log212(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log213Call) -> Self { - Self::Log213(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log13Call) -> Self { - Self::Log13(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log14Call) -> Self { - Self::Log14(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log214Call) -> Self { - Self::Log214(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log215Call) -> Self { - Self::Log215(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log216Call) -> Self { - Self::Log216(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log53Call) -> Self { - Self::Log53(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log54Call) -> Self { - Self::Log54(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log217Call) -> Self { - Self::Log217(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log218Call) -> Self { - Self::Log218(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log219Call) -> Self { - Self::Log219(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log220Call) -> Self { - Self::Log220(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log221Call) -> Self { - Self::Log221(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log222Call) -> Self { - Self::Log222(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log223Call) -> Self { - Self::Log223(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log224Call) -> Self { - Self::Log224(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log225Call) -> Self { - Self::Log225(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log226Call) -> Self { - Self::Log226(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log227Call) -> Self { - Self::Log227(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log15Call) -> Self { - Self::Log15(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log55Call) -> Self { - Self::Log55(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log16Call) -> Self { - Self::Log16(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log228Call) -> Self { - Self::Log228(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log56Call) -> Self { - Self::Log56(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log229Call) -> Self { - Self::Log229(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log230Call) -> Self { - Self::Log230(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log231Call) -> Self { - Self::Log231(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log232Call) -> Self { - Self::Log232(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log233Call) -> Self { - Self::Log233(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log234Call) -> Self { - Self::Log234(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log235Call) -> Self { - Self::Log235(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log236Call) -> Self { - Self::Log236(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log237Call) -> Self { - Self::Log237(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log238Call) -> Self { - Self::Log238(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log239Call) -> Self { - Self::Log239(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log240Call) -> Self { - Self::Log240(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log241Call) -> Self { - Self::Log241(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log17Call) -> Self { - Self::Log17(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log242Call) -> Self { - Self::Log242(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log243Call) -> Self { - Self::Log243(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log244Call) -> Self { - Self::Log244(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log245Call) -> Self { - Self::Log245(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log246Call) -> Self { - Self::Log246(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log57Call) -> Self { - Self::Log57(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log247Call) -> Self { - Self::Log247(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log248Call) -> Self { - Self::Log248(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log249Call) -> Self { - Self::Log249(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log58Call) -> Self { - Self::Log58(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log59Call) -> Self { - Self::Log59(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log250Call) -> Self { - Self::Log250(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log251Call) -> Self { - Self::Log251(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log252Call) -> Self { - Self::Log252(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log253Call) -> Self { - Self::Log253(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log60Call) -> Self { - Self::Log60(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log254Call) -> Self { - Self::Log254(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log61Call) -> Self { - Self::Log61(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log255Call) -> Self { - Self::Log255(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log256Call) -> Self { - Self::Log256(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log257Call) -> Self { - Self::Log257(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log258Call) -> Self { - Self::Log258(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log259Call) -> Self { - Self::Log259(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log260Call) -> Self { - Self::Log260(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log261Call) -> Self { - Self::Log261(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log262Call) -> Self { - Self::Log262(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log62Call) -> Self { - Self::Log62(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log263Call) -> Self { - Self::Log263(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log264Call) -> Self { - Self::Log264(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log265Call) -> Self { - Self::Log265(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log266Call) -> Self { - Self::Log266(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log267Call) -> Self { - Self::Log267(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log268Call) -> Self { - Self::Log268(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log269Call) -> Self { - Self::Log269(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log270Call) -> Self { - Self::Log270(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log271Call) -> Self { - Self::Log271(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log272Call) -> Self { - Self::Log272(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log273Call) -> Self { - Self::Log273(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log274Call) -> Self { - Self::Log274(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log275Call) -> Self { - Self::Log275(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log276Call) -> Self { - Self::Log276(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log277Call) -> Self { - Self::Log277(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log63Call) -> Self { - Self::Log63(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log64Call) -> Self { - Self::Log64(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log65Call) -> Self { - Self::Log65(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log278Call) -> Self { - Self::Log278(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log279Call) -> Self { - Self::Log279(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log280Call) -> Self { - Self::Log280(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log18Call) -> Self { - Self::Log18(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log66Call) -> Self { - Self::Log66(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log281Call) -> Self { - Self::Log281(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log282Call) -> Self { - Self::Log282(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log283Call) -> Self { - Self::Log283(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log284Call) -> Self { - Self::Log284(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log285Call) -> Self { - Self::Log285(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log67Call) -> Self { - Self::Log67(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log286Call) -> Self { - Self::Log286(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log287Call) -> Self { - Self::Log287(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log288Call) -> Self { - Self::Log288(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log289Call) -> Self { - Self::Log289(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log290Call) -> Self { - Self::Log290(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log291Call) -> Self { - Self::Log291(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log292Call) -> Self { - Self::Log292(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log19Call) -> Self { - Self::Log19(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log68Call) -> Self { - Self::Log68(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log293Call) -> Self { - Self::Log293(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log294Call) -> Self { - Self::Log294(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log295Call) -> Self { - Self::Log295(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log296Call) -> Self { - Self::Log296(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log297Call) -> Self { - Self::Log297(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log69Call) -> Self { - Self::Log69(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log70Call) -> Self { - Self::Log70(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log71Call) -> Self { - Self::Log71(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log72Call) -> Self { - Self::Log72(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log298Call) -> Self { - Self::Log298(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log299Call) -> Self { - Self::Log299(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log300Call) -> Self { - Self::Log300(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log301Call) -> Self { - Self::Log301(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log302Call) -> Self { - Self::Log302(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log73Call) -> Self { - Self::Log73(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log303Call) -> Self { - Self::Log303(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log304Call) -> Self { - Self::Log304(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log74Call) -> Self { - Self::Log74(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log75Call) -> Self { - Self::Log75(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log305Call) -> Self { - Self::Log305(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log306Call) -> Self { - Self::Log306(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log307Call) -> Self { - Self::Log307(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log308Call) -> Self { - Self::Log308(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log309Call) -> Self { - Self::Log309(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log20Call) -> Self { - Self::Log20(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log76Call) -> Self { - Self::Log76(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log310Call) -> Self { - Self::Log310(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log311Call) -> Self { - Self::Log311(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log312Call) -> Self { - Self::Log312(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log313Call) -> Self { - Self::Log313(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log314Call) -> Self { - Self::Log314(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log77Call) -> Self { - Self::Log77(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log315Call) -> Self { - Self::Log315(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log316Call) -> Self { - Self::Log316(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log317Call) -> Self { - Self::Log317(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log78Call) -> Self { - Self::Log78(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log318Call) -> Self { - Self::Log318(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log79Call) -> Self { - Self::Log79(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log319Call) -> Self { - Self::Log319(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log320Call) -> Self { - Self::Log320(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log321Call) -> Self { - Self::Log321(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log322Call) -> Self { - Self::Log322(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log323Call) -> Self { - Self::Log323(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log324Call) -> Self { - Self::Log324(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log80Call) -> Self { - Self::Log80(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log325Call) -> Self { - Self::Log325(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log326Call) -> Self { - Self::Log326(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log81Call) -> Self { - Self::Log81(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log327Call) -> Self { - Self::Log327(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log328Call) -> Self { - Self::Log328(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log329Call) -> Self { - Self::Log329(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log330Call) -> Self { - Self::Log330(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log331Call) -> Self { - Self::Log331(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log82Call) -> Self { - Self::Log82(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log83Call) -> Self { - Self::Log83(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log84Call) -> Self { - Self::Log84(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log332Call) -> Self { - Self::Log332(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log333Call) -> Self { - Self::Log333(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log334Call) -> Self { - Self::Log334(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log21Call) -> Self { - Self::Log21(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log335Call) -> Self { - Self::Log335(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log336Call) -> Self { - Self::Log336(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log4Call) -> Self { - Self::Log4(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log337Call) -> Self { - Self::Log337(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log338Call) -> Self { - Self::Log338(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log339Call) -> Self { - Self::Log339(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log85Call) -> Self { - Self::Log85(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log340Call) -> Self { - Self::Log340(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log86Call) -> Self { - Self::Log86(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log341Call) -> Self { - Self::Log341(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log342Call) -> Self { - Self::Log342(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log5Call) -> Self { - Self::Log5(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: Log22Call) -> Self { - Self::Log22(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogAddressCall) -> Self { - Self::LogAddress(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBoolCall) -> Self { - Self::LogBool(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytesCall) -> Self { - Self::LogBytes(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes1Call) -> Self { - Self::LogBytes1(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes10Call) -> Self { - Self::LogBytes10(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes11Call) -> Self { - Self::LogBytes11(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes12Call) -> Self { - Self::LogBytes12(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes13Call) -> Self { - Self::LogBytes13(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes14Call) -> Self { - Self::LogBytes14(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes15Call) -> Self { - Self::LogBytes15(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes16Call) -> Self { - Self::LogBytes16(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes17Call) -> Self { - Self::LogBytes17(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes18Call) -> Self { - Self::LogBytes18(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes19Call) -> Self { - Self::LogBytes19(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes2Call) -> Self { - Self::LogBytes2(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes20Call) -> Self { - Self::LogBytes20(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes21Call) -> Self { - Self::LogBytes21(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes22Call) -> Self { - Self::LogBytes22(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes23Call) -> Self { - Self::LogBytes23(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes24Call) -> Self { - Self::LogBytes24(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes25Call) -> Self { - Self::LogBytes25(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes26Call) -> Self { - Self::LogBytes26(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes27Call) -> Self { - Self::LogBytes27(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes28Call) -> Self { - Self::LogBytes28(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes29Call) -> Self { - Self::LogBytes29(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes3Call) -> Self { - Self::LogBytes3(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes30Call) -> Self { - Self::LogBytes30(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes31Call) -> Self { - Self::LogBytes31(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes32Call) -> Self { - Self::LogBytes32(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes4Call) -> Self { - Self::LogBytes4(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes5Call) -> Self { - Self::LogBytes5(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes6Call) -> Self { - Self::LogBytes6(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes7Call) -> Self { - Self::LogBytes7(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes8Call) -> Self { - Self::LogBytes8(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogBytes9Call) -> Self { - Self::LogBytes9(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogIntCall) -> Self { - Self::LogInt(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogStringCall) -> Self { - Self::LogString(value) - } - } - impl ::core::convert::From for HardhatConsoleCalls { - fn from(value: LogUintCall) -> Self { - Self::LogUint(value) - } - } -} diff --git a/crates/abi/src/bindings/hevm.rs b/crates/abi/src/bindings/hevm.rs deleted file mode 100644 index 34e50d45ef032..0000000000000 --- a/crates/abi/src/bindings/hevm.rs +++ /dev/null @@ -1,14358 +0,0 @@ -pub use hevm::*; -/// This module was auto-generated with ethers-rs Abigen. -/// More information at: -#[allow( - clippy::enum_variant_names, - clippy::too_many_arguments, - clippy::upper_case_acronyms, - clippy::type_complexity, - dead_code, - non_camel_case_types, -)] -pub mod hevm { - #[allow(deprecated)] - fn __abi() -> ::ethers_core::abi::Abi { - ::ethers_core::abi::ethabi::Contract { - constructor: ::core::option::Option::None, - functions: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("accesses"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("accesses"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("activeFork"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("activeFork"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("addr"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("addr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("allowCheatcodes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("allowCheatcodes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("assume"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("assume"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("breakpoint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("breakpoint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("breakpoint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("broadcast"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("broadcast"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("broadcast"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("broadcast"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("chainId"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("chainId"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("clearMockedCalls"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("clearMockedCalls"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("closeFile"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("closeFile"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("coinbase"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("coinbase"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("copyFile"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("copyFile"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("createDir"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createDir"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("createFork"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("createSelectFork"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createSelectFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createSelectFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createSelectFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("createWallet"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createWallet"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Address, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ], - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createWallet"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Address, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ], - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("createWallet"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Address, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ], - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("deal"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("deal"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("deriveKey"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("deriveKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(32usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("deriveKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(32usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("deriveKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(32usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("deriveKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(32usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("difficulty"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("difficulty"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envAddress"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envBool"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bool, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envBytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envBytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envInt"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envOr"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bool, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bool, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envOr"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envString"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("envUint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("envUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("etch"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("etch"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("expectCall"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("expectCallMinGas"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCallMinGas"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectCallMinGas"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("expectEmit"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectEmit"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectEmit"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectEmit"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectEmit"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("expectRevert"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectRevert"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectRevert"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectRevert"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 4usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("expectSafeMemory"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("expectSafeMemory"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("expectSafeMemoryCall"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "expectSafeMemoryCall", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("fee"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("fee"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("ffi"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("ffi"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("fsMetadata"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("fsMetadata"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Bool, - ::ethers_core::abi::ethabi::ParamType::Bool, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Bool, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ], - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getCode"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getCode"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getDeployedCode"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getDeployedCode"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getLabel"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getLabel"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getMappingKeyAndParentOf"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "getMappingKeyAndParentOf", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getMappingLength"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getMappingLength"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getMappingSlotAt"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getMappingSlotAt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getNonce"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getNonce"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Address, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ], - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getNonce"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("getRecordedLogs"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("getRecordedLogs"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - ::ethers_core::abi::ethabi::ParamType::Bytes, - ], - ), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("isPersistent"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("isPersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("keyExists"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("keyExists"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("label"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("label"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("load"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("load"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("makePersistent"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("makePersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("makePersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("makePersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("makePersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("mockCall"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("mockCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("mockCall"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("mockCallRevert"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("mockCallRevert"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("mockCallRevert"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("openFile"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("openFile"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseAddress"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseBool"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseBytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseBytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseInt"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJson"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJson"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJson"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonAddress"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonAddressArray"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "parseJsonAddressArray", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonBool"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonBoolArray"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonBoolArray"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bool, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonBytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonBytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonBytes32Array"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "parseJsonBytes32Array", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonBytesArray"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "parseJsonBytesArray", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonInt"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonIntArray"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonIntArray"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonKeys"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonKeys"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonString"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonStringArray"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "parseJsonStringArray", - ), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonUint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseJsonUintArray"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseJsonUintArray"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("parseUint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("parseUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("pauseGasMetering"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("pauseGasMetering"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("prank"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("prank"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("prank"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("prevrandao"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("prevrandao"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("projectRoot"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("projectRoot"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("readCallers"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readCallers"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("readDir"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readDir"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - ::ethers_core::abi::ethabi::ParamType::Bool, - ::ethers_core::abi::ethabi::ParamType::Bool, - ], - ), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readDir"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - ::ethers_core::abi::ethabi::ParamType::Bool, - ::ethers_core::abi::ethabi::ParamType::Bool, - ], - ), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readDir"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - ::ethers_core::abi::ethabi::ParamType::Bool, - ::ethers_core::abi::ethabi::ParamType::Bool, - ], - ), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("readFile"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readFile"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("readFileBinary"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readFileBinary"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("readLine"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readLine"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("readLink"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("readLink"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("record"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("record"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("recordLogs"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("recordLogs"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("rememberKey"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rememberKey"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("removeDir"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("removeDir"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("removeFile"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("removeFile"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("resetNonce"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("resetNonce"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("resumeGasMetering"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("resumeGasMetering"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("revertTo"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("revertTo"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("revokePersistent"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("revokePersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("revokePersistent"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("roll"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("roll"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("rollFork"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rollFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rollFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rollFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rollFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("rpcUrl"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rpcUrl"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("rpcUrlStructs"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rpcUrlStructs"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::String, - ::ethers_core::abi::ethabi::ParamType::String, - ], - ), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("rpcUrls"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("rpcUrls"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedArray( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - 2usize, - ), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("selectFork"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("selectFork"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeAddress"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeAddress"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Address, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeBool"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeBool"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bool, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeBytes"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeBytes"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeBytes32"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeBytes32"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::FixedBytes(32usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeInt"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeInt"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Int(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeString"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::String, - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("serializeUint"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("serializeUint"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ), - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("setEnv"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("setEnv"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("setNonce"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("setNonce"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("setNonceUnsafe"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("setNonceUnsafe"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(64usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("sign"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("sign"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("sign"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers_core::abi::ethabi::ParamType::Address, - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - ], - ), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(8usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("skip"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("skip"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("sleep"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("sleep"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("snapshot"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("snapshot"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("startBroadcast"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("startBroadcast"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("startBroadcast"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("startBroadcast"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("startMappingRecording"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "startMappingRecording", - ), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("startPrank"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("startPrank"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("startPrank"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("stopBroadcast"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("stopBroadcast"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("stopMappingRecording"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "stopMappingRecording", - ), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("stopPrank"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("stopPrank"), - inputs: ::std::vec![], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("store"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("store"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("toString"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("toString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("toString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("toString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("toString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Int(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("toString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("toString"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("transact"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("transact"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("transact"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("txGasPrice"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("txGasPrice"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("warp"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("warp"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("writeFile"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("writeFile"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("writeFileBinary"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("writeFileBinary"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("writeJson"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("writeJson"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("writeJson"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("writeLine"), - ::std::vec![ - ::ethers_core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("writeLine"), - inputs: ::std::vec![ - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ::ethers_core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers_core::abi::ethabi::ParamType::String, - internal_type: ::core::option::Option::None, - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ]), - events: ::std::collections::BTreeMap::new(), - errors: ::std::collections::BTreeMap::new(), - receive: false, - fallback: false, - } - } - ///The parsed human-readable ABI of the contract. - pub static HEVM_ABI: ::ethers_contract::Lazy<::ethers_core::abi::Abi> = ::ethers_contract::Lazy::new( - __abi, - ); - pub struct HEVM(::ethers_contract::Contract); - impl ::core::clone::Clone for HEVM { - fn clone(&self) -> Self { - Self(::core::clone::Clone::clone(&self.0)) - } - } - impl ::core::ops::Deref for HEVM { - type Target = ::ethers_contract::Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl ::core::ops::DerefMut for HEVM { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - impl ::core::fmt::Debug for HEVM { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(HEVM)).field(&self.address()).finish() - } - } - impl HEVM { - /// Creates a new contract instance with the specified `ethers` client at - /// `address`. The contract derefs to a `ethers::Contract` object. - pub fn new>( - address: T, - client: ::std::sync::Arc, - ) -> Self { - Self( - ::ethers_contract::Contract::new( - address.into(), - HEVM_ABI.clone(), - client, - ), - ) - } - ///Calls the contract's `accesses` (0x65bc9481) function - pub fn accesses( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall< - M, - (::std::vec::Vec<[u8; 32]>, ::std::vec::Vec<[u8; 32]>), - > { - self.0 - .method_hash([101, 188, 148, 129], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `activeFork` (0x2f103f22) function - pub fn active_fork( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([47, 16, 63, 34], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `addr` (0xffa18649) function - pub fn addr( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([255, 161, 134, 73], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `allowCheatcodes` (0xea060291) function - pub fn allow_cheatcodes( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([234, 6, 2, 145], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `assume` (0x4c63e562) function - pub fn assume( - &self, - p0: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([76, 99, 229, 98], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `breakpoint` (0xf0259e92) function - pub fn breakpoint_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([240, 37, 158, 146], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `breakpoint` (0xf7d39a8d) function - pub fn breakpoint_1( - &self, - p0: ::std::string::String, - p1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([247, 211, 154, 141], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `broadcast` (0xafc98040) function - pub fn broadcast_0(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([175, 201, 128, 64], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `broadcast` (0xe6962cdb) function - pub fn broadcast_1( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([230, 150, 44, 219], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `broadcast` (0xf67a965b) function - pub fn broadcast_2( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([246, 122, 150, 91], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `chainId` (0x4049ddd2) function - pub fn chain_id( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([64, 73, 221, 210], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `clearMockedCalls` (0x3fdf4e15) function - pub fn clear_mocked_calls( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([63, 223, 78, 21], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `closeFile` (0x48c3241f) function - pub fn close_file( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([72, 195, 36, 31], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `coinbase` (0xff483c54) function - pub fn coinbase( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([255, 72, 60, 84], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `copyFile` (0xa54a87d8) function - pub fn copy_file( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([165, 74, 135, 216], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createDir` (0x168b64d3) function - pub fn create_dir( - &self, - p0: ::std::string::String, - p1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([22, 139, 100, 211], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createFork` (0x6ba3ba2b) function - pub fn create_fork_1( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([107, 163, 186, 43], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createFork` (0x7ca29682) function - pub fn create_fork_2( - &self, - p0: ::std::string::String, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([124, 162, 150, 130], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createFork` (0x31ba3498) function - pub fn create_fork_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([49, 186, 52, 152], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createSelectFork` (0x71ee464d) function - pub fn create_select_fork_1( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([113, 238, 70, 77], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createSelectFork` (0x84d52b7a) function - pub fn create_select_fork_2( - &self, - p0: ::std::string::String, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([132, 213, 43, 122], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createSelectFork` (0x98680034) function - pub fn create_select_fork_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([152, 104, 0, 52], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createWallet` (0x7404f1d2) function - pub fn create_wallet_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ( - ::ethers_core::types::Address, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - > { - self.0 - .method_hash([116, 4, 241, 210], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createWallet` (0x7a675bb6) function - pub fn create_wallet_1( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall< - M, - ( - ::ethers_core::types::Address, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - > { - self.0 - .method_hash([122, 103, 91, 182], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `createWallet` (0xed7c5462) function - pub fn create_wallet_2( - &self, - p0: ::ethers_core::types::U256, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ( - ::ethers_core::types::Address, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - > { - self.0 - .method_hash([237, 124, 84, 98], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `deal` (0xc88a5e6d) function - pub fn deal( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([200, 138, 94, 109], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `deriveKey` (0x6229498b) function - pub fn derive_key_0( - &self, - p0: ::std::string::String, - p1: u32, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([98, 41, 73, 139], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `deriveKey` (0x6bcb2c1b) function - pub fn derive_key_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: u32, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([107, 203, 44, 27], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `deriveKey` (0x32c8176d) function - pub fn derive_key_2( - &self, - p0: ::std::string::String, - p1: u32, - p2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([50, 200, 23, 109], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `deriveKey` (0x29233b1f) function - pub fn derive_key_3( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: u32, - p3: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([41, 35, 59, 31], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `difficulty` (0x46cc92d9) function - pub fn difficulty( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([70, 204, 146, 217], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envAddress` (0x350d56bf) function - pub fn env_address_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([53, 13, 86, 191], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envAddress` (0xad31b9fa) function - pub fn env_address_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::Address>, - > { - self.0 - .method_hash([173, 49, 185, 250], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envBool` (0x7ed1ec7d) function - pub fn env_bool_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([126, 209, 236, 125], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envBool` (0xaaaddeaf) function - pub fn env_bool_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall> { - self.0 - .method_hash([170, 173, 222, 175], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envBytes` (0x4d7baf06) function - pub fn env_bytes_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([77, 123, 175, 6], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envBytes` (0xddc2651b) function - pub fn env_bytes_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::Bytes>, - > { - self.0 - .method_hash([221, 194, 101, 27], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envBytes32` (0x97949042) function - pub fn env_bytes_320( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([151, 148, 144, 66], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envBytes32` (0x5af231c1) function - pub fn env_bytes_321( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall> { - self.0 - .method_hash([90, 242, 49, 193], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envInt` (0x892a0c61) function - pub fn env_int_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([137, 42, 12, 97], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envInt` (0x42181150) function - pub fn env_int_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::I256>, - > { - self.0 - .method_hash([66, 24, 17, 80], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x4777f3cf) function - pub fn env_or_0( - &self, - p0: ::std::string::String, - p1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([71, 119, 243, 207], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x5e97348f) function - pub fn env_or_1( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([94, 151, 52, 143], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0xbbcb713e) function - pub fn env_or_2( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::I256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([187, 203, 113, 62], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x561fe540) function - pub fn env_or_3( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([86, 31, 229, 64], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0xb4a85892) function - pub fn env_or_4( - &self, - p0: ::std::string::String, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([180, 168, 88, 146], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0xd145736c) function - pub fn env_or_5( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([209, 69, 115, 108], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0xb3e47705) function - pub fn env_or_6( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([179, 228, 119, 5], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0xeb85e83b) function - pub fn env_or_7( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec, - ) -> ::ethers_contract::builders::ContractCall> { - self.0 - .method_hash([235, 133, 232, 59], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x74318528) function - pub fn env_or_8( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::U256>, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::U256>, - > { - self.0 - .method_hash([116, 49, 133, 40], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x4700d74b) function - pub fn env_or_9( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::I256>, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::I256>, - > { - self.0 - .method_hash([71, 0, 215, 75], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0xc74e9deb) function - pub fn env_or_10( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::Address>, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::Address>, - > { - self.0 - .method_hash([199, 78, 157, 235], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x2281f367) function - pub fn env_or_11( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<[u8; 32]>, - ) -> ::ethers_contract::builders::ContractCall> { - self.0 - .method_hash([34, 129, 243, 103], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x859216bc) function - pub fn env_or_12( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::std::string::String>, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::std::string::String>, - > { - self.0 - .method_hash([133, 146, 22, 188], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envOr` (0x64bc3e64) function - pub fn env_or_13( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::Bytes>, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::Bytes>, - > { - self.0 - .method_hash([100, 188, 62, 100], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envString` (0xf877cb19) function - pub fn env_string_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([248, 119, 203, 25], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envString` (0x14b02bc9) function - pub fn env_string_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::std::string::String>, - > { - self.0 - .method_hash([20, 176, 43, 201], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envUint` (0xc1978d1f) function - pub fn env_uint_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([193, 151, 141, 31], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `envUint` (0xf3dec099) function - pub fn env_uint_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::U256>, - > { - self.0 - .method_hash([243, 222, 192, 153], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `etch` (0xb4d6c782) function - pub fn etch( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([180, 214, 199, 130], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCall` (0xbd6af434) function - pub fn expect_call_0( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([189, 106, 244, 52], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCall` (0xc1adbbff) function - pub fn expect_call_1( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Bytes, - p2: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([193, 173, 187, 255], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCall` (0xf30c7ba3) function - pub fn expect_call_2( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([243, 12, 123, 163], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCall` (0xa2b1a1ae) function - pub fn expect_call_3( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: ::ethers_core::types::Bytes, - p3: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([162, 177, 161, 174], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCall` (0x23361207) function - pub fn expect_call_4( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: u64, - p3: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([35, 54, 18, 7], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCall` (0x65b7b7cc) function - pub fn expect_call_5( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: u64, - p3: ::ethers_core::types::Bytes, - p4: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([101, 183, 183, 204], (p0, p1, p2, p3, p4)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCallMinGas` (0x08e4e116) function - pub fn expect_call_min_gas_0( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: u64, - p3: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([8, 228, 225, 22], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectCallMinGas` (0xe13a1834) function - pub fn expect_call_min_gas_1( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: u64, - p3: ::ethers_core::types::Bytes, - p4: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([225, 58, 24, 52], (p0, p1, p2, p3, p4)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectEmit` (0x440ed10d) function - pub fn expect_emit_0(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([68, 14, 209, 13], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectEmit` (0x86b9620d) function - pub fn expect_emit_1( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([134, 185, 98, 13], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectEmit` (0x491cc7c2) function - pub fn expect_emit_2( - &self, - p0: bool, - p1: bool, - p2: bool, - p3: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([73, 28, 199, 194], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectEmit` (0x81bad6f3) function - pub fn expect_emit_3( - &self, - p0: bool, - p1: bool, - p2: bool, - p3: bool, - p4: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([129, 186, 214, 243], (p0, p1, p2, p3, p4)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectRevert` (0xf4844814) function - pub fn expect_revert_0( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([244, 132, 72, 20], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectRevert` (0xf28dceb3) function - pub fn expect_revert_1( - &self, - p0: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([242, 141, 206, 179], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectRevert` (0xc31eb0e0) function - pub fn expect_revert_2( - &self, - p0: [u8; 4], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([195, 30, 176, 224], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectSafeMemory` (0x6d016688) function - pub fn expect_safe_memory( - &self, - p0: u64, - p1: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([109, 1, 102, 136], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `expectSafeMemoryCall` (0x05838bf4) function - pub fn expect_safe_memory_call( - &self, - p0: u64, - p1: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([5, 131, 139, 244], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `fee` (0x39b37ab0) function - pub fn fee( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([57, 179, 122, 176], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `ffi` (0x89160467) function - pub fn ffi( - &self, - p0: ::std::vec::Vec<::std::string::String>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([137, 22, 4, 103], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `fsMetadata` (0xaf368a08) function - pub fn fs_metadata( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ( - bool, - bool, - ::ethers_core::types::U256, - bool, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - > { - self.0 - .method_hash([175, 54, 138, 8], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getCode` (0x8d1cc925) function - pub fn get_code( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([141, 28, 201, 37], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getDeployedCode` (0x3ebf73b4) function - pub fn get_deployed_code( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([62, 191, 115, 180], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getLabel` (0x28a249b0) function - pub fn get_label( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([40, 162, 73, 176], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getMappingKeyAndParentOf` (0x876e24e6) function - pub fn get_mapping_key_and_parent_of( - &self, - p0: ::ethers_core::types::Address, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([135, 110, 36, 230], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getMappingLength` (0x2f2fd63f) function - pub fn get_mapping_length( - &self, - p0: ::ethers_core::types::Address, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([47, 47, 214, 63], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getMappingSlotAt` (0xebc73ab4) function - pub fn get_mapping_slot_at( - &self, - p0: ::ethers_core::types::Address, - p1: [u8; 32], - p2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([235, 199, 58, 180], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getNonce` (0xa5748aad) function - pub fn get_nonce_0( - &self, - p0: Wallet, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([165, 116, 138, 173], (p0,)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getNonce` (0x2d0335ab) function - pub fn get_nonce_1( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([45, 3, 53, 171], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `getRecordedLogs` (0x191553a4) function - pub fn get_recorded_logs( - &self, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<(::std::vec::Vec<[u8; 32]>, ::ethers_core::types::Bytes)>, - > { - self.0 - .method_hash([25, 21, 83, 164], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `isPersistent` (0xd92d8efd) function - pub fn is_persistent( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([217, 45, 142, 253], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `keyExists` (0x528a683c) function - pub fn key_exists( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([82, 138, 104, 60], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `label` (0xc657c718) function - pub fn label( - &self, - p0: ::ethers_core::types::Address, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([198, 87, 199, 24], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `load` (0x667f9d70) function - pub fn load( - &self, - p0: ::ethers_core::types::Address, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([102, 127, 157, 112], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `makePersistent` (0x57e22dde) function - pub fn make_persistent_0( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([87, 226, 45, 222], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `makePersistent` (0x4074e0a8) function - pub fn make_persistent_2( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([64, 116, 224, 168], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `makePersistent` (0xefb77a75) function - pub fn make_persistent_3( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Address, - p2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([239, 183, 122, 117], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `makePersistent` (0x1d9e269e) function - pub fn make_persistent_1( - &self, - p0: ::std::vec::Vec<::ethers_core::types::Address>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([29, 158, 38, 158], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `mockCall` (0xb96213e4) function - pub fn mock_call_0( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Bytes, - p2: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([185, 98, 19, 228], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `mockCall` (0x81409b91) function - pub fn mock_call_1( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: ::ethers_core::types::Bytes, - p3: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([129, 64, 155, 145], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `mockCallRevert` (0xdbaad147) function - pub fn mock_call_revert_0( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Bytes, - p2: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([219, 170, 209, 71], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `mockCallRevert` (0xd23cd037) function - pub fn mock_call_revert_1( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::U256, - p2: ::ethers_core::types::Bytes, - p3: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([210, 60, 208, 55], (p0, p1, p2, p3)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `openFile` (0x7e0394bc) function - pub fn open_file( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([126, 3, 148, 188], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseAddress` (0xc6ce059d) function - pub fn parse_address( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([198, 206, 5, 157], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseBool` (0x974ef924) function - pub fn parse_bool( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([151, 78, 249, 36], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseBytes` (0x8f5d232d) function - pub fn parse_bytes( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([143, 93, 35, 45], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseBytes32` (0x087e6e81) function - pub fn parse_bytes_32( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([8, 126, 110, 129], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseInt` (0x42346c5e) function - pub fn parse_int( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([66, 52, 108, 94], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJson` (0x6a82600a) function - pub fn parse_json_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([106, 130, 96, 10], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJson` (0x85940ef1) function - pub fn parse_json_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([133, 148, 14, 241], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonAddress` (0x1e19e657) function - pub fn parse_json_address( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([30, 25, 230, 87], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonAddressArray` (0x2fce7883) function - pub fn parse_json_address_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::Address>, - > { - self.0 - .method_hash([47, 206, 120, 131], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonBool` (0x9f86dc91) function - pub fn parse_json_bool( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([159, 134, 220, 145], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonBoolArray` (0x91f3b94f) function - pub fn parse_json_bool_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall> { - self.0 - .method_hash([145, 243, 185, 79], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonBytes` (0xfd921be8) function - pub fn parse_json_bytes( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([253, 146, 27, 232], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonBytes32` (0x1777e59d) function - pub fn parse_json_bytes_32( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([23, 119, 229, 157], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonBytes32Array` (0x91c75bc3) function - pub fn parse_json_bytes_32_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall> { - self.0 - .method_hash([145, 199, 91, 195], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonBytesArray` (0x6631aa99) function - pub fn parse_json_bytes_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::Bytes>, - > { - self.0 - .method_hash([102, 49, 170, 153], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonInt` (0x7b048ccd) function - pub fn parse_json_int( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([123, 4, 140, 205], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonIntArray` (0x9983c28a) function - pub fn parse_json_int_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::I256>, - > { - self.0 - .method_hash([153, 131, 194, 138], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonKeys` (0x213e4198) function - pub fn parse_json_keys( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::std::string::String>, - > { - self.0 - .method_hash([33, 62, 65, 152], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonString` (0x49c4fac8) function - pub fn parse_json_string( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([73, 196, 250, 200], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonStringArray` (0x498fdcf4) function - pub fn parse_json_string_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::std::string::String>, - > { - self.0 - .method_hash([73, 143, 220, 244], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonUint` (0xaddde2b6) function - pub fn parse_json_uint( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([173, 221, 226, 182], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseJsonUintArray` (0x522074ab) function - pub fn parse_json_uint_array( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<::ethers_core::types::U256>, - > { - self.0 - .method_hash([82, 32, 116, 171], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `parseUint` (0xfa91454d) function - pub fn parse_uint( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([250, 145, 69, 77], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `pauseGasMetering` (0xd1a5b36f) function - pub fn pause_gas_metering( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([209, 165, 179, 111], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `prank` (0xca669fa7) function - pub fn prank_0( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([202, 102, 159, 167], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `prank` (0x47e50cce) function - pub fn prank_1( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([71, 229, 12, 206], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `prevrandao` (0x3b925549) function - pub fn prevrandao( - &self, - p0: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([59, 146, 85, 73], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `projectRoot` (0xd930a0e6) function - pub fn project_root( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([217, 48, 160, 230], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readCallers` (0x4ad0bac9) function - pub fn read_callers( - &self, - ) -> ::ethers_contract::builders::ContractCall< - M, - ( - ::ethers_core::types::U256, - ::ethers_core::types::Address, - ::ethers_core::types::Address, - ), - > { - self.0 - .method_hash([74, 208, 186, 201], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readDir` (0xc4bc59e0) function - pub fn read_dir_0( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec< - (::std::string::String, ::std::string::String, u64, bool, bool), - >, - > { - self.0 - .method_hash([196, 188, 89, 224], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readDir` (0x1497876c) function - pub fn read_dir_1( - &self, - p0: ::std::string::String, - p1: u64, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec< - (::std::string::String, ::std::string::String, u64, bool, bool), - >, - > { - self.0 - .method_hash([20, 151, 135, 108], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readDir` (0x8102d70d) function - pub fn read_dir_2( - &self, - p0: ::std::string::String, - p1: u64, - p2: bool, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec< - (::std::string::String, ::std::string::String, u64, bool, bool), - >, - > { - self.0 - .method_hash([129, 2, 215, 13], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readFile` (0x60f9bb11) function - pub fn read_file( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([96, 249, 187, 17], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readFileBinary` (0x16ed7bc4) function - pub fn read_file_binary( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([22, 237, 123, 196], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readLine` (0x70f55728) function - pub fn read_line( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([112, 245, 87, 40], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `readLink` (0x9f5684a2) function - pub fn read_link( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([159, 86, 132, 162], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `record` (0x266cf109) function - pub fn record(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([38, 108, 241, 9], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `recordLogs` (0x41af2f52) function - pub fn record_logs(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([65, 175, 47, 82], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rememberKey` (0x22100064) function - pub fn remember_key( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::ethers_core::types::Address, - > { - self.0 - .method_hash([34, 16, 0, 100], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `removeDir` (0x45c62011) function - pub fn remove_dir( - &self, - p0: ::std::string::String, - p1: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([69, 198, 32, 17], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `removeFile` (0xf1afe04d) function - pub fn remove_file( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([241, 175, 224, 77], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `resetNonce` (0x1c72346d) function - pub fn reset_nonce( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([28, 114, 52, 109], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `resumeGasMetering` (0x2bcd50e0) function - pub fn resume_gas_metering( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([43, 205, 80, 224], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `revertTo` (0x44d7f0a4) function - pub fn revert_to( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([68, 215, 240, 164], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `revokePersistent` (0x997a0222) function - pub fn revoke_persistent_0( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([153, 122, 2, 34], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `revokePersistent` (0x3ce969e6) function - pub fn revoke_persistent_1( - &self, - p0: ::std::vec::Vec<::ethers_core::types::Address>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([60, 233, 105, 230], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `roll` (0x1f7b4f30) function - pub fn roll( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([31, 123, 79, 48], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rollFork` (0xd9bbf3a1) function - pub fn roll_fork_0( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([217, 187, 243, 161], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rollFork` (0x0f29772b) function - pub fn roll_fork_1( - &self, - p0: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([15, 41, 119, 43], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rollFork` (0xd74c83a4) function - pub fn roll_fork_2( - &self, - p0: ::ethers_core::types::U256, - p1: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([215, 76, 131, 164], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rollFork` (0xf2830f7b) function - pub fn roll_fork_3( - &self, - p0: ::ethers_core::types::U256, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([242, 131, 15, 123], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rpcUrl` (0x975a6ce9) function - pub fn rpc_url( - &self, - p0: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([151, 90, 108, 233], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rpcUrlStructs` (0x9d2ad72a) function - pub fn rpc_url_structs( - &self, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<(::std::string::String, ::std::string::String)>, - > { - self.0 - .method_hash([157, 42, 215, 42], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `rpcUrls` (0xa85a8418) function - pub fn rpc_urls( - &self, - ) -> ::ethers_contract::builders::ContractCall< - M, - ::std::vec::Vec<[::std::string::String; 2]>, - > { - self.0 - .method_hash([168, 90, 132, 24], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `selectFork` (0x9ebf6827) function - pub fn select_fork( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([158, 191, 104, 39], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeAddress` (0x972c6062) function - pub fn serialize_address_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([151, 44, 96, 98], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeAddress` (0x1e356e1a) function - pub fn serialize_address_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::Address>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([30, 53, 110, 26], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeBool` (0xac22e971) function - pub fn serialize_bool_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([172, 34, 233, 113], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeBool` (0x92925aa1) function - pub fn serialize_bool_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([146, 146, 90, 161], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeBytes` (0xf21d52c7) function - pub fn serialize_bytes_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([242, 29, 82, 199], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeBytes` (0x9884b232) function - pub fn serialize_bytes_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::Bytes>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([152, 132, 178, 50], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeBytes32` (0x2d812b44) function - pub fn serialize_bytes_320( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([45, 129, 43, 68], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeBytes32` (0x201e43e2) function - pub fn serialize_bytes_321( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<[u8; 32]>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([32, 30, 67, 226], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeInt` (0x3f33db60) function - pub fn serialize_int_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::ethers_core::types::I256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([63, 51, 219, 96], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeInt` (0x7676e127) function - pub fn serialize_int_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::I256>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([118, 118, 225, 39], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeString` (0x88da6d35) function - pub fn serialize_string_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([136, 218, 109, 53], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeString` (0x561cd6f3) function - pub fn serialize_string_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::std::string::String>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([86, 28, 214, 243], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeUint` (0x129e9002) function - pub fn serialize_uint_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([18, 158, 144, 2], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `serializeUint` (0xfee9a469) function - pub fn serialize_uint_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::vec::Vec<::ethers_core::types::U256>, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([254, 233, 164, 105], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `setEnv` (0x3d5923ee) function - pub fn set_env( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([61, 89, 35, 238], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `setNonce` (0xf8e18b57) function - pub fn set_nonce( - &self, - p0: ::ethers_core::types::Address, - p1: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([248, 225, 139, 87], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `setNonceUnsafe` (0x9b67b21c) function - pub fn set_nonce_unsafe( - &self, - p0: ::ethers_core::types::Address, - p1: u64, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([155, 103, 178, 28], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `sign` (0xe341eaa4) function - pub fn sign_0( - &self, - p0: ::ethers_core::types::U256, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([227, 65, 234, 164], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `sign` (0xb25c5a25) function - pub fn sign_1( - &self, - p0: Wallet, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([178, 92, 90, 37], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `skip` (0xdd82d13e) function - pub fn skip( - &self, - p0: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([221, 130, 209, 62], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `sleep` (0xfa9d8713) function - pub fn sleep( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([250, 157, 135, 19], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `snapshot` (0x9711715a) function - pub fn snapshot( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([151, 17, 113, 90], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `startBroadcast` (0x7fb5297f) function - pub fn start_broadcast_0( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([127, 181, 41, 127], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `startBroadcast` (0x7fec2a8d) function - pub fn start_broadcast_1( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([127, 236, 42, 141], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `startBroadcast` (0xce817d47) function - pub fn start_broadcast_2( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([206, 129, 125, 71], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `startMappingRecording` (0x3e9705c0) function - pub fn start_mapping_recording( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([62, 151, 5, 192], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `startPrank` (0x06447d56) function - pub fn start_prank_0( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([6, 68, 125, 86], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `startPrank` (0x45b56078) function - pub fn start_prank_1( - &self, - p0: ::ethers_core::types::Address, - p1: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([69, 181, 96, 120], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `stopBroadcast` (0x76eadd36) function - pub fn stop_broadcast( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([118, 234, 221, 54], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `stopMappingRecording` (0x0d4aae9b) function - pub fn stop_mapping_recording( - &self, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([13, 74, 174, 155], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `stopPrank` (0x90c5013b) function - pub fn stop_prank(&self) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([144, 197, 1, 59], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `store` (0x70ca10bb) function - pub fn store( - &self, - p0: ::ethers_core::types::Address, - p1: [u8; 32], - p2: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([112, 202, 16, 187], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `toString` (0x71aad10d) function - pub fn to_string_0( - &self, - p0: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([113, 170, 209, 13], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `toString` (0x56ca623e) function - pub fn to_string_1( - &self, - p0: ::ethers_core::types::Address, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([86, 202, 98, 62], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `toString` (0x6900a3ae) function - pub fn to_string_2( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([105, 0, 163, 174], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `toString` (0xa322c40e) function - pub fn to_string_3( - &self, - p0: ::ethers_core::types::I256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([163, 34, 196, 14], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `toString` (0xb11a19e8) function - pub fn to_string_4( - &self, - p0: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([177, 26, 25, 232], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `toString` (0x71dce7da) function - pub fn to_string_5( - &self, - p0: bool, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([113, 220, 231, 218], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `transact` (0xbe646da1) function - pub fn transact_0( - &self, - p0: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([190, 100, 109, 161], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `transact` (0x4d8abc4b) function - pub fn transact_1( - &self, - p0: ::ethers_core::types::U256, - p1: [u8; 32], - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([77, 138, 188, 75], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `txGasPrice` (0x48f50c0f) function - pub fn tx_gas_price( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([72, 245, 12, 15], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `warp` (0xe5d6bf02) function - pub fn warp( - &self, - p0: ::ethers_core::types::U256, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([229, 214, 191, 2], p0) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `writeFile` (0x897e0a97) function - pub fn write_file( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([137, 126, 10, 151], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `writeFileBinary` (0x1f21fc80) function - pub fn write_file_binary( - &self, - p0: ::std::string::String, - p1: ::ethers_core::types::Bytes, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([31, 33, 252, 128], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `writeJson` (0xe23cd19f) function - pub fn write_json_0( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([226, 60, 209, 159], (p0, p1)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `writeJson` (0x35d6ad46) function - pub fn write_json_1( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - p2: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([53, 214, 173, 70], (p0, p1, p2)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `writeLine` (0x619d897f) function - pub fn write_line( - &self, - p0: ::std::string::String, - p1: ::std::string::String, - ) -> ::ethers_contract::builders::ContractCall { - self.0 - .method_hash([97, 157, 137, 127], (p0, p1)) - .expect("method not found (this should never happen)") - } - } - impl From<::ethers_contract::Contract> - for HEVM { - fn from(contract: ::ethers_contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) - } - } - ///Container type for all input parameters for the `accesses` function with signature `accesses(address)` and selector `0x65bc9481` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "accesses", abi = "accesses(address)")] - pub struct AccessesCall(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `activeFork` function with signature `activeFork()` and selector `0x2f103f22` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "activeFork", abi = "activeFork()")] - pub struct ActiveForkCall; - ///Container type for all input parameters for the `addr` function with signature `addr(uint256)` and selector `0xffa18649` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "addr", abi = "addr(uint256)")] - pub struct AddrCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `allowCheatcodes` function with signature `allowCheatcodes(address)` and selector `0xea060291` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "allowCheatcodes", abi = "allowCheatcodes(address)")] - pub struct AllowCheatcodesCall(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `assume` function with signature `assume(bool)` and selector `0x4c63e562` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "assume", abi = "assume(bool)")] - pub struct AssumeCall(pub bool); - ///Container type for all input parameters for the `breakpoint` function with signature `breakpoint(string)` and selector `0xf0259e92` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "breakpoint", abi = "breakpoint(string)")] - pub struct Breakpoint0Call(pub ::std::string::String); - ///Container type for all input parameters for the `breakpoint` function with signature `breakpoint(string,bool)` and selector `0xf7d39a8d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "breakpoint", abi = "breakpoint(string,bool)")] - pub struct Breakpoint1Call(pub ::std::string::String, pub bool); - ///Container type for all input parameters for the `broadcast` function with signature `broadcast()` and selector `0xafc98040` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "broadcast", abi = "broadcast()")] - pub struct Broadcast0Call; - ///Container type for all input parameters for the `broadcast` function with signature `broadcast(address)` and selector `0xe6962cdb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "broadcast", abi = "broadcast(address)")] - pub struct Broadcast1Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `broadcast` function with signature `broadcast(uint256)` and selector `0xf67a965b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "broadcast", abi = "broadcast(uint256)")] - pub struct Broadcast2Call(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `chainId` function with signature `chainId(uint256)` and selector `0x4049ddd2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "chainId", abi = "chainId(uint256)")] - pub struct ChainIdCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `clearMockedCalls` function with signature `clearMockedCalls()` and selector `0x3fdf4e15` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "clearMockedCalls", abi = "clearMockedCalls()")] - pub struct ClearMockedCallsCall; - ///Container type for all input parameters for the `closeFile` function with signature `closeFile(string)` and selector `0x48c3241f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "closeFile", abi = "closeFile(string)")] - pub struct CloseFileCall(pub ::std::string::String); - ///Container type for all input parameters for the `coinbase` function with signature `coinbase(address)` and selector `0xff483c54` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "coinbase", abi = "coinbase(address)")] - pub struct CoinbaseCall(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `copyFile` function with signature `copyFile(string,string)` and selector `0xa54a87d8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "copyFile", abi = "copyFile(string,string)")] - pub struct CopyFileCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `createDir` function with signature `createDir(string,bool)` and selector `0x168b64d3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createDir", abi = "createDir(string,bool)")] - pub struct CreateDirCall(pub ::std::string::String, pub bool); - ///Container type for all input parameters for the `createFork` function with signature `createFork(string,uint256)` and selector `0x6ba3ba2b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createFork", abi = "createFork(string,uint256)")] - pub struct CreateFork1Call( - pub ::std::string::String, - pub ::ethers_core::types::U256, - ); - ///Container type for all input parameters for the `createFork` function with signature `createFork(string,bytes32)` and selector `0x7ca29682` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createFork", abi = "createFork(string,bytes32)")] - pub struct CreateFork2Call(pub ::std::string::String, pub [u8; 32]); - ///Container type for all input parameters for the `createFork` function with signature `createFork(string)` and selector `0x31ba3498` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createFork", abi = "createFork(string)")] - pub struct CreateFork0Call(pub ::std::string::String); - ///Container type for all input parameters for the `createSelectFork` function with signature `createSelectFork(string,uint256)` and selector `0x71ee464d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createSelectFork", abi = "createSelectFork(string,uint256)")] - pub struct CreateSelectFork1Call( - pub ::std::string::String, - pub ::ethers_core::types::U256, - ); - ///Container type for all input parameters for the `createSelectFork` function with signature `createSelectFork(string,bytes32)` and selector `0x84d52b7a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createSelectFork", abi = "createSelectFork(string,bytes32)")] - pub struct CreateSelectFork2Call(pub ::std::string::String, pub [u8; 32]); - ///Container type for all input parameters for the `createSelectFork` function with signature `createSelectFork(string)` and selector `0x98680034` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createSelectFork", abi = "createSelectFork(string)")] - pub struct CreateSelectFork0Call(pub ::std::string::String); - ///Container type for all input parameters for the `createWallet` function with signature `createWallet(string)` and selector `0x7404f1d2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createWallet", abi = "createWallet(string)")] - pub struct CreateWallet0Call(pub ::std::string::String); - ///Container type for all input parameters for the `createWallet` function with signature `createWallet(uint256)` and selector `0x7a675bb6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createWallet", abi = "createWallet(uint256)")] - pub struct CreateWallet1Call(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `createWallet` function with signature `createWallet(uint256,string)` and selector `0xed7c5462` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "createWallet", abi = "createWallet(uint256,string)")] - pub struct CreateWallet2Call( - pub ::ethers_core::types::U256, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `deal` function with signature `deal(address,uint256)` and selector `0xc88a5e6d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "deal", abi = "deal(address,uint256)")] - pub struct DealCall( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - ); - ///Container type for all input parameters for the `deriveKey` function with signature `deriveKey(string,uint32)` and selector `0x6229498b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "deriveKey", abi = "deriveKey(string,uint32)")] - pub struct DeriveKey0Call(pub ::std::string::String, pub u32); - ///Container type for all input parameters for the `deriveKey` function with signature `deriveKey(string,string,uint32)` and selector `0x6bcb2c1b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "deriveKey", abi = "deriveKey(string,string,uint32)")] - pub struct DeriveKey1Call( - pub ::std::string::String, - pub ::std::string::String, - pub u32, - ); - ///Container type for all input parameters for the `deriveKey` function with signature `deriveKey(string,uint32,string)` and selector `0x32c8176d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "deriveKey", abi = "deriveKey(string,uint32,string)")] - pub struct DeriveKey2Call( - pub ::std::string::String, - pub u32, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `deriveKey` function with signature `deriveKey(string,string,uint32,string)` and selector `0x29233b1f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "deriveKey", abi = "deriveKey(string,string,uint32,string)")] - pub struct DeriveKey3Call( - pub ::std::string::String, - pub ::std::string::String, - pub u32, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `difficulty` function with signature `difficulty(uint256)` and selector `0x46cc92d9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "difficulty", abi = "difficulty(uint256)")] - pub struct DifficultyCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `envAddress` function with signature `envAddress(string)` and selector `0x350d56bf` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envAddress", abi = "envAddress(string)")] - pub struct EnvAddress0Call(pub ::std::string::String); - ///Container type for all input parameters for the `envAddress` function with signature `envAddress(string,string)` and selector `0xad31b9fa` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envAddress", abi = "envAddress(string,string)")] - pub struct EnvAddress1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envBool` function with signature `envBool(string)` and selector `0x7ed1ec7d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envBool", abi = "envBool(string)")] - pub struct EnvBool0Call(pub ::std::string::String); - ///Container type for all input parameters for the `envBool` function with signature `envBool(string,string)` and selector `0xaaaddeaf` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envBool", abi = "envBool(string,string)")] - pub struct EnvBool1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envBytes` function with signature `envBytes(string)` and selector `0x4d7baf06` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envBytes", abi = "envBytes(string)")] - pub struct EnvBytes0Call(pub ::std::string::String); - ///Container type for all input parameters for the `envBytes` function with signature `envBytes(string,string)` and selector `0xddc2651b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envBytes", abi = "envBytes(string,string)")] - pub struct EnvBytes1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envBytes32` function with signature `envBytes32(string)` and selector `0x97949042` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envBytes32", abi = "envBytes32(string)")] - pub struct EnvBytes320Call(pub ::std::string::String); - ///Container type for all input parameters for the `envBytes32` function with signature `envBytes32(string,string)` and selector `0x5af231c1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envBytes32", abi = "envBytes32(string,string)")] - pub struct EnvBytes321Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envInt` function with signature `envInt(string)` and selector `0x892a0c61` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envInt", abi = "envInt(string)")] - pub struct EnvInt0Call(pub ::std::string::String); - ///Container type for all input parameters for the `envInt` function with signature `envInt(string,string)` and selector `0x42181150` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envInt", abi = "envInt(string,string)")] - pub struct EnvInt1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,bool)` and selector `0x4777f3cf` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,bool)")] - pub struct EnvOr0Call(pub ::std::string::String, pub bool); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,uint256)` and selector `0x5e97348f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,uint256)")] - pub struct EnvOr1Call(pub ::std::string::String, pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,int256)` and selector `0xbbcb713e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,int256)")] - pub struct EnvOr2Call(pub ::std::string::String, pub ::ethers_core::types::I256); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,address)` and selector `0x561fe540` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,address)")] - pub struct EnvOr3Call(pub ::std::string::String, pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,bytes32)` and selector `0xb4a85892` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,bytes32)")] - pub struct EnvOr4Call(pub ::std::string::String, pub [u8; 32]); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string)` and selector `0xd145736c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string)")] - pub struct EnvOr5Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,bytes)` and selector `0xb3e47705` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,bytes)")] - pub struct EnvOr6Call(pub ::std::string::String, pub ::ethers_core::types::Bytes); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,bool[])` and selector `0xeb85e83b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,bool[])")] - pub struct EnvOr7Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec, - ); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,uint256[])` and selector `0x74318528` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,uint256[])")] - pub struct EnvOr8Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::U256>, - ); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,int256[])` and selector `0x4700d74b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,int256[])")] - pub struct EnvOr9Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::I256>, - ); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,address[])` and selector `0xc74e9deb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,address[])")] - pub struct EnvOr10Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::Address>, - ); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,bytes32[])` and selector `0x2281f367` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,bytes32[])")] - pub struct EnvOr11Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<[u8; 32]>, - ); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,string[])` and selector `0x859216bc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,string[])")] - pub struct EnvOr12Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::std::string::String>, - ); - ///Container type for all input parameters for the `envOr` function with signature `envOr(string,string,bytes[])` and selector `0x64bc3e64` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envOr", abi = "envOr(string,string,bytes[])")] - pub struct EnvOr13Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::Bytes>, - ); - ///Container type for all input parameters for the `envString` function with signature `envString(string)` and selector `0xf877cb19` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envString", abi = "envString(string)")] - pub struct EnvString0Call(pub ::std::string::String); - ///Container type for all input parameters for the `envString` function with signature `envString(string,string)` and selector `0x14b02bc9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envString", abi = "envString(string,string)")] - pub struct EnvString1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `envUint` function with signature `envUint(string)` and selector `0xc1978d1f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envUint", abi = "envUint(string)")] - pub struct EnvUint0Call(pub ::std::string::String); - ///Container type for all input parameters for the `envUint` function with signature `envUint(string,string)` and selector `0xf3dec099` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "envUint", abi = "envUint(string,string)")] - pub struct EnvUint1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `etch` function with signature `etch(address,bytes)` and selector `0xb4d6c782` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "etch", abi = "etch(address,bytes)")] - pub struct EtchCall( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,bytes)` and selector `0xbd6af434` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectCall", abi = "expectCall(address,bytes)")] - pub struct ExpectCall0Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,bytes,uint64)` and selector `0xc1adbbff` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectCall", abi = "expectCall(address,bytes,uint64)")] - pub struct ExpectCall1Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Bytes, - pub u64, - ); - ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,uint256,bytes)` and selector `0xf30c7ba3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectCall", abi = "expectCall(address,uint256,bytes)")] - pub struct ExpectCall2Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,uint256,bytes,uint64)` and selector `0xa2b1a1ae` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectCall", abi = "expectCall(address,uint256,bytes,uint64)")] - pub struct ExpectCall3Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub ::ethers_core::types::Bytes, - pub u64, - ); - ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,uint256,uint64,bytes)` and selector `0x23361207` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectCall", abi = "expectCall(address,uint256,uint64,bytes)")] - pub struct ExpectCall4Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub u64, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `expectCall` function with signature `expectCall(address,uint256,uint64,bytes,uint64)` and selector `0x65b7b7cc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "expectCall", - abi = "expectCall(address,uint256,uint64,bytes,uint64)" - )] - pub struct ExpectCall5Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub u64, - pub ::ethers_core::types::Bytes, - pub u64, - ); - ///Container type for all input parameters for the `expectCallMinGas` function with signature `expectCallMinGas(address,uint256,uint64,bytes)` and selector `0x08e4e116` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "expectCallMinGas", - abi = "expectCallMinGas(address,uint256,uint64,bytes)" - )] - pub struct ExpectCallMinGas0Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub u64, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `expectCallMinGas` function with signature `expectCallMinGas(address,uint256,uint64,bytes,uint64)` and selector `0xe13a1834` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "expectCallMinGas", - abi = "expectCallMinGas(address,uint256,uint64,bytes,uint64)" - )] - pub struct ExpectCallMinGas1Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub u64, - pub ::ethers_core::types::Bytes, - pub u64, - ); - ///Container type for all input parameters for the `expectEmit` function with signature `expectEmit()` and selector `0x440ed10d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectEmit", abi = "expectEmit()")] - pub struct ExpectEmit0Call; - ///Container type for all input parameters for the `expectEmit` function with signature `expectEmit(address)` and selector `0x86b9620d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectEmit", abi = "expectEmit(address)")] - pub struct ExpectEmit1Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `expectEmit` function with signature `expectEmit(bool,bool,bool,bool)` and selector `0x491cc7c2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectEmit", abi = "expectEmit(bool,bool,bool,bool)")] - pub struct ExpectEmit2Call(pub bool, pub bool, pub bool, pub bool); - ///Container type for all input parameters for the `expectEmit` function with signature `expectEmit(bool,bool,bool,bool,address)` and selector `0x81bad6f3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectEmit", abi = "expectEmit(bool,bool,bool,bool,address)")] - pub struct ExpectEmit3Call( - pub bool, - pub bool, - pub bool, - pub bool, - pub ::ethers_core::types::Address, - ); - ///Container type for all input parameters for the `expectRevert` function with signature `expectRevert()` and selector `0xf4844814` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectRevert", abi = "expectRevert()")] - pub struct ExpectRevert0Call; - ///Container type for all input parameters for the `expectRevert` function with signature `expectRevert(bytes)` and selector `0xf28dceb3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectRevert", abi = "expectRevert(bytes)")] - pub struct ExpectRevert1Call(pub ::ethers_core::types::Bytes); - ///Container type for all input parameters for the `expectRevert` function with signature `expectRevert(bytes4)` and selector `0xc31eb0e0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectRevert", abi = "expectRevert(bytes4)")] - pub struct ExpectRevert2Call(pub [u8; 4]); - ///Container type for all input parameters for the `expectSafeMemory` function with signature `expectSafeMemory(uint64,uint64)` and selector `0x6d016688` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "expectSafeMemory", abi = "expectSafeMemory(uint64,uint64)")] - pub struct ExpectSafeMemoryCall(pub u64, pub u64); - ///Container type for all input parameters for the `expectSafeMemoryCall` function with signature `expectSafeMemoryCall(uint64,uint64)` and selector `0x05838bf4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "expectSafeMemoryCall", - abi = "expectSafeMemoryCall(uint64,uint64)" - )] - pub struct ExpectSafeMemoryCallCall(pub u64, pub u64); - ///Container type for all input parameters for the `fee` function with signature `fee(uint256)` and selector `0x39b37ab0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "fee", abi = "fee(uint256)")] - pub struct FeeCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `ffi` function with signature `ffi(string[])` and selector `0x89160467` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "ffi", abi = "ffi(string[])")] - pub struct FfiCall(pub ::std::vec::Vec<::std::string::String>); - ///Container type for all input parameters for the `fsMetadata` function with signature `fsMetadata(string)` and selector `0xaf368a08` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "fsMetadata", abi = "fsMetadata(string)")] - pub struct FsMetadataCall(pub ::std::string::String); - ///Container type for all input parameters for the `getCode` function with signature `getCode(string)` and selector `0x8d1cc925` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getCode", abi = "getCode(string)")] - pub struct GetCodeCall(pub ::std::string::String); - ///Container type for all input parameters for the `getDeployedCode` function with signature `getDeployedCode(string)` and selector `0x3ebf73b4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getDeployedCode", abi = "getDeployedCode(string)")] - pub struct GetDeployedCodeCall(pub ::std::string::String); - ///Container type for all input parameters for the `getLabel` function with signature `getLabel(address)` and selector `0x28a249b0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getLabel", abi = "getLabel(address)")] - pub struct GetLabelCall(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `getMappingKeyAndParentOf` function with signature `getMappingKeyAndParentOf(address,bytes32)` and selector `0x876e24e6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "getMappingKeyAndParentOf", - abi = "getMappingKeyAndParentOf(address,bytes32)" - )] - pub struct GetMappingKeyAndParentOfCall( - pub ::ethers_core::types::Address, - pub [u8; 32], - ); - ///Container type for all input parameters for the `getMappingLength` function with signature `getMappingLength(address,bytes32)` and selector `0x2f2fd63f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getMappingLength", abi = "getMappingLength(address,bytes32)")] - pub struct GetMappingLengthCall(pub ::ethers_core::types::Address, pub [u8; 32]); - ///Container type for all input parameters for the `getMappingSlotAt` function with signature `getMappingSlotAt(address,bytes32,uint256)` and selector `0xebc73ab4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "getMappingSlotAt", - abi = "getMappingSlotAt(address,bytes32,uint256)" - )] - pub struct GetMappingSlotAtCall( - pub ::ethers_core::types::Address, - pub [u8; 32], - pub ::ethers_core::types::U256, - ); - ///Container type for all input parameters for the `getNonce` function with signature `getNonce((address,uint256,uint256,uint256))` and selector `0xa5748aad` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getNonce", abi = "getNonce((address,uint256,uint256,uint256))")] - pub struct GetNonce0Call(pub Wallet); - ///Container type for all input parameters for the `getNonce` function with signature `getNonce(address)` and selector `0x2d0335ab` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getNonce", abi = "getNonce(address)")] - pub struct GetNonce1Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `getRecordedLogs` function with signature `getRecordedLogs()` and selector `0x191553a4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "getRecordedLogs", abi = "getRecordedLogs()")] - pub struct GetRecordedLogsCall; - ///Container type for all input parameters for the `isPersistent` function with signature `isPersistent(address)` and selector `0xd92d8efd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "isPersistent", abi = "isPersistent(address)")] - pub struct IsPersistentCall(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `keyExists` function with signature `keyExists(string,string)` and selector `0x528a683c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "keyExists", abi = "keyExists(string,string)")] - pub struct KeyExistsCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `label` function with signature `label(address,string)` and selector `0xc657c718` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "label", abi = "label(address,string)")] - pub struct LabelCall(pub ::ethers_core::types::Address, pub ::std::string::String); - ///Container type for all input parameters for the `load` function with signature `load(address,bytes32)` and selector `0x667f9d70` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "load", abi = "load(address,bytes32)")] - pub struct LoadCall(pub ::ethers_core::types::Address, pub [u8; 32]); - ///Container type for all input parameters for the `makePersistent` function with signature `makePersistent(address)` and selector `0x57e22dde` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "makePersistent", abi = "makePersistent(address)")] - pub struct MakePersistent0Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `makePersistent` function with signature `makePersistent(address,address)` and selector `0x4074e0a8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "makePersistent", abi = "makePersistent(address,address)")] - pub struct MakePersistent2Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Address, - ); - ///Container type for all input parameters for the `makePersistent` function with signature `makePersistent(address,address,address)` and selector `0xefb77a75` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "makePersistent", abi = "makePersistent(address,address,address)")] - pub struct MakePersistent3Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Address, - ); - ///Container type for all input parameters for the `makePersistent` function with signature `makePersistent(address[])` and selector `0x1d9e269e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "makePersistent", abi = "makePersistent(address[])")] - pub struct MakePersistent1Call(pub ::std::vec::Vec<::ethers_core::types::Address>); - ///Container type for all input parameters for the `mockCall` function with signature `mockCall(address,bytes,bytes)` and selector `0xb96213e4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "mockCall", abi = "mockCall(address,bytes,bytes)")] - pub struct MockCall0Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Bytes, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `mockCall` function with signature `mockCall(address,uint256,bytes,bytes)` and selector `0x81409b91` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "mockCall", abi = "mockCall(address,uint256,bytes,bytes)")] - pub struct MockCall1Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub ::ethers_core::types::Bytes, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `mockCallRevert` function with signature `mockCallRevert(address,bytes,bytes)` and selector `0xdbaad147` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "mockCallRevert", abi = "mockCallRevert(address,bytes,bytes)")] - pub struct MockCallRevert0Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Bytes, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `mockCallRevert` function with signature `mockCallRevert(address,uint256,bytes,bytes)` and selector `0xd23cd037` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "mockCallRevert", - abi = "mockCallRevert(address,uint256,bytes,bytes)" - )] - pub struct MockCallRevert1Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::U256, - pub ::ethers_core::types::Bytes, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `openFile` function with signature `openFile(string)` and selector `0x7e0394bc` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "openFile", abi = "openFile(string)")] - pub struct OpenFileCall(pub ::std::string::String); - ///Container type for all input parameters for the `parseAddress` function with signature `parseAddress(string)` and selector `0xc6ce059d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseAddress", abi = "parseAddress(string)")] - pub struct ParseAddressCall(pub ::std::string::String); - ///Container type for all input parameters for the `parseBool` function with signature `parseBool(string)` and selector `0x974ef924` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseBool", abi = "parseBool(string)")] - pub struct ParseBoolCall(pub ::std::string::String); - ///Container type for all input parameters for the `parseBytes` function with signature `parseBytes(string)` and selector `0x8f5d232d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseBytes", abi = "parseBytes(string)")] - pub struct ParseBytesCall(pub ::std::string::String); - ///Container type for all input parameters for the `parseBytes32` function with signature `parseBytes32(string)` and selector `0x087e6e81` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseBytes32", abi = "parseBytes32(string)")] - pub struct ParseBytes32Call(pub ::std::string::String); - ///Container type for all input parameters for the `parseInt` function with signature `parseInt(string)` and selector `0x42346c5e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseInt", abi = "parseInt(string)")] - pub struct ParseIntCall(pub ::std::string::String); - ///Container type for all input parameters for the `parseJson` function with signature `parseJson(string)` and selector `0x6a82600a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJson", abi = "parseJson(string)")] - pub struct ParseJson0Call(pub ::std::string::String); - ///Container type for all input parameters for the `parseJson` function with signature `parseJson(string,string)` and selector `0x85940ef1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJson", abi = "parseJson(string,string)")] - pub struct ParseJson1Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonAddress` function with signature `parseJsonAddress(string,string)` and selector `0x1e19e657` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonAddress", abi = "parseJsonAddress(string,string)")] - pub struct ParseJsonAddressCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonAddressArray` function with signature `parseJsonAddressArray(string,string)` and selector `0x2fce7883` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "parseJsonAddressArray", - abi = "parseJsonAddressArray(string,string)" - )] - pub struct ParseJsonAddressArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonBool` function with signature `parseJsonBool(string,string)` and selector `0x9f86dc91` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonBool", abi = "parseJsonBool(string,string)")] - pub struct ParseJsonBoolCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonBoolArray` function with signature `parseJsonBoolArray(string,string)` and selector `0x91f3b94f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonBoolArray", abi = "parseJsonBoolArray(string,string)")] - pub struct ParseJsonBoolArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonBytes` function with signature `parseJsonBytes(string,string)` and selector `0xfd921be8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonBytes", abi = "parseJsonBytes(string,string)")] - pub struct ParseJsonBytesCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonBytes32` function with signature `parseJsonBytes32(string,string)` and selector `0x1777e59d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonBytes32", abi = "parseJsonBytes32(string,string)")] - pub struct ParseJsonBytes32Call( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonBytes32Array` function with signature `parseJsonBytes32Array(string,string)` and selector `0x91c75bc3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "parseJsonBytes32Array", - abi = "parseJsonBytes32Array(string,string)" - )] - pub struct ParseJsonBytes32ArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonBytesArray` function with signature `parseJsonBytesArray(string,string)` and selector `0x6631aa99` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonBytesArray", abi = "parseJsonBytesArray(string,string)")] - pub struct ParseJsonBytesArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonInt` function with signature `parseJsonInt(string,string)` and selector `0x7b048ccd` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonInt", abi = "parseJsonInt(string,string)")] - pub struct ParseJsonIntCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonIntArray` function with signature `parseJsonIntArray(string,string)` and selector `0x9983c28a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonIntArray", abi = "parseJsonIntArray(string,string)")] - pub struct ParseJsonIntArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonKeys` function with signature `parseJsonKeys(string,string)` and selector `0x213e4198` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonKeys", abi = "parseJsonKeys(string,string)")] - pub struct ParseJsonKeysCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonString` function with signature `parseJsonString(string,string)` and selector `0x49c4fac8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonString", abi = "parseJsonString(string,string)")] - pub struct ParseJsonStringCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonStringArray` function with signature `parseJsonStringArray(string,string)` and selector `0x498fdcf4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "parseJsonStringArray", - abi = "parseJsonStringArray(string,string)" - )] - pub struct ParseJsonStringArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseJsonUint` function with signature `parseJsonUint(string,string)` and selector `0xaddde2b6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonUint", abi = "parseJsonUint(string,string)")] - pub struct ParseJsonUintCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `parseJsonUintArray` function with signature `parseJsonUintArray(string,string)` and selector `0x522074ab` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseJsonUintArray", abi = "parseJsonUintArray(string,string)")] - pub struct ParseJsonUintArrayCall( - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `parseUint` function with signature `parseUint(string)` and selector `0xfa91454d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "parseUint", abi = "parseUint(string)")] - pub struct ParseUintCall(pub ::std::string::String); - ///Container type for all input parameters for the `pauseGasMetering` function with signature `pauseGasMetering()` and selector `0xd1a5b36f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "pauseGasMetering", abi = "pauseGasMetering()")] - pub struct PauseGasMeteringCall; - ///Container type for all input parameters for the `prank` function with signature `prank(address)` and selector `0xca669fa7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "prank", abi = "prank(address)")] - pub struct Prank0Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `prank` function with signature `prank(address,address)` and selector `0x47e50cce` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "prank", abi = "prank(address,address)")] - pub struct Prank1Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Address, - ); - ///Container type for all input parameters for the `prevrandao` function with signature `prevrandao(bytes32)` and selector `0x3b925549` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "prevrandao", abi = "prevrandao(bytes32)")] - pub struct PrevrandaoCall(pub [u8; 32]); - ///Container type for all input parameters for the `projectRoot` function with signature `projectRoot()` and selector `0xd930a0e6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "projectRoot", abi = "projectRoot()")] - pub struct ProjectRootCall; - ///Container type for all input parameters for the `readCallers` function with signature `readCallers()` and selector `0x4ad0bac9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readCallers", abi = "readCallers()")] - pub struct ReadCallersCall; - ///Container type for all input parameters for the `readDir` function with signature `readDir(string)` and selector `0xc4bc59e0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readDir", abi = "readDir(string)")] - pub struct ReadDir0Call(pub ::std::string::String); - ///Container type for all input parameters for the `readDir` function with signature `readDir(string,uint64)` and selector `0x1497876c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readDir", abi = "readDir(string,uint64)")] - pub struct ReadDir1Call(pub ::std::string::String, pub u64); - ///Container type for all input parameters for the `readDir` function with signature `readDir(string,uint64,bool)` and selector `0x8102d70d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readDir", abi = "readDir(string,uint64,bool)")] - pub struct ReadDir2Call(pub ::std::string::String, pub u64, pub bool); - ///Container type for all input parameters for the `readFile` function with signature `readFile(string)` and selector `0x60f9bb11` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readFile", abi = "readFile(string)")] - pub struct ReadFileCall(pub ::std::string::String); - ///Container type for all input parameters for the `readFileBinary` function with signature `readFileBinary(string)` and selector `0x16ed7bc4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readFileBinary", abi = "readFileBinary(string)")] - pub struct ReadFileBinaryCall(pub ::std::string::String); - ///Container type for all input parameters for the `readLine` function with signature `readLine(string)` and selector `0x70f55728` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readLine", abi = "readLine(string)")] - pub struct ReadLineCall(pub ::std::string::String); - ///Container type for all input parameters for the `readLink` function with signature `readLink(string)` and selector `0x9f5684a2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "readLink", abi = "readLink(string)")] - pub struct ReadLinkCall(pub ::std::string::String); - ///Container type for all input parameters for the `record` function with signature `record()` and selector `0x266cf109` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "record", abi = "record()")] - pub struct RecordCall; - ///Container type for all input parameters for the `recordLogs` function with signature `recordLogs()` and selector `0x41af2f52` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "recordLogs", abi = "recordLogs()")] - pub struct RecordLogsCall; - ///Container type for all input parameters for the `rememberKey` function with signature `rememberKey(uint256)` and selector `0x22100064` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rememberKey", abi = "rememberKey(uint256)")] - pub struct RememberKeyCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `removeDir` function with signature `removeDir(string,bool)` and selector `0x45c62011` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "removeDir", abi = "removeDir(string,bool)")] - pub struct RemoveDirCall(pub ::std::string::String, pub bool); - ///Container type for all input parameters for the `removeFile` function with signature `removeFile(string)` and selector `0xf1afe04d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "removeFile", abi = "removeFile(string)")] - pub struct RemoveFileCall(pub ::std::string::String); - ///Container type for all input parameters for the `resetNonce` function with signature `resetNonce(address)` and selector `0x1c72346d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "resetNonce", abi = "resetNonce(address)")] - pub struct ResetNonceCall(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `resumeGasMetering` function with signature `resumeGasMetering()` and selector `0x2bcd50e0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "resumeGasMetering", abi = "resumeGasMetering()")] - pub struct ResumeGasMeteringCall; - ///Container type for all input parameters for the `revertTo` function with signature `revertTo(uint256)` and selector `0x44d7f0a4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "revertTo", abi = "revertTo(uint256)")] - pub struct RevertToCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `revokePersistent` function with signature `revokePersistent(address)` and selector `0x997a0222` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "revokePersistent", abi = "revokePersistent(address)")] - pub struct RevokePersistent0Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `revokePersistent` function with signature `revokePersistent(address[])` and selector `0x3ce969e6` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "revokePersistent", abi = "revokePersistent(address[])")] - pub struct RevokePersistent1Call(pub ::std::vec::Vec<::ethers_core::types::Address>); - ///Container type for all input parameters for the `roll` function with signature `roll(uint256)` and selector `0x1f7b4f30` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "roll", abi = "roll(uint256)")] - pub struct RollCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `rollFork` function with signature `rollFork(uint256)` and selector `0xd9bbf3a1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rollFork", abi = "rollFork(uint256)")] - pub struct RollFork0Call(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `rollFork` function with signature `rollFork(bytes32)` and selector `0x0f29772b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rollFork", abi = "rollFork(bytes32)")] - pub struct RollFork1Call(pub [u8; 32]); - ///Container type for all input parameters for the `rollFork` function with signature `rollFork(uint256,uint256)` and selector `0xd74c83a4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rollFork", abi = "rollFork(uint256,uint256)")] - pub struct RollFork2Call( - pub ::ethers_core::types::U256, - pub ::ethers_core::types::U256, - ); - ///Container type for all input parameters for the `rollFork` function with signature `rollFork(uint256,bytes32)` and selector `0xf2830f7b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rollFork", abi = "rollFork(uint256,bytes32)")] - pub struct RollFork3Call(pub ::ethers_core::types::U256, pub [u8; 32]); - ///Container type for all input parameters for the `rpcUrl` function with signature `rpcUrl(string)` and selector `0x975a6ce9` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rpcUrl", abi = "rpcUrl(string)")] - pub struct RpcUrlCall(pub ::std::string::String); - ///Container type for all input parameters for the `rpcUrlStructs` function with signature `rpcUrlStructs()` and selector `0x9d2ad72a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rpcUrlStructs", abi = "rpcUrlStructs()")] - pub struct RpcUrlStructsCall; - ///Container type for all input parameters for the `rpcUrls` function with signature `rpcUrls()` and selector `0xa85a8418` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "rpcUrls", abi = "rpcUrls()")] - pub struct RpcUrlsCall; - ///Container type for all input parameters for the `selectFork` function with signature `selectFork(uint256)` and selector `0x9ebf6827` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "selectFork", abi = "selectFork(uint256)")] - pub struct SelectForkCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `serializeAddress` function with signature `serializeAddress(string,string,address)` and selector `0x972c6062` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "serializeAddress", - abi = "serializeAddress(string,string,address)" - )] - pub struct SerializeAddress0Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::ethers_core::types::Address, - ); - ///Container type for all input parameters for the `serializeAddress` function with signature `serializeAddress(string,string,address[])` and selector `0x1e356e1a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "serializeAddress", - abi = "serializeAddress(string,string,address[])" - )] - pub struct SerializeAddress1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::Address>, - ); - ///Container type for all input parameters for the `serializeBool` function with signature `serializeBool(string,string,bool)` and selector `0xac22e971` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeBool", abi = "serializeBool(string,string,bool)")] - pub struct SerializeBool0Call( - pub ::std::string::String, - pub ::std::string::String, - pub bool, - ); - ///Container type for all input parameters for the `serializeBool` function with signature `serializeBool(string,string,bool[])` and selector `0x92925aa1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeBool", abi = "serializeBool(string,string,bool[])")] - pub struct SerializeBool1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec, - ); - ///Container type for all input parameters for the `serializeBytes` function with signature `serializeBytes(string,string,bytes)` and selector `0xf21d52c7` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeBytes", abi = "serializeBytes(string,string,bytes)")] - pub struct SerializeBytes0Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `serializeBytes` function with signature `serializeBytes(string,string,bytes[])` and selector `0x9884b232` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeBytes", abi = "serializeBytes(string,string,bytes[])")] - pub struct SerializeBytes1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::Bytes>, - ); - ///Container type for all input parameters for the `serializeBytes32` function with signature `serializeBytes32(string,string,bytes32)` and selector `0x2d812b44` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "serializeBytes32", - abi = "serializeBytes32(string,string,bytes32)" - )] - pub struct SerializeBytes320Call( - pub ::std::string::String, - pub ::std::string::String, - pub [u8; 32], - ); - ///Container type for all input parameters for the `serializeBytes32` function with signature `serializeBytes32(string,string,bytes32[])` and selector `0x201e43e2` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "serializeBytes32", - abi = "serializeBytes32(string,string,bytes32[])" - )] - pub struct SerializeBytes321Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<[u8; 32]>, - ); - ///Container type for all input parameters for the `serializeInt` function with signature `serializeInt(string,string,int256)` and selector `0x3f33db60` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeInt", abi = "serializeInt(string,string,int256)")] - pub struct SerializeInt0Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::ethers_core::types::I256, - ); - ///Container type for all input parameters for the `serializeInt` function with signature `serializeInt(string,string,int256[])` and selector `0x7676e127` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeInt", abi = "serializeInt(string,string,int256[])")] - pub struct SerializeInt1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::I256>, - ); - ///Container type for all input parameters for the `serializeString` function with signature `serializeString(string,string,string)` and selector `0x88da6d35` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeString", abi = "serializeString(string,string,string)")] - pub struct SerializeString0Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `serializeString` function with signature `serializeString(string,string,string[])` and selector `0x561cd6f3` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeString", abi = "serializeString(string,string,string[])")] - pub struct SerializeString1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::std::string::String>, - ); - ///Container type for all input parameters for the `serializeUint` function with signature `serializeUint(string,string,uint256)` and selector `0x129e9002` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeUint", abi = "serializeUint(string,string,uint256)")] - pub struct SerializeUint0Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::ethers_core::types::U256, - ); - ///Container type for all input parameters for the `serializeUint` function with signature `serializeUint(string,string,uint256[])` and selector `0xfee9a469` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "serializeUint", abi = "serializeUint(string,string,uint256[])")] - pub struct SerializeUint1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::vec::Vec<::ethers_core::types::U256>, - ); - ///Container type for all input parameters for the `setEnv` function with signature `setEnv(string,string)` and selector `0x3d5923ee` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "setEnv", abi = "setEnv(string,string)")] - pub struct SetEnvCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `setNonce` function with signature `setNonce(address,uint64)` and selector `0xf8e18b57` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "setNonce", abi = "setNonce(address,uint64)")] - pub struct SetNonceCall(pub ::ethers_core::types::Address, pub u64); - ///Container type for all input parameters for the `setNonceUnsafe` function with signature `setNonceUnsafe(address,uint64)` and selector `0x9b67b21c` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "setNonceUnsafe", abi = "setNonceUnsafe(address,uint64)")] - pub struct SetNonceUnsafeCall(pub ::ethers_core::types::Address, pub u64); - ///Container type for all input parameters for the `sign` function with signature `sign(uint256,bytes32)` and selector `0xe341eaa4` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "sign", abi = "sign(uint256,bytes32)")] - pub struct Sign0Call(pub ::ethers_core::types::U256, pub [u8; 32]); - ///Container type for all input parameters for the `sign` function with signature `sign((address,uint256,uint256,uint256),bytes32)` and selector `0xb25c5a25` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "sign", abi = "sign((address,uint256,uint256,uint256),bytes32)")] - pub struct Sign1Call(pub Wallet, pub [u8; 32]); - ///Container type for all input parameters for the `skip` function with signature `skip(bool)` and selector `0xdd82d13e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "skip", abi = "skip(bool)")] - pub struct SkipCall(pub bool); - ///Container type for all input parameters for the `sleep` function with signature `sleep(uint256)` and selector `0xfa9d8713` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "sleep", abi = "sleep(uint256)")] - pub struct SleepCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `snapshot` function with signature `snapshot()` and selector `0x9711715a` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "snapshot", abi = "snapshot()")] - pub struct SnapshotCall; - ///Container type for all input parameters for the `startBroadcast` function with signature `startBroadcast()` and selector `0x7fb5297f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "startBroadcast", abi = "startBroadcast()")] - pub struct StartBroadcast0Call; - ///Container type for all input parameters for the `startBroadcast` function with signature `startBroadcast(address)` and selector `0x7fec2a8d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "startBroadcast", abi = "startBroadcast(address)")] - pub struct StartBroadcast1Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `startBroadcast` function with signature `startBroadcast(uint256)` and selector `0xce817d47` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "startBroadcast", abi = "startBroadcast(uint256)")] - pub struct StartBroadcast2Call(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `startMappingRecording` function with signature `startMappingRecording()` and selector `0x3e9705c0` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "startMappingRecording", abi = "startMappingRecording()")] - pub struct StartMappingRecordingCall; - ///Container type for all input parameters for the `startPrank` function with signature `startPrank(address)` and selector `0x06447d56` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "startPrank", abi = "startPrank(address)")] - pub struct StartPrank0Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `startPrank` function with signature `startPrank(address,address)` and selector `0x45b56078` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "startPrank", abi = "startPrank(address,address)")] - pub struct StartPrank1Call( - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Address, - ); - ///Container type for all input parameters for the `stopBroadcast` function with signature `stopBroadcast()` and selector `0x76eadd36` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "stopBroadcast", abi = "stopBroadcast()")] - pub struct StopBroadcastCall; - ///Container type for all input parameters for the `stopMappingRecording` function with signature `stopMappingRecording()` and selector `0x0d4aae9b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "stopMappingRecording", abi = "stopMappingRecording()")] - pub struct StopMappingRecordingCall; - ///Container type for all input parameters for the `stopPrank` function with signature `stopPrank()` and selector `0x90c5013b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "stopPrank", abi = "stopPrank()")] - pub struct StopPrankCall; - ///Container type for all input parameters for the `store` function with signature `store(address,bytes32,bytes32)` and selector `0x70ca10bb` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "store", abi = "store(address,bytes32,bytes32)")] - pub struct StoreCall(pub ::ethers_core::types::Address, pub [u8; 32], pub [u8; 32]); - ///Container type for all input parameters for the `toString` function with signature `toString(bytes)` and selector `0x71aad10d` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "toString", abi = "toString(bytes)")] - pub struct ToString0Call(pub ::ethers_core::types::Bytes); - ///Container type for all input parameters for the `toString` function with signature `toString(address)` and selector `0x56ca623e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "toString", abi = "toString(address)")] - pub struct ToString1Call(pub ::ethers_core::types::Address); - ///Container type for all input parameters for the `toString` function with signature `toString(uint256)` and selector `0x6900a3ae` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "toString", abi = "toString(uint256)")] - pub struct ToString2Call(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `toString` function with signature `toString(int256)` and selector `0xa322c40e` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "toString", abi = "toString(int256)")] - pub struct ToString3Call(pub ::ethers_core::types::I256); - ///Container type for all input parameters for the `toString` function with signature `toString(bytes32)` and selector `0xb11a19e8` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "toString", abi = "toString(bytes32)")] - pub struct ToString4Call(pub [u8; 32]); - ///Container type for all input parameters for the `toString` function with signature `toString(bool)` and selector `0x71dce7da` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "toString", abi = "toString(bool)")] - pub struct ToString5Call(pub bool); - ///Container type for all input parameters for the `transact` function with signature `transact(bytes32)` and selector `0xbe646da1` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "transact", abi = "transact(bytes32)")] - pub struct Transact0Call(pub [u8; 32]); - ///Container type for all input parameters for the `transact` function with signature `transact(uint256,bytes32)` and selector `0x4d8abc4b` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "transact", abi = "transact(uint256,bytes32)")] - pub struct Transact1Call(pub ::ethers_core::types::U256, pub [u8; 32]); - ///Container type for all input parameters for the `txGasPrice` function with signature `txGasPrice(uint256)` and selector `0x48f50c0f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "txGasPrice", abi = "txGasPrice(uint256)")] - pub struct TxGasPriceCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `warp` function with signature `warp(uint256)` and selector `0xe5d6bf02` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "warp", abi = "warp(uint256)")] - pub struct WarpCall(pub ::ethers_core::types::U256); - ///Container type for all input parameters for the `writeFile` function with signature `writeFile(string,string)` and selector `0x897e0a97` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "writeFile", abi = "writeFile(string,string)")] - pub struct WriteFileCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `writeFileBinary` function with signature `writeFileBinary(string,bytes)` and selector `0x1f21fc80` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "writeFileBinary", abi = "writeFileBinary(string,bytes)")] - pub struct WriteFileBinaryCall( - pub ::std::string::String, - pub ::ethers_core::types::Bytes, - ); - ///Container type for all input parameters for the `writeJson` function with signature `writeJson(string,string)` and selector `0xe23cd19f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "writeJson", abi = "writeJson(string,string)")] - pub struct WriteJson0Call(pub ::std::string::String, pub ::std::string::String); - ///Container type for all input parameters for the `writeJson` function with signature `writeJson(string,string,string)` and selector `0x35d6ad46` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "writeJson", abi = "writeJson(string,string,string)")] - pub struct WriteJson1Call( - pub ::std::string::String, - pub ::std::string::String, - pub ::std::string::String, - ); - ///Container type for all input parameters for the `writeLine` function with signature `writeLine(string,string)` and selector `0x619d897f` - #[derive( - Clone, - ::ethers_contract::EthCall, - ::ethers_contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "writeLine", abi = "writeLine(string,string)")] - pub struct WriteLineCall(pub ::std::string::String, pub ::std::string::String); - ///Container type for all of the contract's call - #[derive(Clone, ::ethers_contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum HEVMCalls { - Accesses(AccessesCall), - ActiveFork(ActiveForkCall), - Addr(AddrCall), - AllowCheatcodes(AllowCheatcodesCall), - Assume(AssumeCall), - Breakpoint0(Breakpoint0Call), - Breakpoint1(Breakpoint1Call), - Broadcast0(Broadcast0Call), - Broadcast1(Broadcast1Call), - Broadcast2(Broadcast2Call), - ChainId(ChainIdCall), - ClearMockedCalls(ClearMockedCallsCall), - CloseFile(CloseFileCall), - Coinbase(CoinbaseCall), - CopyFile(CopyFileCall), - CreateDir(CreateDirCall), - CreateFork1(CreateFork1Call), - CreateFork2(CreateFork2Call), - CreateFork0(CreateFork0Call), - CreateSelectFork1(CreateSelectFork1Call), - CreateSelectFork2(CreateSelectFork2Call), - CreateSelectFork0(CreateSelectFork0Call), - CreateWallet0(CreateWallet0Call), - CreateWallet1(CreateWallet1Call), - CreateWallet2(CreateWallet2Call), - Deal(DealCall), - DeriveKey0(DeriveKey0Call), - DeriveKey1(DeriveKey1Call), - DeriveKey2(DeriveKey2Call), - DeriveKey3(DeriveKey3Call), - Difficulty(DifficultyCall), - EnvAddress0(EnvAddress0Call), - EnvAddress1(EnvAddress1Call), - EnvBool0(EnvBool0Call), - EnvBool1(EnvBool1Call), - EnvBytes0(EnvBytes0Call), - EnvBytes1(EnvBytes1Call), - EnvBytes320(EnvBytes320Call), - EnvBytes321(EnvBytes321Call), - EnvInt0(EnvInt0Call), - EnvInt1(EnvInt1Call), - EnvOr0(EnvOr0Call), - EnvOr1(EnvOr1Call), - EnvOr2(EnvOr2Call), - EnvOr3(EnvOr3Call), - EnvOr4(EnvOr4Call), - EnvOr5(EnvOr5Call), - EnvOr6(EnvOr6Call), - EnvOr7(EnvOr7Call), - EnvOr8(EnvOr8Call), - EnvOr9(EnvOr9Call), - EnvOr10(EnvOr10Call), - EnvOr11(EnvOr11Call), - EnvOr12(EnvOr12Call), - EnvOr13(EnvOr13Call), - EnvString0(EnvString0Call), - EnvString1(EnvString1Call), - EnvUint0(EnvUint0Call), - EnvUint1(EnvUint1Call), - Etch(EtchCall), - ExpectCall0(ExpectCall0Call), - ExpectCall1(ExpectCall1Call), - ExpectCall2(ExpectCall2Call), - ExpectCall3(ExpectCall3Call), - ExpectCall4(ExpectCall4Call), - ExpectCall5(ExpectCall5Call), - ExpectCallMinGas0(ExpectCallMinGas0Call), - ExpectCallMinGas1(ExpectCallMinGas1Call), - ExpectEmit0(ExpectEmit0Call), - ExpectEmit1(ExpectEmit1Call), - ExpectEmit2(ExpectEmit2Call), - ExpectEmit3(ExpectEmit3Call), - ExpectRevert0(ExpectRevert0Call), - ExpectRevert1(ExpectRevert1Call), - ExpectRevert2(ExpectRevert2Call), - ExpectSafeMemory(ExpectSafeMemoryCall), - ExpectSafeMemoryCall(ExpectSafeMemoryCallCall), - Fee(FeeCall), - Ffi(FfiCall), - FsMetadata(FsMetadataCall), - GetCode(GetCodeCall), - GetDeployedCode(GetDeployedCodeCall), - GetLabel(GetLabelCall), - GetMappingKeyAndParentOf(GetMappingKeyAndParentOfCall), - GetMappingLength(GetMappingLengthCall), - GetMappingSlotAt(GetMappingSlotAtCall), - GetNonce0(GetNonce0Call), - GetNonce1(GetNonce1Call), - GetRecordedLogs(GetRecordedLogsCall), - IsPersistent(IsPersistentCall), - KeyExists(KeyExistsCall), - Label(LabelCall), - Load(LoadCall), - MakePersistent0(MakePersistent0Call), - MakePersistent2(MakePersistent2Call), - MakePersistent3(MakePersistent3Call), - MakePersistent1(MakePersistent1Call), - MockCall0(MockCall0Call), - MockCall1(MockCall1Call), - MockCallRevert0(MockCallRevert0Call), - MockCallRevert1(MockCallRevert1Call), - OpenFile(OpenFileCall), - ParseAddress(ParseAddressCall), - ParseBool(ParseBoolCall), - ParseBytes(ParseBytesCall), - ParseBytes32(ParseBytes32Call), - ParseInt(ParseIntCall), - ParseJson0(ParseJson0Call), - ParseJson1(ParseJson1Call), - ParseJsonAddress(ParseJsonAddressCall), - ParseJsonAddressArray(ParseJsonAddressArrayCall), - ParseJsonBool(ParseJsonBoolCall), - ParseJsonBoolArray(ParseJsonBoolArrayCall), - ParseJsonBytes(ParseJsonBytesCall), - ParseJsonBytes32(ParseJsonBytes32Call), - ParseJsonBytes32Array(ParseJsonBytes32ArrayCall), - ParseJsonBytesArray(ParseJsonBytesArrayCall), - ParseJsonInt(ParseJsonIntCall), - ParseJsonIntArray(ParseJsonIntArrayCall), - ParseJsonKeys(ParseJsonKeysCall), - ParseJsonString(ParseJsonStringCall), - ParseJsonStringArray(ParseJsonStringArrayCall), - ParseJsonUint(ParseJsonUintCall), - ParseJsonUintArray(ParseJsonUintArrayCall), - ParseUint(ParseUintCall), - PauseGasMetering(PauseGasMeteringCall), - Prank0(Prank0Call), - Prank1(Prank1Call), - Prevrandao(PrevrandaoCall), - ProjectRoot(ProjectRootCall), - ReadCallers(ReadCallersCall), - ReadDir0(ReadDir0Call), - ReadDir1(ReadDir1Call), - ReadDir2(ReadDir2Call), - ReadFile(ReadFileCall), - ReadFileBinary(ReadFileBinaryCall), - ReadLine(ReadLineCall), - ReadLink(ReadLinkCall), - Record(RecordCall), - RecordLogs(RecordLogsCall), - RememberKey(RememberKeyCall), - RemoveDir(RemoveDirCall), - RemoveFile(RemoveFileCall), - ResetNonce(ResetNonceCall), - ResumeGasMetering(ResumeGasMeteringCall), - RevertTo(RevertToCall), - RevokePersistent0(RevokePersistent0Call), - RevokePersistent1(RevokePersistent1Call), - Roll(RollCall), - RollFork0(RollFork0Call), - RollFork1(RollFork1Call), - RollFork2(RollFork2Call), - RollFork3(RollFork3Call), - RpcUrl(RpcUrlCall), - RpcUrlStructs(RpcUrlStructsCall), - RpcUrls(RpcUrlsCall), - SelectFork(SelectForkCall), - SerializeAddress0(SerializeAddress0Call), - SerializeAddress1(SerializeAddress1Call), - SerializeBool0(SerializeBool0Call), - SerializeBool1(SerializeBool1Call), - SerializeBytes0(SerializeBytes0Call), - SerializeBytes1(SerializeBytes1Call), - SerializeBytes320(SerializeBytes320Call), - SerializeBytes321(SerializeBytes321Call), - SerializeInt0(SerializeInt0Call), - SerializeInt1(SerializeInt1Call), - SerializeString0(SerializeString0Call), - SerializeString1(SerializeString1Call), - SerializeUint0(SerializeUint0Call), - SerializeUint1(SerializeUint1Call), - SetEnv(SetEnvCall), - SetNonce(SetNonceCall), - SetNonceUnsafe(SetNonceUnsafeCall), - Sign0(Sign0Call), - Sign1(Sign1Call), - Skip(SkipCall), - Sleep(SleepCall), - Snapshot(SnapshotCall), - StartBroadcast0(StartBroadcast0Call), - StartBroadcast1(StartBroadcast1Call), - StartBroadcast2(StartBroadcast2Call), - StartMappingRecording(StartMappingRecordingCall), - StartPrank0(StartPrank0Call), - StartPrank1(StartPrank1Call), - StopBroadcast(StopBroadcastCall), - StopMappingRecording(StopMappingRecordingCall), - StopPrank(StopPrankCall), - Store(StoreCall), - ToString0(ToString0Call), - ToString1(ToString1Call), - ToString2(ToString2Call), - ToString3(ToString3Call), - ToString4(ToString4Call), - ToString5(ToString5Call), - Transact0(Transact0Call), - Transact1(Transact1Call), - TxGasPrice(TxGasPriceCall), - Warp(WarpCall), - WriteFile(WriteFileCall), - WriteFileBinary(WriteFileBinaryCall), - WriteJson0(WriteJson0Call), - WriteJson1(WriteJson1Call), - WriteLine(WriteLineCall), - } - impl ::ethers_core::abi::AbiDecode for HEVMCalls { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Accesses(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ActiveFork(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Addr(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::AllowCheatcodes(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Assume(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Breakpoint0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Breakpoint1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Broadcast0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Broadcast1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Broadcast2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ChainId(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ClearMockedCalls(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CloseFile(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Coinbase(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CopyFile(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateDir(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateFork1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateFork2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateFork0(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::CreateSelectFork1(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::CreateSelectFork2(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::CreateSelectFork0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateWallet0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateWallet1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::CreateWallet2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Deal(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::DeriveKey0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::DeriveKey1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::DeriveKey2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::DeriveKey3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Difficulty(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvAddress0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvAddress1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvBool0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvBool1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvBytes0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvBytes1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvBytes320(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvBytes321(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvInt0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvInt1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr4(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr5(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr6(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr7(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr8(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr9(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr10(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr11(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr12(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvOr13(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvString0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvString1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvUint0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::EnvUint1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Etch(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectCall0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectCall1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectCall2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectCall3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectCall4(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectCall5(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ExpectCallMinGas0(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ExpectCallMinGas1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectEmit0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectEmit1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectEmit2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectEmit3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectRevert0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectRevert1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectRevert2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ExpectSafeMemory(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ExpectSafeMemoryCall(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Fee(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Ffi(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::FsMetadata(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetCode(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetDeployedCode(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetLabel(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::GetMappingKeyAndParentOf(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetMappingLength(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetMappingSlotAt(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetNonce0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetNonce1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::GetRecordedLogs(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::IsPersistent(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::KeyExists(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Label(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Load(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MakePersistent0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MakePersistent2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MakePersistent3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MakePersistent1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MockCall0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MockCall1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MockCallRevert0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::MockCallRevert1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::OpenFile(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseAddress(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseBool(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseBytes(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseBytes32(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseInt(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJson0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJson1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonAddress(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonAddressArray(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonBool(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonBoolArray(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonBytes(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonBytes32(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonBytes32Array(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonBytesArray(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonInt(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonIntArray(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonKeys(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonString(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonStringArray(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseJsonUint(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ParseJsonUintArray(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ParseUint(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::PauseGasMetering(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Prank0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Prank1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Prevrandao(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ProjectRoot(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadCallers(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadDir0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadDir1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadDir2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadFile(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadFileBinary(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadLine(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ReadLink(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Record(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RecordLogs(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RememberKey(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RemoveDir(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RemoveFile(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ResetNonce(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::ResumeGasMetering(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RevertTo(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::RevokePersistent0(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::RevokePersistent1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Roll(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RollFork0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RollFork1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RollFork2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RollFork3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RpcUrl(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RpcUrlStructs(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::RpcUrls(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SelectFork(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::SerializeAddress0(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::SerializeAddress1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeBool0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeBool1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeBytes0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeBytes1(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::SerializeBytes320(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::SerializeBytes321(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeInt0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeInt1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeString0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeString1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeUint0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SerializeUint1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SetEnv(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SetNonce(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::SetNonceUnsafe(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Sign0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Sign1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Skip(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Sleep(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Snapshot(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StartBroadcast0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StartBroadcast1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StartBroadcast2(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::StartMappingRecording(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StartPrank0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StartPrank1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StopBroadcast(decoded)); - } - if let Ok(decoded) - = ::decode( - data, - ) { - return Ok(Self::StopMappingRecording(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::StopPrank(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Store(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ToString0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ToString1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ToString2(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ToString3(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ToString4(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::ToString5(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Transact0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Transact1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::TxGasPrice(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::Warp(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::WriteFile(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::WriteFileBinary(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::WriteJson0(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::WriteJson1(decoded)); - } - if let Ok(decoded) - = ::decode(data) { - return Ok(Self::WriteLine(decoded)); - } - Err(::ethers_core::abi::Error::InvalidData.into()) - } - } - impl ::ethers_core::abi::AbiEncode for HEVMCalls { - fn encode(self) -> Vec { - match self { - Self::Accesses(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ActiveFork(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Addr(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::AllowCheatcodes(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Assume(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Breakpoint0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Breakpoint1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Broadcast0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Broadcast1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Broadcast2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ChainId(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ClearMockedCalls(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CloseFile(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Coinbase(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::CopyFile(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::CreateDir(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateFork1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateFork2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateFork0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateSelectFork1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateSelectFork2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateSelectFork0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateWallet0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateWallet1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::CreateWallet2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Deal(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::DeriveKey0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::DeriveKey1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::DeriveKey2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::DeriveKey3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Difficulty(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvAddress0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvAddress1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvBool0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvBool1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvBytes0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvBytes1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvBytes320(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvBytes321(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvInt0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvInt1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr2(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr3(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr4(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr5(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr6(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr7(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr8(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr9(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr10(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr11(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr12(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvOr13(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvString0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvString1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::EnvUint0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::EnvUint1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Etch(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ExpectCall0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCall1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCall2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCall3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCall4(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCall5(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCallMinGas0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectCallMinGas1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectEmit0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectEmit1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectEmit2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectEmit3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectRevert0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectRevert1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectRevert2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectSafeMemory(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ExpectSafeMemoryCall(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Fee(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Ffi(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::FsMetadata(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetCode(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::GetDeployedCode(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetLabel(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::GetMappingKeyAndParentOf(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetMappingLength(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetMappingSlotAt(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetNonce0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetNonce1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::GetRecordedLogs(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::IsPersistent(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::KeyExists(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Label(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Load(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::MakePersistent0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MakePersistent2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MakePersistent3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MakePersistent1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MockCall0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MockCall1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MockCallRevert0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::MockCallRevert1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::OpenFile(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ParseAddress(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseBool(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseBytes(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseBytes32(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseInt(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ParseJson0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJson1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonAddress(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonAddressArray(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonBool(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonBoolArray(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonBytes(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonBytes32(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonBytes32Array(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonBytesArray(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonInt(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonIntArray(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonKeys(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonString(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonStringArray(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonUint(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseJsonUintArray(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ParseUint(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::PauseGasMetering(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Prank0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Prank1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Prevrandao(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ProjectRoot(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ReadCallers(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ReadDir0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ReadDir1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ReadDir2(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ReadFile(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ReadFileBinary(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ReadLine(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ReadLink(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Record(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::RecordLogs(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RememberKey(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RemoveDir(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RemoveFile(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ResetNonce(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ResumeGasMetering(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RevertTo(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::RevokePersistent0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RevokePersistent1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Roll(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::RollFork0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RollFork1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RollFork2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RollFork3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RpcUrl(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::RpcUrlStructs(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::RpcUrls(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::SelectFork(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeAddress0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeAddress1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeBool0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeBool1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeBytes0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeBytes1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeBytes320(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeBytes321(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeInt0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeInt1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeString0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeString1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeUint0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SerializeUint1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::SetEnv(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::SetNonce(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::SetNonceUnsafe(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Sign0(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Sign1(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Skip(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Sleep(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::Snapshot(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::StartBroadcast0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StartBroadcast1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StartBroadcast2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StartMappingRecording(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StartPrank0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StartPrank1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StopBroadcast(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StopMappingRecording(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::StopPrank(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Store(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::ToString0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ToString1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ToString2(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ToString3(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ToString4(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::ToString5(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Transact0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Transact1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::TxGasPrice(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::Warp(element) => ::ethers_core::abi::AbiEncode::encode(element), - Self::WriteFile(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::WriteFileBinary(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::WriteJson0(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::WriteJson1(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - Self::WriteLine(element) => { - ::ethers_core::abi::AbiEncode::encode(element) - } - } - } - } - impl ::core::fmt::Display for HEVMCalls { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::Accesses(element) => ::core::fmt::Display::fmt(element, f), - Self::ActiveFork(element) => ::core::fmt::Display::fmt(element, f), - Self::Addr(element) => ::core::fmt::Display::fmt(element, f), - Self::AllowCheatcodes(element) => ::core::fmt::Display::fmt(element, f), - Self::Assume(element) => ::core::fmt::Display::fmt(element, f), - Self::Breakpoint0(element) => ::core::fmt::Display::fmt(element, f), - Self::Breakpoint1(element) => ::core::fmt::Display::fmt(element, f), - Self::Broadcast0(element) => ::core::fmt::Display::fmt(element, f), - Self::Broadcast1(element) => ::core::fmt::Display::fmt(element, f), - Self::Broadcast2(element) => ::core::fmt::Display::fmt(element, f), - Self::ChainId(element) => ::core::fmt::Display::fmt(element, f), - Self::ClearMockedCalls(element) => ::core::fmt::Display::fmt(element, f), - Self::CloseFile(element) => ::core::fmt::Display::fmt(element, f), - Self::Coinbase(element) => ::core::fmt::Display::fmt(element, f), - Self::CopyFile(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateDir(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateFork1(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateFork2(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateFork0(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateSelectFork1(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateSelectFork2(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateSelectFork0(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateWallet0(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateWallet1(element) => ::core::fmt::Display::fmt(element, f), - Self::CreateWallet2(element) => ::core::fmt::Display::fmt(element, f), - Self::Deal(element) => ::core::fmt::Display::fmt(element, f), - Self::DeriveKey0(element) => ::core::fmt::Display::fmt(element, f), - Self::DeriveKey1(element) => ::core::fmt::Display::fmt(element, f), - Self::DeriveKey2(element) => ::core::fmt::Display::fmt(element, f), - Self::DeriveKey3(element) => ::core::fmt::Display::fmt(element, f), - Self::Difficulty(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvAddress0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvAddress1(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvBool0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvBool1(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvBytes0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvBytes1(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvBytes320(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvBytes321(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvInt0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvInt1(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr1(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr2(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr3(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr4(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr5(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr6(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr7(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr8(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr9(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr10(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr11(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr12(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvOr13(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvString0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvString1(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvUint0(element) => ::core::fmt::Display::fmt(element, f), - Self::EnvUint1(element) => ::core::fmt::Display::fmt(element, f), - Self::Etch(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCall0(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCall1(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCall2(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCall3(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCall4(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCall5(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCallMinGas0(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectCallMinGas1(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectEmit0(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectEmit1(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectEmit2(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectEmit3(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectRevert0(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectRevert1(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectRevert2(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectSafeMemory(element) => ::core::fmt::Display::fmt(element, f), - Self::ExpectSafeMemoryCall(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::Fee(element) => ::core::fmt::Display::fmt(element, f), - Self::Ffi(element) => ::core::fmt::Display::fmt(element, f), - Self::FsMetadata(element) => ::core::fmt::Display::fmt(element, f), - Self::GetCode(element) => ::core::fmt::Display::fmt(element, f), - Self::GetDeployedCode(element) => ::core::fmt::Display::fmt(element, f), - Self::GetLabel(element) => ::core::fmt::Display::fmt(element, f), - Self::GetMappingKeyAndParentOf(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::GetMappingLength(element) => ::core::fmt::Display::fmt(element, f), - Self::GetMappingSlotAt(element) => ::core::fmt::Display::fmt(element, f), - Self::GetNonce0(element) => ::core::fmt::Display::fmt(element, f), - Self::GetNonce1(element) => ::core::fmt::Display::fmt(element, f), - Self::GetRecordedLogs(element) => ::core::fmt::Display::fmt(element, f), - Self::IsPersistent(element) => ::core::fmt::Display::fmt(element, f), - Self::KeyExists(element) => ::core::fmt::Display::fmt(element, f), - Self::Label(element) => ::core::fmt::Display::fmt(element, f), - Self::Load(element) => ::core::fmt::Display::fmt(element, f), - Self::MakePersistent0(element) => ::core::fmt::Display::fmt(element, f), - Self::MakePersistent2(element) => ::core::fmt::Display::fmt(element, f), - Self::MakePersistent3(element) => ::core::fmt::Display::fmt(element, f), - Self::MakePersistent1(element) => ::core::fmt::Display::fmt(element, f), - Self::MockCall0(element) => ::core::fmt::Display::fmt(element, f), - Self::MockCall1(element) => ::core::fmt::Display::fmt(element, f), - Self::MockCallRevert0(element) => ::core::fmt::Display::fmt(element, f), - Self::MockCallRevert1(element) => ::core::fmt::Display::fmt(element, f), - Self::OpenFile(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseAddress(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseBool(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseBytes(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseBytes32(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseInt(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJson0(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJson1(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonAddress(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonAddressArray(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ParseJsonBool(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonBoolArray(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ParseJsonBytes(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonBytes32(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonBytes32Array(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ParseJsonBytesArray(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ParseJsonInt(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonIntArray(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonKeys(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonString(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonStringArray(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ParseJsonUint(element) => ::core::fmt::Display::fmt(element, f), - Self::ParseJsonUintArray(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ParseUint(element) => ::core::fmt::Display::fmt(element, f), - Self::PauseGasMetering(element) => ::core::fmt::Display::fmt(element, f), - Self::Prank0(element) => ::core::fmt::Display::fmt(element, f), - Self::Prank1(element) => ::core::fmt::Display::fmt(element, f), - Self::Prevrandao(element) => ::core::fmt::Display::fmt(element, f), - Self::ProjectRoot(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadCallers(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadDir0(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadDir1(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadDir2(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadFile(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadFileBinary(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadLine(element) => ::core::fmt::Display::fmt(element, f), - Self::ReadLink(element) => ::core::fmt::Display::fmt(element, f), - Self::Record(element) => ::core::fmt::Display::fmt(element, f), - Self::RecordLogs(element) => ::core::fmt::Display::fmt(element, f), - Self::RememberKey(element) => ::core::fmt::Display::fmt(element, f), - Self::RemoveDir(element) => ::core::fmt::Display::fmt(element, f), - Self::RemoveFile(element) => ::core::fmt::Display::fmt(element, f), - Self::ResetNonce(element) => ::core::fmt::Display::fmt(element, f), - Self::ResumeGasMetering(element) => ::core::fmt::Display::fmt(element, f), - Self::RevertTo(element) => ::core::fmt::Display::fmt(element, f), - Self::RevokePersistent0(element) => ::core::fmt::Display::fmt(element, f), - Self::RevokePersistent1(element) => ::core::fmt::Display::fmt(element, f), - Self::Roll(element) => ::core::fmt::Display::fmt(element, f), - Self::RollFork0(element) => ::core::fmt::Display::fmt(element, f), - Self::RollFork1(element) => ::core::fmt::Display::fmt(element, f), - Self::RollFork2(element) => ::core::fmt::Display::fmt(element, f), - Self::RollFork3(element) => ::core::fmt::Display::fmt(element, f), - Self::RpcUrl(element) => ::core::fmt::Display::fmt(element, f), - Self::RpcUrlStructs(element) => ::core::fmt::Display::fmt(element, f), - Self::RpcUrls(element) => ::core::fmt::Display::fmt(element, f), - Self::SelectFork(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeAddress0(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeAddress1(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeBool0(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeBool1(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeBytes0(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeBytes1(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeBytes320(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeBytes321(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeInt0(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeInt1(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeString0(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeString1(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeUint0(element) => ::core::fmt::Display::fmt(element, f), - Self::SerializeUint1(element) => ::core::fmt::Display::fmt(element, f), - Self::SetEnv(element) => ::core::fmt::Display::fmt(element, f), - Self::SetNonce(element) => ::core::fmt::Display::fmt(element, f), - Self::SetNonceUnsafe(element) => ::core::fmt::Display::fmt(element, f), - Self::Sign0(element) => ::core::fmt::Display::fmt(element, f), - Self::Sign1(element) => ::core::fmt::Display::fmt(element, f), - Self::Skip(element) => ::core::fmt::Display::fmt(element, f), - Self::Sleep(element) => ::core::fmt::Display::fmt(element, f), - Self::Snapshot(element) => ::core::fmt::Display::fmt(element, f), - Self::StartBroadcast0(element) => ::core::fmt::Display::fmt(element, f), - Self::StartBroadcast1(element) => ::core::fmt::Display::fmt(element, f), - Self::StartBroadcast2(element) => ::core::fmt::Display::fmt(element, f), - Self::StartMappingRecording(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::StartPrank0(element) => ::core::fmt::Display::fmt(element, f), - Self::StartPrank1(element) => ::core::fmt::Display::fmt(element, f), - Self::StopBroadcast(element) => ::core::fmt::Display::fmt(element, f), - Self::StopMappingRecording(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::StopPrank(element) => ::core::fmt::Display::fmt(element, f), - Self::Store(element) => ::core::fmt::Display::fmt(element, f), - Self::ToString0(element) => ::core::fmt::Display::fmt(element, f), - Self::ToString1(element) => ::core::fmt::Display::fmt(element, f), - Self::ToString2(element) => ::core::fmt::Display::fmt(element, f), - Self::ToString3(element) => ::core::fmt::Display::fmt(element, f), - Self::ToString4(element) => ::core::fmt::Display::fmt(element, f), - Self::ToString5(element) => ::core::fmt::Display::fmt(element, f), - Self::Transact0(element) => ::core::fmt::Display::fmt(element, f), - Self::Transact1(element) => ::core::fmt::Display::fmt(element, f), - Self::TxGasPrice(element) => ::core::fmt::Display::fmt(element, f), - Self::Warp(element) => ::core::fmt::Display::fmt(element, f), - Self::WriteFile(element) => ::core::fmt::Display::fmt(element, f), - Self::WriteFileBinary(element) => ::core::fmt::Display::fmt(element, f), - Self::WriteJson0(element) => ::core::fmt::Display::fmt(element, f), - Self::WriteJson1(element) => ::core::fmt::Display::fmt(element, f), - Self::WriteLine(element) => ::core::fmt::Display::fmt(element, f), - } - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: AccessesCall) -> Self { - Self::Accesses(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ActiveForkCall) -> Self { - Self::ActiveFork(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: AddrCall) -> Self { - Self::Addr(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: AllowCheatcodesCall) -> Self { - Self::AllowCheatcodes(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: AssumeCall) -> Self { - Self::Assume(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Breakpoint0Call) -> Self { - Self::Breakpoint0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Breakpoint1Call) -> Self { - Self::Breakpoint1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Broadcast0Call) -> Self { - Self::Broadcast0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Broadcast1Call) -> Self { - Self::Broadcast1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Broadcast2Call) -> Self { - Self::Broadcast2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ChainIdCall) -> Self { - Self::ChainId(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ClearMockedCallsCall) -> Self { - Self::ClearMockedCalls(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CloseFileCall) -> Self { - Self::CloseFile(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CoinbaseCall) -> Self { - Self::Coinbase(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CopyFileCall) -> Self { - Self::CopyFile(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateDirCall) -> Self { - Self::CreateDir(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateFork1Call) -> Self { - Self::CreateFork1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateFork2Call) -> Self { - Self::CreateFork2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateFork0Call) -> Self { - Self::CreateFork0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateSelectFork1Call) -> Self { - Self::CreateSelectFork1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateSelectFork2Call) -> Self { - Self::CreateSelectFork2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateSelectFork0Call) -> Self { - Self::CreateSelectFork0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateWallet0Call) -> Self { - Self::CreateWallet0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateWallet1Call) -> Self { - Self::CreateWallet1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: CreateWallet2Call) -> Self { - Self::CreateWallet2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: DealCall) -> Self { - Self::Deal(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: DeriveKey0Call) -> Self { - Self::DeriveKey0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: DeriveKey1Call) -> Self { - Self::DeriveKey1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: DeriveKey2Call) -> Self { - Self::DeriveKey2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: DeriveKey3Call) -> Self { - Self::DeriveKey3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: DifficultyCall) -> Self { - Self::Difficulty(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvAddress0Call) -> Self { - Self::EnvAddress0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvAddress1Call) -> Self { - Self::EnvAddress1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvBool0Call) -> Self { - Self::EnvBool0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvBool1Call) -> Self { - Self::EnvBool1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvBytes0Call) -> Self { - Self::EnvBytes0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvBytes1Call) -> Self { - Self::EnvBytes1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvBytes320Call) -> Self { - Self::EnvBytes320(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvBytes321Call) -> Self { - Self::EnvBytes321(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvInt0Call) -> Self { - Self::EnvInt0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvInt1Call) -> Self { - Self::EnvInt1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr0Call) -> Self { - Self::EnvOr0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr1Call) -> Self { - Self::EnvOr1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr2Call) -> Self { - Self::EnvOr2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr3Call) -> Self { - Self::EnvOr3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr4Call) -> Self { - Self::EnvOr4(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr5Call) -> Self { - Self::EnvOr5(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr6Call) -> Self { - Self::EnvOr6(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr7Call) -> Self { - Self::EnvOr7(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr8Call) -> Self { - Self::EnvOr8(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr9Call) -> Self { - Self::EnvOr9(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr10Call) -> Self { - Self::EnvOr10(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr11Call) -> Self { - Self::EnvOr11(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr12Call) -> Self { - Self::EnvOr12(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvOr13Call) -> Self { - Self::EnvOr13(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvString0Call) -> Self { - Self::EnvString0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvString1Call) -> Self { - Self::EnvString1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvUint0Call) -> Self { - Self::EnvUint0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EnvUint1Call) -> Self { - Self::EnvUint1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: EtchCall) -> Self { - Self::Etch(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCall0Call) -> Self { - Self::ExpectCall0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCall1Call) -> Self { - Self::ExpectCall1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCall2Call) -> Self { - Self::ExpectCall2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCall3Call) -> Self { - Self::ExpectCall3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCall4Call) -> Self { - Self::ExpectCall4(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCall5Call) -> Self { - Self::ExpectCall5(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCallMinGas0Call) -> Self { - Self::ExpectCallMinGas0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectCallMinGas1Call) -> Self { - Self::ExpectCallMinGas1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectEmit0Call) -> Self { - Self::ExpectEmit0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectEmit1Call) -> Self { - Self::ExpectEmit1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectEmit2Call) -> Self { - Self::ExpectEmit2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectEmit3Call) -> Self { - Self::ExpectEmit3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectRevert0Call) -> Self { - Self::ExpectRevert0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectRevert1Call) -> Self { - Self::ExpectRevert1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectRevert2Call) -> Self { - Self::ExpectRevert2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectSafeMemoryCall) -> Self { - Self::ExpectSafeMemory(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ExpectSafeMemoryCallCall) -> Self { - Self::ExpectSafeMemoryCall(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: FeeCall) -> Self { - Self::Fee(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: FfiCall) -> Self { - Self::Ffi(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: FsMetadataCall) -> Self { - Self::FsMetadata(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetCodeCall) -> Self { - Self::GetCode(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetDeployedCodeCall) -> Self { - Self::GetDeployedCode(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetLabelCall) -> Self { - Self::GetLabel(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetMappingKeyAndParentOfCall) -> Self { - Self::GetMappingKeyAndParentOf(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetMappingLengthCall) -> Self { - Self::GetMappingLength(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetMappingSlotAtCall) -> Self { - Self::GetMappingSlotAt(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetNonce0Call) -> Self { - Self::GetNonce0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetNonce1Call) -> Self { - Self::GetNonce1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: GetRecordedLogsCall) -> Self { - Self::GetRecordedLogs(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: IsPersistentCall) -> Self { - Self::IsPersistent(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: KeyExistsCall) -> Self { - Self::KeyExists(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: LabelCall) -> Self { - Self::Label(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: LoadCall) -> Self { - Self::Load(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MakePersistent0Call) -> Self { - Self::MakePersistent0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MakePersistent2Call) -> Self { - Self::MakePersistent2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MakePersistent3Call) -> Self { - Self::MakePersistent3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MakePersistent1Call) -> Self { - Self::MakePersistent1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MockCall0Call) -> Self { - Self::MockCall0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MockCall1Call) -> Self { - Self::MockCall1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MockCallRevert0Call) -> Self { - Self::MockCallRevert0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: MockCallRevert1Call) -> Self { - Self::MockCallRevert1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: OpenFileCall) -> Self { - Self::OpenFile(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseAddressCall) -> Self { - Self::ParseAddress(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseBoolCall) -> Self { - Self::ParseBool(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseBytesCall) -> Self { - Self::ParseBytes(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseBytes32Call) -> Self { - Self::ParseBytes32(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseIntCall) -> Self { - Self::ParseInt(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJson0Call) -> Self { - Self::ParseJson0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJson1Call) -> Self { - Self::ParseJson1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonAddressCall) -> Self { - Self::ParseJsonAddress(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonAddressArrayCall) -> Self { - Self::ParseJsonAddressArray(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonBoolCall) -> Self { - Self::ParseJsonBool(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonBoolArrayCall) -> Self { - Self::ParseJsonBoolArray(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonBytesCall) -> Self { - Self::ParseJsonBytes(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonBytes32Call) -> Self { - Self::ParseJsonBytes32(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonBytes32ArrayCall) -> Self { - Self::ParseJsonBytes32Array(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonBytesArrayCall) -> Self { - Self::ParseJsonBytesArray(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonIntCall) -> Self { - Self::ParseJsonInt(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonIntArrayCall) -> Self { - Self::ParseJsonIntArray(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonKeysCall) -> Self { - Self::ParseJsonKeys(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonStringCall) -> Self { - Self::ParseJsonString(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonStringArrayCall) -> Self { - Self::ParseJsonStringArray(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonUintCall) -> Self { - Self::ParseJsonUint(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseJsonUintArrayCall) -> Self { - Self::ParseJsonUintArray(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ParseUintCall) -> Self { - Self::ParseUint(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: PauseGasMeteringCall) -> Self { - Self::PauseGasMetering(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Prank0Call) -> Self { - Self::Prank0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Prank1Call) -> Self { - Self::Prank1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: PrevrandaoCall) -> Self { - Self::Prevrandao(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ProjectRootCall) -> Self { - Self::ProjectRoot(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadCallersCall) -> Self { - Self::ReadCallers(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadDir0Call) -> Self { - Self::ReadDir0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadDir1Call) -> Self { - Self::ReadDir1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadDir2Call) -> Self { - Self::ReadDir2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadFileCall) -> Self { - Self::ReadFile(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadFileBinaryCall) -> Self { - Self::ReadFileBinary(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadLineCall) -> Self { - Self::ReadLine(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ReadLinkCall) -> Self { - Self::ReadLink(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RecordCall) -> Self { - Self::Record(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RecordLogsCall) -> Self { - Self::RecordLogs(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RememberKeyCall) -> Self { - Self::RememberKey(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RemoveDirCall) -> Self { - Self::RemoveDir(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RemoveFileCall) -> Self { - Self::RemoveFile(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ResetNonceCall) -> Self { - Self::ResetNonce(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ResumeGasMeteringCall) -> Self { - Self::ResumeGasMetering(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RevertToCall) -> Self { - Self::RevertTo(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RevokePersistent0Call) -> Self { - Self::RevokePersistent0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RevokePersistent1Call) -> Self { - Self::RevokePersistent1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RollCall) -> Self { - Self::Roll(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RollFork0Call) -> Self { - Self::RollFork0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RollFork1Call) -> Self { - Self::RollFork1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RollFork2Call) -> Self { - Self::RollFork2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RollFork3Call) -> Self { - Self::RollFork3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RpcUrlCall) -> Self { - Self::RpcUrl(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RpcUrlStructsCall) -> Self { - Self::RpcUrlStructs(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: RpcUrlsCall) -> Self { - Self::RpcUrls(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SelectForkCall) -> Self { - Self::SelectFork(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeAddress0Call) -> Self { - Self::SerializeAddress0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeAddress1Call) -> Self { - Self::SerializeAddress1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeBool0Call) -> Self { - Self::SerializeBool0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeBool1Call) -> Self { - Self::SerializeBool1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeBytes0Call) -> Self { - Self::SerializeBytes0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeBytes1Call) -> Self { - Self::SerializeBytes1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeBytes320Call) -> Self { - Self::SerializeBytes320(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeBytes321Call) -> Self { - Self::SerializeBytes321(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeInt0Call) -> Self { - Self::SerializeInt0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeInt1Call) -> Self { - Self::SerializeInt1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeString0Call) -> Self { - Self::SerializeString0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeString1Call) -> Self { - Self::SerializeString1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeUint0Call) -> Self { - Self::SerializeUint0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SerializeUint1Call) -> Self { - Self::SerializeUint1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SetEnvCall) -> Self { - Self::SetEnv(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SetNonceCall) -> Self { - Self::SetNonce(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SetNonceUnsafeCall) -> Self { - Self::SetNonceUnsafe(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Sign0Call) -> Self { - Self::Sign0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Sign1Call) -> Self { - Self::Sign1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SkipCall) -> Self { - Self::Skip(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SleepCall) -> Self { - Self::Sleep(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: SnapshotCall) -> Self { - Self::Snapshot(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StartBroadcast0Call) -> Self { - Self::StartBroadcast0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StartBroadcast1Call) -> Self { - Self::StartBroadcast1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StartBroadcast2Call) -> Self { - Self::StartBroadcast2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StartMappingRecordingCall) -> Self { - Self::StartMappingRecording(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StartPrank0Call) -> Self { - Self::StartPrank0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StartPrank1Call) -> Self { - Self::StartPrank1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StopBroadcastCall) -> Self { - Self::StopBroadcast(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StopMappingRecordingCall) -> Self { - Self::StopMappingRecording(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StopPrankCall) -> Self { - Self::StopPrank(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: StoreCall) -> Self { - Self::Store(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ToString0Call) -> Self { - Self::ToString0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ToString1Call) -> Self { - Self::ToString1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ToString2Call) -> Self { - Self::ToString2(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ToString3Call) -> Self { - Self::ToString3(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ToString4Call) -> Self { - Self::ToString4(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: ToString5Call) -> Self { - Self::ToString5(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Transact0Call) -> Self { - Self::Transact0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: Transact1Call) -> Self { - Self::Transact1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: TxGasPriceCall) -> Self { - Self::TxGasPrice(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: WarpCall) -> Self { - Self::Warp(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: WriteFileCall) -> Self { - Self::WriteFile(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: WriteFileBinaryCall) -> Self { - Self::WriteFileBinary(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: WriteJson0Call) -> Self { - Self::WriteJson0(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: WriteJson1Call) -> Self { - Self::WriteJson1(value) - } - } - impl ::core::convert::From for HEVMCalls { - fn from(value: WriteLineCall) -> Self { - Self::WriteLine(value) - } - } - ///Container type for all return fields from the `accesses` function with signature `accesses(address)` and selector `0x65bc9481` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct AccessesReturn( - pub ::std::vec::Vec<[u8; 32]>, - pub ::std::vec::Vec<[u8; 32]>, - ); - ///Container type for all return fields from the `activeFork` function with signature `activeFork()` and selector `0x2f103f22` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ActiveForkReturn(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `addr` function with signature `addr(uint256)` and selector `0xffa18649` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct AddrReturn(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `createFork` function with signature `createFork(string,uint256)` and selector `0x6ba3ba2b` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateFork1Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `createFork` function with signature `createFork(string,bytes32)` and selector `0x7ca29682` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateFork2Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `createFork` function with signature `createFork(string)` and selector `0x31ba3498` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateFork0Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `createSelectFork` function with signature `createSelectFork(string,uint256)` and selector `0x71ee464d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateSelectFork1Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `createSelectFork` function with signature `createSelectFork(string,bytes32)` and selector `0x84d52b7a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateSelectFork2Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `createSelectFork` function with signature `createSelectFork(string)` and selector `0x98680034` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateSelectFork0Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `createWallet` function with signature `createWallet(string)` and selector `0x7404f1d2` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateWallet0Return( - pub ( - ::ethers_core::types::Address, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - ); - ///Container type for all return fields from the `createWallet` function with signature `createWallet(uint256)` and selector `0x7a675bb6` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateWallet1Return( - pub ( - ::ethers_core::types::Address, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - ); - ///Container type for all return fields from the `createWallet` function with signature `createWallet(uint256,string)` and selector `0xed7c5462` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct CreateWallet2Return( - pub ( - ::ethers_core::types::Address, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - ); - ///Container type for all return fields from the `deriveKey` function with signature `deriveKey(string,uint32)` and selector `0x6229498b` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DeriveKey0Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `deriveKey` function with signature `deriveKey(string,string,uint32)` and selector `0x6bcb2c1b` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DeriveKey1Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `deriveKey` function with signature `deriveKey(string,uint32,string)` and selector `0x32c8176d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DeriveKey2Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `deriveKey` function with signature `deriveKey(string,string,uint32,string)` and selector `0x29233b1f` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DeriveKey3Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `envAddress` function with signature `envAddress(string)` and selector `0x350d56bf` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvAddress0Return(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `envAddress` function with signature `envAddress(string,string)` and selector `0xad31b9fa` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvAddress1Return(pub ::std::vec::Vec<::ethers_core::types::Address>); - ///Container type for all return fields from the `envBool` function with signature `envBool(string)` and selector `0x7ed1ec7d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvBool0Return(pub bool); - ///Container type for all return fields from the `envBool` function with signature `envBool(string,string)` and selector `0xaaaddeaf` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvBool1Return(pub ::std::vec::Vec); - ///Container type for all return fields from the `envBytes` function with signature `envBytes(string)` and selector `0x4d7baf06` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvBytes0Return(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `envBytes` function with signature `envBytes(string,string)` and selector `0xddc2651b` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvBytes1Return(pub ::std::vec::Vec<::ethers_core::types::Bytes>); - ///Container type for all return fields from the `envBytes32` function with signature `envBytes32(string)` and selector `0x97949042` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvBytes320Return(pub [u8; 32]); - ///Container type for all return fields from the `envBytes32` function with signature `envBytes32(string,string)` and selector `0x5af231c1` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvBytes321Return(pub ::std::vec::Vec<[u8; 32]>); - ///Container type for all return fields from the `envInt` function with signature `envInt(string)` and selector `0x892a0c61` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvInt0Return(pub ::ethers_core::types::I256); - ///Container type for all return fields from the `envInt` function with signature `envInt(string,string)` and selector `0x42181150` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvInt1Return(pub ::std::vec::Vec<::ethers_core::types::I256>); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,bool)` and selector `0x4777f3cf` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr0Return(pub bool); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,uint256)` and selector `0x5e97348f` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr1Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,int256)` and selector `0xbbcb713e` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr2Return(pub ::ethers_core::types::I256); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,address)` and selector `0x561fe540` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr3Return(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,bytes32)` and selector `0xb4a85892` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr4Return(pub [u8; 32]); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string)` and selector `0xd145736c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr5Return(pub ::std::string::String); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,bytes)` and selector `0xb3e47705` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr6Return(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,bool[])` and selector `0xeb85e83b` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr7Return(pub ::std::vec::Vec); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,uint256[])` and selector `0x74318528` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr8Return(pub ::std::vec::Vec<::ethers_core::types::U256>); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,int256[])` and selector `0x4700d74b` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr9Return(pub ::std::vec::Vec<::ethers_core::types::I256>); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,address[])` and selector `0xc74e9deb` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr10Return(pub ::std::vec::Vec<::ethers_core::types::Address>); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,bytes32[])` and selector `0x2281f367` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr11Return(pub ::std::vec::Vec<[u8; 32]>); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,string[])` and selector `0x859216bc` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr12Return(pub ::std::vec::Vec<::std::string::String>); - ///Container type for all return fields from the `envOr` function with signature `envOr(string,string,bytes[])` and selector `0x64bc3e64` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvOr13Return(pub ::std::vec::Vec<::ethers_core::types::Bytes>); - ///Container type for all return fields from the `envString` function with signature `envString(string)` and selector `0xf877cb19` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvString0Return(pub ::std::string::String); - ///Container type for all return fields from the `envString` function with signature `envString(string,string)` and selector `0x14b02bc9` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvString1Return(pub ::std::vec::Vec<::std::string::String>); - ///Container type for all return fields from the `envUint` function with signature `envUint(string)` and selector `0xc1978d1f` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvUint0Return(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `envUint` function with signature `envUint(string,string)` and selector `0xf3dec099` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EnvUint1Return(pub ::std::vec::Vec<::ethers_core::types::U256>); - ///Container type for all return fields from the `ffi` function with signature `ffi(string[])` and selector `0x89160467` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct FfiReturn(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `fsMetadata` function with signature `fsMetadata(string)` and selector `0xaf368a08` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct FsMetadataReturn( - pub ( - bool, - bool, - ::ethers_core::types::U256, - bool, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ::ethers_core::types::U256, - ), - ); - ///Container type for all return fields from the `getLabel` function with signature `getLabel(address)` and selector `0x28a249b0` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct GetLabelReturn(pub ::std::string::String); - ///Container type for all return fields from the `getNonce` function with signature `getNonce((address,uint256,uint256,uint256))` and selector `0xa5748aad` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct GetNonce0Return(pub u64); - ///Container type for all return fields from the `getRecordedLogs` function with signature `getRecordedLogs()` and selector `0x191553a4` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct GetRecordedLogsReturn( - pub ::std::vec::Vec<(::std::vec::Vec<[u8; 32]>, ::ethers_core::types::Bytes)>, - ); - ///Container type for all return fields from the `isPersistent` function with signature `isPersistent(address)` and selector `0xd92d8efd` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct IsPersistentReturn(pub bool); - ///Container type for all return fields from the `keyExists` function with signature `keyExists(string,string)` and selector `0x528a683c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct KeyExistsReturn(pub bool); - ///Container type for all return fields from the `load` function with signature `load(address,bytes32)` and selector `0x667f9d70` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct LoadReturn(pub [u8; 32]); - ///Container type for all return fields from the `parseAddress` function with signature `parseAddress(string)` and selector `0xc6ce059d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseAddressReturn(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `parseBool` function with signature `parseBool(string)` and selector `0x974ef924` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseBoolReturn(pub bool); - ///Container type for all return fields from the `parseBytes` function with signature `parseBytes(string)` and selector `0x8f5d232d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseBytesReturn(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `parseBytes32` function with signature `parseBytes32(string)` and selector `0x087e6e81` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseBytes32Return(pub [u8; 32]); - ///Container type for all return fields from the `parseInt` function with signature `parseInt(string)` and selector `0x42346c5e` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseIntReturn(pub ::ethers_core::types::I256); - ///Container type for all return fields from the `parseJson` function with signature `parseJson(string)` and selector `0x6a82600a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJson0Return(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `parseJson` function with signature `parseJson(string,string)` and selector `0x85940ef1` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJson1Return(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `parseJsonAddress` function with signature `parseJsonAddress(string,string)` and selector `0x1e19e657` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonAddressReturn(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `parseJsonAddressArray` function with signature `parseJsonAddressArray(string,string)` and selector `0x2fce7883` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonAddressArrayReturn( - pub ::std::vec::Vec<::ethers_core::types::Address>, - ); - ///Container type for all return fields from the `parseJsonBool` function with signature `parseJsonBool(string,string)` and selector `0x9f86dc91` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonBoolReturn(pub bool); - ///Container type for all return fields from the `parseJsonBoolArray` function with signature `parseJsonBoolArray(string,string)` and selector `0x91f3b94f` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonBoolArrayReturn(pub ::std::vec::Vec); - ///Container type for all return fields from the `parseJsonBytes` function with signature `parseJsonBytes(string,string)` and selector `0xfd921be8` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonBytesReturn(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `parseJsonBytes32` function with signature `parseJsonBytes32(string,string)` and selector `0x1777e59d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonBytes32Return(pub [u8; 32]); - ///Container type for all return fields from the `parseJsonBytes32Array` function with signature `parseJsonBytes32Array(string,string)` and selector `0x91c75bc3` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonBytes32ArrayReturn(pub ::std::vec::Vec<[u8; 32]>); - ///Container type for all return fields from the `parseJsonBytesArray` function with signature `parseJsonBytesArray(string,string)` and selector `0x6631aa99` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonBytesArrayReturn( - pub ::std::vec::Vec<::ethers_core::types::Bytes>, - ); - ///Container type for all return fields from the `parseJsonInt` function with signature `parseJsonInt(string,string)` and selector `0x7b048ccd` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonIntReturn(pub ::ethers_core::types::I256); - ///Container type for all return fields from the `parseJsonIntArray` function with signature `parseJsonIntArray(string,string)` and selector `0x9983c28a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonIntArrayReturn(pub ::std::vec::Vec<::ethers_core::types::I256>); - ///Container type for all return fields from the `parseJsonKeys` function with signature `parseJsonKeys(string,string)` and selector `0x213e4198` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonKeysReturn(pub ::std::vec::Vec<::std::string::String>); - ///Container type for all return fields from the `parseJsonString` function with signature `parseJsonString(string,string)` and selector `0x49c4fac8` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonStringReturn(pub ::std::string::String); - ///Container type for all return fields from the `parseJsonStringArray` function with signature `parseJsonStringArray(string,string)` and selector `0x498fdcf4` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonStringArrayReturn(pub ::std::vec::Vec<::std::string::String>); - ///Container type for all return fields from the `parseJsonUint` function with signature `parseJsonUint(string,string)` and selector `0xaddde2b6` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonUintReturn(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `parseJsonUintArray` function with signature `parseJsonUintArray(string,string)` and selector `0x522074ab` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseJsonUintArrayReturn(pub ::std::vec::Vec<::ethers_core::types::U256>); - ///Container type for all return fields from the `parseUint` function with signature `parseUint(string)` and selector `0xfa91454d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ParseUintReturn(pub ::ethers_core::types::U256); - ///Container type for all return fields from the `projectRoot` function with signature `projectRoot()` and selector `0xd930a0e6` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ProjectRootReturn(pub ::std::string::String); - ///Container type for all return fields from the `readCallers` function with signature `readCallers()` and selector `0x4ad0bac9` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadCallersReturn( - pub ::ethers_core::types::U256, - pub ::ethers_core::types::Address, - pub ::ethers_core::types::Address, - ); - ///Container type for all return fields from the `readDir` function with signature `readDir(string)` and selector `0xc4bc59e0` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadDir0Return( - pub ::std::vec::Vec< - (::std::string::String, ::std::string::String, u64, bool, bool), - >, - ); - ///Container type for all return fields from the `readDir` function with signature `readDir(string,uint64)` and selector `0x1497876c` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadDir1Return( - pub ::std::vec::Vec< - (::std::string::String, ::std::string::String, u64, bool, bool), - >, - ); - ///Container type for all return fields from the `readDir` function with signature `readDir(string,uint64,bool)` and selector `0x8102d70d` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadDir2Return( - pub ::std::vec::Vec< - (::std::string::String, ::std::string::String, u64, bool, bool), - >, - ); - ///Container type for all return fields from the `readFile` function with signature `readFile(string)` and selector `0x60f9bb11` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadFileReturn(pub ::std::string::String); - ///Container type for all return fields from the `readFileBinary` function with signature `readFileBinary(string)` and selector `0x16ed7bc4` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadFileBinaryReturn(pub ::ethers_core::types::Bytes); - ///Container type for all return fields from the `readLine` function with signature `readLine(string)` and selector `0x70f55728` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadLineReturn(pub ::std::string::String); - ///Container type for all return fields from the `readLink` function with signature `readLink(string)` and selector `0x9f5684a2` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ReadLinkReturn(pub ::std::string::String); - ///Container type for all return fields from the `rememberKey` function with signature `rememberKey(uint256)` and selector `0x22100064` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct RememberKeyReturn(pub ::ethers_core::types::Address); - ///Container type for all return fields from the `revertTo` function with signature `revertTo(uint256)` and selector `0x44d7f0a4` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct RevertToReturn(pub bool); - ///Container type for all return fields from the `rpcUrl` function with signature `rpcUrl(string)` and selector `0x975a6ce9` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct RpcUrlReturn(pub ::std::string::String); - ///Container type for all return fields from the `rpcUrlStructs` function with signature `rpcUrlStructs()` and selector `0x9d2ad72a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct RpcUrlStructsReturn( - pub ::std::vec::Vec<(::std::string::String, ::std::string::String)>, - ); - ///Container type for all return fields from the `rpcUrls` function with signature `rpcUrls()` and selector `0xa85a8418` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct RpcUrlsReturn(pub ::std::vec::Vec<[::std::string::String; 2]>); - ///Container type for all return fields from the `serializeAddress` function with signature `serializeAddress(string,string,address)` and selector `0x972c6062` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeAddress0Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeAddress` function with signature `serializeAddress(string,string,address[])` and selector `0x1e356e1a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeAddress1Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeBool` function with signature `serializeBool(string,string,bool)` and selector `0xac22e971` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeBool0Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeBool` function with signature `serializeBool(string,string,bool[])` and selector `0x92925aa1` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeBool1Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeBytes` function with signature `serializeBytes(string,string,bytes)` and selector `0xf21d52c7` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeBytes0Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeBytes` function with signature `serializeBytes(string,string,bytes[])` and selector `0x9884b232` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeBytes1Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeBytes32` function with signature `serializeBytes32(string,string,bytes32)` and selector `0x2d812b44` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeBytes320Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeBytes32` function with signature `serializeBytes32(string,string,bytes32[])` and selector `0x201e43e2` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeBytes321Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeInt` function with signature `serializeInt(string,string,int256)` and selector `0x3f33db60` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeInt0Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeInt` function with signature `serializeInt(string,string,int256[])` and selector `0x7676e127` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeInt1Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeString` function with signature `serializeString(string,string,string)` and selector `0x88da6d35` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeString0Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeString` function with signature `serializeString(string,string,string[])` and selector `0x561cd6f3` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeString1Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeUint` function with signature `serializeUint(string,string,uint256)` and selector `0x129e9002` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeUint0Return(pub ::std::string::String); - ///Container type for all return fields from the `serializeUint` function with signature `serializeUint(string,string,uint256[])` and selector `0xfee9a469` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SerializeUint1Return(pub ::std::string::String); - ///Container type for all return fields from the `sign` function with signature `sign(uint256,bytes32)` and selector `0xe341eaa4` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct Sign0Return(pub u8, pub [u8; 32], pub [u8; 32]); - ///Container type for all return fields from the `sign` function with signature `sign((address,uint256,uint256,uint256),bytes32)` and selector `0xb25c5a25` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct Sign1Return(pub u8, pub [u8; 32], pub [u8; 32]); - ///Container type for all return fields from the `snapshot` function with signature `snapshot()` and selector `0x9711715a` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct SnapshotReturn(pub ::ethers_core::types::U256); - ///`DirEntry(string,string,uint64,bool,bool)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DirEntry { - pub error_message: ::std::string::String, - pub path: ::std::string::String, - pub depth: u64, - pub is_dir: bool, - pub is_symlink: bool, - } - ///`FsMetadata(bool,bool,uint256,bool,uint256,uint256,uint256)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct FsMetadata { - pub is_dir: bool, - pub is_symlink: bool, - pub length: ::ethers_core::types::U256, - pub read_only: bool, - pub modified: ::ethers_core::types::U256, - pub accessed: ::ethers_core::types::U256, - pub created: ::ethers_core::types::U256, - } - ///`Log(bytes32[],bytes)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct Log { - pub topics: ::std::vec::Vec<[u8; 32]>, - pub data: ::ethers_core::types::Bytes, - } - ///`Rpc(string,string)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct Rpc { - pub name: ::std::string::String, - pub url: ::std::string::String, - } - ///`Wallet(address,uint256,uint256,uint256)` - #[derive( - Clone, - ::ethers_contract::EthAbiType, - ::ethers_contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct Wallet { - pub addr: ::ethers_core::types::Address, - pub public_key_x: ::ethers_core::types::U256, - pub public_key_y: ::ethers_core::types::U256, - pub private_key: ::ethers_core::types::U256, - } -} diff --git a/crates/abi/src/bindings/mod.rs b/crates/abi/src/bindings/mod.rs deleted file mode 100644 index 52aa1655ecca7..0000000000000 --- a/crates/abi/src/bindings/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -#![allow(clippy::all)] -//! This module contains abigen! generated bindings for solidity contracts. -//! This is autogenerated code. -//! Do not manually edit these files. -//! These files may be overwritten by the codegen system at any time. -pub mod console; -pub mod hardhat_console; -pub mod hevm; diff --git a/crates/abi/src/lib.rs b/crates/abi/src/lib.rs deleted file mode 100644 index 16dcdd1365185..0000000000000 --- a/crates/abi/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Foundry's Solidity ABI bindings. -//! -//! Automatically generated by [abigen](ethers_contract::abigen). - -#![warn(unused_crate_dependencies)] - -mod bindings; - -pub use bindings::{console, hardhat_console, hevm}; diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index 9d7a24ba379ba..4b6356d981dae 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -10,74 +10,118 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "anvil" path = "src/anvil.rs" required-features = ["cli"] -[build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } - [dependencies] # foundry internal -anvil-core = { path = "core", features = ["fastrlp", "serde", "impersonated-tx"] } +anvil-core = { path = "core", features = ["serde", "impersonated-tx"] } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } +foundry-cli.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true -foundry-utils.workspace = true # evm support -bytes = "1.4.0" -ethers = { workspace = true, features = ["rustls", "ws", "ipc"] } -trie-db = { version = "0.23" } -hash-db = { version = "0.15" } -memory-db = { version = "0.29" } +bytes.workspace = true +k256.workspace = true +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } +alloy-primitives = { workspace = true, features = ["serde"] } +alloy-consensus = { workspace = true, features = ["k256", "kzg"] } +alloy-contract = { workspace = true, features = ["pubsub"] } +alloy-network.workspace = true +alloy-eips.workspace = true +alloy-rlp.workspace = true +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 = ["anvil", "trace", "txpool"] } +alloy-serde.workspace = true +alloy-provider = { workspace = true, features = [ + "reqwest", + "ws", + "ipc", + "debug-api", + "trace-api", +] } +alloy-transport.workspace = true +alloy-chains.workspace = true +alloy-genesis.workspace = true +alloy-trie.workspace = true +op-alloy-consensus = { workspace = true, features = ["serde"] } # axum related -axum = { version = "0.5", features = ["ws"] } -hyper = "0.14" -tower = "0.4" -tower-http = { version = "0.4", features = ["trace"] } +axum.workspace = true +hyper.workspace = true +tower.workspace = true # tracing -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } +tracing.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter"] } # async -tokio = { version = "1", features = ["time"] } -parking_lot = "0.12" -futures = "0.3" -async-trait = "0.1" +tokio = { workspace = true, features = ["time"] } +parking_lot.workspace = true +futures.workspace = true +async-trait.workspace = true # misc flate2 = "1.0" -serde_json = "1" -serde = { version = "1", features = ["derive"] } -thiserror = "1" -yansi = "0.5" -tempfile = "3" +serde_repr = "0.1" +serde_json.workspace = true +serde.workspace = true +thiserror.workspace = true +yansi.workspace = true +tempfile.workspace = true itertools.workspace = true +rand.workspace = true +eyre.workspace = true # cli -clap = { version = "4", features = ["derive", "env", "wrap_help"], optional = true } +clap = { version = "4", features = [ + "derive", + "env", + "wrap_help", +], optional = true } clap_complete = { version = "4", optional = true } chrono.workspace = true -auto_impl = "1" ctrlc = { version = "3", optional = true } -fdlimit = { version = "0.2", optional = true } +fdlimit = { version = "0.3", optional = true } clap_complete_fig = "4" -ethereum-forkid = "0.12" + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] -ethers = { workspace = true, features = ["abigen"] } -ethers-solc = { workspace = true, features = ["project-util", "full"] } -pretty_assertions = "1.3.0" -tokio = { version = "1", features = ["full"] } -crc = "3.0.1" +alloy-json-abi.workspace = true +alloy-rpc-client = { workspace = true, features = ["pubsub"] } +alloy-transport-ipc = { workspace = true, features = ["mock"] } +alloy-provider = { workspace = true, features = ["txpool-api"] } +alloy-transport-ws.workspace = true +alloy-json-rpc.workspace = true +alloy-pubsub.workspace = true +foundry-test-utils.workspace = true +similar-asserts.workspace = true +tokio = { workspace = true, features = ["full"] } + +op-alloy-rpc-types.workspace = true + [features] -default = ["cli"] +default = ["cli", "jemalloc"] cmd = ["clap", "clap_complete", "ctrlc", "anvil-server/clap"] cli = ["tokio/full", "cmd", "fdlimit"] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] diff --git a/crates/anvil/README.md b/crates/anvil/README.md deleted file mode 100644 index 48e42c24412a3..0000000000000 --- a/crates/anvil/README.md +++ /dev/null @@ -1,96 +0,0 @@ -## Anvil - -A local Ethereum node, akin to Ganache, designed for development with [**Forge**](../../bin/forge). - -## Features - -- Network forking: fork any EVM-compatible blockchain, same as in `forge` -- [Ethereum JSON-RPC](https://eth.wiki/json-rpc/API) support -- Additional JSON-RPC endpoints, compatible with ganache and hardhat - - snapshot/revert state - - mining modes: auto, interval, manual, none - - ... - -## Installation - -`anvil` binary is available via [`foundryup`](../../README.md#installation). - -### Installing from source - -```sh -git clone https://github.com/foundry-rs/foundry -cd foundry -cargo install --path ./anvil --bins --locked --force -``` - -## Getting started - -```console -$ anvil - - _ _ - (_) | | - __ _ _ __ __ __ _ | | - / _` | | '_ \ \ \ / / | | | | - | (_| | | | | | \ V / | | | | - \__,_| |_| |_| \_/ |_| |_| - - 0.1.0 (8d507b4 2023-08-05T00:20:34.048397801Z) - https://github.com/foundry-rs/foundry - -Available Accounts -================== - -(0) "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" (10000.000000000000000000 ETH) -(1) "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" (10000.000000000000000000 ETH) -(2) "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" (10000.000000000000000000 ETH) -(3) "0x90F79bf6EB2c4f870365E785982E1f101E93b906" (10000.000000000000000000 ETH) -(4) "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65" (10000.000000000000000000 ETH) -(5) "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc" (10000.000000000000000000 ETH) -(6) "0x976EA74026E726554dB657fA54763abd0C3a0aa9" (10000.000000000000000000 ETH) -(7) "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955" (10000.000000000000000000 ETH) -(8) "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f" (10000.000000000000000000 ETH) -(9) "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720" (10000.000000000000000000 ETH) - -Private Keys -================== - -(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d -(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a -(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 -(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a -(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba -(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e -(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356 -(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97 -(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6 - -Wallet -================== -Mnemonic: test test test test test test test test test test test junk -Derivation path: m/44'/60'/0'/0/ - - -Chain ID -================== - -31337 - -Base Fee -================== - -1000000000 - -Gas Limit -================== - -30000000 - -Genesis Timestamp -================== - -1692087429 - -Listening on 127.0.0.1:8545 -``` diff --git a/crates/anvil/build.rs b/crates/anvil/build.rs deleted file mode 100644 index c2f550fb6f829..0000000000000 --- a/crates/anvil/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index 2bf66e8d9756b..dacc7f20e0ea3 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -9,29 +9,38 @@ license.workspace = true homepage.workspace = true repository.workspace = true -[dependencies] -# foundry internal -foundry-evm = { path = "../../evm" } -revm = { version = "3", default-features = false, features = ["std", "serde", "memory_limit"] } +[lints] +workspace = true -ethers-core.workspace = true -serde = { version = "1", features = ["derive"], optional = true } -serde_json = "1" -bytes = { version = "1.4" } -open-fastrlp = { version = "0.1.4", optional = true } +[dependencies] +foundry-common.workspace = true +foundry-evm.workspace = true +revm = { workspace = true, default-features = false, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } -# trie -hash-db = { version = "0.15", default-features = false } -hash256-std-hasher = { version = "0.15", default-features = false } -triehash = { version = "0.8", default-features = false } -reference-trie = { version = "0.25" } -keccak-hasher = { version = "0.15" } +alloy-primitives = { workspace = true, features = ["serde", "rlp"] } +alloy-rpc-types = { workspace = true, features = ["anvil", "trace"] } +alloy-serde.workspace = true +alloy-rlp.workspace = true +alloy-eips.workspace = true +alloy-consensus = { workspace = true, features = ["k256", "kzg"] } +alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } +alloy-trie.workspace = true +op-alloy-consensus = { workspace = true, features = ["serde"] } +alloy-network.workspace = true +serde = { workspace = true, optional = true } +serde_json.workspace = true +bytes.workspace = true -[dev-dependencies] -serde = { version = "1.0", features = ["derive"] } +# misc +rand.workspace = true +thiserror.workspace = true [features] -default = [] +default = ["serde"] impersonated-tx = [] -fastrlp = ["dep:open-fastrlp"] serde = ["dep:serde"] diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index 7ad94455d5c0e..c9f9048b81998 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -1,335 +1,137 @@ -use crate::eth::{receipt::TypedReceipt, transaction::TransactionInfo, trie}; -use ethers_core::{ - types::{Address, Bloom, Bytes, H256, H64, U256}, - utils::{ - keccak256, rlp, - rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}, - }, +use super::{ + transaction::{TransactionInfo, TypedReceipt}, + trie, }; +use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256}; +use alloy_rlp::{RlpDecodable, RlpEncodable}; + +// Type alias to optionally support impersonated transactions +#[cfg(not(feature = "impersonated-tx"))] +type Transaction = crate::eth::transaction::TypedTransaction; +#[cfg(feature = "impersonated-tx")] +type Transaction = crate::eth::transaction::MaybeImpersonatedTransaction; /// Container type that gathers all block data -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct BlockInfo { pub block: Block, pub transactions: Vec, pub receipts: Vec, } -// Type alias to optionally support impersonated transactions -#[cfg(not(feature = "impersonated-tx"))] -type Transaction = crate::eth::transaction::TypedTransaction; -#[cfg(feature = "impersonated-tx")] -type Transaction = crate::eth::transaction::MaybeImpersonatedTransaction; - -/// An Ethereum block -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +/// An Ethereum Block +#[derive(Clone, Debug, PartialEq, Eq, RlpEncodable, RlpDecodable)] pub struct Block { pub header: Header, - /// Note: this supports impersonated transactions pub transactions: Vec, pub ommers: Vec
, } -// == impl Block == - impl Block { - /// Creates a new block + /// Creates a new block. /// - /// Note: if the `impersonate-tx` feature is enabled this will also accept - /// [MaybeImpersonatedTransaction] - pub fn new( - partial_header: PartialHeader, - transactions: impl IntoIterator, - ommers: Vec
, - ) -> Self + /// Note: if the `impersonate-tx` feature is enabled this will also accept + /// `MaybeImpersonatedTransaction`. + pub fn new(partial_header: PartialHeader, transactions: impl IntoIterator) -> Self where T: Into, { let transactions: Vec<_> = transactions.into_iter().map(Into::into).collect(); - let ommers_hash = H256::from_slice(keccak256(&rlp::encode_list(&ommers)[..]).as_slice()); let transactions_root = - trie::ordered_trie_root(transactions.iter().map(|r| rlp::encode(r).freeze())); + trie::ordered_trie_root(transactions.iter().map(|r| r.encoded_2718())); Self { - header: Header::new(partial_header, ommers_hash, transactions_root), - transactions, - ommers, - } - } -} - -impl Encodable for Block { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(3); - s.append(&self.header); - s.append_list(&self.transactions); - s.append_list(&self.ommers); - } -} - -impl Decodable for Block { - fn decode(rlp: &Rlp) -> Result { - Ok(Self { header: rlp.val_at(0)?, transactions: rlp.list_at(1)?, ommers: rlp.list_at(2)? }) - } -} - -/// ethereum block header -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct Header { - pub parent_hash: H256, - pub ommers_hash: H256, - pub beneficiary: Address, - pub state_root: H256, - pub transactions_root: H256, - pub receipts_root: H256, - pub logs_bloom: Bloom, - pub difficulty: U256, - pub number: U256, - pub gas_limit: U256, - pub gas_used: U256, - pub timestamp: u64, - pub extra_data: Bytes, - pub mix_hash: H256, - pub nonce: H64, - /// BaseFee was added by EIP-1559 and is ignored in legacy headers. - pub base_fee_per_gas: Option, -} - -// == impl Header == - -impl Header { - pub fn new(partial_header: PartialHeader, ommers_hash: H256, transactions_root: H256) -> Self { - Self { - parent_hash: partial_header.parent_hash, - ommers_hash, - beneficiary: partial_header.beneficiary, - state_root: partial_header.state_root, - transactions_root, - receipts_root: partial_header.receipts_root, - logs_bloom: partial_header.logs_bloom, - difficulty: partial_header.difficulty, - number: partial_header.number, - gas_limit: partial_header.gas_limit, - gas_used: partial_header.gas_used, - timestamp: partial_header.timestamp, - extra_data: partial_header.extra_data, - mix_hash: partial_header.mix_hash, - nonce: partial_header.nonce, - base_fee_per_gas: partial_header.base_fee, - } - } - - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) - } - - /// Returns the rlp length of the Header body, _not including_ trailing EIP155 fields or the - /// rlp list header - /// To get the length including the rlp list header, refer to the Encodable implementation. - #[cfg(feature = "fastrlp")] - pub(crate) fn header_payload_length(&self) -> usize { - use open_fastrlp::Encodable; - - let mut length = 0; - length += self.parent_hash.length(); - length += self.ommers_hash.length(); - length += self.beneficiary.length(); - length += self.state_root.length(); - length += self.transactions_root.length(); - length += self.receipts_root.length(); - length += self.logs_bloom.length(); - length += self.difficulty.length(); - length += self.number.length(); - length += self.gas_limit.length(); - length += self.gas_used.length(); - length += self.timestamp.length(); - length += self.extra_data.length(); - length += self.mix_hash.length(); - length += self.nonce.length(); - length += self.base_fee_per_gas.map(|fee| fee.length()).unwrap_or_default(); - length - } -} - -impl rlp::Encodable for Header { - fn rlp_append(&self, s: &mut rlp::RlpStream) { - if self.base_fee_per_gas.is_none() { - s.begin_list(15); - } else { - s.begin_list(16); - } - s.append(&self.parent_hash); - s.append(&self.ommers_hash); - s.append(&self.beneficiary); - s.append(&self.state_root); - s.append(&self.transactions_root); - s.append(&self.receipts_root); - s.append(&self.logs_bloom); - s.append(&self.difficulty); - s.append(&self.number); - s.append(&self.gas_limit); - s.append(&self.gas_used); - s.append(&self.timestamp); - s.append(&self.extra_data.as_ref()); - s.append(&self.mix_hash); - s.append(&self.nonce); - if let Some(ref base_fee) = self.base_fee_per_gas { - s.append(base_fee); - } - } -} - -impl rlp::Decodable for Header { - fn decode(rlp: &rlp::Rlp) -> Result { - let result = Header { - parent_hash: rlp.val_at(0)?, - ommers_hash: rlp.val_at(1)?, - beneficiary: rlp.val_at(2)?, - state_root: rlp.val_at(3)?, - transactions_root: rlp.val_at(4)?, - receipts_root: rlp.val_at(5)?, - logs_bloom: rlp.val_at(6)?, - difficulty: rlp.val_at(7)?, - number: rlp.val_at(8)?, - gas_limit: rlp.val_at(9)?, - gas_used: rlp.val_at(10)?, - timestamp: rlp.val_at(11)?, - extra_data: rlp.val_at::>(12)?.into(), - mix_hash: rlp.val_at(13)?, - nonce: rlp.val_at(14)?, - base_fee_per_gas: if let Ok(base_fee) = rlp.at(15) { - Some(::decode(&base_fee)?) - } else { - None + header: Header { + parent_hash: partial_header.parent_hash, + beneficiary: partial_header.beneficiary, + ommers_hash: EMPTY_OMMER_ROOT_HASH, + state_root: partial_header.state_root, + transactions_root, + receipts_root: partial_header.receipts_root, + logs_bloom: partial_header.logs_bloom, + difficulty: partial_header.difficulty, + number: partial_header.number, + gas_limit: partial_header.gas_limit, + gas_used: partial_header.gas_used, + timestamp: partial_header.timestamp, + extra_data: partial_header.extra_data, + mix_hash: partial_header.mix_hash, + withdrawals_root: partial_header.withdrawals_root, + blob_gas_used: partial_header.blob_gas_used, + excess_blob_gas: partial_header.excess_blob_gas, + parent_beacon_block_root: partial_header.parent_beacon_block_root, + nonce: partial_header.nonce, + base_fee_per_gas: partial_header.base_fee, + requests_hash: partial_header.requests_hash, }, - }; - Ok(result) - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for Header { - fn length(&self) -> usize { - // add each of the fields' rlp encoded lengths - let mut length = 0; - length += self.header_payload_length(); - length += open_fastrlp::length_of_length(length); - - length - } - - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - let list_header = - open_fastrlp::Header { list: true, payload_length: self.header_payload_length() }; - list_header.encode(out); - self.parent_hash.encode(out); - self.ommers_hash.encode(out); - self.beneficiary.encode(out); - self.state_root.encode(out); - self.transactions_root.encode(out); - self.receipts_root.encode(out); - self.logs_bloom.encode(out); - self.difficulty.encode(out); - self.number.encode(out); - self.gas_limit.encode(out); - self.gas_used.encode(out); - self.timestamp.encode(out); - self.extra_data.encode(out); - self.mix_hash.encode(out); - self.nonce.encode(out); - if let Some(base_fee_per_gas) = self.base_fee_per_gas { - base_fee_per_gas.encode(out); + transactions, + ommers: vec![], } } } -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for Header { - fn decode(buf: &mut &[u8]) -> Result { - // slice out the rlp list header - let header = open_fastrlp::Header::decode(buf)?; - let start_len = buf.len(); - - Ok(Header { - parent_hash: ::decode(buf)?, - ommers_hash: ::decode(buf)?, - beneficiary:
::decode(buf)?, - state_root: ::decode(buf)?, - transactions_root: ::decode(buf)?, - receipts_root: ::decode(buf)?, - logs_bloom: ::decode(buf)?, - difficulty: ::decode(buf)?, - number: ::decode(buf)?, - gas_limit: ::decode(buf)?, - gas_used: ::decode(buf)?, - timestamp: ::decode(buf)?, - extra_data: ::decode(buf)?, - mix_hash: ::decode(buf)?, - nonce: ::decode(buf)?, - base_fee_per_gas: if start_len - header.payload_length < buf.len() { - // if there is leftover data in the payload, decode the base fee - Some(::decode(buf)?) - } else { - None - }, - }) - } -} - /// Partial header definition without ommers hash and transactions root -#[derive(Clone, Debug, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct PartialHeader { - pub parent_hash: H256, + pub parent_hash: B256, pub beneficiary: Address, - pub state_root: H256, - pub receipts_root: H256, + pub state_root: B256, + pub receipts_root: B256, pub logs_bloom: Bloom, pub difficulty: U256, - pub number: U256, - pub gas_limit: U256, - pub gas_used: U256, + pub number: u64, + pub gas_limit: u64, + pub gas_used: u64, pub timestamp: u64, pub extra_data: Bytes, - pub mix_hash: H256, - pub nonce: H64, - pub base_fee: Option, + pub mix_hash: B256, + pub blob_gas_used: Option, + pub excess_blob_gas: Option, + pub parent_beacon_block_root: Option, + pub nonce: B64, + pub base_fee: Option, + pub withdrawals_root: Option, + pub requests_hash: Option, } impl From
for PartialHeader { - fn from(header: Header) -> PartialHeader { + fn from(value: Header) -> Self { Self { - parent_hash: header.parent_hash, - beneficiary: header.beneficiary, - state_root: header.state_root, - receipts_root: header.receipts_root, - logs_bloom: header.logs_bloom, - difficulty: header.difficulty, - number: header.number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - extra_data: header.extra_data, - mix_hash: header.mix_hash, - nonce: header.nonce, - base_fee: header.base_fee_per_gas, + parent_hash: value.parent_hash, + beneficiary: value.beneficiary, + state_root: value.state_root, + receipts_root: value.receipts_root, + logs_bloom: value.logs_bloom, + difficulty: value.difficulty, + number: value.number, + gas_limit: value.gas_limit, + gas_used: value.gas_used, + timestamp: value.timestamp, + extra_data: value.extra_data, + mix_hash: value.mix_hash, + nonce: value.nonce, + base_fee: value.base_fee_per_gas, + blob_gas_used: value.blob_gas_used, + excess_blob_gas: value.excess_blob_gas, + parent_beacon_block_root: value.parent_beacon_block_root, + requests_hash: value.requests_hash, + withdrawals_root: value.withdrawals_root, } } } #[cfg(test)] mod tests { - use std::str::FromStr; - - use ethers_core::{ - types::H160, - utils::{hex, hex::FromHex}, + use alloy_primitives::{ + b256, + hex::{self, FromHex}, }; + use alloy_rlp::Decodable; use super::*; + use std::str::FromStr; #[test] fn header_rlp_roundtrip() { @@ -342,158 +144,137 @@ mod tests { receipts_root: Default::default(), logs_bloom: Default::default(), difficulty: Default::default(), - number: 124u64.into(), + number: 124u64, gas_limit: Default::default(), - gas_used: 1337u64.into(), + gas_used: 1337u64, timestamp: 0, extra_data: Default::default(), mix_hash: Default::default(), - nonce: 99u64.to_be_bytes().into(), + nonce: B64::with_last_byte(99), + withdrawals_root: Default::default(), + blob_gas_used: Default::default(), + excess_blob_gas: Default::default(), + parent_beacon_block_root: Default::default(), base_fee_per_gas: None, + requests_hash: None, }; - let encoded = rlp::encode(&header); - let decoded: Header = rlp::decode(encoded.as_ref()).unwrap(); + let encoded = alloy_rlp::encode(&header); + let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); - header.base_fee_per_gas = Some(12345u64.into()); + header.base_fee_per_gas = Some(12345u64); - let encoded = rlp::encode(&header); - let decoded: Header = rlp::decode(encoded.as_ref()).unwrap(); + let encoded = alloy_rlp::encode(&header); + let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); } #[test] - #[cfg(feature = "fastrlp")] - fn header_fastrlp_roundtrip() { - let mut header = Header { - parent_hash: Default::default(), - ommers_hash: Default::default(), - beneficiary: Default::default(), - state_root: Default::default(), - transactions_root: Default::default(), - receipts_root: Default::default(), - logs_bloom: Default::default(), - difficulty: Default::default(), - number: 124u64.into(), - gas_limit: Default::default(), - gas_used: 1337u64.into(), - timestamp: 0, - extra_data: Default::default(), - mix_hash: Default::default(), - nonce: H64::from_low_u64_be(99u64), - base_fee_per_gas: None, - }; - - let mut encoded = vec![]; -
::encode(&header, &mut encoded); - let decoded: Header = -
::decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(header, decoded); - - header.base_fee_per_gas = Some(12345u64.into()); - - encoded.clear(); -
::encode(&header, &mut encoded); - let decoded: Header = -
::decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(header, decoded); - } - - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_encode_block_header() { - use open_fastrlp::Encodable; + use alloy_rlp::Encodable; let expected = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); let mut data = vec![]; let header = Header { - parent_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ommers_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - beneficiary: H160::from_str("0000000000000000000000000000000000000000").unwrap(), - state_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - transactions_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - receipts_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x8aeu64.into(), - number: 0xd05u64.into(), - gas_limit: 0x115cu64.into(), - gas_used: 0x15b3u64.into(), + parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(), + state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), + difficulty: U256::from(2222), + number: 0xd05u64, + gas_limit: 0x115cu64, + gas_used: 0x15b3u64, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), - mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: H64::from_low_u64_be(0x0), + mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + nonce: B64::ZERO, base_fee_per_gas: None, + requests_hash: None, }; + header.encode(&mut data); assert_eq!(hex::encode(&data), hex::encode(expected)); assert_eq!(header.length(), data.len()); } #[test] - // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36 - fn test_eip1559_block_header_hash() { - let expected_hash = - H256::from_str("6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f") - .unwrap(); - let header = Header { - parent_hash: H256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(), - ommers_hash: H256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(), - beneficiary: H160::from_str("ba5e000000000000000000000000000000000000").unwrap(), - state_root: H256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(), - transactions_root: H256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(), - receipts_root: H256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(), - logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x020000.into(), - number: 0x01.into(), - gas_limit: U256::from_str("016345785d8a0000").unwrap(), - gas_used: 0x015534.into(), - timestamp: 0x079e, - extra_data: hex::decode("42").unwrap().into(), - mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: H64::from_low_u64_be(0x0), - base_fee_per_gas: Some(0x036b.into()), - }; - assert_eq!(header.hash(), expected_hash); - } - - #[test] - #[cfg(feature = "fastrlp")] // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 fn test_decode_block_header() { let data = hex::decode("f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000").unwrap(); let expected = Header { - parent_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - ommers_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - beneficiary: H160::from_str("0000000000000000000000000000000000000000").unwrap(), - state_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - transactions_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - receipts_root: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + parent_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ommers_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + beneficiary: Address::from_str("0000000000000000000000000000000000000000").unwrap(), + state_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + transactions_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + receipts_root: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), - difficulty: 0x8aeu64.into(), - number: 0xd05u64.into(), - gas_limit: 0x115cu64.into(), - gas_used: 0x15b3u64.into(), + difficulty: U256::from(2222), + number: 0xd05u64, + gas_limit: 0x115cu64, + gas_used: 0x15b3u64, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), - mix_hash: H256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: H64::from_low_u64_be(0x0), + mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + nonce: B64::ZERO, + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, base_fee_per_gas: None, + requests_hash: None, }; - let header =
::decode(&mut data.as_slice()).unwrap(); + let header = Header::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); } #[test] - #[cfg(feature = "fastrlp")] + // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36 + fn test_eip1559_block_header_hash() { + let expected_hash = + b256!("6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f"); + let header = Header { + parent_hash: B256::from_str("e0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a").unwrap(), + ommers_hash: B256::from_str("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(), + beneficiary: Address::from_str("ba5e000000000000000000000000000000000000").unwrap(), + state_root: B256::from_str("ec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7").unwrap(), + transactions_root: B256::from_str("50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf").unwrap(), + receipts_root: B256::from_str("29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9").unwrap(), + logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), + difficulty: U256::from(0x020000), + number: 1u64, + gas_limit: U256::from(0x016345785d8a0000u128).to::(), + gas_used: U256::from(0x015534).to::(), + timestamp: 0x079e, + extra_data: hex::decode("42").unwrap().into(), + mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + nonce: B64::ZERO, + base_fee_per_gas: Some(875), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + }; + assert_eq!(header.hash_slow(), expected_hash); + } + + #[test] // Test vector from network - fn block_network_fastrlp_roundtrip() { - use open_fastrlp::Encodable; + fn block_network_roundtrip() { + use alloy_rlp::Encodable; let data = hex::decode("f9034df90348a0fbdbd8d2d0ac5f14bd5fa90e547fe6f1d15019c724f8e7b60972d381cd5d9cf8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794c9577e7945db22e38fc060909f2278c7746b0f9ba05017cfa3b0247e35197215ae8d610265ffebc8edca8ea66d6567eb0adecda867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018355bb7b871fffffffffffff808462bd0e1ab9014bf90148a00000000000000000000000000000000000000000000000000000000000000000f85494319fa8f1bc4e53410e92d10d918659b16540e60a945a573efb304d04c1224cd012313e827eca5dce5d94a9c831c5a268031176ebf5f3de5051e8cba0dbfe94c9577e7945db22e38fc060909f2278c7746b0f9b808400000000f8c9b841a6946f2d16f68338cbcbd8b117374ab421128ce422467088456bceba9d70c34106128e6d4564659cf6776c08a4186063c0a05f7cffd695c10cf26a6f301b67f800b8412b782100c18c35102dc0a37ece1a152544f04ad7dc1868d18a9570f744ace60870f822f53d35e89a2ea9709ccbf1f4a25ee5003944faa845d02dde0a41d5704601b841d53caebd6c8a82456e85c2806a9e08381f959a31fb94a77e58f00e38ad97b2e0355b8519ab2122662cbe022f2a4ef7ff16adc0b2d5dcd123181ec79705116db300a063746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365880000000000000000c0c0").unwrap(); - let block = ::decode(&mut data.as_slice()).unwrap(); + let block = Block::decode(&mut data.as_slice()).unwrap(); // encode and check that it matches the original data let mut encoded = Vec::new(); diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index c0ea52c65e70d..1dc2fac312e48 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,39 +1,38 @@ -use self::state::StateOverride; -use crate::{ - eth::{ - subscription::{SubscriptionId, SubscriptionKind, SubscriptionParams}, - transaction::EthTransactionRequest, - }, - types::{EvmMineOptions, Forking, Index}, -}; -use ethers_core::{ - abi::ethereum_types::H64, - types::{ - transaction::eip712::TypedData, Address, BlockId, BlockNumber, Bytes, Filter, - GethDebugTracingOptions, TxHash, H256, U256, +use crate::{eth::subscription::SubscriptionId, types::ReorgOptions}; +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, + trace::{ + filter::TraceFilter, + geth::{GethDebugTracingCallOptions, GethDebugTracingOptions}, }, + BlockId, BlockNumberOrTag as BlockNumber, Filter, Index, }; +use alloy_serde::WithOtherFields; pub mod block; pub mod proof; -pub mod receipt; -pub mod state; pub mod subscription; pub mod transaction; pub mod trie; -pub mod utils; +pub mod wallet; #[cfg(feature = "serde")] pub mod serde_helpers; #[cfg(feature = "serde")] -use ethers_core::types::serde_helpers::*; +use self::serde_helpers::*; #[cfg(feature = "serde")] -use self::serde_helpers::*; +use foundry_common::serde_helpers::{ + deserialize_number, deserialize_number_opt, deserialize_number_seq, +}; /// Wrapper type that ensures the type is named `params` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] pub struct Params { #[cfg_attr(feature = "serde", serde(default))] @@ -41,7 +40,7 @@ pub struct Params { } /// Represents ethereum JSON-RPC API -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "method", content = "params"))] pub enum EthRequest { @@ -72,6 +71,9 @@ pub enum EthRequest { )] EthMaxPriorityFeePerGas(()), + #[cfg_attr(feature = "serde", serde(rename = "eth_blobBaseFee", with = "empty_params"))] + EthBlobBaseFee(()), + #[cfg_attr( feature = "serde", serde(rename = "eth_accounts", alias = "eth_requestAccounts", with = "empty_params") @@ -84,15 +86,21 @@ 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), #[cfg_attr(feature = "serde", serde(rename = "eth_getBlockByHash"))] - EthGetBlockByHash(H256, bool), + EthGetBlockByHash(B256, bool), #[cfg_attr(feature = "serde", serde(rename = "eth_getBlockByNumber"))] EthGetBlockByNumber( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number") + )] BlockNumber, bool, ), @@ -104,13 +112,13 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_getBlockTransactionCountByHash", with = "sequence") )] - EthGetTransactionCountByHash(H256), + EthGetTransactionCountByHash(B256), #[cfg_attr( feature = "serde", serde( rename = "eth_getBlockTransactionCountByNumber", - deserialize_with = "lenient_block_number_seq" + deserialize_with = "lenient_block_number::lenient_block_number_seq" ) )] EthGetTransactionCountByNumber(BlockNumber), @@ -119,13 +127,13 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_getUncleCountByBlockHash", with = "sequence") )] - EthGetUnclesCountByHash(H256), + EthGetUnclesCountByHash(B256), #[cfg_attr( feature = "serde", serde( rename = "eth_getUncleCountByBlockNumber", - deserialize_with = "lenient_block_number_seq" + deserialize_with = "lenient_block_number::lenient_block_number_seq" ) )] EthGetUnclesCountByNumber(BlockNumber), @@ -136,14 +144,19 @@ pub enum EthRequest { /// Returns the account and storage values of the specified account including the Merkle-proof. /// This call can be used to verify that the data you are pulling from is not tampered with. #[cfg_attr(feature = "serde", serde(rename = "eth_getProof"))] - EthGetProof(Address, Vec, Option), + EthGetProof(Address, Vec, Option), /// The sign method calculates an Ethereum specific signature with: #[cfg_attr(feature = "serde", serde(rename = "eth_sign"))] EthSign(Address, Bytes), - #[cfg_attr(feature = "serde", serde(rename = "eth_signTransaction"))] - EthSignTransaction(Box), + /// 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>), /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). #[cfg_attr(feature = "serde", serde(rename = "eth_signTypedData"))] @@ -155,31 +168,32 @@ pub enum EthRequest { /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md), and includes full support of arrays and recursive data structures. #[cfg_attr(feature = "serde", serde(rename = "eth_signTypedData_v4"))] - EthSignTypedDataV4(Address, TypedData), + EthSignTypedDataV4(Address, alloy_dyn_abi::TypedData), #[cfg_attr(feature = "serde", serde(rename = "eth_sendTransaction", with = "sequence"))] - EthSendTransaction(Box), + EthSendTransaction(Box>), #[cfg_attr(feature = "serde", serde(rename = "eth_sendRawTransaction", with = "sequence"))] EthSendRawTransaction(Bytes), #[cfg_attr(feature = "serde", serde(rename = "eth_call"))] EthCall( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_createAccessList"))] EthCreateAccessList( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_estimateGas"))] EthEstimateGas( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, + #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionByHash", with = "sequence"))] @@ -189,21 +203,35 @@ pub enum EthRequest { EthGetTransactionByBlockHashAndIndex(TxHash, Index), #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionByBlockNumberAndIndex"))] - EthGetTransactionByBlockNumberAndIndex( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number"))] - BlockNumber, - Index, - ), + EthGetTransactionByBlockNumberAndIndex(BlockNumber, Index), + + #[cfg_attr( + feature = "serde", + serde(rename = "eth_getRawTransactionByHash", with = "sequence") + )] + EthGetRawTransactionByHash(TxHash), + + #[cfg_attr(feature = "serde", serde(rename = "eth_getRawTransactionByBlockHashAndIndex"))] + EthGetRawTransactionByBlockHashAndIndex(TxHash, Index), + + #[cfg_attr(feature = "serde", serde(rename = "eth_getRawTransactionByBlockNumberAndIndex"))] + EthGetRawTransactionByBlockNumberAndIndex(BlockNumber, Index), #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionReceipt", with = "sequence"))] - EthGetTransactionReceipt(H256), + EthGetTransactionReceipt(B256), + + #[cfg_attr(feature = "serde", serde(rename = "eth_getBlockReceipts", with = "sequence"))] + EthGetBlockReceipts(BlockId), #[cfg_attr(feature = "serde", serde(rename = "eth_getUncleByBlockHashAndIndex"))] - EthGetUncleByBlockHashAndIndex(H256, Index), + EthGetUncleByBlockHashAndIndex(B256, Index), #[cfg_attr(feature = "serde", serde(rename = "eth_getUncleByBlockNumberAndIndex"))] EthGetUncleByBlockNumberAndIndex( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number") + )] BlockNumber, Index, ), @@ -244,10 +272,10 @@ pub enum EthRequest { EthGetWork(()), #[cfg_attr(feature = "serde", serde(rename = "eth_submitWork"))] - EthSubmitWork(H64, H256, H256), + EthSubmitWork(B64, B256, B256), #[cfg_attr(feature = "serde", serde(rename = "eth_submitHashrate"))] - EthSubmitHashRate(U256, H256), + EthSubmitHashRate(U256, B256), #[cfg_attr(feature = "serde", serde(rename = "eth_feeHistory"))] EthFeeHistory( @@ -259,32 +287,43 @@ pub enum EthRequest { #[cfg_attr(feature = "serde", serde(rename = "eth_syncing", with = "empty_params"))] EthSyncing(()), + /// geth's `debug_getRawTransaction` endpoint + #[cfg_attr(feature = "serde", serde(rename = "debug_getRawTransaction", with = "sequence"))] + DebugGetRawTransaction(TxHash), + /// geth's `debug_traceTransaction` endpoint #[cfg_attr(feature = "serde", serde(rename = "debug_traceTransaction"))] DebugTraceTransaction( - H256, + B256, #[cfg_attr(feature = "serde", serde(default))] GethDebugTracingOptions, ), /// geth's `debug_traceCall` endpoint #[cfg_attr(feature = "serde", serde(rename = "debug_traceCall"))] DebugTraceCall( - EthTransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, - #[cfg_attr(feature = "serde", serde(default))] GethDebugTracingOptions, + #[cfg_attr(feature = "serde", serde(default))] GethDebugTracingCallOptions, ), /// Trace transaction endpoint for parity's `trace_transaction` #[cfg_attr(feature = "serde", serde(rename = "trace_transaction", with = "sequence"))] - TraceTransaction(H256), + TraceTransaction(B256), /// Trace transaction endpoint for parity's `trace_block` #[cfg_attr( feature = "serde", - serde(rename = "trace_block", deserialize_with = "lenient_block_number_seq") + serde( + rename = "trace_block", + deserialize_with = "lenient_block_number::lenient_block_number_seq" + ) )] TraceBlock(BlockNumber), + // Return filtered traces over blocks + #[cfg_attr(feature = "serde", serde(rename = "trace_filter", with = "sequence"))] + 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( @@ -354,6 +393,13 @@ pub enum EthRequest { )] SetIntervalMining(u64), + /// Gets the current mining behavior + #[cfg_attr( + feature = "serde", + serde(rename = "anvil_getIntervalMining", with = "empty_params") + )] + GetIntervalMining(()), + /// Removes transactions from the pool #[cfg_attr( feature = "serde", @@ -363,7 +409,18 @@ pub enum EthRequest { with = "sequence" ) )] - DropTransaction(H256), + DropTransaction(B256), + + /// Removes transactions from the pool + #[cfg_attr( + feature = "serde", + serde( + rename = "anvil_dropAllTransactions", + alias = "hardhat_dropAllTransactions", + with = "empty_params" + ) + )] + DropAllTransactions(), /// Reset the fork to a fresh forked state, and optionally update the fork config #[cfg_attr(feature = "serde", serde(rename = "anvil_reset", alias = "hardhat_reset"))] @@ -411,7 +468,7 @@ pub enum EthRequest { /// slot U256, /// value - H256, + B256, ), /// Sets the coinbase address @@ -421,6 +478,10 @@ pub enum EthRequest { )] SetCoinbase(Address), + /// Sets the chain id + #[cfg_attr(feature = "serde", serde(rename = "anvil_setChainId", with = "sequence"))] + SetChainId(u64), + /// Enable or disable logging #[cfg_attr( feature = "serde", @@ -468,12 +529,9 @@ pub enum EthRequest { EvmSetTime(U256), /// Serializes the current state (including contracts code, contract's storage, accounts - /// properties, etc.) into a savable data blob - #[cfg_attr( - feature = "serde", - serde(rename = "anvil_dumpState", alias = "hardhat_dumpState", with = "empty_params") - )] - DumpState(()), + /// properties, etc.) into a saveable data blob + #[cfg_attr(feature = "serde", serde(rename = "anvil_dumpState", alias = "hardhat_dumpState"))] + DumpState(#[cfg_attr(feature = "serde", serde(default))] Option>>), /// Adds state previously dumped with `DumpState` to the current chain #[cfg_attr( @@ -486,8 +544,17 @@ pub enum EthRequest { #[cfg_attr(feature = "serde", serde(rename = "anvil_nodeInfo", with = "empty_params"))] NodeInfo(()), + /// Retrieves the Anvil node metadata. + #[cfg_attr( + feature = "serde", + serde(rename = "anvil_metadata", alias = "hardhat_metadata", with = "empty_params") + )] + AnvilMetadata(()), + // Ganache compatible calls /// Snapshot the state of the blockchain at the current block. + /// + /// Ref #[cfg_attr( feature = "serde", serde(rename = "anvil_snapshot", alias = "evm_snapshot", with = "empty_params") @@ -496,6 +563,8 @@ pub enum EthRequest { /// Revert the state of the blockchain to a previous snapshot. /// Takes a single parameter, which is the snapshot id to revert to. + /// + /// Ref #[cfg_attr( feature = "serde", serde( @@ -557,7 +626,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 /// @@ -568,7 +637,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 @@ -576,7 +645,7 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_sendUnsignedTransaction", with = "sequence") )] - EthSendUnsignedTransaction(Box), + EthSendUnsignedTransaction(Box>), /// Turn on call traces for transactions that are returned to the user when they execute a /// transaction (instead of just txhash/receipt) @@ -585,29 +654,32 @@ pub enum EthRequest { /// Returns the number of transactions currently pending for inclusion in the next block(s), as /// well as the ones that are being scheduled for future execution only. - /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_status) + /// Ref: #[cfg_attr(feature = "serde", serde(rename = "txpool_status", with = "empty_params"))] TxPoolStatus(()), /// Returns a summary of all the transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. - /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect) + /// Ref: #[cfg_attr(feature = "serde", serde(rename = "txpool_inspect", with = "empty_params"))] TxPoolInspect(()), /// Returns the details of all transactions currently pending for inclusion in the next /// block(s), as well as the ones that are being scheduled for future execution only. - /// Ref: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) + /// Ref: #[cfg_attr(feature = "serde", serde(rename = "txpool_content", with = "empty_params"))] TxPoolContent(()), /// Otterscan's `ots_getApiLevel` endpoint /// Otterscan currently requires this endpoint, even though it's not part of the ots_* - /// https://github.com/otterscan/otterscan/blob/071d8c55202badf01804f6f8d53ef9311d4a9e47/src/useProvider.ts#L71 - /// Related upstream issue: https://github.com/otterscan/otterscan/issues/1081 + /// + /// Related upstream issue: #[cfg_attr(feature = "serde", serde(rename = "erigon_getHeaderByNumber"))] ErigonGetHeaderByNumber( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number_seq"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number_seq") + )] BlockNumber, ), @@ -620,26 +692,29 @@ pub enum EthRequest { /// Traces internal ETH transfers, contracts creation (CREATE/CREATE2) and self-destructs for a /// certain transaction. #[cfg_attr(feature = "serde", serde(rename = "ots_getInternalOperations", with = "sequence"))] - OtsGetInternalOperations(H256), + OtsGetInternalOperations(B256), /// Otterscan's `ots_hasCode` endpoint /// Check if an ETH address contains code at a certain block number. #[cfg_attr(feature = "serde", serde(rename = "ots_hasCode"))] OtsHasCode( Address, - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number", default))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number", default) + )] BlockNumber, ), /// Otterscan's `ots_traceTransaction` endpoint /// Trace a transaction and generate a trace call tree. #[cfg_attr(feature = "serde", serde(rename = "ots_traceTransaction", with = "sequence"))] - OtsTraceTransaction(H256), + OtsTraceTransaction(B256), /// Otterscan's `ots_getTransactionError` endpoint /// Given a transaction hash, returns its raw revert reason. #[cfg_attr(feature = "serde", serde(rename = "ots_getTransactionError", with = "sequence"))] - OtsGetTransactionError(H256), + OtsGetTransactionError(B256), /// Otterscan's `ots_getBlockDetails` endpoint /// Given a block number, return its data. Similar to the standard eth_getBlockByNumber/Hash @@ -647,14 +722,17 @@ pub enum EthRequest { /// logBloom #[cfg_attr(feature = "serde", serde(rename = "ots_getBlockDetails"))] OtsGetBlockDetails( - #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_block_number_seq"))] + #[cfg_attr( + feature = "serde", + serde(deserialize_with = "lenient_block_number::lenient_block_number_seq", default) + )] BlockNumber, ), /// Otterscan's `ots_getBlockDetails` endpoint /// Same as `ots_getBlockDetails`, but receiving a block hash instead of number #[cfg_attr(feature = "serde", serde(rename = "ots_getBlockDetailsByHash", with = "sequence"))] - OtsGetBlockDetailsByHash(H256), + OtsGetBlockDetailsByHash(B256), /// Otterscan's `ots_getBlockTransactions` endpoint /// Gets paginated transaction data for a certain block. Return data is similar to @@ -687,6 +765,46 @@ pub enum EthRequest { /// contract. #[cfg_attr(feature = "serde", serde(rename = "ots_getContractCreator", with = "sequence"))] OtsGetContractCreator(Address), + + /// Removes transactions from the pool by sender origin. + #[cfg_attr( + feature = "serde", + serde(rename = "anvil_removePoolTransactions", with = "sequence") + )] + RemovePoolTransactions(Address), + + /// Reorg the chain + #[cfg_attr(feature = "serde", serde(rename = "anvil_reorg",))] + Reorg(ReorgOptions), + + /// Rollback the chain + #[cfg_attr(feature = "serde", serde(rename = "anvil_rollback", with = "sequence"))] + Rollback(Option), + + /// Wallet + #[cfg_attr(feature = "serde", serde(rename = "wallet_getCapabilities", with = "empty_params"))] + WalletGetCapabilities(()), + + /// Wallet send_tx + #[cfg_attr( + feature = "serde", + serde( + rename = "wallet_sendTransaction", + alias = "odyssey_sendTransaction", + with = "sequence" + ) + )] + WalletSendTransaction(Box>), + + /// Add an address to the [`DelegationCapability`] of the wallet + /// + /// [`DelegationCapability`]: wallet::DelegationCapability + #[cfg_attr(feature = "serde", serde(rename = "anvil_addCapability", with = "sequence"))] + AnvilAddCapability(Address), + + /// Set the executor (sponsor) wallet + #[cfg_attr(feature = "serde", serde(rename = "anvil_setExecutor", with = "sequence"))] + AnvilSetExecutor(String), } /// Represents ethereum JSON-RPC API @@ -707,7 +825,7 @@ pub enum EthPubSub { } /// Container type for either a request or a pub sub -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "serde", serde(untagged))] pub enum EthRpcCall { @@ -791,14 +909,16 @@ mod tests { #[test] fn test_custom_impersonate_account() { - let s = r#"{"method": "anvil_impersonateAccount", "params": ["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; + let s = r#"{"method": "anvil_impersonateAccount", "params": +["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_stop_impersonate_account() { - let s = r#"{"method": "anvil_stopImpersonatingAccount", "params": ["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; + let s = r#"{"method": "anvil_stopImpersonatingAccount", "params": +["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -829,8 +949,8 @@ mod tests { } _ => unreachable!(), } - let s = - r#"{"method": "anvil_mine", "params": ["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; + let s = r#"{"method": "anvil_mine", "params": +["0xd84de507f3fada7df80908082d3239466db55a71"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); match req { @@ -874,13 +994,34 @@ mod tests { #[test] fn test_custom_drop_tx() { - let s = r#"{"method": "anvil_dropTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; + let s = r#"{"method": "anvil_dropTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_reset() { + let s = r#"{"method": "anvil_reset", "params": [{"forking": {"jsonRpcUrl": "https://ethereumpublicnode.com", + "blockNumber": "18441649" + } + }]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let req = serde_json::from_value::(value).unwrap(); + match req { + EthRequest::Reset(forking) => { + let forking = forking.and_then(|f| f.params); + assert_eq!( + forking, + Some(Forking { + json_rpc_url: Some("https://ethereumpublicnode.com".into()), + block_number: Some(18441649) + }) + ) + } + _ => unreachable!(), + } + let s = r#"{"method": "anvil_reset", "params": [ { "forking": { "jsonRpcUrl": "https://eth-mainnet.alchemyapi.io/v2/", "blockNumber": 11095000 @@ -955,6 +1096,20 @@ mod tests { _ => unreachable!(), } + let s = r#"{"method":"anvil_reset","params":[{ "blockNumber": "14000000"}]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let req = serde_json::from_value::(value).unwrap(); + match req { + EthRequest::Reset(forking) => { + let forking = forking.and_then(|f| f.params); + assert_eq!( + forking, + Some(Forking { json_rpc_url: None, block_number: Some(14000000) }) + ) + } + _ => unreachable!(), + } + let s = r#"{"method":"anvil_reset","params":[{"jsonRpcUrl": "http://localhost:8545"}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let req = serde_json::from_value::(value).unwrap(); @@ -985,57 +1140,71 @@ mod tests { #[test] fn test_custom_set_balance() { - let s = r#"{"method": "anvil_setBalance", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": "anvil_setBalance", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "anvil_setBalance", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", 1337]}"#; + let s = r#"{"method": "anvil_setBalance", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", 1337]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_set_code() { - let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0123456789abcdef"]}"#; + let s = r#"{"method": "anvil_setCode", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0123456789abcdef"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x"]}"#; + let s = r#"{"method": "anvil_setCode", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "anvil_setCode", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", ""]}"#; + let s = r#"{"method": "anvil_setCode", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", ""]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_custom_set_nonce() { - let s = r#"{"method": "anvil_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": "anvil_setNonce", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "hardhat_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": +"hardhat_setNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "evm_setAccountNonce", "params": ["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; + let s = r#"{"method": "evm_setAccountNonce", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x0"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_set_storage_at() { - let s = r#"{"method": "anvil_setStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; + let s = r#"{"method": "anvil_setStorageAt", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", +"0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "hardhat_setStorageAt", "params": ["0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", "0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; + let s = r#"{"method": "hardhat_setStorageAt", "params": +["0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", +"0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49", +"0x0000000000000000000000000000000000000000000000000000000000003039"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } #[test] fn test_serde_custom_coinbase() { - let s = r#"{"method": "anvil_setCoinbase", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251"]}"#; + let s = r#"{"method": "anvil_setCoinbase", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1074,9 +1243,19 @@ mod tests { #[test] fn test_serde_custom_dump_state() { - let s = r#"{"method": "anvil_dumpState", "params": [] }"#; + let s = r#"{"method": "anvil_dumpState", "params": [true]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); + + let s = r#"{"method": "anvil_dumpState"}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let req = serde_json::from_value::(value).unwrap(); + match req { + EthRequest::DumpState(param) => { + assert!(param.is_none()); + } + _ => unreachable!(), + } } #[test] @@ -1173,7 +1352,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!(), @@ -1210,7 +1389,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!(), @@ -1241,7 +1420,7 @@ mod tests { EthRequest::EvmMine(params) => { assert_eq!( params.unwrap().params.unwrap_or_default(), - EvmMineOptions::Timestamp(Some(1672937224)) + MineOptions::Timestamp(Some(1672937224)) ) } _ => unreachable!(), @@ -1254,7 +1433,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!(), @@ -1291,7 +1470,8 @@ mod tests { #[test] fn test_serde_eth_unsubscribe() { - let s = r#"{"id": 1, "method": "eth_unsubscribe", "params": ["0x9cef478923ff08bf67fde6c64013158d"]}"#; + let s = r#"{"id": 1, "method": "eth_unsubscribe", "params": +["0x9cef478923ff08bf67fde6c64013158d"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1302,7 +1482,9 @@ mod tests { let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["logs", {"address": "0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", "topics": ["0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902"]}]}"#; + let s = r#"{"id": 1, "method": "eth_subscribe", "params": ["logs", {"address": +"0x8320fe7702b96808f7bbc0d4a888ed1468216cfd", "topics": +["0xd78a0cb8bb633d06981248b816e7bd33c2a35a6089241d099fa519e361cab902"]}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); @@ -1315,17 +1497,40 @@ mod tests { let _req = serde_json::from_value::(value).unwrap(); } + #[test] + fn test_serde_debug_raw_transaction() { + let s = r#"{"jsonrpc":"2.0","method":"debug_getRawTransaction","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + + let s = r#"{"jsonrpc":"2.0","method":"eth_getRawTransactionByHash","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c"],"id":1}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + + let s = r#"{"jsonrpc":"2.0","method":"eth_getRawTransactionByBlockHashAndIndex","params":["0x3ed3a89bc10115a321aee238c02de214009f8532a65368e5df5eaf732ee7167c",1],"id":1}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + + let s = r#"{"jsonrpc":"2.0","method":"eth_getRawTransactionByBlockNumberAndIndex","params":["0x3ed3a89b",0],"id":1}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + } + #[test] fn test_serde_debug_trace_transaction() { - let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; + let s = r#"{"method": "debug_traceTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {}]}"#; + let s = r#"{"method": "debug_traceTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); - let s = r#"{"method": "debug_traceTransaction", "params": ["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {"disableStorage": true}]}"#; + let s = r#"{"method": "debug_traceTransaction", "params": +["0x4a3b0fce2cb9707b0baa68640cf2fe858c8bb4121b2a8cb904ff369d38a560ff", {"disableStorage": +true}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1355,7 +1560,8 @@ mod tests { #[test] fn test_serde_eth_storage() { - let s = r#"{"method": "eth_getStorageAt", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]}"#; + let s = r#"{"method": "eth_getStorageAt", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } @@ -1363,27 +1569,28 @@ mod tests { #[test] fn test_eth_call() { let req = r#"{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}"#; - let _req = serde_json::from_str::(req).unwrap(); + let _req = serde_json::from_str::(req).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"},"latest"]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"},"latest"]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "latest" }]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockNumber": "0x0" }]}"#; let _req = serde_json::from_str::(s).unwrap(); - let s = r#"{"method": "eth_call", "params": [{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#; + let s = r#"{"method": "eth_call", "params":[{"data":"0xcfae3217","from":"0xd84de507f3fada7df80908082d3239466db55a71","to":"0xcbe828fdc46e3b1c351ec90b1a5e7d9742c0398d"}, { "blockHash":"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" }]}"#; let _req = serde_json::from_str::(s).unwrap(); } #[test] fn test_serde_eth_balance() { - let s = r#"{"method": "eth_getBalance", "params": ["0x295a70b2de5e3953354a6a8344e616ed314d7251", "latest"]}"#; + let s = r#"{"method": "eth_getBalance", "params": +["0x295a70b2de5e3953354a6a8344e616ed314d7251", "latest"]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); @@ -1410,10 +1617,104 @@ mod tests { let _req = serde_json::from_value::(value).unwrap(); } + #[test] + fn test_eth_sign() { + let s = r#"{"method": "eth_sign", "params": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x00"]}"#; + 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": +["0x00", "0xd84de507f3fada7df80908082d3239466db55a71"]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + } + #[test] fn test_eth_sign_typed_data() { let s = r#"{"method":"eth_signTypedData_v4","params":["0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", {"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":1,"verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","wallet":"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB"},"contents":"Hello, Bob!"}}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } + + #[test] + fn test_remove_pool_transactions() { + let s = r#"{"method": "anvil_removePoolTransactions", "params":["0x364d6D0333432C3Ac016Ca832fb8594A8cE43Ca6"]}"#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + } + + #[test] + fn test_serde_anvil_reorg() { + // TransactionData::JSON + let s = r#" + { + "method": "anvil_reorg", + "params": [ + 5, + [ + [ + { + "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", + "value": 100 + }, + 1 + ], + [ + { + "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", + "value": 200 + }, + 2 + ] + ] + ] + } + "#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + // TransactionData::Raw + let s = r#" + { + "method": "anvil_reorg", + "params": [ + 5, + [ + [ + "0x19d55c67e1ba8f1bbdfed75f8ad524ebf087e4ecb848a2d19881d7a5e3d2c54e1732cb1b462da3b3fdb05bdf4c4d3c8e3c9fcebdc2ab5fa5d59a3f752888f27e1b", + 1 + ] + ] + ] + } + "#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + // TransactionData::Raw and TransactionData::JSON + let s = r#" + { + "method": "anvil_reorg", + "params": [ + 5, + [ + [ + "0x19d55c67e1ba8f1bbdfed75f8ad524ebf087e4ecb848a2d19881d7a5e3d2c54e1732cb1b462da3b3fdb05bdf4c4d3c8e3c9fcebdc2ab5fa5d59a3f752888f27e1b", + 1 + ], + [ + { + "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", + "value": 200 + }, + 2 + ] + ] + ] + } + "#; + 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/proof.rs b/crates/anvil/core/src/eth/proof.rs index a1a2f3b5a3c24..450e53e471410 100644 --- a/crates/anvil/core/src/eth/proof.rs +++ b/crates/anvil/core/src/eth/proof.rs @@ -1,57 +1,24 @@ //! Return types for `eth_getProof` use crate::eth::trie::KECCAK_NULL_RLP; -use ethers_core::{ - types::{H256, U256}, - utils::rlp, -}; +use alloy_primitives::{B256, U256}; use revm::primitives::KECCAK_EMPTY; -// reexport for convenience -pub use ethers_core::types::{EIP1186ProofResponse as AccountProof, StorageProof}; -/// Basic account type. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] +#[derive(Clone, Debug, PartialEq, Eq, alloy_rlp::RlpEncodable, alloy_rlp::RlpDecodable)] pub struct BasicAccount { - /// Nonce of the account. pub nonce: U256, - /// Balance of the account. pub balance: U256, - /// Storage root of the account. - pub storage_root: H256, - /// Code hash of the account. - pub code_hash: H256, + pub storage_root: B256, + pub code_hash: B256, } impl Default for BasicAccount { fn default() -> Self { - BasicAccount { - balance: 0.into(), - nonce: 0.into(), - code_hash: KECCAK_EMPTY.into(), + Self { + balance: U256::ZERO, + nonce: U256::ZERO, + code_hash: KECCAK_EMPTY, storage_root: KECCAK_NULL_RLP, } } } - -impl rlp::Encodable for BasicAccount { - fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(4); - stream.append(&self.nonce); - stream.append(&self.balance); - stream.append(&self.storage_root); - stream.append(&self.code_hash); - } -} - -impl rlp::Decodable for BasicAccount { - fn decode(rlp: &rlp::Rlp) -> Result { - let result = BasicAccount { - nonce: rlp.val_at(0)?, - balance: rlp.val_at(1)?, - storage_root: rlp.val_at(2)?, - code_hash: rlp.val_at(3)?, - }; - Ok(result) - } -} diff --git a/crates/anvil/core/src/eth/receipt.rs b/crates/anvil/core/src/eth/receipt.rs deleted file mode 100644 index e663b33329086..0000000000000 --- a/crates/anvil/core/src/eth/receipt.rs +++ /dev/null @@ -1,355 +0,0 @@ -use crate::eth::utils::enveloped; -use ethers_core::{ - types::{Address, Bloom, Bytes, H256, U256}, - utils::{ - rlp, - rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}, - }, -}; -use foundry_evm::utils::{b256_to_h256, h256_to_b256}; - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Log { - pub address: Address, - pub topics: Vec, - pub data: Bytes, -} - -impl From for Log { - fn from(log: revm::primitives::Log) -> Self { - let revm::primitives::Log { address, topics, data } = log; - Log { - address: address.into(), - topics: topics.into_iter().map(b256_to_h256).collect(), - data: data.into(), - } - } -} - -impl From for revm::primitives::Log { - fn from(log: Log) -> Self { - let Log { address, topics, data } = log; - revm::primitives::Log { - address: address.into(), - topics: topics.into_iter().map(h256_to_b256).collect(), - data: data.0, - } - } -} - -impl Encodable for Log { - fn rlp_append(&self, stream: &mut rlp::RlpStream) { - stream.begin_list(3); - stream.append(&self.address); - stream.append_list(&self.topics); - stream.append(&self.data.as_ref()); - } -} - -impl Decodable for Log { - fn decode(rlp: &Rlp) -> Result { - let result = Log { - address: rlp.val_at(0)?, - topics: rlp.list_at(1)?, - data: rlp.val_at::>(2)?.into(), - }; - Ok(result) - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EIP658Receipt { - pub status_code: u8, - pub gas_used: U256, - pub logs_bloom: Bloom, - pub logs: Vec, -} - -impl Encodable for EIP658Receipt { - fn rlp_append(&self, stream: &mut RlpStream) { - stream.begin_list(4); - stream.append(&self.status_code); - stream.append(&self.gas_used); - stream.append(&self.logs_bloom); - stream.append_list(&self.logs); - } -} - -impl Decodable for EIP658Receipt { - fn decode(rlp: &Rlp) -> Result { - let result = EIP658Receipt { - status_code: rlp.val_at(0)?, - gas_used: rlp.val_at(1)?, - logs_bloom: rlp.val_at(2)?, - logs: rlp.list_at(3)?, - }; - Ok(result) - } -} - -// same underlying data structure -pub type EIP2930Receipt = EIP658Receipt; -pub type EIP1559Receipt = EIP658Receipt; - -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TypedReceipt { - /// Legacy receipt - Legacy(EIP658Receipt), - /// EIP-2930 receipt - EIP2930(EIP2930Receipt), - /// EIP-1559 receipt - EIP1559(EIP1559Receipt), -} - -// == impl TypedReceipt == - -impl TypedReceipt { - /// Returns the gas used by the transactions - pub fn gas_used(&self) -> U256 { - match self { - TypedReceipt::Legacy(r) | TypedReceipt::EIP2930(r) | TypedReceipt::EIP1559(r) => { - r.gas_used - } - } - } - - /// Returns the gas used by the transactions - pub fn logs_bloom(&self) -> &Bloom { - match self { - TypedReceipt::Legacy(r) | TypedReceipt::EIP2930(r) | TypedReceipt::EIP1559(r) => { - &r.logs_bloom - } - } - } -} - -impl Encodable for TypedReceipt { - fn rlp_append(&self, s: &mut RlpStream) { - match self { - TypedReceipt::Legacy(r) => r.rlp_append(s), - TypedReceipt::EIP2930(r) => enveloped(1, r, s), - TypedReceipt::EIP1559(r) => enveloped(2, r, s), - } - } -} - -impl Decodable for TypedReceipt { - fn decode(rlp: &Rlp) -> Result { - let slice = rlp.data()?; - - let first = *slice.first().ok_or(DecoderError::Custom("empty receipt"))?; - - if rlp.is_list() { - return Ok(TypedReceipt::Legacy(Decodable::decode(rlp)?)) - } - - let s = slice.get(1..).ok_or(DecoderError::Custom("no receipt content"))?; - - if first == 0x01 { - return rlp::decode(s).map(TypedReceipt::EIP2930) - } - - if first == 0x02 { - return rlp::decode(s).map(TypedReceipt::EIP1559) - } - - Err(DecoderError::Custom("unknown receipt type")) - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for TypedReceipt { - fn length(&self) -> usize { - match self { - TypedReceipt::Legacy(r) => r.length(), - receipt => { - let payload_len = match receipt { - TypedReceipt::EIP2930(r) => r.length() + 1, - TypedReceipt::EIP1559(r) => r.length() + 1, - _ => unreachable!("receipt already matched"), - }; - - // we include a string header for typed receipts, so include the length here - payload_len + open_fastrlp::length_of_length(payload_len) - } - } - } - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - use open_fastrlp::Header; - - match self { - TypedReceipt::Legacy(r) => r.encode(out), - receipt => { - let payload_len = match receipt { - TypedReceipt::EIP2930(r) => r.length() + 1, - TypedReceipt::EIP1559(r) => r.length() + 1, - _ => unreachable!("receipt already matched"), - }; - - match receipt { - TypedReceipt::EIP2930(r) => { - let receipt_string_header = - Header { list: false, payload_length: payload_len }; - - receipt_string_header.encode(out); - out.put_u8(0x01); - r.encode(out); - } - TypedReceipt::EIP1559(r) => { - let receipt_string_header = - Header { list: false, payload_length: payload_len }; - - receipt_string_header.encode(out); - out.put_u8(0x02); - r.encode(out); - } - _ => unreachable!("receipt already matched"), - } - } - } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for TypedReceipt { - fn decode(buf: &mut &[u8]) -> Result { - use bytes::Buf; - use open_fastrlp::Header; - use std::cmp::Ordering; - - // a receipt is either encoded as a string (non legacy) or a list (legacy). - // We should not consume the buffer if we are decoding a legacy receipt, so let's - // check if the first byte is between 0x80 and 0xbf. - let rlp_type = *buf - .first() - .ok_or(open_fastrlp::DecodeError::Custom("cannot decode a receipt from empty bytes"))?; - - match rlp_type.cmp(&open_fastrlp::EMPTY_LIST_CODE) { - Ordering::Less => { - // strip out the string header - let _header = Header::decode(buf)?; - let receipt_type = *buf.first().ok_or(open_fastrlp::DecodeError::Custom( - "typed receipt cannot be decoded from an empty slice", - ))?; - if receipt_type == 0x01 { - buf.advance(1); - ::decode(buf) - .map(TypedReceipt::EIP2930) - } else if receipt_type == 0x02 { - buf.advance(1); - ::decode(buf) - .map(TypedReceipt::EIP1559) - } else { - Err(open_fastrlp::DecodeError::Custom("invalid receipt type")) - } - } - Ordering::Equal => Err(open_fastrlp::DecodeError::Custom( - "an empty list is not a valid receipt encoding", - )), - Ordering::Greater => { - ::decode(buf).map(TypedReceipt::Legacy) - } - } - } -} - -impl From for EIP658Receipt { - fn from(v3: TypedReceipt) -> Self { - match v3 { - TypedReceipt::Legacy(receipt) => receipt, - TypedReceipt::EIP2930(receipt) => receipt, - TypedReceipt::EIP1559(receipt) => receipt, - } - } -} - -#[cfg(test)] -mod tests { - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 - fn encode_legacy_receipt() { - use std::str::FromStr; - - use ethers_core::{ - types::{Bytes, H160, H256}, - utils::hex, - }; - use open_fastrlp::Encodable; - - use crate::eth::receipt::{EIP658Receipt, Log, TypedReceipt}; - - let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - - let mut data = vec![]; - let receipt = TypedReceipt::Legacy(EIP658Receipt { - logs_bloom: [0; 256].into(), - gas_used: 0x1u64.into(), - logs: vec![Log { - address: H160::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ], - data: Bytes::from_str("0100ff").unwrap(), - }], - status_code: 0, - }); - receipt.encode(&mut data); - - // check that the rlp length equals the length of the expected rlp - assert_eq!(receipt.length(), expected.len()); - assert_eq!(data, expected); - } - - #[test] - #[cfg(feature = "fastrlp")] - // Test vector from: https://eips.ethereum.org/EIPS/eip-2481 - fn decode_legacy_receipt() { - use std::str::FromStr; - - use ethers_core::{ - types::{Bytes, H160, H256}, - utils::hex, - }; - use open_fastrlp::Decodable; - - use crate::eth::receipt::{EIP658Receipt, Log, TypedReceipt}; - - let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); - - let expected = TypedReceipt::Legacy(EIP658Receipt { - logs_bloom: [0; 256].into(), - gas_used: 0x1u64.into(), - logs: vec![Log { - address: H160::from_str("0000000000000000000000000000000000000011").unwrap(), - topics: vec![ - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000dead", - ) - .unwrap(), - H256::from_str( - "000000000000000000000000000000000000000000000000000000000000beef", - ) - .unwrap(), - ], - data: Bytes::from_str("0100ff").unwrap(), - }], - status_code: 0, - }); - - let receipt = TypedReceipt::decode(&mut &data[..]).unwrap(); - assert_eq!(receipt, expected); - } -} diff --git a/crates/anvil/core/src/eth/serde_helpers.rs b/crates/anvil/core/src/eth/serde_helpers.rs index 0deeb69d6c8c1..f7d5bd46dba2e 100644 --- a/crates/anvil/core/src/eth/serde_helpers.rs +++ b/crates/anvil/core/src/eth/serde_helpers.rs @@ -51,3 +51,60 @@ pub mod empty_params { Ok(()) } } + +/// A module that deserializes either a BlockNumberOrTag, or a simple number. +pub mod lenient_block_number { + use alloy_rpc_types::BlockNumberOrTag; + use serde::{Deserialize, Deserializer}; + /// Following the spec the block parameter is either: + /// + /// > HEX String - an integer block number + /// > String "earliest" for the earliest/genesis block + /// > String "latest" - for the latest mined block + /// > String "pending" - for the pending state/transactions + /// + /// and with EIP-1898: + /// > blockNumber: QUANTITY - a block number + /// > blockHash: DATA - a block hash + /// + /// + /// + /// EIP-1898 does not all calls that use `BlockNumber` like `eth_getBlockByNumber` and doesn't + /// list raw integers as supported. + /// + /// However, there are dev node implementations that support integers, such as ganache: + /// + /// N.B.: geth does not support ints in `eth_getBlockByNumber` + pub fn lenient_block_number<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + LenientBlockNumber::deserialize(deserializer).map(Into::into) + } + + /// Same as `lenient_block_number` but requires to be `[num; 1]` + pub fn lenient_block_number_seq<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let num = <[LenientBlockNumber; 1]>::deserialize(deserializer)?[0].into(); + Ok(num) + } + + /// Various block number representations, See [`lenient_block_number()`] + #[derive(Clone, Copy, Deserialize)] + #[serde(untagged)] + pub enum LenientBlockNumber { + BlockNumber(BlockNumberOrTag), + Num(u64), + } + + impl From for BlockNumberOrTag { + fn from(b: LenientBlockNumber) -> Self { + match b { + LenientBlockNumber::BlockNumber(b) => b, + LenientBlockNumber::Num(b) => b.into(), + } + } + } +} diff --git a/crates/anvil/core/src/eth/state.rs b/crates/anvil/core/src/eth/state.rs deleted file mode 100644 index 406d59cf72f52..0000000000000 --- a/crates/anvil/core/src/eth/state.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::collections::HashMap; - -use ethers_core::types::{Address, Bytes, H256, U256}; - -#[derive(Clone, Debug, PartialEq, Eq, Default)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct AccountOverride { - pub nonce: Option, - pub code: Option, - pub balance: Option, - pub state: Option>, - pub state_diff: Option>, -} - -pub type StateOverride = HashMap; diff --git a/crates/anvil/core/src/eth/subscription.rs b/crates/anvil/core/src/eth/subscription.rs index c363f52ffc2bf..9d347e9a60333 100644 --- a/crates/anvil/core/src/eth/subscription.rs +++ b/crates/anvil/core/src/eth/subscription.rs @@ -1,78 +1,8 @@ //! Subscription types - -use crate::eth::block::Header; -use ethers_core::{ - rand::{distributions::Alphanumeric, thread_rng, Rng}, - types::{Filter, Log, TxHash}, - utils::hex, -}; +use alloy_primitives::hex; +use rand::{distributions::Alphanumeric, thread_rng, Rng}; use std::fmt; -/// Result of a subscription -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", serde(untagged))] -pub enum SubscriptionResult { - /// New block header - Header(Box
), - /// Log - Log(Box), - /// Transaction hash - TransactionHash(TxHash), - /// SyncStatus - Sync(SyncStatus), -} - -/// Sync status -#[derive(Debug, Eq, PartialEq, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct SyncStatus { - pub syncing: bool, -} - -/// Params for a subscription request -#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)] -pub struct SubscriptionParams { - /// holds the filter params field if present in the request - pub filter: Option, -} - -#[cfg(feature = "serde")] -impl<'a> serde::Deserialize<'a> for SubscriptionParams { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'a>, - { - use serde::de::Error; - - let val = serde_json::Value::deserialize(deserializer)?; - if val.is_null() { - return Ok(SubscriptionParams::default()) - } - - let filter: Filter = serde_json::from_value(val) - .map_err(|e| D::Error::custom(format!("Invalid Subscription parameters: {e}")))?; - Ok(SubscriptionParams { filter: Some(filter) }) - } -} - -/// Subscription kind -#[derive(Debug, PartialEq, Eq, Hash, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub enum SubscriptionKind { - /// subscribe to new heads - NewHeads, - /// subscribe to new logs - Logs, - /// subscribe to pending transactions - NewPendingTransactions, - /// syncing subscription - Syncing, -} - /// Unique subscription id #[derive(Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] @@ -84,41 +14,37 @@ pub enum SubscriptionId { String(String), } -// === impl SubscriptionId === - impl SubscriptionId { /// Generates a new random hex identifier pub fn random_hex() -> Self { - SubscriptionId::String(hex_id()) + Self::String(hex_id()) } } impl fmt::Display for SubscriptionId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SubscriptionId::Number(num) => num.fmt(f), - SubscriptionId::String(s) => s.fmt(f), + Self::Number(num) => num.fmt(f), + Self::String(s) => s.fmt(f), } } } impl fmt::Debug for SubscriptionId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SubscriptionId::Number(num) => num.fmt(f), - SubscriptionId::String(s) => s.fmt(f), + Self::Number(num) => num.fmt(f), + Self::String(s) => s.fmt(f), } } } /// Provides random hex identifier with a certain length -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct HexIdProvider { len: usize, } -// === impl HexIdProvider === - impl HexIdProvider { /// Generates a random hex encoded Id pub fn gen(&self) -> String { diff --git a/crates/anvil/core/src/eth/transaction/ethers_compat.rs b/crates/anvil/core/src/eth/transaction/ethers_compat.rs deleted file mode 100644 index 21e2cb8b91307..0000000000000 --- a/crates/anvil/core/src/eth/transaction/ethers_compat.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! ethers compatibility, this is mainly necessary so we can use all of `ethers` signers - -use super::EthTransactionRequest; -use crate::eth::transaction::{ - EIP1559TransactionRequest, EIP2930TransactionRequest, LegacyTransactionRequest, - MaybeImpersonatedTransaction, TypedTransaction, TypedTransactionRequest, -}; -use ethers_core::types::{ - transaction::eip2718::TypedTransaction as EthersTypedTransactionRequest, Address, - Eip1559TransactionRequest as EthersEip1559TransactionRequest, - Eip2930TransactionRequest as EthersEip2930TransactionRequest, NameOrAddress, - Transaction as EthersTransaction, TransactionRequest as EthersLegacyTransactionRequest, - TransactionRequest, H256, U256, U64, -}; - -impl From for EthersTypedTransactionRequest { - fn from(tx: TypedTransactionRequest) -> Self { - match tx { - TypedTransactionRequest::Legacy(tx) => { - let LegacyTransactionRequest { - nonce, - gas_price, - gas_limit, - kind, - value, - input, - chain_id, - } = tx; - EthersTypedTransactionRequest::Legacy(EthersLegacyTransactionRequest { - from: None, - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - gas_price: Some(gas_price), - value: Some(value), - data: Some(input), - nonce: Some(nonce), - chain_id: chain_id.map(Into::into), - }) - } - TypedTransactionRequest::EIP2930(tx) => { - let EIP2930TransactionRequest { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - EthersTypedTransactionRequest::Eip2930(EthersEip2930TransactionRequest { - tx: EthersLegacyTransactionRequest { - from: None, - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - gas_price: Some(gas_price), - value: Some(value), - data: Some(input), - nonce: Some(nonce), - chain_id: Some(chain_id.into()), - }, - access_list: access_list.into(), - }) - } - TypedTransactionRequest::EIP1559(tx) => { - let EIP1559TransactionRequest { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - EthersTypedTransactionRequest::Eip1559(EthersEip1559TransactionRequest { - from: None, - to: kind.as_call().cloned().map(Into::into), - gas: Some(gas_limit), - value: Some(value), - data: Some(input), - nonce: Some(nonce), - access_list: access_list.into(), - max_priority_fee_per_gas: Some(max_priority_fee_per_gas), - max_fee_per_gas: Some(max_fee_per_gas), - chain_id: Some(chain_id.into()), - }) - } - } - } -} - -fn to_ethers_transaction_with_hash_and_sender( - transaction: TypedTransaction, - hash: H256, - from: Address, -) -> EthersTransaction { - match transaction { - TypedTransaction::Legacy(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: Some(t.gas_price), - max_fee_per_gas: Some(t.gas_price), - max_priority_fee_per_gas: Some(t.gas_price), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: t.chain_id().map(Into::into), - v: t.signature.v.into(), - r: t.signature.r, - s: t.signature.s, - access_list: None, - transaction_type: None, - other: Default::default(), - }, - TypedTransaction::EIP2930(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: Some(t.gas_price), - max_fee_per_gas: Some(t.gas_price), - max_priority_fee_per_gas: Some(t.gas_price), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: Some(t.chain_id.into()), - v: U64::from(t.odd_y_parity as u8), - r: U256::from(t.r.as_bytes()), - s: U256::from(t.s.as_bytes()), - access_list: Some(t.access_list), - transaction_type: Some(1u64.into()), - other: Default::default(), - }, - TypedTransaction::EIP1559(t) => EthersTransaction { - hash, - nonce: t.nonce, - block_hash: None, - block_number: None, - transaction_index: None, - from, - to: None, - value: t.value, - gas_price: None, - max_fee_per_gas: Some(t.max_fee_per_gas), - max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), - gas: t.gas_limit, - input: t.input.clone(), - chain_id: Some(t.chain_id.into()), - v: U64::from(t.odd_y_parity as u8), - r: U256::from(t.r.as_bytes()), - s: U256::from(t.s.as_bytes()), - access_list: Some(t.access_list), - transaction_type: Some(2u64.into()), - other: Default::default(), - }, - } -} - -impl From for EthersTransaction { - fn from(transaction: TypedTransaction) -> Self { - let hash = transaction.hash(); - let sender = transaction.recover().unwrap_or_default(); - to_ethers_transaction_with_hash_and_sender(transaction, hash, sender) - } -} - -impl From for EthersTransaction { - fn from(transaction: MaybeImpersonatedTransaction) -> Self { - let hash = transaction.hash(); - let sender = transaction.recover().unwrap_or_default(); - to_ethers_transaction_with_hash_and_sender(transaction.into(), hash, sender) - } -} - -impl From for EthTransactionRequest { - fn from(req: TransactionRequest) -> Self { - let TransactionRequest { from, to, gas, gas_price, value, data, nonce, chain_id, .. } = req; - EthTransactionRequest { - from, - to: to.and_then(|to| match to { - NameOrAddress::Name(_) => None, - NameOrAddress::Address(to) => Some(to), - }), - gas_price, - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - gas, - value, - data, - nonce, - chain_id, - access_list: None, - transaction_type: None, - } - } -} - -impl From for TransactionRequest { - fn from(req: EthTransactionRequest) -> Self { - let EthTransactionRequest { from, to, gas_price, gas, value, data, nonce, .. } = req; - TransactionRequest { - from, - to: to.map(NameOrAddress::Address), - gas, - gas_price, - value, - data, - nonce, - chain_id: None, - } - } -} diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 8445f7dfd663c..9a4bf1b0d7480 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -1,432 +1,187 @@ -//! transaction related data +//! Transaction related types -use crate::eth::{ - receipt::Log, - utils::{enveloped, to_revm_access_list}, -}; -use ethers_core::{ - types::{ - transaction::eip2930::{AccessList, AccessListItem}, - Address, Bloom, Bytes, Signature, SignatureError, TxHash, H256, U256, U64, - }, - utils::{ - keccak256, rlp, - rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}, +use crate::eth::transaction::optimism::DepositTransaction; +use alloy_consensus::{ + transaction::{ + eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, + TxEip7702, }, + Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, TxEnvelope, TxLegacy, + TxReceipt, Typed2718, +}; +use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; +use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope}; +use alloy_primitives::{ + Address, Bloom, Bytes, Log, PrimitiveSignature, TxHash, TxKind, B256, U256, U64, }; -use foundry_evm::trace::CallTraceArena; +use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; +use alloy_rpc_types::{ + request::TransactionRequest, trace::otterscan::OtsReceipt, AccessList, ConversionError, + Transaction as RpcTransaction, TransactionReceipt, +}; +use alloy_serde::{OtherFields, WithOtherFields}; +use bytes::BufMut; +use foundry_evm::traces::CallTraceNode; +use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; use revm::{ interpreter::InstructionResult, - primitives::{CreateScheme, TransactTo, TxEnv}, + primitives::{OptimismFields, TxEnv}, }; -use std::ops::Deref; - -/// compatibility with `ethers-rs` types -mod ethers_compat; - -/// The signature used to bypass signing via the `eth_sendUnsignedTransaction` cheat RPC -#[cfg(feature = "impersonated-tx")] -pub const IMPERSONATED_SIGNATURE: Signature = - Signature { r: U256([0, 0, 0, 0]), s: U256([0, 0, 0, 0]), v: 0 }; - -/// Container type for various Ethereum transaction requests -/// -/// Its variants correspond to specific allowed transactions: -/// 1. Legacy (pre-EIP2718) [`LegacyTransactionRequest`] -/// 2. EIP2930 (state access lists) [`EIP2930TransactionRequest`] -/// 3. EIP1559 [`EIP1559TransactionRequest`] -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum TypedTransactionRequest { - Legacy(LegacyTransactionRequest), - EIP2930(EIP2930TransactionRequest), - EIP1559(EIP1559TransactionRequest), -} - -/// Represents _all_ transaction requests received from RPC -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct EthTransactionRequest { - /// from address - pub from: Option
, - /// to address - pub to: Option
, - /// legacy, gas Price - #[cfg_attr(feature = "serde", serde(default))] - pub gas_price: Option, - /// max base fee per gas sender is willing to pay - #[cfg_attr(feature = "serde", serde(default))] - pub max_fee_per_gas: Option, - /// miner tip - #[cfg_attr(feature = "serde", serde(default))] - pub max_priority_fee_per_gas: Option, - /// gas - pub gas: Option, - /// value of th tx in wei - pub value: Option, - /// Any additional data sent - pub data: Option, - /// Transaction nonce - pub nonce: Option, - /// chain id - #[cfg_attr(feature = "serde", serde(default))] - pub chain_id: Option, - /// warm storage access pre-payment - #[cfg_attr(feature = "serde", serde(default))] - pub access_list: Option>, - /// EIP-2718 type - #[cfg_attr(feature = "serde", serde(rename = "type"))] - pub transaction_type: Option, -} - -// == impl EthTransactionRequest == - -impl EthTransactionRequest { - /// Converts the request into a [TypedTransactionRequest] - pub fn into_typed_request(self) -> Option { - let EthTransactionRequest { - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - data, - nonce, - mut access_list, - chain_id, - transaction_type, - .. - } = self; - let chain_id = chain_id.map(|id| id.as_u64()); - let transaction_type = transaction_type.map(|id| id.as_u64()); - match ( - transaction_type, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - access_list.take(), - ) { - // legacy transaction - (Some(0), _, None, None, None) | (None, Some(_), None, None, None) => { - Some(TypedTransactionRequest::Legacy(LegacyTransactionRequest { - nonce: nonce.unwrap_or(U256::zero()), - gas_price: gas_price.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::zero()), - input: data.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - chain_id, - })) - } - // EIP2930 - (Some(1), _, None, None, _) | (None, _, None, None, Some(_)) => { - Some(TypedTransactionRequest::EIP2930(EIP2930TransactionRequest { - nonce: nonce.unwrap_or(U256::zero()), - gas_price: gas_price.unwrap_or_default(), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::zero()), - input: data.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - chain_id: chain_id.unwrap_or_default(), - access_list: access_list.unwrap_or_default(), - })) - } - // EIP1559 - (Some(2), None, _, _, _) | - (None, None, Some(_), _, _) | - (None, None, _, Some(_), _) | - (None, None, None, None, None) => { - // Empty fields fall back to the canonical transaction schema. - Some(TypedTransactionRequest::EIP1559(EIP1559TransactionRequest { - nonce: nonce.unwrap_or(U256::zero()), - max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), - max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or(U256::zero()), - gas_limit: gas.unwrap_or_default(), - value: value.unwrap_or(U256::zero()), - input: data.unwrap_or_default(), - kind: match to { - Some(to) => TransactionKind::Call(to), - None => TransactionKind::Create, - }, - chain_id: chain_id.unwrap_or_default(), - access_list: access_list.unwrap_or_default(), - })) - } - _ => None, - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TransactionKind { - Call(Address), - Create, -} - -// == impl TransactionKind == - -impl TransactionKind { - /// If this transaction is a call this returns the address of the callee - pub fn as_call(&self) -> Option<&Address> { - match self { - TransactionKind::Call(to) => Some(to), - TransactionKind::Create => None, - } - } -} - -impl Encodable for TransactionKind { - fn rlp_append(&self, s: &mut RlpStream) { - match self { - TransactionKind::Call(address) => { - s.encoder().encode_value(&address[..]); - } - TransactionKind::Create => s.encoder().encode_value(&[]), - } - } -} - -impl Decodable for TransactionKind { - fn decode(rlp: &Rlp) -> Result { - if rlp.is_empty() { - if rlp.is_data() { - Ok(TransactionKind::Create) - } else { - Err(DecoderError::RlpExpectedToBeData) - } - } else { - Ok(TransactionKind::Call(rlp.as_val()?)) - } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for TransactionKind { - fn length(&self) -> usize { - match self { - TransactionKind::Call(to) => to.length(), - TransactionKind::Create => ([]).length(), +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, Mul}; + +pub mod optimism; + +/// Converts a [TransactionRequest] into a [TypedTransactionRequest]. +/// Should be removed once the call builder abstraction for providers is in place. +pub fn transaction_request_to_typed( + tx: WithOtherFields, +) -> Option { + let WithOtherFields:: { + inner: + TransactionRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + blob_versioned_hashes, + gas, + value, + input, + nonce, + access_list, + sidecar, + transaction_type, + .. + }, + other, + } = tx; + + // Special case: OP-stack deposit tx + if transaction_type == Some(0x7E) || has_optimism_fields(&other) { + let mint = other.get_deserialized::("mint")?.map(|m| m.to::()).ok()?; + + return Some(TypedTransactionRequest::Deposit(TxDeposit { + from: from.unwrap_or_default(), + source_hash: other.get_deserialized::("sourceHash")?.ok()?, + to: to.unwrap_or_default(), + mint: Some(mint), + value: value.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + is_system_transaction: other.get_deserialized::("isSystemTx")?.ok()?, + input: input.into_input().unwrap_or_default(), + })); + } + + match ( + transaction_type, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list.as_ref(), + max_fee_per_blob_gas, + blob_versioned_hashes.as_ref(), + sidecar, + to, + ) { + // legacy transaction + (Some(0), _, None, None, None, None, None, None, _) | + (None, Some(_), None, None, None, None, None, None, _) => { + Some(TypedTransactionRequest::Legacy(TxLegacy { + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: to.unwrap_or_default(), + chain_id: None, + })) } - } - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - match self { - TransactionKind::Call(to) => to.encode(out), - TransactionKind::Create => ([]).encode(out), + // EIP2930 + (Some(1), _, None, None, _, None, None, None, _) | + (None, _, None, None, Some(_), None, None, None, _) => { + Some(TypedTransactionRequest::EIP2930(TxEip2930 { + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: to.unwrap_or_default(), + chain_id: 0, + access_list: access_list.unwrap_or_default(), + })) } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for TransactionKind { - fn decode(buf: &mut &[u8]) -> Result { - use bytes::Buf; - - if let Some(&first) = buf.first() { - if first == 0x80 { - buf.advance(1); - Ok(TransactionKind::Create) - } else { - let addr =
::decode(buf)?; - Ok(TransactionKind::Call(addr)) - } - } else { - Err(open_fastrlp::DecodeError::InputTooShort) + // EIP1559 + (Some(2), None, _, _, _, _, None, None, _) | + (None, None, Some(_), _, _, _, None, None, _) | + (None, None, _, Some(_), _, _, None, None, _) | + (None, None, None, None, None, _, None, None, _) => { + // Empty fields fall back to the canonical transaction schema. + Some(TypedTransactionRequest::EIP1559(TxEip1559 { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: to.unwrap_or_default(), + chain_id: 0, + access_list: access_list.unwrap_or_default(), + })) } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -pub struct EIP2930TransactionRequest { - pub chain_id: u64, - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: Vec, -} - -impl EIP2930TransactionRequest { - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 1; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) - } -} - -impl From for EIP2930TransactionRequest { - fn from(tx: EIP2930Transaction) -> Self { - Self { - chain_id: tx.chain_id, - nonce: tx.nonce, - gas_price: tx.gas_price, - gas_limit: tx.gas_limit, - kind: tx.kind, - value: tx.value, - input: tx.input, - access_list: tx.access_list.0, + // EIP4844 + (Some(3), None, _, _, _, _, Some(_), Some(sidecar), to) => { + let tx = TxEip4844 { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: match to.unwrap_or(TxKind::Create) { + TxKind::Call(to) => to, + TxKind::Create => Address::ZERO, + }, + chain_id: 0, + access_list: access_list.unwrap_or_default(), + blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(), + }; + Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar), + ))) } + _ => None, } } -impl Encodable for EIP2930TransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(8); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append_list(&self.access_list); - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct LegacyTransactionRequest { - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub chain_id: Option, -} - -// == impl LegacyTransactionRequest == - -impl LegacyTransactionRequest { - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) - } -} - -impl From for LegacyTransactionRequest { - fn from(tx: LegacyTransaction) -> Self { - let chain_id = tx.chain_id(); - Self { - nonce: tx.nonce, - gas_price: tx.gas_price, - gas_limit: tx.gas_limit, - kind: tx.kind, - value: tx.value, - input: tx.input, - chain_id, - } - } -} - -impl Encodable for LegacyTransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - if let Some(chain_id) = self.chain_id { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&chain_id); - s.append(&0u8); - s.append(&0u8); - } else { - s.begin_list(6); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -pub struct EIP1559TransactionRequest { - pub chain_id: u64, - pub nonce: U256, - pub max_priority_fee_per_gas: U256, - pub max_fee_per_gas: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: Vec, -} - -// == impl EIP1559TransactionRequest == - -impl EIP1559TransactionRequest { - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 2; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) - } -} - -impl From for EIP1559TransactionRequest { - fn from(t: EIP1559Transaction) -> Self { - Self { - chain_id: t.chain_id, - nonce: t.nonce, - max_priority_fee_per_gas: t.max_priority_fee_per_gas, - max_fee_per_gas: t.max_fee_per_gas, - gas_limit: t.gas_limit, - kind: t.kind, - value: t.value, - input: t.input, - access_list: t.access_list.0, - } - } +fn has_optimism_fields(other: &OtherFields) -> bool { + other.contains_key("sourceHash") && + other.contains_key("mint") && + other.contains_key("isSystemTx") } -impl Encodable for EIP1559TransactionRequest { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(9); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append_list(&self.access_list); - } +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TypedTransactionRequest { + Legacy(TxLegacy), + EIP2930(TxEip2930), + EIP1559(TxEip1559), + EIP4844(TxEip4844Variant), + Deposit(TxDeposit), } -/// A wrapper for `TypedTransaction` that allows impersonating accounts. +/// A wrapper for [TypedTransaction] that allows impersonating accounts. /// /// 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, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct MaybeImpersonatedTransaction { - #[cfg_attr(feature = "serde", serde(flatten))] pub transaction: TypedTransaction, - #[cfg_attr(feature = "serde", serde(skip))] pub impersonated_sender: Option
, } -// === impl MaybeImpersonatedTransaction === - impl MaybeImpersonatedTransaction { /// Creates a new wrapper for the given transaction pub fn new(transaction: TypedTransaction) -> Self { @@ -443,31 +198,38 @@ impl MaybeImpersonatedTransaction { /// Note: this is feature gated so it does not conflict with the `Deref`ed /// [TypedTransaction::recover] function by default. #[cfg(feature = "impersonated-tx")] - pub fn recover(&self) -> Result { + 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) -> H256 { - if self.transaction.is_impersonated() { - if let Some(sender) = self.impersonated_sender { - return self.transaction.impersonated_hash(sender) - } + pub fn hash(&self) -> B256 { + if let Some(sender) = self.impersonated_sender { + return self.transaction.impersonated_hash(sender) } self.transaction.hash() } } impl Encodable for MaybeImpersonatedTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - self.transaction.rlp_append(s) + fn encode(&self, out: &mut dyn bytes::BufMut) { + self.transaction.encode(out) } } @@ -479,31 +241,13 @@ impl From for TypedTransaction { impl From for MaybeImpersonatedTransaction { fn from(value: TypedTransaction) -> Self { - MaybeImpersonatedTransaction::new(value) + Self::new(value) } } impl Decodable for MaybeImpersonatedTransaction { - fn decode(rlp: &Rlp) -> Result { - let transaction = TypedTransaction::decode(rlp)?; - Ok(Self { transaction, impersonated_sender: None }) - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for MaybeImpersonatedTransaction { - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - self.transaction.encode(out) - } - fn length(&self) -> usize { - self.transaction.length() - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for MaybeImpersonatedTransaction { - fn decode(buf: &mut &[u8]) -> Result { - Ok(Self { transaction: open_fastrlp::Decodable::decode(buf)?, impersonated_sender: None }) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + TypedTransaction::decode(buf).map(Self::new) } } @@ -521,1057 +265,1477 @@ impl Deref for MaybeImpersonatedTransaction { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +impl From for RpcTransaction { + fn from(value: MaybeImpersonatedTransaction) -> Self { + let hash = value.hash(); + let sender = value.recover().unwrap_or_default(); + to_alloy_transaction_with_hash_and_sender(value.transaction, hash, sender) + } +} + +pub fn to_alloy_transaction_with_hash_and_sender( + transaction: TypedTransaction, + hash: B256, + from: Address, +) -> RpcTransaction { + match transaction { + TypedTransaction::Legacy(t) => { + let (tx, sig, _) = t.into_parts(); + RpcTransaction { + block_hash: None, + block_number: None, + transaction_index: None, + from, + effective_gas_price: None, + inner: TxEnvelope::Legacy(Signed::new_unchecked(tx, sig, hash)), + } + } + TypedTransaction::EIP2930(t) => { + let (tx, sig, _) = t.into_parts(); + RpcTransaction { + block_hash: None, + block_number: None, + transaction_index: None, + from, + effective_gas_price: None, + inner: TxEnvelope::Eip2930(Signed::new_unchecked(tx, sig, hash)), + } + } + TypedTransaction::EIP1559(t) => { + let (tx, sig, _) = t.into_parts(); + RpcTransaction { + block_hash: None, + block_number: None, + transaction_index: None, + from, + effective_gas_price: None, + inner: TxEnvelope::Eip1559(Signed::new_unchecked(tx, sig, hash)), + } + } + TypedTransaction::EIP4844(t) => { + let (tx, sig, _) = t.into_parts(); + RpcTransaction { + block_hash: None, + block_number: None, + transaction_index: None, + from, + effective_gas_price: None, + inner: TxEnvelope::Eip4844(Signed::new_unchecked(tx, sig, hash)), + } + } + TypedTransaction::EIP7702(t) => { + let (tx, sig, _) = t.into_parts(); + RpcTransaction { + block_hash: None, + block_number: None, + transaction_index: None, + from, + effective_gas_price: None, + inner: TxEnvelope::Eip7702(Signed::new_unchecked(tx, sig, hash)), + } + } + TypedTransaction::Deposit(_t) => { + unreachable!("cannot reach here, handled in `transaction_build` ") + } + } +} + +/// Queued transaction +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PendingTransaction { + /// The actual transaction + pub transaction: MaybeImpersonatedTransaction, + /// the recovered sender of this transaction + sender: Address, + /// hash of `transaction`, so it can easily be reused with encoding and hashing agan + hash: TxHash, +} + +impl PendingTransaction { + pub fn new(transaction: TypedTransaction) -> Result { + let sender = transaction.recover()?; + let hash = transaction.hash(); + Ok(Self { transaction: MaybeImpersonatedTransaction::new(transaction), sender, hash }) + } + + #[cfg(feature = "impersonated-tx")] + pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self { + let hash = transaction.impersonated_hash(sender); + Self { + transaction: MaybeImpersonatedTransaction::impersonated(transaction, sender), + sender, + hash, + } + } + + pub fn nonce(&self) -> u64 { + self.transaction.nonce() + } + + pub fn hash(&self) -> &TxHash { + &self.hash + } + + pub fn sender(&self) -> &Address { + &self.sender + } + + /// 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) -> TxKind { + match kind { + TxKind::Call(c) => TxKind::Call(*c), + TxKind::Create => TxKind::Create, + } + } + + let caller = *self.sender(); + match &self.transaction.transaction { + TypedTransaction::Legacy(tx) => { + let chain_id = tx.tx().chain_id; + let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx(); + TxEnv { + caller, + transact_to: transact_to(to), + data: input.clone(), + chain_id, + nonce: Some(*nonce), + value: (*value), + gas_price: U256::from(*gas_price), + gas_priority_fee: None, + gas_limit: *gas_limit, + access_list: vec![], + ..Default::default() + } + } + TypedTransaction::EIP2930(tx) => { + let TxEip2930 { + chain_id, + nonce, + gas_price, + gas_limit, + to, + value, + input, + access_list, + .. + } = tx.tx(); + TxEnv { + caller, + transact_to: transact_to(to), + data: input.clone(), + chain_id: Some(*chain_id), + nonce: Some(*nonce), + value: *value, + gas_price: U256::from(*gas_price), + gas_priority_fee: None, + gas_limit: *gas_limit, + access_list: access_list.clone().into(), + ..Default::default() + } + } + TypedTransaction::EIP1559(tx) => { + let TxEip1559 { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas_limit, + to, + value, + input, + access_list, + .. + } = tx.tx(); + TxEnv { + caller, + transact_to: 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, + access_list: access_list.clone().into(), + ..Default::default() + } + } + TypedTransaction::EIP4844(tx) => { + let TxEip4844 { + chain_id, + nonce, + max_fee_per_blob_gas, + max_fee_per_gas, + max_priority_fee_per_gas, + gas_limit, + to, + value, + input, + access_list, + blob_versioned_hashes, + .. + } = tx.tx().tx(); + TxEnv { + caller, + transact_to: TxKind::Call(*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)), + max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)), + blob_hashes: blob_versioned_hashes.clone(), + gas_limit: *gas_limit, + 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: TxKind::Call(*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, + access_list: access_list.clone().into(), + authorization_list: Some(authorization_list.clone().into()), + ..Default::default() + } + } + TypedTransaction::Deposit(tx) => { + let chain_id = tx.chain_id(); + let DepositTransaction { + nonce, + source_hash, + gas_limit, + value, + kind, + mint, + input, + is_system_tx, + .. + } = tx; + TxEnv { + caller, + transact_to: transact_to(kind), + data: input.clone(), + chain_id, + nonce: Some(*nonce), + value: *value, + gas_price: U256::ZERO, + gas_priority_fee: None, + gas_limit: { *gas_limit }, + access_list: vec![], + optimism: OptimismFields { + source_hash: Some(*source_hash), + mint: Some(mint.to::()), + is_system_transaction: Some(*is_system_tx), + enveloped_tx: None, + }, + ..Default::default() + } + } + } + } +} + +/// Container type for signed, typed transactions. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TypedTransaction { /// Legacy transaction type - Legacy(LegacyTransaction), + Legacy(Signed), /// EIP-2930 transaction - EIP2930(EIP2930Transaction), + EIP2930(Signed), /// EIP-1559 transaction - EIP1559(EIP1559Transaction), + EIP1559(Signed), + /// EIP-4844 transaction + EIP4844(Signed), + /// EIP-7702 transaction + EIP7702(Signed), + /// op-stack deposit transaction + Deposit(DepositTransaction), +} + +/// This is a function that demotes TypedTransaction to TransactionRequest for greater flexibility +/// over the type. +/// +/// This function is purely for convenience and specific use cases, e.g. RLP encoded transactions +/// decode to TypedTransactions where the API over TypedTransctions is quite strict. +impl TryFrom for TransactionRequest { + type Error = ConversionError; + + fn try_from(value: TypedTransaction) -> Result { + let from = + value.recover().map_err(|_| ConversionError::Custom("InvalidSignature".to_string()))?; + let essentials = value.essentials(); + let tx_type = value.r#type(); + Ok(Self { + from: Some(from), + to: Some(value.kind()), + gas_price: essentials.gas_price, + max_fee_per_gas: essentials.max_fee_per_gas, + max_priority_fee_per_gas: essentials.max_priority_fee_per_gas, + max_fee_per_blob_gas: essentials.max_fee_per_blob_gas, + gas: Some(essentials.gas_limit), + value: Some(essentials.value), + input: essentials.input.into(), + nonce: Some(essentials.nonce), + chain_id: essentials.chain_id, + transaction_type: tx_type, + ..Default::default() + }) + } } -// == impl TypedTransaction == +impl TryFrom for TypedTransaction { + type Error = ConversionError; + + fn try_from(value: AnyRpcTransaction) -> Result { + let AnyRpcTransaction { inner, .. } = value; + let from = inner.from; + match inner.inner { + AnyTxEnvelope::Ethereum(tx) => match tx { + TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), + TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), + TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)), + }, + AnyTxEnvelope::Unknown(mut tx) => { + // Try to convert to deposit transaction + if tx.ty() == DEPOSIT_TX_TYPE_ID { + let nonce = get_field::(&tx.inner.fields, "nonce")?; + tx.inner.fields.insert("from".to_string(), serde_json::to_value(from).unwrap()); + let deposit_tx = + tx.inner.fields.deserialize_into::().map_err(|e| { + ConversionError::Custom(format!( + "Failed to deserialize deposit tx: {e}" + )) + })?; + + let TxDeposit { + source_hash, + is_system_transaction, + value, + gas_limit, + input, + mint, + from, + to, + } = deposit_tx; + + let deposit_tx = DepositTransaction { + nonce: nonce.to(), + source_hash, + from, + kind: to, + mint: mint.map(|m| U256::from(m)).unwrap_or_default(), + value, + gas_limit, + is_system_tx: is_system_transaction, + input, + }; + + return Ok(Self::Deposit(deposit_tx)); + }; + + Err(ConversionError::Custom("UnknownTxType".to_string())) + } + } + } +} + +fn get_field( + fields: &OtherFields, + key: &str, +) -> Result { + fields + .get_deserialized::(key) + .ok_or_else(|| ConversionError::Custom(format!("Missing{key}")))? + .map_err(|e| ConversionError::Custom(format!("Failed to deserialize {key}: {e}"))) +} impl TypedTransaction { - /// Returns true if the transaction uses dynamic fees: EIP1559 + /// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702 pub fn is_dynamic_fee(&self) -> bool { - matches!(self, TypedTransaction::EIP1559(_)) + matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_)) } - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { match self { - TypedTransaction::Legacy(tx) => tx.gas_price, - TypedTransaction::EIP2930(tx) => tx.gas_price, - TypedTransaction::EIP1559(tx) => tx.max_fee_per_gas, + Self::Legacy(tx) => tx.tx().gas_price, + Self::EIP2930(tx) => tx.tx().gas_price, + Self::EIP1559(tx) => tx.tx().max_fee_per_gas, + Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas, + Self::EIP7702(tx) => tx.tx().max_fee_per_gas, + Self::Deposit(_) => 0, } } - pub fn gas_limit(&self) -> U256 { + pub fn gas_limit(&self) -> u64 { match self { - TypedTransaction::Legacy(tx) => tx.gas_limit, - TypedTransaction::EIP2930(tx) => tx.gas_limit, - TypedTransaction::EIP1559(tx) => tx.gas_limit, + Self::Legacy(tx) => tx.tx().gas_limit, + Self::EIP2930(tx) => tx.tx().gas_limit, + Self::EIP1559(tx) => tx.tx().gas_limit, + Self::EIP4844(tx) => tx.tx().tx().gas_limit, + Self::EIP7702(tx) => tx.tx().gas_limit, + Self::Deposit(tx) => tx.gas_limit, } } pub fn value(&self) -> U256 { - match self { - TypedTransaction::Legacy(tx) => tx.value, - TypedTransaction::EIP2930(tx) => tx.value, - TypedTransaction::EIP1559(tx) => tx.value, - } + U256::from(match self { + Self::Legacy(tx) => tx.tx().value, + Self::EIP2930(tx) => tx.tx().value, + Self::EIP1559(tx) => tx.tx().value, + Self::EIP4844(tx) => tx.tx().tx().value, + Self::EIP7702(tx) => tx.tx().value, + Self::Deposit(tx) => tx.value, + }) } pub fn data(&self) -> &Bytes { match self { - TypedTransaction::Legacy(tx) => &tx.input, - TypedTransaction::EIP2930(tx) => &tx.input, - TypedTransaction::EIP1559(tx) => &tx.input, + Self::Legacy(tx) => &tx.tx().input, + Self::EIP2930(tx) => &tx.tx().input, + Self::EIP1559(tx) => &tx.tx().input, + Self::EIP4844(tx) => &tx.tx().tx().input, + Self::EIP7702(tx) => &tx.tx().input, + Self::Deposit(tx) => &tx.input, } } /// Returns the transaction type pub fn r#type(&self) -> Option { match self { - TypedTransaction::Legacy(_) => None, - TypedTransaction::EIP2930(_) => Some(1), - TypedTransaction::EIP1559(_) => Some(2), + Self::Legacy(_) => None, + Self::EIP2930(_) => Some(1), + Self::EIP1559(_) => Some(2), + Self::EIP4844(_) => Some(3), + Self::EIP7702(_) => Some(4), + Self::Deposit(_) => Some(0x7E), } } /// Max cost of the transaction - pub fn max_cost(&self) -> U256 { - self.gas_limit().saturating_mul(self.gas_price()) + /// It is the gas limit multiplied by the gas price, + /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob + /// gas) is also added + pub fn max_cost(&self) -> u128 { + let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price()); + + if self.is_eip4844() { + max_cost = max_cost.saturating_add( + self.blob_gas() + .map(|g| g as u128) + .unwrap_or(0) + .mul(self.max_fee_per_blob_gas().unwrap_or(0)), + ) + } + + max_cost + } + + pub fn blob_gas(&self) -> Option { + match self { + Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()), + _ => None, + } + } + + pub fn max_fee_per_blob_gas(&self) -> Option { + match self { + Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas), + _ => None, + } } /// Returns a helper type that contains commonly used values as fields pub fn essentials(&self) -> TransactionEssentials { match self { - TypedTransaction::Legacy(t) => TransactionEssentials { - kind: t.kind, - input: t.input.clone(), - nonce: t.nonce, - gas_limit: t.gas_limit, - gas_price: Some(t.gas_price), + Self::Legacy(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: Some(t.tx().gas_price), max_fee_per_gas: None, max_priority_fee_per_gas: None, - value: t.value, - chain_id: t.chain_id(), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: t.tx().chain_id, access_list: Default::default(), }, - TypedTransaction::EIP2930(t) => TransactionEssentials { - kind: t.kind, - input: t.input.clone(), - nonce: t.nonce, - gas_limit: t.gas_limit, - gas_price: Some(t.gas_price), + Self::EIP2930(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: Some(t.tx().gas_price), max_fee_per_gas: None, max_priority_fee_per_gas: None, - value: t.value, - chain_id: Some(t.chain_id), - access_list: t.access_list.clone(), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, + Self::EIP1559(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: None, + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, + Self::EIP4844(t) => TransactionEssentials { + kind: TxKind::Call(t.tx().tx().to), + input: t.tx().tx().input.clone(), + nonce: t.tx().tx().nonce, + gas_limit: t.tx().tx().gas_limit, + gas_price: Some(t.tx().tx().max_fee_per_blob_gas), + max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas), + max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), + blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), + value: t.tx().tx().value, + chain_id: Some(t.tx().tx().chain_id), + access_list: t.tx().tx().access_list.clone(), }, - TypedTransaction::EIP1559(t) => TransactionEssentials { + Self::EIP7702(t) => TransactionEssentials { + kind: TxKind::Call(t.tx().to), + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, + gas_price: Some(t.tx().max_fee_per_gas), + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, + Self::Deposit(t) => TransactionEssentials { kind: t.kind, input: t.input.clone(), nonce: t.nonce, gas_limit: t.gas_limit, - gas_price: None, - max_fee_per_gas: Some(t.max_fee_per_gas), - max_priority_fee_per_gas: Some(t.max_priority_fee_per_gas), + gas_price: Some(0), + max_fee_per_gas: None, + max_priority_fee_per_gas: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, value: t.value, - chain_id: Some(t.chain_id), - access_list: t.access_list.clone(), + chain_id: t.chain_id(), + access_list: Default::default(), }, } } - pub fn nonce(&self) -> &U256 { + pub fn nonce(&self) -> u64 { match self { - TypedTransaction::Legacy(t) => t.nonce(), - TypedTransaction::EIP2930(t) => t.nonce(), - TypedTransaction::EIP1559(t) => t.nonce(), + Self::Legacy(t) => t.tx().nonce, + Self::EIP2930(t) => t.tx().nonce, + Self::EIP1559(t) => t.tx().nonce, + Self::EIP4844(t) => t.tx().tx().nonce, + Self::EIP7702(t) => t.tx().nonce, + Self::Deposit(t) => t.nonce, } } pub fn chain_id(&self) -> Option { match self { - TypedTransaction::Legacy(t) => t.chain_id(), - TypedTransaction::EIP2930(t) => Some(t.chain_id), - TypedTransaction::EIP1559(t) => Some(t.chain_id), + Self::Legacy(t) => t.tx().chain_id, + Self::EIP2930(t) => Some(t.tx().chain_id), + Self::EIP1559(t) => Some(t.tx().chain_id), + Self::EIP4844(t) => Some(t.tx().tx().chain_id), + Self::EIP7702(t) => Some(t.tx().chain_id), + Self::Deposit(t) => t.chain_id(), } } - pub fn as_legacy(&self) -> Option<&LegacyTransaction> { + pub fn as_legacy(&self) -> Option<&Signed> { match self { - TypedTransaction::Legacy(tx) => Some(tx), + Self::Legacy(tx) => Some(tx), _ => None, } } /// Returns true whether this tx is a legacy transaction pub fn is_legacy(&self) -> bool { - matches!(self, TypedTransaction::Legacy(_)) + matches!(self, Self::Legacy(_)) } /// Returns true whether this tx is a EIP1559 transaction pub fn is_eip1559(&self) -> bool { - matches!(self, TypedTransaction::EIP1559(_)) + matches!(self, Self::EIP1559(_)) + } + + /// Returns true whether this tx is a EIP2930 transaction + pub fn is_eip2930(&self) -> bool { + matches!(self, Self::EIP2930(_)) + } + + /// Returns true whether this tx is a EIP4844 transaction + pub fn is_eip4844(&self) -> bool { + matches!(self, Self::EIP4844(_)) } /// Returns the hash of the transaction. /// /// Note: If this transaction has the Impersonated signature then this returns a modified unique /// hash. This allows us to treat impersonated transactions as unique. - pub fn hash(&self) -> H256 { + pub fn hash(&self) -> B256 { match self { - TypedTransaction::Legacy(t) => t.hash(), - TypedTransaction::EIP2930(t) => t.hash(), - TypedTransaction::EIP1559(t) => t.hash(), + Self::Legacy(t) => *t.hash(), + Self::EIP2930(t) => *t.hash(), + Self::EIP1559(t) => *t.hash(), + Self::EIP4844(t) => *t.hash(), + Self::EIP7702(t) => *t.hash(), + Self::Deposit(t) => t.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 #[cfg(feature = "impersonated-tx")] - pub fn impersonated_hash(&self, sender: Address) -> H256 { - let mut bytes = rlp::encode(self); - bytes.extend_from_slice(sender.as_ref()); - H256::from_slice(keccak256(&bytes).as_slice()) + pub fn impersonated_hash(&self, sender: Address) -> B256 { + let mut buffer = Vec::new(); + Encodable::encode(self, &mut buffer); + buffer.extend_from_slice(sender.as_ref()); + B256::from_slice(alloy_primitives::utils::keccak256(&buffer).as_slice()) } /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { + pub fn recover(&self) -> Result { match self { - TypedTransaction::Legacy(tx) => tx.recover(), - TypedTransaction::EIP2930(tx) => tx.recover(), - TypedTransaction::EIP1559(tx) => tx.recover(), + Self::Legacy(tx) => tx.recover_signer(), + Self::EIP2930(tx) => tx.recover_signer(), + Self::EIP1559(tx) => tx.recover_signer(), + Self::EIP4844(tx) => tx.recover_signer(), + Self::EIP7702(tx) => tx.recover_signer(), + Self::Deposit(tx) => tx.recover(), } } /// Returns what kind of transaction this is - pub fn kind(&self) -> &TransactionKind { + pub fn kind(&self) -> TxKind { match self { - TypedTransaction::Legacy(tx) => &tx.kind, - TypedTransaction::EIP2930(tx) => &tx.kind, - TypedTransaction::EIP1559(tx) => &tx.kind, + Self::Legacy(tx) => tx.tx().to, + Self::EIP2930(tx) => tx.tx().to, + Self::EIP1559(tx) => tx.tx().to, + Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to), + Self::EIP7702(tx) => TxKind::Call(tx.tx().to), + Self::Deposit(tx) => tx.kind, } } /// Returns the callee if this transaction is a call - pub fn to(&self) -> Option<&Address> { - self.kind().as_call() + pub fn to(&self) -> Option
{ + self.kind().to().copied() } /// Returns the Signature of the transaction - pub fn signature(&self) -> Signature { + pub fn signature(&self) -> PrimitiveSignature { match self { - TypedTransaction::Legacy(tx) => tx.signature, - TypedTransaction::EIP2930(tx) => { - let v = tx.odd_y_parity as u8; - let r = U256::from_big_endian(&tx.r[..]); - let s = U256::from_big_endian(&tx.s[..]); - Signature { r, s, v: v.into() } - } - TypedTransaction::EIP1559(tx) => { - let v = tx.odd_y_parity as u8; - let r = U256::from_big_endian(&tx.r[..]); - let s = U256::from_big_endian(&tx.s[..]); - Signature { r, s, v: v.into() } - } + Self::Legacy(tx) => *tx.signature(), + Self::EIP2930(tx) => *tx.signature(), + Self::EIP1559(tx) => *tx.signature(), + Self::EIP4844(tx) => *tx.signature(), + Self::EIP7702(tx) => *tx.signature(), + Self::Deposit(_) => PrimitiveSignature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + false, + ), } } } impl Encodable for TypedTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - match self { - TypedTransaction::Legacy(tx) => tx.rlp_append(s), - TypedTransaction::EIP2930(tx) => enveloped(1, tx, s), - TypedTransaction::EIP1559(tx) => enveloped(2, tx, s), + fn encode(&self, out: &mut dyn bytes::BufMut) { + if !self.is_legacy() { + Header { list: false, payload_length: self.encode_2718_len() }.encode(out); } - } -} -impl Decodable for TypedTransaction { - fn decode(rlp: &Rlp) -> Result { - let data = rlp.data()?; - let first = *data.first().ok_or(DecoderError::Custom("empty slice"))?; - if rlp.is_list() { - return Ok(TypedTransaction::Legacy(rlp.as_val()?)) - } - let s = data.get(1..).ok_or(DecoderError::Custom("no tx body"))?; - if first == 0x01 { - return rlp::decode(s).map(TypedTransaction::EIP2930) - } - if first == 0x02 { - return rlp::decode(s).map(TypedTransaction::EIP1559) - } - Err(DecoderError::Custom("invalid tx type")) + self.encode_2718(out); } } -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Encodable for TypedTransaction { - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - match self { - TypedTransaction::Legacy(tx) => tx.encode(out), - tx => { - let payload_len = match tx { - TypedTransaction::EIP2930(tx) => tx.length() + 1, - TypedTransaction::EIP1559(tx) => tx.length() + 1, - _ => unreachable!("legacy tx length already matched"), - }; - - match tx { - TypedTransaction::EIP2930(tx) => { - let tx_string_header = - open_fastrlp::Header { list: false, payload_length: payload_len }; - - tx_string_header.encode(out); - out.put_u8(0x01); - tx.encode(out); - } - TypedTransaction::EIP1559(tx) => { - let tx_string_header = - open_fastrlp::Header { list: false, payload_length: payload_len }; +impl Decodable for TypedTransaction { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let mut h_decode_copy = *buf; + let header = alloy_rlp::Header::decode(&mut h_decode_copy)?; - tx_string_header.encode(out); - out.put_u8(0x02); - tx.encode(out); - } - _ => unreachable!("legacy tx encode already matched"), - } - } - } - } - fn length(&self) -> usize { - match self { - TypedTransaction::Legacy(tx) => tx.length(), - tx => { - let payload_len = match tx { - TypedTransaction::EIP2930(tx) => tx.length() + 1, - TypedTransaction::EIP1559(tx) => tx.length() + 1, - _ => unreachable!("legacy tx length already matched"), - }; - // we include a string header for signed types txs, so include the length here - payload_len + open_fastrlp::length_of_length(payload_len) - } + // Legacy TX + if header.list { + return Ok(TxEnvelope::decode(buf)?.into()); } - } -} - -#[cfg(feature = "fastrlp")] -impl open_fastrlp::Decodable for TypedTransaction { - fn decode(buf: &mut &[u8]) -> Result { - use bytes::Buf; - use std::cmp::Ordering; - let first = *buf.first().ok_or(open_fastrlp::DecodeError::Custom("empty slice"))?; + // Check byte after header + let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; - // a signed transaction is either encoded as a string (non legacy) or a list (legacy). - // We should not consume the buffer if we are decoding a legacy transaction, so let's - // check if the first byte is between 0x80 and 0xbf. - match first.cmp(&open_fastrlp::EMPTY_LIST_CODE) { - Ordering::Less => { - // strip out the string header - // NOTE: typed transaction encodings either contain a "rlp header" which contains - // the type of the payload and its length, or they do not contain a header and - // start with the tx type byte. - // - // This line works for both types of encodings because byte slices starting with - // 0x01 and 0x02 return a Header { list: false, payload_length: 1 } when input to - // Header::decode. - // If the encoding includes a header, the header will be properly decoded and - // consumed. - // Otherwise, header decoding will succeed but nothing is consumed. - let _header = open_fastrlp::Header::decode(buf)?; - let tx_type = *buf.first().ok_or(open_fastrlp::DecodeError::Custom( - "typed tx cannot be decoded from an empty slice", - ))?; - if tx_type == 0x01 { - buf.advance(1); - ::decode(buf) - .map(TypedTransaction::EIP2930) - } else if tx_type == 0x02 { - buf.advance(1); - ::decode(buf) - .map(TypedTransaction::EIP1559) - } else { - Err(open_fastrlp::DecodeError::Custom("invalid tx type")) - } - } - Ordering::Equal => Err(open_fastrlp::DecodeError::Custom( - "an empty list is not a valid transaction encoding", - )), - Ordering::Greater => ::decode(buf) - .map(TypedTransaction::Legacy), + if ty != 0x7E { + Ok(TxEnvelope::decode(buf)?.into()) + } else { + Ok(Self::Deposit(DepositTransaction::decode_2718(buf)?)) } } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct LegacyTransaction { - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub signature: Signature, -} - -impl LegacyTransaction { - pub fn nonce(&self) -> &U256 { - &self.nonce - } - - pub fn hash(&self) -> H256 { - H256::from_slice(keccak256(&rlp::encode(self)).as_slice()) +impl Typed2718 for TypedTransaction { + fn ty(&self) -> u8 { + self.r#type().unwrap_or(0) } +} - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - self.signature.recover(LegacyTransactionRequest::from(self.clone()).hash()) +impl Encodable2718 for TypedTransaction { + fn encode_2718_len(&self) -> usize { + match self { + Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::Deposit(tx) => 1 + tx.length(), + } } - pub fn chain_id(&self) -> Option { - if self.signature.v > 36 { - Some((self.signature.v - 35) / 2) - } else { - None + fn encode_2718(&self, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::Deposit(tx) => { + tx.encode_2718(out); + } } } +} - /// See - /// > If you do, then the v of the signature MUST be set to {0,1} + CHAIN_ID * 2 + 35 where - /// > {0,1} is the parity of the y value of the curve point for which r is the x-value in the - /// > secp256k1 signing process. - pub fn meets_eip155(&self, chain_id: u64) -> bool { - let double_chain_id = chain_id.saturating_mul(2); - let v = self.signature.v; - v == double_chain_id + 35 || v == double_chain_id + 36 +impl Decodable2718 for TypedTransaction { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { + if ty == 0x7E { + return Ok(Self::Deposit(DepositTransaction::decode(buf)?)) + } + match TxEnvelope::typed_decode(ty, buf)? { + TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), + TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), + TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)), + _ => unreachable!(), + } } -} -impl Encodable for LegacyTransaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&self.signature.v); - s.append(&self.signature.r); - s.append(&self.signature.s); + fn fallback_decode(buf: &mut &[u8]) -> Result { + match TxEnvelope::fallback_decode(buf)? { + TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + _ => unreachable!(), + } } } -impl Decodable for LegacyTransaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 9 { - return Err(DecoderError::RlpIncorrectListLen) +impl From for TypedTransaction { + fn from(value: TxEnvelope) -> Self { + match value { + TxEnvelope::Legacy(tx) => Self::Legacy(tx), + TxEnvelope::Eip2930(tx) => Self::EIP2930(tx), + TxEnvelope::Eip1559(tx) => Self::EIP1559(tx), + TxEnvelope::Eip4844(tx) => Self::EIP4844(tx), + _ => unreachable!(), } - - let v = rlp.val_at(6)?; - let r = rlp.val_at::(7)?; - let s = rlp.val_at::(8)?; - - Ok(Self { - nonce: rlp.val_at(0)?, - gas_price: rlp.val_at(1)?, - gas_limit: rlp.val_at(2)?, - kind: rlp.val_at(3)?, - value: rlp.val_at(4)?, - input: rlp.val_at::>(5)?.into(), - signature: Signature { v, r, s }, - }) } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EIP2930Transaction { - pub chain_id: u64, - pub nonce: U256, - pub gas_price: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TransactionEssentials { + pub kind: TxKind, pub input: Bytes, + pub nonce: u64, + pub gas_limit: u64, + pub gas_price: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + pub max_fee_per_blob_gas: Option, + pub blob_versioned_hashes: Option>, + pub value: U256, + pub chain_id: Option, pub access_list: AccessList, - pub odd_y_parity: bool, - pub r: H256, - pub s: H256, } -impl EIP2930Transaction { - pub fn nonce(&self) -> &U256 { - &self.nonce - } - - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 1; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) - } - - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - let mut sig = [0u8; 65]; - sig[0..32].copy_from_slice(&self.r[..]); - sig[32..64].copy_from_slice(&self.s[..]); - sig[64] = self.odd_y_parity as u8; - let signature = Signature::try_from(&sig[..])?; - signature.recover(EIP2930TransactionRequest::from(self.clone()).hash()) - } +/// Represents all relevant information of an executed transaction +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct TransactionInfo { + pub transaction_hash: B256, + pub transaction_index: u64, + pub from: Address, + pub to: Option
, + pub contract_address: Option
, + pub traces: Vec, + pub exit: InstructionResult, + pub out: Option, + pub nonce: u64, + pub gas_used: u64, } -impl Encodable for EIP2930Transaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(11); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&self.access_list); - s.append(&self.odd_y_parity); - s.append(&U256::from_big_endian(&self.r[..])); - s.append(&U256::from_big_endian(&self.s[..])); - } +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DepositReceipt> { + #[serde(flatten)] + pub inner: ReceiptWithBloom, + #[serde(default, with = "alloy_serde::quantity::opt")] + pub deposit_nonce: Option, + #[serde(default, with = "alloy_serde::quantity::opt")] + pub deposit_receipt_version: Option, } -impl Decodable for EIP2930Transaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 11 { - return Err(DecoderError::RlpIncorrectListLen) +impl DepositReceipt { + fn payload_len(&self) -> usize { + self.inner.receipt.status.length() + + self.inner.receipt.cumulative_gas_used.length() + + self.inner.logs_bloom.length() + + self.inner.receipt.logs.length() + + self.deposit_nonce.map_or(0, |n| n.length()) + + self.deposit_receipt_version.map_or(0, |n| n.length()) + } + + /// Returns the rlp header for the receipt payload. + fn receipt_rlp_header(&self) -> alloy_rlp::Header { + alloy_rlp::Header { list: true, payload_length: self.payload_len() } + } + + /// Encodes the receipt data. + fn encode_fields(&self, out: &mut dyn BufMut) { + self.receipt_rlp_header().encode(out); + self.inner.status().encode(out); + self.inner.receipt.cumulative_gas_used.encode(out); + self.inner.logs_bloom.encode(out); + self.inner.receipt.logs.encode(out); + if let Some(n) = self.deposit_nonce { + n.encode(out); } + if let Some(n) = self.deposit_receipt_version { + n.encode(out); + } + } - Ok(Self { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - gas_price: rlp.val_at(2)?, - gas_limit: rlp.val_at(3)?, - kind: rlp.val_at(4)?, - value: rlp.val_at(5)?, - input: rlp.val_at::>(6)?.into(), - access_list: rlp.val_at(7)?, - odd_y_parity: rlp.val_at(8)?, - r: { - let mut rarr = [0u8; 32]; - rlp.val_at::(9)?.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0u8; 32]; - rlp.val_at::(10)?.to_big_endian(&mut sarr); - H256::from(sarr) + /// Decodes the receipt payload + fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result { + let b: &mut &[u8] = &mut &**buf; + let rlp_head = alloy_rlp::Header::decode(b)?; + if !rlp_head.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let started_len = b.len(); + let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0; + + let status = Decodable::decode(b)?; + let cumulative_gas_used = Decodable::decode(b)?; + let logs_bloom = Decodable::decode(b)?; + let logs: Vec = Decodable::decode(b)?; + let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; + let deposit_nonce_version = + remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; + + let this = Self { + inner: ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, }, - }) - } -} + deposit_nonce, + deposit_receipt_version: deposit_nonce_version, + }; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "fastrlp", derive(open_fastrlp::RlpEncodable, open_fastrlp::RlpDecodable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct EIP1559Transaction { - pub chain_id: u64, - pub nonce: U256, - pub max_priority_fee_per_gas: U256, - pub max_fee_per_gas: U256, - pub gas_limit: U256, - pub kind: TransactionKind, - pub value: U256, - pub input: Bytes, - pub access_list: AccessList, - pub odd_y_parity: bool, - pub r: H256, - pub s: H256, -} + let consumed = started_len - b.len(); + if consumed != rlp_head.payload_length { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: rlp_head.payload_length, + got: consumed, + }); + } -impl EIP1559Transaction { - pub fn nonce(&self) -> &U256 { - &self.nonce + *buf = *b; + Ok(this) } +} - pub fn hash(&self) -> H256 { - let encoded = rlp::encode(self); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = 2; - out[1..].copy_from_slice(&encoded); - H256::from_slice(keccak256(&out).as_slice()) +impl alloy_rlp::Encodable for DepositReceipt { + fn encode(&self, out: &mut dyn BufMut) { + self.encode_fields(out); } - /// Recovers the Ethereum address which was used to sign the transaction. - pub fn recover(&self) -> Result { - let mut sig = [0u8; 65]; - sig[0..32].copy_from_slice(&self.r[..]); - sig[32..64].copy_from_slice(&self.s[..]); - sig[64] = self.odd_y_parity as u8; - let signature = Signature::try_from(&sig[..])?; - signature.recover(EIP1559TransactionRequest::from(self.clone()).hash()) + fn length(&self) -> usize { + let payload_length = self.payload_len(); + payload_length + length_of_length(payload_length) } } -impl Encodable for EIP1559Transaction { - fn rlp_append(&self, s: &mut RlpStream) { - s.begin_list(12); - s.append(&self.chain_id); - s.append(&self.nonce); - s.append(&self.max_priority_fee_per_gas); - s.append(&self.max_fee_per_gas); - s.append(&self.gas_limit); - s.append(&self.kind); - s.append(&self.value); - s.append(&self.input.as_ref()); - s.append(&self.access_list); - s.append(&self.odd_y_parity); - s.append(&U256::from_big_endian(&self.r[..])); - s.append(&U256::from_big_endian(&self.s[..])); +impl alloy_rlp::Decodable for DepositReceipt { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::decode_receipt(buf) } } -impl Decodable for EIP1559Transaction { - fn decode(rlp: &Rlp) -> Result { - if rlp.item_count()? != 12 { - return Err(DecoderError::RlpIncorrectListLen) - } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum TypedReceipt> { + #[serde(rename = "0x0", alias = "0x00")] + Legacy(ReceiptWithBloom), + #[serde(rename = "0x1", alias = "0x01")] + EIP2930(ReceiptWithBloom), + #[serde(rename = "0x2", alias = "0x02")] + EIP1559(ReceiptWithBloom), + #[serde(rename = "0x3", alias = "0x03")] + EIP4844(ReceiptWithBloom), + #[serde(rename = "0x4", alias = "0x04")] + EIP7702(ReceiptWithBloom), + #[serde(rename = "0x7E", alias = "0x7e")] + Deposit(DepositReceipt), +} - Ok(Self { - chain_id: rlp.val_at(0)?, - nonce: rlp.val_at(1)?, - max_priority_fee_per_gas: rlp.val_at(2)?, - max_fee_per_gas: rlp.val_at(3)?, - gas_limit: rlp.val_at(4)?, - kind: rlp.val_at(5)?, - value: rlp.val_at(6)?, - input: rlp.val_at::>(7)?.into(), - access_list: rlp.val_at(8)?, - odd_y_parity: rlp.val_at(9)?, - r: { - let mut rarr = [0u8; 32]; - rlp.val_at::(10)?.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0u8; 32]; - rlp.val_at::(11)?.to_big_endian(&mut sarr); - H256::from(sarr) - }, - }) +impl TypedReceipt { + pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom { + match self { + Self::Legacy(r) | + Self::EIP1559(r) | + Self::EIP2930(r) | + Self::EIP4844(r) | + Self::EIP7702(r) => r, + Self::Deposit(r) => &r.inner, + } } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TransactionEssentials { - pub kind: TransactionKind, - pub input: Bytes, - pub nonce: U256, - pub gas_limit: U256, - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_gas: Option, - pub value: U256, - pub chain_id: Option, - pub access_list: AccessList, +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, + } + } } -/// Queued transaction -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct PendingTransaction { - /// The actual transaction - pub transaction: MaybeImpersonatedTransaction, - /// the recovered sender of this transaction - sender: Address, - /// hash of `transaction`, so it can easily be reused with encoding and hashing agan - hash: TxHash, +impl From>> for OtsReceipt { + fn from(value: TypedReceipt>) -> Self { + let r#type = match value { + TypedReceipt::Legacy(_) => 0x00, + TypedReceipt::EIP2930(_) => 0x01, + TypedReceipt::EIP1559(_) => 0x02, + TypedReceipt::EIP4844(_) => 0x03, + TypedReceipt::EIP7702(_) => 0x04, + TypedReceipt::Deposit(_) => 0x7E, + } as u8; + let receipt = ReceiptWithBloom::>::from(value); + let status = receipt.status(); + let cumulative_gas_used = receipt.cumulative_gas_used(); + let logs = receipt.logs().to_vec(); + let logs_bloom = receipt.logs_bloom; + + Self { status, cumulative_gas_used, logs: Some(logs), logs_bloom: Some(logs_bloom), r#type } + } } -// == impl PendingTransaction == - -impl PendingTransaction { - /// Creates a new pending transaction and tries to verify transaction and recover sender. - pub fn new(transaction: TypedTransaction) -> Result { - let sender = transaction.recover()?; - Ok(Self { hash: transaction.hash(), transaction: transaction.into(), sender }) +impl TypedReceipt { + pub fn cumulative_gas_used(&self) -> u64 { + self.as_receipt_with_bloom().cumulative_gas_used() } - /// Creates a new transaction with the given sender. - /// - /// In order to prevent collisions from multiple different impersonated accounts, we update the - /// transaction's hash with the address to make it unique. - /// - /// See: - #[cfg(feature = "impersonated-tx")] - pub fn with_impersonated(transaction: TypedTransaction, sender: Address) -> Self { - let hash = transaction.impersonated_hash(sender); - let transaction = MaybeImpersonatedTransaction::impersonated(transaction, sender); - Self { hash, transaction, sender } + pub fn logs_bloom(&self) -> &Bloom { + &self.as_receipt_with_bloom().logs_bloom } - pub fn nonce(&self) -> &U256 { - self.transaction.nonce() + pub fn logs(&self) -> &[Log] { + self.as_receipt_with_bloom().logs() } +} - pub fn hash(&self) -> &TxHash { - &self.hash +impl From> for TypedReceipt> { + fn from(value: ReceiptEnvelope) -> Self { + match value { + ReceiptEnvelope::Legacy(r) => Self::Legacy(r), + ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r), + ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r), + ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r), + _ => unreachable!(), + } } +} - pub fn sender(&self) -> &Address { - &self.sender - } +impl Encodable for TypedReceipt { + fn encode(&self, out: &mut dyn bytes::BufMut) { + match self { + Self::Legacy(r) => r.encode(out), + receipt => { + let payload_len = match receipt { + Self::EIP2930(r) => r.length() + 1, + Self::EIP1559(r) => r.length() + 1, + Self::EIP4844(r) => r.length() + 1, + Self::Deposit(r) => r.length() + 1, + _ => unreachable!("receipt already matched"), + }; - /// Converts the [PendingTransaction] into the [TxEnv] context that [`revm`](foundry_evm) - /// expects. - pub fn to_revm_tx_env(&self) -> TxEnv { - fn transact_to(kind: &TransactionKind) -> TransactTo { - match kind { - TransactionKind::Call(c) => TransactTo::Call((*c).into()), - TransactionKind::Create => TransactTo::Create(CreateScheme::Create), + match receipt { + Self::EIP2930(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 1u8.encode(out); + r.encode(out); + } + Self::EIP1559(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 2u8.encode(out); + r.encode(out); + } + Self::EIP4844(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 3u8.encode(out); + r.encode(out); + } + Self::Deposit(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 0x7Eu8.encode(out); + r.encode(out); + } + _ => unreachable!("receipt already matched"), + } } } + } +} - let caller = *self.sender(); - match &self.transaction.transaction { - TypedTransaction::Legacy(tx) => { - let chain_id = tx.chain_id(); - let LegacyTransaction { nonce, gas_price, gas_limit, value, kind, input, .. } = tx; - TxEnv { - caller: caller.into(), - transact_to: transact_to(kind), - data: input.0.clone(), - chain_id, - nonce: Some(nonce.as_u64()), - value: (*value).into(), - gas_price: (*gas_price).into(), - gas_priority_fee: None, - gas_limit: gas_limit.as_u64(), - access_list: vec![], +impl Decodable for TypedReceipt { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + use bytes::Buf; + use std::cmp::Ordering; + + // a receipt is either encoded as a string (non legacy) or a list (legacy). + // We should not consume the buffer if we are decoding a legacy receipt, so let's + // check if the first byte is between 0x80 and 0xbf. + let rlp_type = *buf + .first() + .ok_or(alloy_rlp::Error::Custom("cannot decode a receipt from empty bytes"))?; + + match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) { + Ordering::Less => { + // strip out the string header + let _header = Header::decode(buf)?; + let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom( + "typed receipt cannot be decoded from an empty slice", + ))?; + if receipt_type == 0x01 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP2930) + } else if receipt_type == 0x02 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP1559) + } else if receipt_type == 0x03 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP4844) + } else if receipt_type == 0x7E { + buf.advance(1); + ::decode(buf).map(TypedReceipt::Deposit) + } else { + Err(alloy_rlp::Error::Custom("invalid receipt type")) } } - TypedTransaction::EIP2930(tx) => { - let EIP2930Transaction { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list, - .. - } = tx; - TxEnv { - caller: caller.into(), - transact_to: transact_to(kind), - data: input.0.clone(), - chain_id: Some(*chain_id), - nonce: Some(nonce.as_u64()), - value: (*value).into(), - gas_price: (*gas_price).into(), - gas_priority_fee: None, - gas_limit: gas_limit.as_u64(), - access_list: to_revm_access_list(access_list.0.clone()), - } + Ordering::Equal => { + Err(alloy_rlp::Error::Custom("an empty list is not a valid receipt encoding")) } - TypedTransaction::EIP1559(tx) => { - let EIP1559Transaction { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, - gas_limit, - kind, - value, - input, - access_list, - .. - } = tx; - TxEnv { - caller: caller.into(), - transact_to: transact_to(kind), - data: input.0.clone(), - chain_id: Some(*chain_id), - nonce: Some(nonce.as_u64()), - value: (*value).into(), - gas_price: (*max_fee_per_gas).into(), - gas_priority_fee: Some((*max_priority_fee_per_gas).into()), - gas_limit: gas_limit.as_u64(), - access_list: to_revm_access_list(access_list.0.clone()), - } + Ordering::Greater => { + ::decode(buf).map(TypedReceipt::Legacy) } } } } -/// Represents all relevant information of an executed transaction -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct TransactionInfo { - pub transaction_hash: H256, - pub transaction_index: u32, - pub from: Address, - pub to: Option
, - pub contract_address: Option
, - pub logs: Vec, - pub logs_bloom: Bloom, - pub traces: CallTraceArena, - pub exit: InstructionResult, - pub out: Option, +impl Typed2718 for TypedReceipt { + fn ty(&self) -> u8 { + match self { + Self::Legacy(_) => alloy_consensus::constants::LEGACY_TX_TYPE_ID, + Self::EIP2930(_) => alloy_consensus::constants::EIP2930_TX_TYPE_ID, + Self::EIP1559(_) => alloy_consensus::constants::EIP1559_TX_TYPE_ID, + Self::EIP4844(_) => alloy_consensus::constants::EIP4844_TX_TYPE_ID, + Self::EIP7702(_) => alloy_consensus::constants::EIP7702_TX_TYPE_ID, + Self::Deposit(_) => DEPOSIT_TX_TYPE_ID, + } + } } -// === impl TransactionInfo === +impl Encodable2718 for TypedReceipt { + fn encode_2718_len(&self) -> usize { + match self { + Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(), + Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(), + Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(), + Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(), + Self::EIP7702(r) => 1 + r.length(), + Self::Deposit(r) => 1 + r.length(), + } + } -impl TransactionInfo { - /// Returns the `traceAddress` of the node in the arena - /// - /// The `traceAddress` field of all returned traces, gives the exact location in the call trace - /// [index in root, index in first CALL, index in second CALL, …]. - /// - /// # Panics - /// - /// if the `idx` does not belong to a node - pub fn trace_address(&self, idx: usize) -> Vec { - if idx == 0 { - // root call has empty traceAddress - return vec![] + fn encode_2718(&self, out: &mut dyn BufMut) { + if let Some(ty) = self.type_flag() { + out.put_u8(ty); + } + match self { + Self::Legacy(r) | + Self::EIP2930(r) | + Self::EIP1559(r) | + Self::EIP4844(r) | + Self::EIP7702(r) => r.encode(out), + Self::Deposit(r) => r.encode(out), + } + } +} + +impl Decodable2718 for TypedReceipt { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { + if ty == 0x7E { + return Ok(Self::Deposit(DepositReceipt::decode(buf)?)); + } + match ReceiptEnvelope::typed_decode(ty, buf)? { + ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), + ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), + ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + _ => unreachable!(), } - let mut graph = vec![]; - let mut node = &self.traces.arena[idx]; - while let Some(parent) = node.parent { - // the index of the child call in the arena - let child_idx = node.idx; - node = &self.traces.arena[parent]; - // find the index of the child call in the parent node - let call_idx = node - .children - .iter() - .position(|child| *child == child_idx) - .expect("child exists in parent"); - graph.push(call_idx); + } + + fn fallback_decode(buf: &mut &[u8]) -> Result { + match ReceiptEnvelope::fallback_decode(buf)? { + ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + _ => unreachable!(), } - graph.reverse(); - graph } } +pub type ReceiptResponse = TransactionReceipt>>; + +pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option { + let WithOtherFields { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type }, + }, + other, + } = receipt; + + Some(TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + inner: match r#type { + 0x00 => TypedReceipt::Legacy(receipt_with_bloom), + 0x01 => TypedReceipt::EIP2930(receipt_with_bloom), + 0x02 => TypedReceipt::EIP1559(receipt_with_bloom), + 0x03 => TypedReceipt::EIP4844(receipt_with_bloom), + 0x7E => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: other + .get_deserialized::("depositNonce") + .transpose() + .ok()? + .map(|v| v.to()), + deposit_receipt_version: other + .get_deserialized::("depositReceiptVersion") + .transpose() + .ok()? + .map(|v| v.to()), + }), + _ => return None, + }, + }) +} + #[cfg(test)] mod tests { + use alloy_primitives::{b256, hex, LogData}; + use std::str::FromStr; + use super::*; - use ethers_core::utils::hex; #[test] - fn can_recover_sender() { - let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); + fn test_decode_call() { + let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; + let decoded = TypedTransaction::decode(&mut &bytes_first[..]).unwrap(); - let tx: TypedTransaction = rlp::decode(&bytes).expect("decoding TypedTransaction failed"); - let tx = match tx { - TypedTransaction::Legacy(tx) => tx, - _ => panic!("Invalid typed transaction"), + let tx = TxLegacy { + nonce: 2u64, + gas_price: 1000000000u128, + gas_limit: 100000, + to: TxKind::Call(Address::from_slice( + &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], + )), + value: U256::from(1000000000000000u64), + input: Bytes::default(), + chain_id: Some(4), }; - assert_eq!(tx.input, Bytes::from(b"")); - assert_eq!(tx.gas_price, U256::from(0x01u64)); - assert_eq!(tx.gas_limit, U256::from(0x5208u64)); - assert_eq!(tx.nonce, U256::from(0x00u64)); - if let TransactionKind::Call(ref to) = tx.kind { - assert_eq!(*to, "095e7baea6a6c7c4c2dfeb977efac326af552d87".parse().unwrap()); - } else { - panic!(); - } - assert_eq!(tx.value, U256::from(0x0au64)); - assert_eq!( - tx.recover().unwrap(), - "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse().unwrap() - ); - } - #[test] - #[cfg(feature = "fastrlp")] - fn test_decode_fastrlp_create() { - use bytes::BytesMut; - use open_fastrlp::Encodable; - - // tests that a contract creation tx encodes and decodes properly - - let tx = TypedTransaction::EIP2930(EIP2930Transaction { - chain_id: 1u64, - nonce: U256::from(0), - gas_price: U256::from(1), - gas_limit: U256::from(2), - kind: TransactionKind::Create, - value: U256::from(3), - input: Bytes::from(vec![1, 2]), - odd_y_parity: true, - r: H256::default(), - s: H256::default(), - access_list: vec![].into(), - }); + let signature = PrimitiveSignature::from_str("0eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca182b").unwrap(); - let mut encoded = BytesMut::new(); - tx.encode(&mut encoded); + let tx = TypedTransaction::Legacy(Signed::new_unchecked( + tx, + signature, + b256!("a517b206d2223278f860ea017d3626cacad4f52ff51030dc9a96b432f17f8d34"), + )); - let decoded = - ::decode(&mut &*encoded).unwrap(); - assert_eq!(decoded, tx); + assert_eq!(tx, decoded); } #[test] - #[cfg(feature = "fastrlp")] - fn test_decode_fastrlp_create_goerli() { + fn test_decode_create_goerli() { // test that an example create tx from goerli decodes properly let tx_bytes = hex::decode("02f901ee05228459682f008459682f11830209bf8080b90195608060405234801561001057600080fd5b50610175806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80630c49c36c14610030575b600080fd5b61003861004e565b604051610045919061011d565b60405180910390f35b60606020600052600f6020527f68656c6c6f2073746174656d696e64000000000000000000000000000000000060405260406000f35b600081519050919050565b600082825260208201905092915050565b60005b838110156100be5780820151818401526020810190506100a3565b838111156100cd576000848401525b50505050565b6000601f19601f8301169050919050565b60006100ef82610084565b6100f9818561008f565b93506101098185602086016100a0565b610112816100d3565b840191505092915050565b6000602082019050818103600083015261013781846100e4565b90509291505056fea264697066735822122051449585839a4ea5ac23cae4552ef8a96b64ff59d0668f76bfac3796b2bdbb3664736f6c63430008090033c080a0136ebffaa8fc8b9fda9124de9ccb0b1f64e90fbd44251b4c4ac2501e60b104f9a07eb2999eec6d185ef57e91ed099afb0a926c5b536f0155dd67e537c7476e1471") .unwrap(); - let _decoded = - ::decode(&mut &tx_bytes[..]).unwrap(); + let _decoded = TypedTransaction::decode(&mut &tx_bytes[..]).unwrap(); } #[test] - #[cfg(feature = "fastrlp")] - fn test_decode_fastrlp_call() { - use bytes::BytesMut; - use open_fastrlp::Encodable; - - let tx = TypedTransaction::EIP2930(EIP2930Transaction { - chain_id: 1u64, - nonce: U256::from(0), - gas_price: U256::from(1), - gas_limit: U256::from(2), - kind: TransactionKind::Call(Address::default()), - value: U256::from(3), - input: Bytes::from(vec![1, 2]), - odd_y_parity: true, - r: H256::default(), - s: H256::default(), - access_list: vec![].into(), - }); + fn can_recover_sender() { + // random mainnet tx: https://etherscan.io/tx/0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f + let bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap(); - let mut encoded = BytesMut::new(); - tx.encode(&mut encoded); + let Ok(TypedTransaction::EIP1559(tx)) = TypedTransaction::decode(&mut &bytes[..]) else { + panic!("decoding TypedTransaction failed"); + }; - let decoded = - ::decode(&mut &*encoded).unwrap(); - assert_eq!(decoded, tx); + assert_eq!( + tx.hash(), + &"0x86718885c4b4218c6af87d3d0b0d83e3cc465df2a05c048aa4db9f1a6f9de91f" + .parse::() + .unwrap() + ); + assert_eq!( + tx.recover_signer().unwrap(), + "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5".parse::
().unwrap() + ); } + // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 #[test] - #[cfg(feature = "fastrlp")] - fn decode_transaction_consumes_buffer() { - let bytes = &mut &hex::decode("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469").unwrap()[..]; - let _transaction_res = - ::decode(bytes).unwrap(); + fn test_decode_live_4844_tx() { + use alloy_primitives::{address, b256}; + + // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap(); + let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap(); + assert_eq!(res.r#type(), Some(3)); + + let tx = match res { + TypedTransaction::EIP4844(tx) => tx, + _ => unreachable!(), + }; + + assert_eq!(tx.tx().tx().to, address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064")); + assert_eq!( - bytes.len(), - 0, - "did not consume all bytes in the buffer, {:?} remaining", - bytes.len() + tx.tx().tx().blob_versioned_hashes, + vec![ + b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"), + b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"), + b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"), + b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"), + b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549") + ] ); + + let from = tx.recover_signer().unwrap(); + assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); } #[test] - #[cfg(feature = "fastrlp")] - fn decode_multiple_network_txs() { - use std::str::FromStr; + fn test_decode_encode_deposit_tx() { + // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" + .parse::() + .unwrap(); - let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 2u64.into(), - gas_price: 1000000000u64.into(), - gas_limit: 100000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: 1000000000000000u64.into(), - input: Bytes::default(), - signature: Signature { - v: 43, - r: U256::from_str( - "eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5ae", - ) - .unwrap(), - s: U256::from_str( - "3a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18", - ) - .unwrap(), - }, - }); - assert_eq!( - expected, - ::decode(bytes_first).unwrap() - ); + // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let raw_tx = alloy_primitives::hex::decode( + "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", + ) + .unwrap(); + let dep_tx = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap(); - let bytes_second = &mut &hex::decode("f86b01843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac3960468702769bb01b2a00802ba0e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0aa05406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 1u64.into(), - gas_price: 1000000000u64.into(), - gas_limit: 100000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: 693361000000000u64.into(), - input: Bytes::default(), - signature: Signature { - v: 43, - r: U256::from_str( - "e24d8bd32ad906d6f8b8d7741e08d1959df021698b19ee232feba15361587d0a", - ) - .unwrap(), - s: U256::from_str( - "5406ad177223213df262cb66ccbb2f46bfdccfdfbbb5ffdda9e2c02d977631da", - ) - .unwrap(), - }, - }); + let mut encoded = Vec::new(); + dep_tx.encode_2718(&mut encoded); + + assert_eq!(raw_tx, encoded); + + assert_eq!(tx_hash, dep_tx.hash()); + } + + #[test] + fn can_recover_sender_not_normalized() { + let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); + + let Ok(TypedTransaction::Legacy(tx)) = TypedTransaction::decode(&mut &bytes[..]) else { + panic!("decoding TypedTransaction failed"); + }; + + assert_eq!(tx.tx().input, Bytes::from(b"")); + assert_eq!(tx.tx().gas_price, 1); + assert_eq!(tx.tx().gas_limit, 21000); + assert_eq!(tx.tx().nonce, 0); + if let TxKind::Call(to) = tx.tx().to { + assert_eq!( + to, + "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::
().unwrap() + ); + } else { + panic!("expected a call transaction"); + } + assert_eq!(tx.tx().value, U256::from(0x0au64)); assert_eq!( - expected, - ::decode(bytes_second).unwrap() + tx.recover_signer().unwrap(), + "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::
().unwrap() ); + } - let bytes_third = &mut &hex::decode("f86b0384773594008398968094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba0ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071a03ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 3u64.into(), - gas_price: 2000000000u64.into(), - gas_limit: 10000000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], - )), - value: 1000000000000000u64.into(), - input: Bytes::default(), - signature: Signature { - v: 43, - r: U256::from_str( - "ce6834447c0a4193c40382e6c57ae33b241379c5418caac9cdc18d786fd12071", - ) - .unwrap(), - s: U256::from_str( - "3ca3ae86580e94550d7c071e3a02eadb5a77830947c9225165cf9100901bee88", - ) - .unwrap(), + #[test] + fn encode_legacy_receipt() { + let expected = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let mut data = vec![]; + let receipt = TypedReceipt::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false.into(), + cumulative_gas_used: 0x1, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + Bytes::from_str("0100ff").unwrap(), + ), + }], }, + logs_bloom: [0; 256].into(), }); - assert_eq!( - expected, - ::decode(bytes_third).unwrap() - ); - let bytes_fourth = &mut &hex::decode("b87502f872041a8459682f008459682f0d8252089461815774383099e24810ab832a5b2a5425c154d58829a2241af62c000080c001a059e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafda0016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469").unwrap()[..]; - let expected = TypedTransaction::EIP1559(EIP1559Transaction { - chain_id: 4, - nonce: 26u64.into(), - max_priority_fee_per_gas: 1500000000u64.into(), - max_fee_per_gas: 1500000013u64.into(), - gas_limit: 21000u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("61815774383099e24810ab832a5b2a5425c154d5").unwrap()[..], - )), - value: 3000000000000000000u64.into(), - input: Bytes::default(), - access_list: AccessList::default(), - odd_y_parity: true, - r: H256::from_str("59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd") - .unwrap(), - s: H256::from_str("016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469") - .unwrap(), - }); - assert_eq!( - expected, - ::decode(bytes_fourth).unwrap() - ); + receipt.encode(&mut data); - let bytes_fifth = &mut &hex::decode("f8650f84832156008287fb94cf7f9e66af820a19257a2108375b180b0ec491678204d2802ca035b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981a0612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860").unwrap()[..]; - let expected = TypedTransaction::Legacy(LegacyTransaction { - nonce: 15u64.into(), - gas_price: 2200000000u64.into(), - gas_limit: 34811u64.into(), - kind: TransactionKind::Call(Address::from_slice( - &hex::decode("cf7f9e66af820a19257a2108375b180b0ec49167").unwrap()[..], - )), - value: 1234u64.into(), - input: Bytes::default(), - signature: Signature { - v: 44, - r: U256::from_str( - "35b7bfeb9ad9ece2cbafaaf8e202e706b4cfaeb233f46198f00b44d4a566a981", - ) - .unwrap(), - s: U256::from_str( - "612638fb29427ca33b9a3be2a0a561beecfe0269655be160d35e72d366a6a860", - ) - .unwrap(), + // check that the rlp length equals the length of the expected rlp + assert_eq!(receipt.length(), expected.len()); + assert_eq!(data, expected); + } + + #[test] + fn decode_legacy_receipt() { + let data = hex::decode("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff").unwrap(); + + let expected = TypedReceipt::Legacy(ReceiptWithBloom { + receipt: Receipt { + status: false.into(), + cumulative_gas_used: 0x1, + logs: vec![Log { + address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), + data: LogData::new_unchecked( + vec![ + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000dead", + ) + .unwrap(), + B256::from_str( + "000000000000000000000000000000000000000000000000000000000000beef", + ) + .unwrap(), + ], + Bytes::from_str("0100ff").unwrap(), + ), + }], }, + logs_bloom: [0; 256].into(), }); - assert_eq!( - expected, - ::decode(bytes_fifth).unwrap() - ); + + let receipt = TypedReceipt::decode(&mut &data[..]).unwrap(); + + assert_eq!(receipt, expected); } - // #[test] - fn test_recover_legacy_tx() { - let raw_tx = "f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8"; + fn deser_to_type_tx() { + let tx = r#" + { + "EIP1559": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + } + }"#; - let tx: TypedTransaction = rlp::decode(&hex::decode(raw_tx).unwrap()).unwrap(); - let recovered = tx.recover().unwrap(); - let expected: Address = "0xa12e1462d0ced572f396f58b6e2d03894cd7c8a4".parse().unwrap(); - assert_eq!(expected, recovered); + let _typed_tx: TypedTransaction = serde_json::from_str(tx).unwrap(); } } diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs new file mode 100644 index 0000000000000..6bb4b2abb8a4f --- /dev/null +++ b/crates/anvil/core/src/eth/transaction/optimism.rs @@ -0,0 +1,195 @@ +use alloy_primitives::{Address, Bytes, TxKind, B256, U256}; +use alloy_rlp::{Decodable, Encodable, Error as DecodeError, Header as RlpHeader}; +use op_alloy_consensus::TxDeposit; +use serde::{Deserialize, Serialize}; + +pub const DEPOSIT_TX_TYPE_ID: u8 = 0x7E; + +impl From for TxDeposit { + fn from(tx: DepositTransaction) -> Self { + Self { + from: tx.from, + source_hash: tx.source_hash, + to: tx.kind, + mint: Some(tx.mint.to::()), + value: tx.value, + gas_limit: tx.gas_limit, + is_system_transaction: tx.is_system_tx, + input: tx.input, + } + } +} + +/// An op-stack deposit transaction. +/// See +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct DepositTransaction { + pub nonce: u64, + pub source_hash: B256, + pub from: Address, + pub kind: TxKind, + pub mint: U256, + pub value: U256, + pub gas_limit: u64, + pub is_system_tx: bool, + pub input: Bytes, +} + +impl DepositTransaction { + pub fn nonce(&self) -> &u64 { + &self.nonce + } + + pub fn hash(&self) -> B256 { + let mut encoded = Vec::new(); + self.encode_2718(&mut encoded); + B256::from_slice(alloy_primitives::keccak256(encoded).as_slice()) + } + + // /// Recovers the Ethereum address which was used to sign the transaction. + pub fn recover(&self) -> Result { + Ok(self.from) + } + + pub fn chain_id(&self) -> Option { + None + } + + pub fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + out.put_u8(DEPOSIT_TX_TYPE_ID); + self.encode(out); + } + + /// Encodes only the transaction's fields into the desired buffer, without a RLP header. + pub(crate) fn encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) { + self.source_hash.encode(out); + self.from.encode(out); + self.kind.encode(out); + self.mint.encode(out); + self.value.encode(out); + self.gas_limit.encode(out); + self.is_system_tx.encode(out); + self.input.encode(out); + } + + /// Calculates the length of the RLP-encoded transaction's fields. + pub(crate) fn fields_len(&self) -> usize { + let mut len = 0; + len += self.source_hash.length(); + len += self.from.length(); + len += self.kind.length(); + len += self.mint.length(); + len += self.value.length(); + len += self.gas_limit.length(); + len += self.is_system_tx.length(); + len += self.input.length(); + len + } + + pub fn decode_2718(buf: &mut &[u8]) -> Result { + use bytes::Buf; + + let tx_type = *buf.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; + + if tx_type != DEPOSIT_TX_TYPE_ID { + return Err(alloy_rlp::Error::Custom("invalid tx type: expected deposit tx type")); + } + + // Skip the tx type byte + buf.advance(1); + Self::decode(buf) + } + + /// Decodes the inner fields from RLP bytes + /// + /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following + /// RLP fields in the following order: + /// + /// - `source_hash` + /// - `from` + /// - `kind` + /// - `mint` + /// - `value` + /// - `gas_limit` + /// - `is_system_tx` + /// - `input` + pub fn decode_inner(buf: &mut &[u8]) -> Result { + Ok(Self { + nonce: 0, + source_hash: Decodable::decode(buf)?, + from: Decodable::decode(buf)?, + kind: Decodable::decode(buf)?, + mint: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + is_system_tx: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + }) + } +} + +impl Encodable for DepositTransaction { + fn encode(&self, out: &mut dyn bytes::BufMut) { + RlpHeader { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } +} + +impl Decodable for DepositTransaction { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header = RlpHeader::decode(buf)?; + let remaining_len = buf.len(); + if header.payload_length > remaining_len { + return Err(alloy_rlp::Error::InputTooShort); + } + + Self::decode_inner(buf) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_encode_decode() { + let tx = DepositTransaction { + nonce: 0, + source_hash: B256::default(), + from: Address::default(), + kind: TxKind::Call(Address::default()), + mint: U256::from(100), + value: U256::from(100), + gas_limit: 50000, + is_system_tx: false, + input: Bytes::default(), + }; + + let encoded_tx: Vec = alloy_rlp::encode(&tx); + + let decoded_tx = DepositTransaction::decode(&mut encoded_tx.as_slice()).unwrap(); + + assert_eq!(tx, decoded_tx); + } + #[test] + fn test_encode_decode_2718() { + let tx = DepositTransaction { + nonce: 0, + source_hash: B256::default(), + from: Address::default(), + kind: TxKind::Call(Address::default()), + mint: U256::from(100), + value: U256::from(100), + gas_limit: 50000, + is_system_tx: false, + input: Bytes::default(), + }; + + let mut encoded_tx: Vec = Vec::new(); + tx.encode_2718(&mut encoded_tx); + + let decoded_tx = DepositTransaction::decode_2718(&mut encoded_tx.as_slice()).unwrap(); + + assert_eq!(tx, decoded_tx); + } +} diff --git a/crates/anvil/core/src/eth/trie.rs b/crates/anvil/core/src/eth/trie.rs index 6187c5087d950..59c5cd910a33e 100644 --- a/crates/anvil/core/src/eth/trie.rs +++ b/crates/anvil/core/src/eth/trie.rs @@ -1,42 +1,30 @@ -//! Utility functions for Ethereum adapted from https://github.dev/rust-blockchain/ethereum/blob/755dffaa4903fbec1269f50cde9863cf86269a14/src/util.rs -use ethers_core::types::H256; +//! Utility functions for Ethereum adapted from -pub use keccak_hasher::KeccakHasher; - -// reexport some trie types -pub use reference_trie::*; +use alloy_primitives::{fixed_bytes, B256}; +use alloy_trie::{HashBuilder, Nibbles}; +use std::collections::BTreeMap; /// The KECCAK of the RLP encoding of empty data. -pub const KECCAK_NULL_RLP: H256 = H256([ - 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, - 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, -]); - -/// Generates a trie root hash for a vector of key-value tuples -pub fn trie_root(input: I) -> H256 -where - I: IntoIterator, - K: AsRef<[u8]> + Ord, - V: AsRef<[u8]>, -{ - H256::from(triehash::trie_root::(input)) -} - -/// Generates a key-hashed (secure) trie root hash for a vector of key-value tuples. -pub fn sec_trie_root(input: I) -> H256 -where - I: IntoIterator, - K: AsRef<[u8]>, - V: AsRef<[u8]>, -{ - H256::from(triehash::sec_trie_root::(input)) -} +pub const KECCAK_NULL_RLP: B256 = + fixed_bytes!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); /// Generates a trie root hash for a vector of values -pub fn ordered_trie_root(input: I) -> H256 +pub fn ordered_trie_root(input: I) -> B256 where I: IntoIterator, V: AsRef<[u8]>, { - H256::from(triehash::ordered_trie_root::(input)) + let mut builder = HashBuilder::default(); + + let input = input + .into_iter() + .enumerate() + .map(|(i, v)| (alloy_rlp::encode(i), v)) + .collect::>(); + + for (key, value) in input { + builder.add_leaf(Nibbles::unpack(key), value.as_ref()); + } + + builder.root() } diff --git a/crates/anvil/core/src/eth/utils.rs b/crates/anvil/core/src/eth/utils.rs deleted file mode 100644 index f6a0f6f412b28..0000000000000 --- a/crates/anvil/core/src/eth/utils.rs +++ /dev/null @@ -1,34 +0,0 @@ -use ethers_core::{ - types::{transaction::eip2930::AccessListItem, Address, U256}, - utils::{ - rlp, - rlp::{Encodable, RlpStream}, - }, -}; -use foundry_evm::utils::{h160_to_b160, h256_to_u256_be, u256_to_ru256}; -use revm::primitives::{B160, U256 as rU256}; - -pub fn enveloped(id: u8, v: &T, s: &mut RlpStream) { - let encoded = rlp::encode(v); - let mut out = vec![0; 1 + encoded.len()]; - out[0] = id; - out[1..].copy_from_slice(&encoded); - out.rlp_append(s) -} - -pub fn to_access_list(list: Vec) -> Vec<(Address, Vec)> { - list.into_iter() - .map(|item| (item.address, item.storage_keys.into_iter().map(h256_to_u256_be).collect())) - .collect() -} - -pub fn to_revm_access_list(list: Vec) -> Vec<(B160, Vec)> { - list.into_iter() - .map(|item| { - ( - h160_to_b160(item.address), - item.storage_keys.into_iter().map(h256_to_u256_be).map(u256_to_ru256).collect(), - ) - }) - .collect() -} diff --git a/crates/anvil/core/src/eth/wallet.rs b/crates/anvil/core/src/eth/wallet.rs new file mode 100644 index 0000000000000..8676ec2fbf053 --- /dev/null +++ b/crates/anvil/core/src/eth/wallet.rs @@ -0,0 +1,79 @@ +use alloy_primitives::{map::HashMap, Address, ChainId, U64}; +use serde::{Deserialize, Serialize}; + +/// The capability to perform [EIP-7702][eip-7702] delegations, sponsored by the sequencer. +/// +/// The sequencer will only perform delegations, and act on behalf of delegated accounts, if the +/// account delegates to one of the addresses specified within this capability. +/// +/// [eip-7702]: https://eips.ethereum.org/EIPS/eip-7702 +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] +pub struct DelegationCapability { + /// A list of valid delegation contracts. + pub addresses: Vec
, +} + +/// Wallet capabilities for a specific chain. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] +pub struct Capabilities { + /// The capability to delegate. + pub delegation: DelegationCapability, +} + +/// A map of wallet capabilities per chain ID. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Default)] +pub struct WalletCapabilities(HashMap); + +impl WalletCapabilities { + /// Get the capabilities of the wallet API for the specified chain ID. + pub fn get(&self, chain_id: ChainId) -> Option<&Capabilities> { + self.0.get(&U64::from(chain_id)) + } + + pub fn insert(&mut self, chain_id: ChainId, capabilities: Capabilities) { + self.0.insert(U64::from(chain_id), capabilities); + } +} + +#[derive(Debug, thiserror::Error)] +pub enum WalletError { + /// The transaction value is not 0. + /// + /// The value should be 0 to prevent draining the sequencer. + #[error("tx value not zero")] + ValueNotZero, + /// The from field is set on the transaction. + /// + /// Requests with the from field are rejected, since it is implied that it will always be the + /// sequencer. + #[error("tx from field is set")] + FromSet, + /// The nonce field is set on the transaction. + /// + /// Requests with the nonce field set are rejected, as this is managed by the sequencer. + #[error("tx nonce is set")] + NonceSet, + /// An authorization item was invalid. + /// + /// The item is invalid if it tries to delegate an account to a contract that is not + /// whitelisted. + #[error("invalid authorization address")] + InvalidAuthorization, + /// The to field of the transaction was invalid. + /// + /// The destination is invalid if: + /// + /// - There is no bytecode at the destination, or + /// - The bytecode is not an EIP-7702 delegation designator, or + /// - The delegation designator points to a contract that is not whitelisted + #[error("the destination of the transaction is not a delegated account")] + IllegalDestination, + /// The transaction request was invalid. + /// + /// This is likely an internal error, as most of the request is built by the sequencer. + #[error("invalid tx request")] + InvalidTransactionRequest, + /// An internal error occurred. + #[error("internal error")] + InternalError, +} diff --git a/crates/anvil/core/src/lib.rs b/crates/anvil/core/src/lib.rs index a09e7cd684321..5d51d4691da7c 100644 --- a/crates/anvil/core/src/lib.rs +++ b/crates/anvil/core/src/lib.rs @@ -1,3 +1,10 @@ +//! # anvil-core +//! +//! Core Ethereum types for Anvil. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + /// Various Ethereum types pub mod eth; diff --git a/crates/anvil/core/src/types.rs b/crates/anvil/core/src/types.rs index 8725b9f4a384d..ca756358840aa 100644 --- a/crates/anvil/core/src/types.rs +++ b/crates/anvil/core/src/types.rs @@ -1,94 +1,17 @@ -use ethers_core::types::{H256, U256, U64}; -use revm::primitives::SpecId; +use alloy_primitives::{Bytes, B256, U256}; +use alloy_rpc_types::TransactionRequest; +use serde::Deserialize; #[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, PartialEq, Eq, Default)] -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, - 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 }) => { - Forking { json_rpc_url, block_number } - } - ForkingVariants::Tagged(f) => Forking { - 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( - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_u64_opt" - ) - )] - 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( - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_u64_opt" - ) - )] - Timestamp(Option), -} - -impl Default for EvmMineOptions { - fn default() -> Self { - EvmMineOptions::Options { timestamp: None, blocks: None } - } -} +use serde::Serializer; /// Represents the result of `eth_getWork` /// This may or may not include the block number -#[derive(Debug, PartialEq, Eq, Default)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct Work { - pub pow_hash: H256, - pub seed_hash: H256, - pub target: H256, + pub pow_hash: B256, + pub seed_hash: B256, + pub target: B256, pub number: Option, } @@ -106,96 +29,18 @@ impl serde::Serialize for Work { } } -/// A hex encoded or decimal index -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -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(Debug, Clone, Eq, PartialEq)] -#[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: H256, - pub hard_fork: SpecId, - pub transaction_order: String, - pub environment: NodeEnvironment, - pub fork_config: NodeForkConfig, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] -pub struct NodeEnvironment { - pub base_fee: U256, - pub chain_id: U256, - pub gas_limit: U256, - pub gas_price: U256, +/// Represents the options used in `anvil_reorg` +#[derive(Debug, Clone, Deserialize)] +pub struct ReorgOptions { + // The depth of the reorg + pub depth: u64, + // List of transaction requests and blocks pairs to be mined into the new chain + pub tx_block_pairs: Vec<(TransactionData, u64)>, } -#[derive(Debug, Clone, Default, Eq, PartialEq)] -#[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, +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum TransactionData { + JSON(TransactionRequest), + Raw(Bytes), } diff --git a/crates/anvil/rpc/Cargo.toml b/crates/anvil/rpc/Cargo.toml index 5a66eb8419d32..46a148ea42627 100644 --- a/crates/anvil/rpc/Cargo.toml +++ b/crates/anvil/rpc/Cargo.toml @@ -9,9 +9,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true -[dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = { version = "1.0" } +[lints] +workspace = true -[dev-dependencies] -rand = "0.8" +[dependencies] +serde.workspace = true +serde_json.workspace = true diff --git a/crates/anvil/rpc/src/error.rs b/crates/anvil/rpc/src/error.rs index eb95f8413ecab..4eec040acf59c 100644 --- a/crates/anvil/rpc/src/error.rs +++ b/crates/anvil/rpc/src/error.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{borrow::Cow, fmt}; /// Represents a JSON-RPC error -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcError { pub code: ErrorCode, @@ -14,68 +14,64 @@ pub struct RpcError { } impl RpcError { - /// New [Error] with the given [ErrorCode] + /// New [`RpcError`] with the given [`ErrorCode`]. pub const fn new(code: ErrorCode) -> Self { - RpcError { message: Cow::Borrowed(code.message()), code, data: None } + Self { message: Cow::Borrowed(code.message()), code, data: None } } - /// Creates a new `ParseError` + /// Creates a new `ParseError` error. pub const fn parse_error() -> Self { Self::new(ErrorCode::ParseError) } - /// Creates a new `MethodNotFound` + /// Creates a new `MethodNotFound` error. pub const fn method_not_found() -> Self { Self::new(ErrorCode::MethodNotFound) } - /// Creates a new `InvalidRequest` + /// Creates a new `InvalidRequest` error. pub const fn invalid_request() -> Self { Self::new(ErrorCode::InvalidRequest) } - /// Creates a new `InternalError` + /// Creates a new `InternalError` error. pub const fn internal_error() -> Self { Self::new(ErrorCode::InternalError) } - /// Creates a new `InvalidParams` + /// Creates a new `InvalidParams` error. pub fn invalid_params(message: M) -> Self where M: Into, { - RpcError { code: ErrorCode::InvalidParams, message: message.into().into(), data: None } + Self { code: ErrorCode::InvalidParams, message: message.into().into(), data: None } } - /// Creates a new `InternalError` with a message + /// Creates a new `InternalError` error with a message. pub fn internal_error_with(message: M) -> Self where M: Into, { - RpcError { code: ErrorCode::InternalError, message: message.into().into(), data: None } + Self { code: ErrorCode::InternalError, message: message.into().into(), data: None } } - /// Creates a new rpc error for when a transaction was rejected + /// Creates a new RPC error for when a transaction was rejected. pub fn transaction_rejected(message: M) -> Self where M: Into, { - RpcError { - code: ErrorCode::TransactionRejected, - message: message.into().into(), - data: None, - } + Self { code: ErrorCode::TransactionRejected, message: message.into().into(), data: None } } } impl fmt::Display for RpcError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}: {}", self.code.message(), self.message) } } /// List of JSON-RPC error codes -#[derive(Debug, Copy, PartialEq, Eq, Clone)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ErrorCode { /// Server received Invalid JSON. /// server side error while parsing JSON @@ -100,28 +96,28 @@ impl ErrorCode { /// Returns the error code as `i64` pub fn code(&self) -> i64 { match *self { - ErrorCode::ParseError => -32700, - ErrorCode::InvalidRequest => -32600, - ErrorCode::MethodNotFound => -32601, - ErrorCode::InvalidParams => -32602, - ErrorCode::InternalError => -32603, - ErrorCode::TransactionRejected => -32003, - ErrorCode::ExecutionError => 3, - ErrorCode::ServerError(c) => c, + Self::ParseError => -32700, + Self::InvalidRequest => -32600, + Self::MethodNotFound => -32601, + Self::InvalidParams => -32602, + Self::InternalError => -32603, + Self::TransactionRejected => -32003, + Self::ExecutionError => 3, + Self::ServerError(c) => c, } } /// Returns the message associated with the error pub const fn message(&self) -> &'static str { match *self { - ErrorCode::ParseError => "Parse error", - ErrorCode::InvalidRequest => "Invalid request", - ErrorCode::MethodNotFound => "Method not found", - ErrorCode::InvalidParams => "Invalid params", - ErrorCode::InternalError => "Internal error", - ErrorCode::TransactionRejected => "Transaction rejected", - ErrorCode::ServerError(_) => "Server error", - ErrorCode::ExecutionError => "Execution error", + Self::ParseError => "Parse error", + Self::InvalidRequest => "Invalid request", + Self::MethodNotFound => "Method not found", + Self::InvalidParams => "Invalid params", + Self::InternalError => "Internal error", + Self::TransactionRejected => "Transaction rejected", + Self::ServerError(_) => "Server error", + Self::ExecutionError => "Execution error", } } } @@ -136,7 +132,7 @@ impl Serialize for ErrorCode { } impl<'a> Deserialize<'a> for ErrorCode { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'a>, { @@ -147,14 +143,14 @@ impl<'a> Deserialize<'a> for ErrorCode { impl From for ErrorCode { fn from(code: i64) -> Self { match code { - -32700 => ErrorCode::ParseError, - -32600 => ErrorCode::InvalidRequest, - -32601 => ErrorCode::MethodNotFound, - -32602 => ErrorCode::InvalidParams, - -32603 => ErrorCode::InternalError, - -32003 => ErrorCode::TransactionRejected, - 3 => ErrorCode::ExecutionError, - _ => ErrorCode::ServerError(code), + -32700 => Self::ParseError, + -32600 => Self::InvalidRequest, + -32601 => Self::MethodNotFound, + -32602 => Self::InvalidParams, + -32603 => Self::InternalError, + -32003 => Self::TransactionRejected, + 3 => Self::ExecutionError, + _ => Self::ServerError(code), } } } diff --git a/crates/anvil/rpc/src/lib.rs b/crates/anvil/rpc/src/lib.rs index 939fbff1690b1..bd5382cee17ad 100644 --- a/crates/anvil/rpc/src/lib.rs +++ b/crates/anvil/rpc/src/lib.rs @@ -1,3 +1,10 @@ +//! # anvil-rpc +//! +//! JSON-RPC types. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + /// JSON-RPC request bindings pub mod request; diff --git a/crates/anvil/rpc/src/request.rs b/crates/anvil/rpc/src/request.rs index 8745cf2a3b09e..5cb8510b80f2b 100644 --- a/crates/anvil/rpc/src/request.rs +++ b/crates/anvil/rpc/src/request.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::fmt; /// A JSON-RPC request object, a method call -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcMethodCall { /// The version of the protocol @@ -26,7 +26,7 @@ impl RpcMethodCall { /// Represents a JSON-RPC request which is considered a notification (missing [Id] optional /// [Version]) -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcNotification { pub jsonrpc: Option, @@ -36,7 +36,7 @@ pub struct RpcNotification { } /// Representation of a single JSON-RPC call -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum RpcCall { /// the RPC method to invoke @@ -52,7 +52,7 @@ pub enum RpcCall { } /// Represents a JSON-RPC request. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Request { @@ -63,7 +63,7 @@ pub enum Request { } /// Request parameters -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged, deny_unknown_fields)] pub enum RequestParams { /// no parameters provided @@ -77,7 +77,7 @@ pub enum RequestParams { impl From for serde_json::Value { fn from(params: RequestParams) -> Self { match params { - RequestParams::None => serde_json::Value::Null, + RequestParams::None => Self::Null, RequestParams::Array(arr) => arr.into(), RequestParams::Object(obj) => obj.into(), } @@ -89,13 +89,13 @@ fn no_params() -> RequestParams { } /// Represents the version of the RPC protocol -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Version { #[serde(rename = "2.0")] V2, } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum Id { String(String), @@ -106,9 +106,9 @@ pub enum Id { impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Id::String(s) => s.fmt(f), - Id::Number(n) => n.fmt(f), - Id::Null => f.write_str("null"), + Self::String(s) => s.fmt(f), + Self::Number(n) => n.fmt(f), + Self::Null => f.write_str("null"), } } } diff --git a/crates/anvil/rpc/src/response.rs b/crates/anvil/rpc/src/response.rs index 8600996f46b6c..2fd01327d80a3 100644 --- a/crates/anvil/rpc/src/response.rs +++ b/crates/anvil/rpc/src/response.rs @@ -5,7 +5,7 @@ use crate::{ use serde::{Deserialize, Serialize}; /// Response of a _single_ rpc call -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct RpcResponse { // JSON RPC version @@ -24,7 +24,7 @@ impl From for RpcResponse { impl RpcResponse { pub fn new(id: Id, content: impl Into) -> Self { - RpcResponse { jsonrpc: Version::V2, id: Some(id), result: content.into() } + Self { jsonrpc: Version::V2, id: Some(id), result: content.into() } } pub fn invalid_request(id: Id) -> Self { @@ -33,7 +33,7 @@ impl RpcResponse { } /// Represents the result of a call either success or error -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum ResponseResult { #[serde(rename = "result")] @@ -47,21 +47,21 @@ impl ResponseResult { where S: Serialize + 'static, { - ResponseResult::Success(serde_json::to_value(&content).unwrap()) + Self::Success(serde_json::to_value(&content).unwrap()) } pub fn error(error: RpcError) -> Self { - ResponseResult::Error(error) + Self::Error(error) } } impl From for ResponseResult { fn from(err: RpcError) -> Self { - ResponseResult::error(err) + Self::error(err) } } /// Synchronous response -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] #[serde(untagged)] pub enum Response { @@ -72,7 +72,7 @@ pub enum Response { } impl Response { - /// Creates new [Response] with the given [Error] + /// Creates new [`Response`] with the given [`RpcError`]. pub fn error(error: RpcError) -> Self { RpcResponse::new(Id::Null, ResponseResult::Error(error)).into() } @@ -80,12 +80,12 @@ impl Response { impl From for Response { fn from(err: RpcError) -> Self { - Response::error(err) + Self::error(err) } } impl From for Response { fn from(resp: RpcResponse) -> Self { - Response::Single(resp) + Self::Single(resp) } } diff --git a/crates/anvil/server/Cargo.toml b/crates/anvil/server/Cargo.toml index ae70fcfee34bb..c8856939c21f3 100644 --- a/crates/anvil/server/Cargo.toml +++ b/crates/anvil/server/Cargo.toml @@ -10,35 +10,37 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] anvil-rpc = { path = "../rpc" } # axum related -axum = { version = "0.5", features = ["ws"] } -hyper = "0.14" -tower-http = { version = "0.4", features = ["trace", "cors"] } +axum = { workspace = true, features = ["ws"] } +tower-http = { workspace = true, features = ["trace", "cors"] } # tracing -tracing = "0.1" +tracing.workspace = true # async -parking_lot = "0.12" -futures = "0.3" +parking_lot.workspace = true +futures.workspace = true # ipc -parity-tokio-ipc = { version = "0.9", optional = true } -bytes = { version = "1.4", optional = true } +interprocess = { version = "2", optional = true, features = ["tokio"] } +bytes = { workspace = true, optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } # misc -serde_json = "1" -serde = { version = "1", features = ["derive"] } -async-trait = "0.1" -thiserror = "1" +serde_json.workspace = true +serde.workspace = true +async-trait.workspace = true +thiserror.workspace = true clap = { version = "4", features = ["derive", "env"], optional = true } pin-project = "1" [features] default = ["ipc"] -ipc = ["parity-tokio-ipc", "bytes", "tokio-util"] +ipc = ["dep:interprocess", "dep:bytes", "dep:tokio-util"] diff --git a/crates/anvil/server/src/config.rs b/crates/anvil/server/src/config.rs index 3e7170718809a..dd15959b113a6 100644 --- a/crates/anvil/server/src/config.rs +++ b/crates/anvil/server/src/config.rs @@ -3,59 +3,54 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::str::FromStr; /// Additional server options. -#[derive(Debug, Clone, Serialize, Deserialize)] -#[cfg_attr(feature = "clap", derive(clap::Parser), clap(next_help_heading = "Server options"))] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[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", - clap( - long, - help = "Set the CORS allow_origin", - default_value = "*", - name = "allow-origin", - value_name = "ALLOW_ORIGIN" - ) - )] + #[cfg_attr(feature = "clap", arg(long, default_value = "*"))] pub allow_origin: HeaderValueWrapper, - /// Whether to enable CORS - #[cfg_attr( - feature = "clap", - clap(long, help = "Disable CORS", conflicts_with = "allow-origin") - )] + + /// Disable CORS. + #[cfg_attr(feature = "clap", arg(long, conflicts_with = "allow_origin"))] pub no_cors: bool, -} -// === impl ServerConfig === + /// 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, + } } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct HeaderValueWrapper(pub HeaderValue); impl FromStr for HeaderValueWrapper { type Err = ::Err; fn from_str(s: &str) -> Result { - Ok(HeaderValueWrapper(s.parse()?)) + Ok(Self(s.parse()?)) } } @@ -94,6 +89,6 @@ impl From for HeaderValue { impl From for HeaderValueWrapper { fn from(header: HeaderValue) -> Self { - HeaderValueWrapper(header) + Self(header) } } diff --git a/crates/anvil/server/src/handler.rs b/crates/anvil/server/src/handler.rs index 07b808a4bb3f9..844cf6a46e0f1 100644 --- a/crates/anvil/server/src/handler.rs +++ b/crates/anvil/server/src/handler.rs @@ -5,27 +5,26 @@ use anvil_rpc::{ response::{Response, RpcResponse}, }; use axum::{ - extract::{rejection::JsonRejection, Extension}, + extract::{rejection::JsonRejection, State}, Json, }; use futures::{future, FutureExt}; -use tracing::{trace, warn}; -/// Handles incoming JSON-RPC Request -pub async fn handle( +/// Handles incoming JSON-RPC Request. +// NOTE: `handler` must come first because the `request` extractor consumes the request body. +pub async fn handle( + State((handler, _)): State<(Http, Ws)>, request: Result, JsonRejection>, - Extension(handler): Extension, ) -> Json { - match request { + Json(match request { + Ok(Json(req)) => handle_request(req, handler) + .await + .unwrap_or_else(|| Response::error(RpcError::invalid_request())), Err(err) => { warn!(target: "rpc", ?err, "invalid request"); - Response::error(RpcError::invalid_request()).into() + Response::error(RpcError::invalid_request()) } - Ok(req) => handle_request(req.0, handler) - .await - .unwrap_or_else(|| Response::error(RpcError::invalid_request())) - .into(), - } + }) } /// Handle the JSON-RPC [Request] diff --git a/crates/anvil/server/src/ipc.rs b/crates/anvil/server/src/ipc.rs index 4b34abbb7f249..392eb47acfd46 100644 --- a/crates/anvil/server/src/ipc.rs +++ b/crates/anvil/server/src/ipc.rs @@ -2,16 +2,15 @@ use crate::{error::RequestError, pubsub::PubSubConnection, PubSubRpcHandler}; use anvil_rpc::request::Request; -use bytes::BytesMut; +use bytes::{BufMut, BytesMut}; use futures::{ready, Sink, Stream, StreamExt}; -use parity_tokio_ipc::Endpoint; +use interprocess::local_socket::{self as ls, tokio::prelude::*}; use std::{ future::Future, io, pin::Pin, task::{Context, Poll}, }; -use tracing::{error, trace, warn}; /// An IPC connection for anvil /// @@ -19,55 +18,58 @@ use tracing::{error, trace, warn}; pub struct IpcEndpoint { /// the handler for the websocket connection handler: Handler, - /// The endpoint we listen for incoming transactions - endpoint: Endpoint, + /// The path to the socket + path: String, } impl IpcEndpoint { /// Creates a new endpoint with the given handler - pub fn new(handler: Handler, endpoint: impl Into) -> Self { - Self { handler, endpoint: Endpoint::new(endpoint.into()) } + pub fn new(handler: Handler, path: String) -> Self { + Self { handler, path } } - /// Returns a stream of incoming connection handlers + /// Returns a stream of incoming connection handlers. /// - /// This establishes the ipc endpoint, converts the incoming connections into handled eth - /// connections, See [`PubSubConnection`] that should be spawned - #[tracing::instrument(target = "ipc", skip_all)] + /// This establishes the IPC endpoint, converts the incoming connections into handled + /// connections. + #[instrument(target = "ipc", skip_all)] pub fn incoming(self) -> io::Result>> { - let IpcEndpoint { handler, endpoint } = self; - trace!( endpoint=?endpoint.path(), "starting ipc server" ); + let Self { handler, path } = self; + + trace!(%path, "starting IPC server"); if cfg!(unix) { // ensure the file does not exist - if std::fs::remove_file(endpoint.path()).is_ok() { - warn!( endpoint=?endpoint.path(), "removed existing file"); + if std::fs::remove_file(&path).is_ok() { + warn!(%path, "removed existing file"); } } - let connections = match endpoint.incoming() { - Ok(connections) => connections, - Err(err) => { - error!(?err, "Failed to create ipc listener"); - return Err(err) - } - }; + let name = to_name(path.as_ref())?; + let listener = ls::ListenerOptions::new().name(name).create_tokio()?; + let connections = futures::stream::unfold(listener, |listener| async move { + let conn = listener.accept().await; + Some((conn, listener)) + }); trace!("established connection listener"); - let connections = connections.filter_map(move |stream| { + Ok(connections.filter_map(move |stream| { let handler = handler.clone(); - Box::pin(async move { - if let Ok(stream) = stream { - trace!("successful incoming IPC connection"); - let framed = tokio_util::codec::Decoder::framed(JsonRpcCodec, stream); - Some(PubSubConnection::new(IpcConn(framed), handler)) - } else { - None + async move { + match stream { + Ok(stream) => { + trace!("successful incoming IPC connection"); + let framed = tokio_util::codec::Decoder::framed(JsonRpcCodec, stream); + Some(PubSubConnection::new(IpcConn(framed), handler)) + } + Err(err) => { + trace!(%err, "unsuccessful incoming IPC connection"); + None + } } - }) - }); - Ok(connections) + } + })) } } @@ -119,7 +121,7 @@ where struct JsonRpcCodec; -// Adapted from +// Adapted from impl tokio_util::codec::Decoder for JsonRpcCodec { type Item = String; type Error = io::Error; @@ -169,6 +171,16 @@ impl tokio_util::codec::Encoder for JsonRpcCodec { fn encode(&mut self, msg: String, buf: &mut BytesMut) -> io::Result<()> { buf.extend_from_slice(msg.as_bytes()); + // Add newline character + buf.put_u8(b'\n'); Ok(()) } } + +fn to_name(path: &std::ffi::OsStr) -> io::Result> { + if cfg!(windows) && !path.as_encoded_bytes().starts_with(br"\\.\pipe\") { + ls::ToNsName::to_ns_name::(path) + } else { + ls::ToFsName::to_fs_name::(path) + } +} diff --git a/crates/anvil/server/src/lib.rs b/crates/anvil/server/src/lib.rs index 3cd044c94b375..07567466772e5 100644 --- a/crates/anvil/server/src/lib.rs +++ b/crates/anvil/server/src/lib.rs @@ -1,6 +1,10 @@ -//! Bootstrap [axum] RPC servers +//! Bootstrap [axum] RPC servers. -#![deny(missing_docs, unsafe_code, unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; use anvil_rpc::{ error::RpcError, @@ -8,95 +12,71 @@ use anvil_rpc::{ response::{ResponseResult, RpcResponse}, }; use axum::{ - extract::Extension, + extract::DefaultBodyLimit, http::{header, HeaderValue, Method}, - routing::{post, IntoMakeService}, - Router, Server, + routing::{post, MethodRouter}, + Router, }; -use hyper::server::conn::AddrIncoming; use serde::de::DeserializeOwned; -use std::{fmt, net::SocketAddr}; +use std::fmt; use tower_http::{cors::CorsLayer, trace::TraceLayer}; -use tracing::{error, trace}; mod config; +pub use config::ServerConfig; mod error; -/// handlers for axum server mod handler; -#[cfg(feature = "ipc")] -pub mod ipc; + mod pubsub; -mod ws; +pub use pubsub::{PubSubContext, PubSubRpcHandler}; -pub use crate::pubsub::{PubSubContext, PubSubRpcHandler}; -pub use config::ServerConfig; +mod ws; -/// Type alias for the configured axum server -pub type AnvilServer = Server>; +#[cfg(feature = "ipc")] +pub mod ipc; -/// Configures an [axum::Server] that handles RPC-Calls, both HTTP requests and requests via -/// websocket -pub fn serve_http_ws( - addr: SocketAddr, - config: ServerConfig, - http: Http, - ws: Ws, -) -> AnvilServer +/// Configures an [`axum::Router`] that handles JSON-RPC calls via both HTTP and WS. +pub fn http_ws_router(config: ServerConfig, http: Http, ws: Ws) -> Router where Http: RpcHandler, Ws: PubSubRpcHandler, { - let ServerConfig { allow_origin, no_cors } = config; - - let svc = Router::new() - .route("/", post(handler::handle::).get(ws::handle_ws::)) - .layer(Extension(http)) - .layer(Extension(ws)) - .layer(TraceLayer::new_for_http()); - - let svc = if no_cors { - svc - } else { - svc.layer( - // see https://docs.rs/tower-http/latest/tower_http/cors/index.html - // for more details - CorsLayer::new() - .allow_origin(allow_origin.0) - .allow_headers(vec![header::CONTENT_TYPE]) - .allow_methods(vec![Method::GET, Method::POST]), - ) - } - .into_make_service(); - Server::bind(&addr).serve(svc) + router_inner(config, post(handler::handle).get(ws::handle_ws), (http, ws)) } -/// Configures an [axum::Server] that handles RPC-Calls listing for POST on `/` -pub fn serve_http(addr: SocketAddr, config: ServerConfig, http: Http) -> AnvilServer +/// Configures an [`axum::Router`] that handles JSON-RPC calls via HTTP. +pub fn http_router(config: ServerConfig, http: Http) -> Router where Http: RpcHandler, { - let ServerConfig { allow_origin, no_cors } = config; + router_inner(config, post(handler::handle), (http, ())) +} - let svc = Router::new() - .route("/", post(handler::handle::)) - .layer(Extension(http)) +fn router_inner( + config: ServerConfig, + root_method_router: MethodRouter, + state: S, +) -> Router { + let ServerConfig { allow_origin, no_cors, no_request_size_limit } = config; + + let mut router = Router::new() + .route("/", root_method_router) + .with_state(state) .layer(TraceLayer::new_for_http()); - let svc = if no_cors { - svc - } else { - svc.layer( - // see https://docs.rs/tower-http/latest/tower_http/cors/index.html - // for more details + if !no_cors { + // See [`tower_http::cors`](https://docs.rs/tower-http/latest/tower_http/cors/index.html) + // for more details. + router = router.layer( CorsLayer::new() .allow_origin(allow_origin.0) - .allow_headers(vec![header::CONTENT_TYPE]) - .allow_methods(vec![Method::GET, Method::POST]), - ) + .allow_headers([header::CONTENT_TYPE]) + .allow_methods([Method::GET, Method::POST]), + ); } - .into_make_service(); - - Server::bind(&addr).serve(svc) + if no_request_size_limit { + router = router.layer(DefaultBodyLimit::disable()); + } + router } /// Helper trait that is used to execute ethereum rpc calls @@ -117,7 +97,7 @@ pub trait RpcHandler: Clone + Send + Sync + 'static { /// **Note**: override this function if the expected `Request` deviates from `{ "method" : /// "", "params": "" }` async fn on_call(&self, call: RpcMethodCall) -> RpcResponse { - trace!(target: "rpc", id = ?call.id , method = ?call.method, "received method call"); + trace!(target: "rpc", id = ?call.id , method = ?call.method, params = ?call.params, "received method call"); let RpcMethodCall { method, params, id, .. } = call; let params: serde_json::Value = params.into(); diff --git a/crates/anvil/server/src/pubsub.rs b/crates/anvil/server/src/pubsub.rs index 1ceee635687d0..8e5ac9b3849bb 100644 --- a/crates/anvil/server/src/pubsub.rs +++ b/crates/anvil/server/src/pubsub.rs @@ -17,7 +17,6 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -use tracing::{error, trace}; /// The general purpose trait for handling RPC requests and subscriptions #[async_trait::async_trait] @@ -41,8 +40,6 @@ pub struct PubSubContext { subscriptions: Subscriptions, } -// === impl PubSubContext === - impl PubSubContext { /// Adds new active subscription /// @@ -126,8 +123,6 @@ pub struct PubSubConnection { pending: VecDeque, } -// === impl PubSubConnection === - impl PubSubConnection { pub fn new(connection: Connection, handler: Handler) -> Self { Self { @@ -171,8 +166,8 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pin = self.get_mut(); loop { - // drive the sink - while let Poll::Ready(Ok(())) = pin.connection.poll_ready_unpin(cx) { + // drive the websocket + 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) { @@ -183,6 +178,14 @@ where } } + // Ensure any pending messages are flushed + // this needs to be called manually for tungsenite websocket: + if let Poll::Ready(Err(err)) = pin.connection.poll_flush_unpin(cx) { + trace!(target: "rpc", ?err, "websocket err"); + // close the connection + return Poll::Ready(()) + } + loop { match pin.connection.poll_next_unpin(cx) { Poll::Ready(Some(req)) => match req { diff --git a/crates/anvil/server/src/ws.rs b/crates/anvil/server/src/ws.rs index 346e7ceb5dd33..e31652be5a205 100644 --- a/crates/anvil/server/src/ws.rs +++ b/crates/anvil/server/src/ws.rs @@ -3,25 +3,23 @@ use anvil_rpc::request::Request; use axum::{ extract::{ ws::{Message, WebSocket}, - WebSocketUpgrade, + State, WebSocketUpgrade, }, - response::IntoResponse, - Extension, + response::Response, }; use futures::{ready, Sink, Stream}; use std::{ pin::Pin, task::{Context, Poll}, }; -use tracing::trace; /// Handles incoming Websocket upgrade /// /// This is the entrypoint invoked by the axum server for a websocket request -pub async fn handle_ws( +pub async fn handle_ws( ws: WebSocketUpgrade, - Extension(handler): Extension, -) -> impl IntoResponse { + State((_, handler)): State<(Http, Ws)>, +) -> Response { ws.on_upgrade(|socket| PubSubConnection::new(SocketConn(socket), handler)) } diff --git a/crates/anvil/src/anvil.rs b/crates/anvil/src/anvil.rs index ebdb1fadb7975..e8e1b9edd5258 100644 --- a/crates/anvil/src/anvil.rs +++ b/crates/anvil/src/anvil.rs @@ -1,50 +1,73 @@ //! The `anvil` cli + use anvil::cmd::NodeArgs; use clap::{CommandFactory, Parser, Subcommand}; +use eyre::Result; +use foundry_cli::{handler, opts::GlobalArgs, utils}; +use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; + +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; /// A fast local Ethereum development node. -#[derive(Debug, Parser)] -#[clap(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)] -pub struct App { - #[clap(flatten)] +#[derive(Parser)] +#[command(name = "anvil", version = SHORT_VERSION, long_version = LONG_VERSION, next_display_order = None)] +pub struct Anvil { + /// Include the global arguments. + #[command(flatten)] + pub global: GlobalArgs, + + #[command(flatten)] pub node: NodeArgs, - #[clap(subcommand)] - pub cmd: Option, + #[command(subcommand)] + pub cmd: Option, } -#[derive(Clone, Debug, Subcommand, Eq, PartialEq)] -pub enum Commands { +#[derive(Subcommand)] +pub enum AnvilSubcommand { /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, } -#[tokio::main] -async fn main() -> Result<(), Box> { - let mut app = App::parse(); - app.node.evm_opts.resolve_rpc_alias(); +fn main() { + if let Err(err) = run() { + let _ = foundry_common::sh_err!("{err:?}"); + std::process::exit(1); + } +} - if let Some(ref cmd) = app.cmd { +fn run() -> Result<()> { + handler::install(); + utils::load_dotenv(); + utils::enable_paint(); + + let mut args = Anvil::parse(); + args.global.init()?; + args.node.evm.resolve_rpc_alias(); + + if let Some(cmd) = &args.cmd { match cmd { - Commands::Completions { shell } => { + AnvilSubcommand::Completions { shell } => { clap_complete::generate( *shell, - &mut App::command(), + &mut Anvil::command(), "anvil", &mut std::io::stdout(), ); } - Commands::GenerateFigSpec => clap_complete::generate( + AnvilSubcommand::GenerateFigSpec => clap_complete::generate( clap_complete_fig::Fig, - &mut App::command(), + &mut Anvil::command(), "anvil", &mut std::io::stdout(), ), @@ -53,23 +76,39 @@ async fn main() -> Result<(), Box> { } let _ = fdlimit::raise_fd_limit(); - app.node.run().await?; - - Ok(()) + tokio::runtime::Builder::new_multi_thread().enable_all().build()?.block_on(args.node.run()) } #[cfg(test)] mod tests { use super::*; + #[test] + fn verify_cli() { + Anvil::command().debug_assert(); + } + #[test] fn can_parse_help() { - let _: App = App::parse_from(["anvil", "--help"]); + let _: Anvil = Anvil::parse_from(["anvil", "--help"]); + } + + #[test] + fn can_parse_short_version() { + let _: Anvil = Anvil::parse_from(["anvil", "-V"]); + } + + #[test] + fn can_parse_long_version() { + let _: Anvil = Anvil::parse_from(["anvil", "--version"]); } #[test] fn can_parse_completions() { - let args: App = App::parse_from(["anvil", "completions", "bash"]); - assert_eq!(args.cmd, Some(Commands::Completions { shell: clap_complete::Shell::Bash })); + let args: Anvil = Anvil::parse_from(["anvil", "completions", "bash"]); + assert!(matches!( + args.cmd, + Some(AnvilSubcommand::Completions { shell: clap_complete::Shell::Bash }) + )); } } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 98c6b0d851570..0bf102e186b85 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -1,19 +1,23 @@ use crate::{ - config::DEFAULT_MNEMONIC, + config::{ForkChoice, DEFAULT_MNEMONIC}, eth::{backend::db::SerializableState, pool::transactions::TransactionOrder, EthApi}, - genesis::Genesis, - AccountGenerator, Hardfork, NodeConfig, CHAIN_ID, + hardfork::OptimismHardfork, + AccountGenerator, EthereumHardfork, NodeConfig, CHAIN_ID, }; +use alloy_genesis::Genesis; +use alloy_primitives::{utils::Unit, B256, U256}; +use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; use clap::Parser; use core::fmt; -use ethers::utils::WEI_IN_ETHER; -use foundry_config::{Chain, Config}; +use foundry_common::shell; +use foundry_config::{Chain, Config, FigmentProviders}; use futures::FutureExt; +use rand::{rngs::StdRng, SeedableRng}; use std::{ future::Future, net::IpAddr, - path::PathBuf, + path::{Path, PathBuf}, pin::Pin, str::FromStr, sync::{ @@ -24,61 +28,79 @@ use std::{ time::Duration, }; use tokio::time::{Instant, Interval}; -use tracing::{error, trace}; #[derive(Clone, Debug, Parser)] pub struct NodeArgs { /// Port number to listen on. - #[clap(long, short, default_value = "8545", value_name = "NUM")] + #[arg(long, short, default_value = "8545", value_name = "NUM")] pub port: u16, /// Number of dev accounts to generate and configure. - #[clap(long, short, default_value = "10", value_name = "NUM")] + #[arg(long, short, default_value = "10", value_name = "NUM")] pub accounts: u64, /// The balance of every dev account in Ether. - #[clap(long, default_value = "10000", value_name = "NUM")] + #[arg(long, default_value = "10000", value_name = "NUM")] pub balance: u64, /// The timestamp of the genesis block. - #[clap(long, value_name = "NUM")] + #[arg(long, value_name = "NUM")] pub timestamp: Option, /// BIP39 mnemonic phrase used for generating accounts. - #[clap(long, short)] + /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used. + #[arg(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])] pub mnemonic: Option, + /// Automatically generates a BIP39 mnemonic phrase, and derives accounts from it. + /// Cannot be used with other `mnemonic` options. + /// You can specify the number of words you want in the mnemonic. + /// [default: 12] + #[arg(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))] + pub mnemonic_random: Option, + + /// Generates a BIP39 mnemonic phrase from a given seed + /// Cannot be used with other `mnemonic` options. + /// + /// CAREFUL: This is NOT SAFE and should only be used for testing. + /// Never use the private keys generated in production. + #[arg(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])] + pub mnemonic_seed: Option, + /// Sets the derivation path of the child key to be derived. /// /// [default: m/44'/60'/0'/0/] - #[clap(long)] + #[arg(long)] pub derivation_path: Option, - /// Don't print anything on startup and don't print logs - #[clap(long)] - pub silent: bool, - /// The EVM hardfork to use. /// - /// Choose the hardfork by name, e.g. `shanghai`, `paris`, `london`, etc... + /// Choose the hardfork by name, e.g. `cancun`, `shanghai`, `paris`, `london`, etc... /// [default: latest] - #[clap(long, value_parser = Hardfork::from_str)] - pub hardfork: Option, + #[arg(long)] + pub hardfork: Option, /// Block time in seconds for interval mining. - #[clap(short, long, visible_alias = "blockTime", name = "block-time", value_name = "SECONDS")] - pub block_time: Option, + #[arg(short, long, visible_alias = "blockTime", value_name = "SECONDS", value_parser = duration_from_secs_f64)] + pub block_time: Option, + + /// Slots in an epoch + #[arg(long, value_name = "SLOTS_IN_AN_EPOCH", default_value_t = 32)] + pub slots_in_an_epoch: u64, /// Writes output of `anvil` as json to user-specified file. - #[clap(long, value_name = "OUT_FILE")] - pub config_out: Option, + #[arg(long, value_name = "FILE", value_hint = clap::ValueHint::FilePath)] + pub config_out: Option, /// Disable auto and interval mining, and mine on demand instead. - #[clap(long, visible_alias = "no-mine", conflicts_with = "block-time")] + #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")] pub no_mining: bool, + #[arg(long, visible_alias = "mixed-mining", requires = "block_time")] + pub mixed_mining: bool, + /// The hosts the server will listen on. - #[clap( + #[arg( long, value_name = "IP_ADDR", env = "ANVIL_IP_ADDR", @@ -89,18 +111,18 @@ pub struct NodeArgs { pub host: Vec, /// How transactions are sorted in the mempool. - #[clap(long, default_value = "fees")] + #[arg(long, default_value = "fees")] pub order: TransactionOrder, /// Initialize the genesis block with the given `genesis.json` file. - #[clap(long, value_name = "PATH", value_parser = Genesis::parse)] + #[arg(long, value_name = "PATH", value_parser= read_genesis_file)] pub init: Option, /// This is an alias for both --load-state and --dump-state. /// - /// It initializes the chain with the state stored at the file, if it exists, and dumps the - /// chain's state on exit. - #[clap( + /// It initializes the chain with the state and block environment stored at the file, if it + /// exists, and dumps the chain's state on exit. + #[arg( long, value_name = "PATH", value_parser = StateFile::parse, @@ -112,20 +134,29 @@ pub struct NodeArgs { )] pub state: Option, - /// Interval in seconds at which the status is to be dumped to disk. + /// Interval in seconds at which the state and block environment is to be dumped to disk. /// /// See --state and --dump-state - #[clap(short, long, value_name = "SECONDS")] + #[arg(short, long, value_name = "SECONDS")] pub state_interval: Option, - /// Dump the state of chain on exit to the given file. + /// Dump the state and block environment of chain on exit to the given file. /// /// If the value is a directory, the state will be written to `/state.json`. - #[clap(long, value_name = "PATH", conflicts_with = "init")] + #[arg(long, value_name = "PATH", conflicts_with = "init")] pub dump_state: Option, + /// Preserve historical state snapshots when dumping the state. + /// + /// This will save the in-memory states of the chain at particular block hashes. + /// + /// These historical states will be loaded into the memory when `--load-state` / `--state`, and + /// aids in RPC calls beyond the block at which state was dumped. + #[arg(long, conflicts_with = "init", default_value = "false")] + pub preserve_historical_states: bool, + /// Initialize the chain from a previously saved state snapshot. - #[clap( + #[arg( long, value_name = "PATH", value_parser = SerializableState::parse, @@ -133,23 +164,35 @@ pub struct NodeArgs { )] pub load_state: Option, - #[clap(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] + #[arg(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] pub ipc: Option>, /// Don't keep full chain history. /// If a number argument is specified, at most this number of states is kept in memory. - #[clap(long)] + /// + /// If enabled, no state will be persisted on disk, so `max_persisted_states` will be 0. + #[arg(long)] pub prune_history: Option>, + /// Max number of states to persist on disk. + /// + /// Note that `prune_history` will overwrite `max_persisted_states` to 0. + #[arg(long, conflicts_with = "prune_history")] + pub max_persisted_states: Option, + /// Number of blocks with transactions to keep in memory. - #[clap(long)] + #[arg(long)] pub transaction_block_keeper: Option, - #[clap(flatten)] - pub evm_opts: AnvilEvmArgs, + #[command(flatten)] + pub evm: AnvilEvmArgs, - #[clap(flatten)] + #[command(flatten)] pub server_config: ServerConfig, + + /// Path to the cache directory where states are stored. + #[arg(long, value_name = "PATH")] + pub cache_path: Option, } #[cfg(windows)] @@ -164,60 +207,91 @@ const IPC_HELP: &str = "Launch an ipc server at the given path or default path = const DEFAULT_DUMP_INTERVAL: Duration = Duration::from_secs(60); impl NodeArgs { - pub fn into_node_config(self) -> NodeConfig { - let genesis_balance = WEI_IN_ETHER.saturating_mul(self.balance.into()); - let compute_units_per_second = if self.evm_opts.no_rate_limit { - Some(u64::MAX) - } else { - self.evm_opts.compute_units_per_second + pub fn into_node_config(self) -> eyre::Result { + let genesis_balance = Unit::ETHER.wei().saturating_mul(U256::from(self.balance)); + let compute_units_per_second = + if self.evm.no_rate_limit { Some(u64::MAX) } else { self.evm.compute_units_per_second }; + + let hardfork = match &self.hardfork { + Some(hf) => { + if self.evm.optimism { + Some(OptimismHardfork::from_str(hf)?.into()) + } else { + Some(EthereumHardfork::from_str(hf)?.into()) + } + } + None => None, }; - NodeConfig::default() - .with_gas_limit(self.evm_opts.gas_limit) - .disable_block_gas_limit(self.evm_opts.disable_block_gas_limit) - .with_gas_price(self.evm_opts.gas_price) - .with_hardfork(self.hardfork) - .with_blocktime(self.block_time.map(Duration::from_secs)) + Ok(NodeConfig::default() + .with_gas_limit(self.evm.gas_limit) + .disable_block_gas_limit(self.evm.disable_block_gas_limit) + .with_gas_price(self.evm.gas_price) + .with_hardfork(hardfork) + .with_blocktime(self.block_time) .with_no_mining(self.no_mining) + .with_mixed_mining(self.mixed_mining, self.block_time) .with_account_generator(self.account_generator()) .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_chain_id(self.evm_opts.fork_chain_id) - .fork_request_timeout(self.evm_opts.fork_request_timeout.map(Duration::from_millis)) - .fork_request_retries(self.evm_opts.fork_request_retries) - .fork_retry_backoff(self.evm_opts.fork_retry_backoff.map(Duration::from_millis)) + .with_fork_choice(match (self.evm.fork_block_number, self.evm.fork_transaction_hash) { + (Some(block), None) => Some(ForkChoice::Block(block)), + (None, Some(hash)) => Some(ForkChoice::Transaction(hash)), + _ => self.evm.fork_url.as_ref().and_then(|f| f.block).map(ForkChoice::Block), + }) + .with_fork_headers(self.evm.fork_headers) + .with_fork_chain_id(self.evm.fork_chain_id.map(u64::from).map(U256::from)) + .fork_request_timeout(self.evm.fork_request_timeout.map(Duration::from_millis)) + .fork_request_retries(self.evm.fork_request_retries) + .fork_retry_backoff(self.evm.fork_retry_backoff.map(Duration::from_millis)) .fork_compute_units_per_second(compute_units_per_second) - .with_eth_rpc_url(self.evm_opts.fork_url.map(|fork| fork.url)) - .with_base_fee(self.evm_opts.block_base_fee_per_gas) - .with_storage_caching(self.evm_opts.no_storage_caching) + .with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url)) + .with_base_fee(self.evm.block_base_fee_per_gas) + .disable_min_priority_fee(self.evm.disable_min_priority_fee) + .with_storage_caching(self.evm.no_storage_caching) .with_server_config(self.server_config) .with_host(self.host) - .set_silent(self.silent) + .set_silent(shell::is_quiet()) .set_config_out(self.config_out) - .with_chain_id(self.evm_opts.chain_id) + .with_chain_id(self.evm.chain_id) .with_transaction_order(self.order) .with_genesis(self.init) - .with_steps_tracing(self.evm_opts.steps_tracing) - .with_auto_impersonate(self.evm_opts.auto_impersonate) + .with_steps_tracing(self.evm.steps_tracing) + .with_print_logs(!self.evm.disable_console_log) + .with_auto_impersonate(self.evm.auto_impersonate) .with_ipc(self.ipc) - .with_code_size_limit(self.evm_opts.code_size_limit) + .with_code_size_limit(self.evm.code_size_limit) + .disable_code_size_limit(self.evm.disable_code_size_limit) .set_pruned_history(self.prune_history) .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) + .with_max_persisted_states(self.max_persisted_states) + .with_optimism(self.evm.optimism) + .with_odyssey(self.evm.odyssey) + .with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer) + .with_slots_in_an_epoch(self.slots_in_an_epoch) + .with_memory_limit(self.evm.memory_limit) + .with_cache_path(self.cache_path)) } fn account_generator(&self) -> AccountGenerator { let mut gen = AccountGenerator::new(self.accounts as usize) .phrase(DEFAULT_MNEMONIC) - .chain_id(self.evm_opts.chain_id.unwrap_or_else(|| CHAIN_ID.into())); + .chain_id(self.evm.chain_id.unwrap_or_else(|| CHAIN_ID.into())); if let Some(ref mnemonic) = self.mnemonic { gen = gen.phrase(mnemonic); + } else if let Some(count) = self.mnemonic_random { + let mut rng = rand::thread_rng(); + let mnemonic = match Mnemonic::::new_with_count(&mut rng, count) { + Ok(mnemonic) => mnemonic.to_phrase(), + Err(_) => DEFAULT_MNEMONIC.to_string(), + }; + gen = gen.phrase(mnemonic); + } else if let Some(seed) = self.mnemonic_seed { + let mut seed = StdRng::seed_from_u64(seed); + let mnemonic = Mnemonic::::new(&mut seed).to_phrase(); + gen = gen.phrase(mnemonic); } if let Some(ref derivation) = self.derivation_path { gen = gen.derivation_path(derivation); @@ -233,15 +307,16 @@ impl NodeArgs { /// Starts the node /// /// See also [crate::spawn()] - pub async fn run(self) -> Result<(), Box> { + pub async fn run(self) -> eyre::Result<()> { let dump_state = self.dump_state_path(); let dump_interval = self.state_interval.map(Duration::from_secs).unwrap_or(DEFAULT_DUMP_INTERVAL); + let preserve_historical_states = self.preserve_historical_states; - let (api, mut handle) = crate::spawn(self.into_node_config()).await; + let (api, mut handle) = crate::try_spawn(self.into_node_config()?).await?; // sets the signal handler to gracefully shutdown. - let mut fork = api.get_fork().cloned(); + let mut fork = api.get_fork(); let running = Arc::new(AtomicUsize::new(0)); // handle for the currently running rt, this must be obtained before setting the crtlc @@ -251,7 +326,8 @@ impl NodeArgs { let task_manager = handle.task_manager(); let mut on_shutdown = task_manager.on_shutdown(); - let mut state_dumper = PeriodicStateDumper::new(api, dump_state, dump_interval); + let mut state_dumper = + PeriodicStateDumper::new(api, dump_state, dump_interval, preserve_historical_states); task_manager.spawn(async move { // wait for the SIGTERM signal on unix systems @@ -288,7 +364,11 @@ impl NodeArgs { // this will make sure that the fork RPC cache is flushed if caching is configured if let Some(fork) = fork.take() { trace!("flushing cache on shutdown"); - fork.database.read().await.flush_cache(); + fork.database + .read() + .await + .maybe_flush_cache() + .expect("Could not flush cache on fork DB"); // cleaning up and shutting down // this will make sure that the fork RPC cache is flushed if caching is configured } @@ -309,13 +389,13 @@ impl NodeArgs { } /// Anvil's EVM related arguments. -#[derive(Debug, Clone, Parser)] -#[clap(next_help_heading = "EVM options")] +#[derive(Clone, Debug, Parser)] +#[command(next_help_heading = "EVM options")] pub struct AnvilEvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument. - #[clap( + #[arg( long, short, visible_alias = "rpc-url", @@ -324,38 +404,51 @@ pub struct AnvilEvmArgs { )] pub fork_url: Option, - /// Timeout in ms for requests sent to remote JSON-RPC server in forking mode. + /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// - /// Default value 45000 - #[clap( - long = "timeout", - name = "timeout", + /// See --fork-url. + #[arg( + long = "fork-header", + value_name = "HEADERS", help_heading = "Fork config", requires = "fork_url" )] + pub fork_headers: Vec, + + /// Timeout in ms for requests sent to remote JSON-RPC server in forking mode. + /// + /// Default value 45000 + #[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")] pub fork_request_timeout: Option, /// Number of retry requests for spurious networks (timed out requests) /// /// Default value 5 - #[clap( - long = "retries", - name = "retries", - help_heading = "Fork config", - requires = "fork_url" - )] + #[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")] pub fork_request_retries: Option, /// Fetch state from a specific block number over a remote endpoint. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config")] + #[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. - #[clap(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")] pub fork_retry_backoff: Option, /// Specify chain id to skip fetching it from remote endpoint. This enables offline-start mode. @@ -363,7 +456,7 @@ pub struct AnvilEvmArgs { /// You still must pass both `--fork-url` and `--fork-block-number`, and already have your /// required state cached on disk, anything missing locally would be fetched from the /// remote. - #[clap( + #[arg( long, help_heading = "Fork config", value_name = "CHAIN", @@ -375,9 +468,8 @@ pub struct AnvilEvmArgs { /// /// default value: 330 /// - /// See --fork-url. - /// See also, https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups - #[clap( + /// See also --fork-url and + #[arg( long, requires = "fork_url", alias = "cups", @@ -390,9 +482,8 @@ pub struct AnvilEvmArgs { /// /// default value: false /// - /// See --fork-url. - /// See also, https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups - #[clap( + /// See also --fork-url and + #[arg( long, requires = "fork_url", value_name = "NO_RATE_LIMITS", @@ -408,15 +499,15 @@ pub struct AnvilEvmArgs { /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap(long, requires = "fork_url", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", help_heading = "Fork config")] pub no_storage_caching: bool, /// The block gas limit. - #[clap(long, alias = "block-gas-limit", help_heading = "Environment config")] - pub gas_limit: Option, + #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")] + pub gas_limit: Option, /// Disable the `call.gas_limit <= block.gas_limit` constraint. - #[clap( + #[arg( long, value_name = "DISABLE_GAS_LIMIT", help_heading = "Environment config", @@ -425,17 +516,26 @@ pub struct AnvilEvmArgs { )] pub disable_block_gas_limit: bool, - /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By - /// default, it is 0x6000 (~25kb). - #[clap(long, value_name = "CODE_SIZE", help_heading = "Environment config")] + /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. To + /// disable entirely, use `--disable-code-size-limit`. By default, it is 0x6000 (~25kb). + #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] pub code_size_limit: Option, + /// Disable EIP-170: Contract code size limit. + #[arg( + long, + value_name = "DISABLE_CODE_SIZE_LIMIT", + conflicts_with = "code_size_limit", + help_heading = "Environment config" + )] + pub disable_code_size_limit: bool, + /// The gas price. - #[clap(long, help_heading = "Environment config")] - pub gas_price: Option, + #[arg(long, help_heading = "Environment config")] + pub gas_price: Option, /// The base fee in a block. - #[clap( + #[arg( long, visible_alias = "base-fee", value_name = "FEE", @@ -443,17 +543,42 @@ pub struct AnvilEvmArgs { )] pub block_base_fee_per_gas: Option, + /// Disable the enforcement of a minimum suggested priority fee. + #[arg(long, visible_alias = "no-priority-fee", help_heading = "Environment config")] + pub disable_min_priority_fee: bool, + /// The chain ID. - #[clap(long, alias = "chain", help_heading = "Environment config")] + #[arg(long, alias = "chain", help_heading = "Environment config")] pub chain_id: Option, /// Enable steps tracing used for debug calls returning geth-style traces - #[clap(long, visible_alias = "tracing")] + #[arg(long, visible_alias = "tracing")] pub steps_tracing: bool, - /// Enable autoImpersonate on startup - #[clap(long, visible_alias = "auto-impersonate")] + /// Disable printing of `console.log` invocations to stdout. + #[arg(long, visible_alias = "no-console-log")] + pub disable_console_log: bool, + + /// Enables automatic impersonation on startup. This allows any transaction sender to be + /// simulated as different accounts, which is useful for testing contract behavior. + #[arg(long, visible_alias = "auto-unlock")] pub auto_impersonate: bool, + + /// Run an Optimism chain + #[arg(long, visible_alias = "optimism")] + pub optimism: bool, + + /// Disable the default create2 deployer + #[arg(long, visible_alias = "no-create2")] + pub disable_default_create2_deployer: bool, + + /// The memory limit per EVM execution in bytes. + #[arg(long)] + pub memory_limit: Option, + + /// Enable Odyssey features + #[arg(long, alias = "alphanet")] + pub odyssey: bool, } /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section @@ -462,9 +587,10 @@ pub struct AnvilEvmArgs { impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Some(fork_url) = &self.fork_url { - let config = Config::load(); - if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { - self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); + if let Ok(config) = Config::load_with_providers(FigmentProviders::Anvil) { + if let Some(Ok(url)) = config.get_rpc_url_with_alias(&fork_url.url) { + self.fork_url = Some(ForkUrl { url: url.to_string(), block: fork_url.block }); + } } } } @@ -475,11 +601,17 @@ struct PeriodicStateDumper { in_progress_dump: Option + Send + Sync + 'static>>>, api: EthApi, dump_state: Option, + preserve_historical_states: bool, interval: Interval, } impl PeriodicStateDumper { - fn new(api: EthApi, dump_state: Option, interval: Duration) -> Self { + fn new( + api: EthApi, + dump_state: Option, + interval: Duration, + preserve_historical_states: bool, + ) -> Self { let dump_state = dump_state.map(|mut dump_state| { if dump_state.is_dir() { dump_state = dump_state.join("state.json"); @@ -489,19 +621,19 @@ impl PeriodicStateDumper { // periodically flush the state let interval = tokio::time::interval_at(Instant::now() + interval, interval); - Self { in_progress_dump: None, api, dump_state, interval } + Self { in_progress_dump: None, api, dump_state, preserve_historical_states, interval } } async fn dump(&self) { if let Some(state) = self.dump_state.clone() { - Self::dump_state(self.api.clone(), state).await + Self::dump_state(self.api.clone(), state, self.preserve_historical_states).await } } /// Infallible state dump - async fn dump_state(api: EthApi, dump_state: PathBuf) { + async fn dump_state(api: EthApi, dump_state: PathBuf, preserve_historical_states: bool) { trace!(path=?dump_state, "Dumping state on shutdown"); - match api.serialized_state().await { + match api.serialized_state(preserve_historical_states).await { Ok(state) => { if let Err(err) = foundry_common::fs::write_json_file(&dump_state, &state) { error!(?err, "Failed to dump state"); @@ -542,7 +674,8 @@ impl Future for PeriodicStateDumper { if this.interval.poll_tick(cx).is_ready() { let api = this.api.clone(); let path = this.dump_state.clone().expect("exists; see above"); - this.in_progress_dump = Some(Box::pin(PeriodicStateDumper::dump_state(api, path))); + this.in_progress_dump = + Some(Box::pin(Self::dump_state(api, path, this.preserve_historical_states))); } else { break } @@ -553,7 +686,7 @@ impl Future for PeriodicStateDumper { } /// Represents the --state flag and where to load from, or dump the state to -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct StateFile { pub path: PathBuf, pub state: Option, @@ -563,7 +696,12 @@ impl StateFile { /// This is used as the clap `value_parser` implementation to parse from file but only if it /// exists fn parse(path: &str) -> Result { - let mut path = PathBuf::from(path); + Self::parse_path(path) + } + + /// Parse from file but only if it exists + pub fn parse_path(path: impl AsRef) -> Result { + let mut path = path.as_ref().to_path_buf(); if path.is_dir() { path = path.join("state.json"); } @@ -580,7 +718,7 @@ impl StateFile { /// Represents the input URL for a fork with an optional trailing block number: /// `http://localhost:8545@1000000` -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ForkUrl { /// The endpoint url pub url: String, @@ -604,25 +742,39 @@ impl FromStr for ForkUrl { fn from_str(s: &str) -> Result { if let Some((url, block)) = s.rsplit_once('@') { if block == "latest" { - return Ok(ForkUrl { url: url.to_string(), block: None }) + return Ok(Self { url: url.to_string(), block: None }) } // this will prevent false positives for auths `user:password@example.com` if !block.is_empty() && !block.contains(':') && !block.contains('.') { let block: u64 = block .parse() .map_err(|_| format!("Failed to parse block number: `{block}`"))?; - return Ok(ForkUrl { url: url.to_string(), block: Some(block) }) + return Ok(Self { url: url.to_string(), block: Some(block) }) } } - Ok(ForkUrl { url: s.to_string(), block: None }) + Ok(Self { url: s.to_string(), block: None }) } } +/// Clap's value parser for genesis. Loads a genesis.json file. +fn read_genesis_file(path: &str) -> Result { + foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string()) +} + +fn duration_from_secs_f64(s: &str) -> Result { + let s = s.parse::().map_err(|e| e.to_string())?; + if s == 0.0 { + return Err("Duration must be greater than 0".to_string()); + } + Duration::try_from_secs_f64(s).map_err(|e| e.to_string()) +} + #[cfg(test)] mod tests { - use std::{env, net::Ipv4Addr}; + use crate::EthereumHardfork; use super::*; + use std::{env, net::Ipv4Addr}; #[test] fn test_parse_fork_url() { @@ -655,9 +807,39 @@ mod tests { } #[test] - fn can_parse_hardfork() { + fn can_parse_ethereum_hardfork() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "berlin"]); - assert_eq!(args.hardfork, Some(Hardfork::Berlin)); + let config = args.into_node_config().unwrap(); + assert_eq!(config.hardfork, Some(EthereumHardfork::Berlin.into())); + } + + #[test] + fn can_parse_optimism_hardfork() { + let args: NodeArgs = + NodeArgs::parse_from(["anvil", "--optimism", "--hardfork", "Regolith"]); + let config = args.into_node_config().unwrap(); + assert_eq!(config.hardfork, Some(OptimismHardfork::Regolith.into())); + } + + #[test] + fn cant_parse_invalid_hardfork() { + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--hardfork", "Regolith"]); + let config = args.into_node_config(); + assert!(config.is_err()); + } + + #[test] + fn can_parse_fork_headers() { + let args: NodeArgs = NodeArgs::parse_from([ + "anvil", + "--fork-url", + "http,://localhost:8545", + "--fork-header", + "User-Agent: test-agent", + "--fork-header", + "Referrer: example.com", + ]); + assert_eq!(args.evm.fork_headers, vec!["User-Agent: test-agent", "Referrer: example.com"]); } #[test] @@ -669,16 +851,37 @@ mod tests { assert_eq!(args.prune_history, Some(Some(100))); } + #[test] + fn can_parse_max_persisted_states_config() { + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--max-persisted-states", "500"]); + assert_eq!(args.max_persisted_states, (Some(500))); + } + #[test] fn can_parse_disable_block_gas_limit() { let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-block-gas-limit"]); - assert!(args.evm_opts.disable_block_gas_limit); + assert!(args.evm.disable_block_gas_limit); let args = NodeArgs::try_parse_from(["anvil", "--disable-block-gas-limit", "--gas-limit", "100"]); assert!(args.is_err()); } + #[test] + fn can_parse_disable_code_size_limit() { + let args: NodeArgs = NodeArgs::parse_from(["anvil", "--disable-code-size-limit"]); + assert!(args.evm.disable_code_size_limit); + + let args = NodeArgs::try_parse_from([ + "anvil", + "--disable-code-size-limit", + "--code-size-limit", + "100", + ]); + // can't be used together + assert!(args.is_err()); + } + #[test] fn can_parse_host() { let args = NodeArgs::parse_from(["anvil"]); diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index f32aaf1ad8a8e..9b0eaa56e088c 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -1,4 +1,5 @@ use crate::{ + cmd::StateFile, eth::{ backend::{ db::{Db, SerializableState}, @@ -8,72 +9,68 @@ use crate::{ time::duration_since_unix_epoch, }, fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, - pool::transactions::TransactionOrder, + pool::transactions::{PoolTransaction, TransactionOrder}, }, - genesis::Genesis, - mem, - mem::in_memory_db::MemDb, - FeeManager, Hardfork, + hardfork::{ChainHardfork, OptimismHardfork}, + mem::{self, in_memory_db::MemDb}, + EthereumHardfork, FeeManager, PrecompileFactory, }; -use anvil_server::ServerConfig; -use ethers::{ - core::k256::ecdsa::SigningKey, - prelude::{rand::thread_rng, Wallet, U256}, - providers::Middleware, - signers::{ - coins_bip39::{English, Mnemonic}, - MnemonicBuilder, Signer, - }, - types::BlockNumber, - utils::{format_ether, hex, to_checksum, WEI_IN_ETHER}, +use alloy_consensus::BlockHeader; +use alloy_genesis::Genesis; +use alloy_network::{AnyNetwork, TransactionResponse}; +use alloy_primitives::{hex, map::HashMap, utils::Unit, BlockNumber, TxHash, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{Block, BlockNumberOrTag}; +use alloy_signer::Signer; +use alloy_signer_local::{ + coins_bip39::{English, Mnemonic}, + MnemonicBuilder, PrivateKeySigner, }; +use alloy_transport::TransportError; +use anvil_server::ServerConfig; +use eyre::{Context, Result}; use foundry_common::{ - 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::{ - executor::fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, - revm, - revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv, U256 as rU256}, - utils::{apply_chain_and_block_specific_env_changes, h256_to_b256, u256_to_ru256}, + backend::{BlockchainDb, BlockchainDbMeta, SharedBackend}, + constants::DEFAULT_CREATE2_DEPLOYER, + revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}, + utils::apply_chain_and_block_specific_env_changes, }; +use itertools::Itertools; use parking_lot::RwLock; -use serde_json::{json, to_writer, Value}; +use rand::thread_rng; +use revm::primitives::BlobExcessGasAndPrice; +use serde_json::{json, Value}; use std::{ - collections::HashMap, fmt::Write as FmtWrite, fs::File, + io, net::{IpAddr, Ipv4Addr}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, time::Duration, }; +use tokio::sync::RwLock as TokioRwLock; use yansi::Paint; +pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE; + /// Default port the rpc will open pub const NODE_PORT: u16 = 8545; /// Default chain id of the node pub const CHAIN_ID: u64 = 31337; +/// The default gas limit for all transactions +pub const DEFAULT_GAS_LIMIT: u128 = 30_000_000; /// Default mnemonic for dev accounts pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk"; /// The default IPC endpoint -#[cfg(windows)] -pub const DEFAULT_IPC_ENDPOINT: &str = r"\\.\pipe\anvil.ipc"; - -/// The default IPC endpoint -#[cfg(not(windows))] -pub const DEFAULT_IPC_ENDPOINT: &str = "/tmp/anvil.ipc"; - -/// `anvil 0.1.0 (f01b232bc 2022-04-13T23:28:39.493201+00:00)` -pub const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); +pub const DEFAULT_IPC_ENDPOINT: &str = + if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" }; const BANNER: &str = r" _ _ @@ -85,42 +82,48 @@ const BANNER: &str = r" "; /// Configurations of the EVM node -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct NodeConfig { /// Chain ID of the EVM chain pub chain_id: Option, /// Default gas limit for all txs - pub gas_limit: U256, + pub gas_limit: Option, /// If set to `true`, disables the block gas limit pub disable_block_gas_limit: bool, /// Default gas price for all txs - pub gas_price: Option, + pub gas_price: Option, /// Default base fee - pub base_fee: Option, + pub base_fee: Option, + /// If set to `true`, disables the enforcement of a minimum suggested priority fee + pub disable_min_priority_fee: bool, + /// Default blob excess gas and price + pub blob_excess_gas_and_price: Option, /// The hardfork to use - pub hardfork: Option, + pub hardfork: Option, /// Signer accounts that will be initialised with `genesis_balance` in the genesis block - pub genesis_accounts: Vec>, + pub genesis_accounts: Vec, /// Native token balance of every genesis account in the genesis block pub genesis_balance: U256, /// Genesis block timestamp pub genesis_timestamp: Option, /// Signer accounts that can sign messages/transactions from the EVM node - pub signer_accounts: Vec>, + pub signer_accounts: Vec, /// Configured block time for the EVM chain. Use `None` to mine a new block for every tx pub block_time: Option, /// Disable auto, interval mining mode uns use `MiningMode::None` instead pub no_mining: bool, + /// Enables auto and interval mining mode + pub mixed_mining: bool, /// port to use for the server pub port: u16, /// maximum number of transactions in a block pub max_transactions: usize, - /// don't print anything on startup - 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 pub fork_chain_id: Option, /// The generator used to generate the dev accounts @@ -136,7 +139,7 @@ pub struct NodeConfig { /// How transactions are sorted in the mempool pub transaction_order: TransactionOrder, /// Filename to write anvil output as json - pub config_out: Option, + pub config_out: Option, /// The genesis to use to initialize the node pub genesis: Option, /// Timeout in for requests sent to remote JSON-RPC server in forking mode @@ -151,6 +154,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 @@ -159,42 +164,52 @@ pub struct NodeConfig { /// /// If set to `Some(num)` keep latest num state in memory only. pub prune_history: PruneStateHistoryConfig, + /// Max number of states cached on disk. + pub max_persisted_states: Option, /// The file where to load the state from pub init_state: Option, /// max number of blocks with transactions in memory pub transaction_block_keeper: Option, + /// Disable the default CREATE2 deployer + pub disable_default_create2_deployer: bool, + /// Enable Optimism deposit transaction + pub enable_optimism: bool, + /// Slots in an epoch + pub slots_in_an_epoch: u64, + /// The memory limit per EVM execution in bytes. + pub memory_limit: Option, + /// Factory used by `anvil` to extend the EVM's precompiles. + pub precompile_factory: Option>, + /// Enable Odyssey features. + pub odyssey: bool, + /// Do not print log messages. + pub silent: bool, + /// The path where states are cached. + pub cache_path: Option, } impl NodeConfig { fn as_string(&self, fork: Option<&ClientFork>) -> String { - let mut config_string: String = "".to_owned(); - let _ = write!(config_string, "\n{}", Paint::green(BANNER)); - let _ = write!(config_string, "\n {VERSION_MESSAGE}"); - let _ = write!( - config_string, - "\n {}", - Paint::green("https://github.com/foundry-rs/foundry") - ); + let mut s: String = String::new(); + let _ = write!(s, "\n{}", BANNER.green()); + let _ = write!(s, "\n {VERSION_MESSAGE}"); + let _ = write!(s, "\n {}", "https://github.com/foundry-rs/foundry".green()); let _ = write!( - config_string, + s, r#" Available Accounts ================== "# ); - let balance = format_ether(self.genesis_balance); + let balance = alloy_primitives::utils::format_ether(self.genesis_balance); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { - let _ = write!( - config_string, - "\n({idx}) {:?} ({balance} ETH)", - to_checksum(&wallet.address(), None) - ); + write!(s, "\n({idx}) {} ({balance} ETH)", wallet.address()).unwrap(); } let _ = write!( - config_string, + s, r#" Private Keys @@ -203,13 +218,13 @@ Private Keys ); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { - let hex = hex::encode(wallet.signer().to_bytes()); - let _ = write!(config_string, "\n({idx}) 0x{hex}"); + let hex = hex::encode(wallet.credential().to_bytes()); + let _ = write!(s, "\n({idx}) 0x{hex}"); } if let Some(ref gen) = self.account_generator { let _ = write!( - config_string, + s, r#" Wallet @@ -224,7 +239,7 @@ Derivation path: {} if let Some(fork) = fork { let _ = write!( - config_string, + s, r#" Fork @@ -239,62 +254,84 @@ Chain ID: {} fork.block_hash(), fork.chain_id() ); + + if let Some(tx_hash) = fork.transaction_hash() { + let _ = writeln!(s, "Transaction hash: {tx_hash}"); + } } else { let _ = write!( - config_string, + s, r#" Chain ID ================== + {} "#, - Paint::green(format!("\n{}", self.get_chain_id())) + self.get_chain_id().green() ); } if (SpecId::from(self.get_hardfork()) as u8) < (SpecId::LONDON as u8) { let _ = write!( - config_string, + s, r#" Gas Price ================== + {} "#, - Paint::green(format!("\n{}", self.get_gas_price())) + self.get_gas_price().green() ); } else { let _ = write!( - config_string, + s, r#" Base Fee ================== + {} "#, - Paint::green(format!("\n{}", self.get_base_fee())) + self.get_base_fee().green() ); } let _ = write!( - config_string, + s, r#" Gas Limit ================== + {} "#, - Paint::green(format!("\n{}", self.gas_limit)) + { + if self.disable_block_gas_limit { + "Disabled".to_string() + } else { + self.gas_limit.map(|l| l.to_string()).unwrap_or_else(|| { + if self.fork_choice.is_some() { + "Forked".to_string() + } else { + DEFAULT_GAS_LIMIT.to_string() + } + }) + } + } + .green() ); let _ = write!( - config_string, + s, r#" Genesis Timestamp ================== + {} "#, - Paint::green(format!("\n{}", self.get_genesis_timestamp())) + self.get_genesis_timestamp().green() ); - config_string + s } fn as_json(&self, fork: Option<&ClientFork>) -> Value { @@ -304,7 +341,7 @@ Genesis Timestamp for wallet in &self.genesis_accounts { available_accounts.push(format!("{:?}", wallet.address())); - private_keys.push(format!("0x{}", hex::encode(wallet.signer().to_bytes()))); + private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes()))); } if let Some(ref gen) = self.account_generator { @@ -315,6 +352,13 @@ Genesis Timestamp wallet_description.insert("mnemonic".to_string(), phrase); }; + let gas_limit = match self.gas_limit { + // if we have a disabled flag we should max out the limit + Some(_) | None if self.disable_block_gas_limit => Some(u64::MAX.to_string()), + Some(limit) => Some(limit.to_string()), + _ => None, + }; + if let Some(fork) = fork { json!({ "available_accounts": available_accounts, @@ -326,7 +370,7 @@ Genesis Timestamp "wallet": wallet_description, "base_fee": format!("{}", self.get_base_fee()), "gas_price": format!("{}", self.get_gas_price()), - "gas_limit": format!("{}", self.gas_limit), + "gas_limit": gas_limit, }) } else { json!({ @@ -335,21 +379,29 @@ Genesis Timestamp "wallet": wallet_description, "base_fee": format!("{}", self.get_base_fee()), "gas_price": format!("{}", self.get_gas_price()), - "gas_limit": format!("{}", self.gas_limit), + "gas_limit": gas_limit, "genesis_timestamp": format!("{}", self.get_genesis_timestamp()), }) } } } -// === impl NodeConfig === - impl NodeConfig { /// Returns a new config intended to be used in tests, which does not print and binds to a /// random, free port by setting it to `0` #[doc(hidden)] pub fn test() -> Self { - Self { enable_tracing: false, silent: true, port: 0, ..Default::default() } + Self { enable_tracing: true, port: 0, silent: true, ..Default::default() } + } + + /// Returns a new config which does not initialize any accounts on node startup. + pub fn empty_state() -> Self { + Self { + genesis_accounts: vec![], + signer_accounts: vec![], + disable_default_create2_deployer: true, + ..Default::default() + } } } @@ -359,7 +411,7 @@ impl Default for NodeConfig { let genesis_accounts = AccountGenerator::new(10).phrase(DEFAULT_MNEMONIC).gen(); Self { chain_id: None, - gas_limit: U256::from(30_000_000), + gas_limit: None, disable_block_gas_limit: false, gas_price: None, hardfork: None, @@ -367,19 +419,22 @@ impl Default for NodeConfig { genesis_timestamp: None, genesis_accounts, // 100ETH default balance - genesis_balance: WEI_IN_ETHER.saturating_mul(100u64.into()), + genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)), block_time: None, no_mining: false, + mixed_mining: false, port: NODE_PORT, // TODO make this something dependent on block capacity max_transactions: 1_000, - silent: false, eth_rpc_url: None, - fork_block_number: None, + fork_choice: None, account_generator: None, base_fee: None, + disable_min_priority_fee: false, + 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(), @@ -388,36 +443,73 @@ impl Default for NodeConfig { config_out: None, genesis: None, fork_request_timeout: REQUEST_TIMEOUT, + fork_headers: vec![], fork_request_retries: 5, fork_retry_backoff: Duration::from_millis(1_000), fork_chain_id: None, - // alchemy max cpus + // alchemy max cpus compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, ipc_path: None, code_size_limit: None, prune_history: Default::default(), + max_persisted_states: None, init_state: None, transaction_block_keeper: None, + disable_default_create2_deployer: false, + enable_optimism: false, + slots_in_an_epoch: 32, + memory_limit: None, + precompile_factory: None, + odyssey: false, + silent: false, + cache_path: None, } } } impl NodeConfig { + /// Returns the memory limit of the node + #[must_use] + pub fn with_memory_limit(mut self, mems_value: Option) -> Self { + self.memory_limit = mems_value; + self + } /// Returns the base fee to use - pub fn get_base_fee(&self) -> U256 { + pub fn get_base_fee(&self) -> u64 { self.base_fee - .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas)) - .unwrap_or_else(|| INITIAL_BASE_FEE.into()) + .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64))) + .unwrap_or(INITIAL_BASE_FEE) } /// Returns the base fee to use - pub fn get_gas_price(&self) -> U256 { - self.gas_price.unwrap_or_else(|| INITIAL_GAS_PRICE.into()) + pub fn get_gas_price(&self) -> u128 { + self.gas_price.unwrap_or(INITIAL_GAS_PRICE) } - /// Returns the base fee to use - pub fn get_hardfork(&self) -> Hardfork { - self.hardfork.unwrap_or_default() + pub fn get_blob_excess_gas_and_price(&self) -> BlobExcessGasAndPrice { + if let Some(blob_excess_gas_and_price) = &self.blob_excess_gas_and_price { + blob_excess_gas_and_price.clone() + } else if let Some(excess_blob_gas) = self.genesis.as_ref().and_then(|g| g.excess_blob_gas) + { + BlobExcessGasAndPrice::new(excess_blob_gas, false) + } else { + // If no excess blob gas is configured, default to 0 + BlobExcessGasAndPrice::new(0, false) + } + } + + /// Returns the hardfork to use + pub fn get_hardfork(&self) -> ChainHardfork { + if self.odyssey { + return ChainHardfork::Ethereum(EthereumHardfork::PragueEOF); + } + if let Some(hardfork) = self.hardfork { + return hardfork; + } + if self.enable_optimism { + return OptimismHardfork::default().into(); + } + EthereumHardfork::default().into() } /// Sets a custom code size limit @@ -426,14 +518,29 @@ impl NodeConfig { self.code_size_limit = code_size_limit; self } + /// Disables code size limit + #[must_use] + pub fn disable_code_size_limit(mut self, disable_code_size_limit: bool) -> Self { + if disable_code_size_limit { + self.code_size_limit = Some(usize::MAX); + } + self + } - /// Sets a custom code size limit + /// Sets the init state if any #[must_use] pub fn with_init_state(mut self, init_state: Option) -> Self { self.init_state = init_state; self } + /// Loads the init state from a file if it exists + #[must_use] + pub fn with_init_state_path(mut self, path: impl AsRef) -> Self { + self.init_state = StateFile::parse_path(path).ok().and_then(|file| file.state); + self + } + /// Sets the chain ID #[must_use] pub fn with_chain_id>(mut self, chain_id: Option) -> Self { @@ -444,7 +551,7 @@ impl NodeConfig { /// Returns the chain ID to use pub fn get_chain_id(&self) -> u64 { self.chain_id - .or_else(|| self.genesis.as_ref().and_then(|g| g.chain_id())) + .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id)) .unwrap_or(CHAIN_ID) } @@ -453,19 +560,17 @@ impl NodeConfig { self.chain_id = chain_id.map(Into::into); let chain_id = self.get_chain_id(); self.genesis_accounts.iter_mut().for_each(|wallet| { - *wallet = wallet.clone().with_chain_id(chain_id); + *wallet = wallet.clone().with_chain_id(Some(chain_id)); }); self.signer_accounts.iter_mut().for_each(|wallet| { - *wallet = wallet.clone().with_chain_id(chain_id); + *wallet = wallet.clone().with_chain_id(Some(chain_id)); }) } /// Sets the gas limit #[must_use] - pub fn with_gas_limit>(mut self, gas_limit: Option) -> Self { - if let Some(gas_limit) = gas_limit { - self.gas_limit = gas_limit.into(); - } + pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { + self.gas_limit = gas_limit; self } @@ -480,8 +585,8 @@ impl NodeConfig { /// Sets the gas price #[must_use] - pub fn with_gas_price>(mut self, gas_price: Option) -> Self { - self.gas_price = gas_price.map(Into::into); + pub fn with_gas_price(mut self, gas_price: Option) -> Self { + self.gas_price = gas_price; self } @@ -492,6 +597,16 @@ impl NodeConfig { self } + /// Sets max number of states to cache on disk. + #[must_use] + pub fn with_max_persisted_states>( + mut self, + max_persisted_states: Option, + ) -> Self { + self.max_persisted_states = max_persisted_states.map(Into::into); + self + } + /// Sets max number of blocks with transactions to keep in memory #[must_use] pub fn with_transaction_block_keeper>( @@ -504,8 +619,15 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee>(mut self, base_fee: Option) -> Self { - self.base_fee = base_fee.map(Into::into); + pub fn with_base_fee(mut self, base_fee: Option) -> Self { + self.base_fee = base_fee; + self + } + + /// Disable the enforcement of a minimum suggested priority fee + #[must_use] + pub fn disable_min_priority_fee(mut self, disable_min_priority_fee: bool) -> Self { + self.disable_min_priority_fee = disable_min_priority_fee; self } @@ -519,7 +641,7 @@ impl NodeConfig { /// Returns the genesis timestamp to use pub fn get_genesis_timestamp(&self) -> u64 { self.genesis_timestamp - .or_else(|| self.genesis.as_ref().and_then(|g| g.timestamp)) + .or_else(|| self.genesis.as_ref().map(|g| g.timestamp)) .unwrap_or_else(|| duration_since_unix_epoch().as_secs()) } @@ -534,21 +656,21 @@ impl NodeConfig { /// Sets the hardfork #[must_use] - pub fn with_hardfork(mut self, hardfork: Option) -> Self { + pub fn with_hardfork(mut self, hardfork: Option) -> Self { self.hardfork = hardfork; self } /// Sets the genesis accounts #[must_use] - pub fn with_genesis_accounts(mut self, accounts: Vec>) -> Self { + pub fn with_genesis_accounts(mut self, accounts: Vec) -> Self { self.genesis_accounts = accounts; self } /// Sets the signer accounts #[must_use] - pub fn with_signer_accounts(mut self, accounts: Vec>) -> Self { + pub fn with_signer_accounts(mut self, accounts: Vec) -> Self { self.signer_accounts = accounts; self } @@ -576,29 +698,35 @@ impl NodeConfig { self } - /// If set to `true` auto mining will be disabled #[must_use] - pub fn with_no_mining(mut self, no_mining: bool) -> Self { - self.no_mining = no_mining; + pub fn with_mixed_mining>( + mut self, + mixed_mining: bool, + block_time: Option, + ) -> Self { + self.block_time = block_time.map(Into::into); + self.mixed_mining = mixed_mining; self } - /// Sets the port to use + /// If set to `true` auto mining will be disabled #[must_use] - pub fn with_port(mut self, port: u16) -> Self { - self.port = port; + pub fn with_no_mining(mut self, no_mining: bool) -> Self { + self.no_mining = no_mining; self } - /// Makes the node silent to not emit anything on stdout + /// Sets the slots in an epoch #[must_use] - pub fn silent(self) -> Self { - self.set_silent(true) + pub fn with_slots_in_an_epoch(mut self, slots_in_an_epoch: u64) -> Self { + self.slots_in_an_epoch = slots_in_an_epoch; + self } + /// Sets the port to use #[must_use] - pub fn set_silent(mut self, silent: bool) -> Self { - self.silent = silent; + pub fn with_port(mut self, port: u16) -> Self { + self.port = port; self } @@ -616,12 +744,12 @@ impl NodeConfig { /// Sets the file path to write the Anvil node's config info to. #[must_use] - pub fn set_config_out(mut self, config_out: Option) -> Self { + pub fn set_config_out(mut self, config_out: Option) -> Self { self.config_out = config_out; self } - /// Makes the node silent to not emit anything on stdout + /// Disables storage caching #[must_use] pub fn no_storage_caching(self) -> Self { self.with_storage_caching(true) @@ -640,17 +768,39 @@ 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 } /// Sets the `fork_chain_id` to use to fork off local cache from #[must_use] - pub fn with_fork_chain_id>(mut self, fork_chain_id: Option) -> Self { - self.fork_chain_id = fork_chain_id.map(Into::into); + pub fn with_fork_chain_id(mut self, fork_chain_id: Option) -> Self { + self.fork_chain_id = fork_chain_id; + self + } + + /// Sets the `fork_headers` to use with `eth_rpc_url` + #[must_use] + pub fn with_fork_headers(mut self, headers: Vec) -> Self { + self.fork_headers = headers; self } @@ -683,7 +833,7 @@ impl NodeConfig { /// Sets the number of assumed available compute units per second /// - /// See also, + /// See also, #[must_use] pub fn fork_compute_units_per_second(mut self, compute_units_per_second: Option) -> Self { if let Some(compute_units_per_second) = compute_units_per_second { @@ -706,6 +856,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 { @@ -734,27 +891,25 @@ impl NodeConfig { /// Returns the ipc path for the ipc endpoint if any pub fn get_ipc_path(&self) -> Option { - match self.ipc_path.as_ref() { + match &self.ipc_path { Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())), None => None, } } /// Prints the config info - pub fn print(&self, fork: Option<&ClientFork>) { - if self.config_out.is_some() { - let config_out = self.config_out.as_deref().unwrap(); - to_writer( - &File::create(config_out).expect("Unable to create anvil config description file"), - &self.as_json(fork), - ) - .expect("Failed writing json"); + pub fn print(&self, fork: Option<&ClientFork>) -> Result<()> { + if let Some(path) = &self.config_out { + let file = io::BufWriter::new( + File::create(path).wrap_err("unable to create anvil config description file")?, + ); + let value = self.as_json(fork); + serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?; } - if self.silent { - return + if !self.silent { + sh_println!("{}", self.as_string(fork))?; } - - println!("{}", self.as_string(fork)) + Ok(()) } /// Returns the path where the cache file should be stored @@ -762,232 +917,125 @@ impl NodeConfig { /// See also [ Config::foundry_block_cache_file()] pub fn block_cache_path(&self, block: u64) -> Option { if self.no_storage_caching || self.eth_rpc_url.is_none() { - return None + return None; } let chain_id = self.get_chain_id(); Config::foundry_block_cache_file(chain_id, block) } + /// Sets whether to enable optimism support + #[must_use] + pub fn with_optimism(mut self, enable_optimism: bool) -> Self { + self.enable_optimism = enable_optimism; + self + } + + /// Sets whether to disable the default create2 deployer + #[must_use] + pub fn with_disable_default_create2_deployer(mut self, yes: bool) -> Self { + self.disable_default_create2_deployer = yes; + self + } + + /// Injects precompiles to `anvil`'s EVM. + #[must_use] + pub fn with_precompile_factory(mut self, factory: impl PrecompileFactory + 'static) -> Self { + self.precompile_factory = Some(Arc::new(factory)); + self + } + + /// Sets whether to enable Odyssey support + #[must_use] + pub fn with_odyssey(mut self, odyssey: bool) -> Self { + self.odyssey = odyssey; + self + } + + /// Makes the node silent to not emit anything on stdout + #[must_use] + pub fn silent(self) -> Self { + self.set_silent(true) + } + + #[must_use] + pub fn set_silent(mut self, silent: bool) -> Self { + self.silent = silent; + self + } + + /// Sets the path where states are cached + #[must_use] + pub fn with_cache_path(mut self, cache_path: Option) -> Self { + self.cache_path = cache_path; + self + } + /// Configures everything related to env, backend and database and returns the /// [Backend](mem::Backend) /// /// *Note*: only memory based backend for now - pub(crate) async fn setup(&mut self) -> mem::Backend { + pub(crate) async fn setup(&mut self) -> Result { // configure the revm environment - let mut env = revm::primitives::Env { - cfg: CfgEnv { - spec_id: self.get_hardfork().into(), - chain_id: rU256::from(self.get_chain_id()), - limit_contract_code_size: self.code_size_limit, - // EIP-3607 rejects transactions from senders with deployed code. - // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the - // caller is a contract. So we disable the check by default. - disable_eip3607: true, - disable_block_gas_limit: self.disable_block_gas_limit, - ..Default::default() - }, + + let mut cfg = + CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), self.get_hardfork().into()); + cfg.chain_id = self.get_chain_id(); + cfg.limit_contract_code_size = self.code_size_limit; + // EIP-3607 rejects transactions from senders with deployed code. + // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the + // caller is a contract. So we disable the check by default. + cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = self.disable_block_gas_limit; + cfg.handler_cfg.is_optimism = self.enable_optimism; + + if let Some(value) = self.memory_limit { + cfg.memory_limit = value; + } + + let env = revm::primitives::Env { + cfg: cfg.cfg_env, block: BlockEnv { - gas_limit: self.gas_limit.into(), - basefee: self.get_base_fee().into(), + gas_limit: U256::from(self.gas_limit()), + basefee: U256::from(self.get_base_fee()), ..Default::default() }, tx: TxEnv { chain_id: self.get_chain_id().into(), ..Default::default() }, }; - let fees = FeeManager::new(env.cfg.spec_id, self.get_base_fee(), self.get_gas_price()); + let mut env = EnvWithHandlerCfg::new(Box::new(env), cfg.handler_cfg); + + let fees = FeeManager::new( + cfg.handler_cfg.spec_id, + self.get_base_fee(), + !self.disable_min_priority_fee, + self.get_gas_price(), + self.get_blob_excess_gas_and_price(), + ); - let (db, fork): (Arc>, Option) = + let (db, fork): (Arc>>, Option) = if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { - // TODO make provider agnostic - let provider = Arc::new( - ProviderBuilder::new(ð_rpc_url) - .timeout(self.fork_request_timeout) - .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) - .initial_backoff(1000) - .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 chain_id = if let Some(chain_id) = self.fork_chain_id { - Some(chain_id) - } else if self.hardfork.is_none() { - // auto adjust hardfork if not specified - // but only if we're forking mainnet - let chain_id = - provider.get_chainid().await.expect("Failed to fetch network chain id"); - if chain_id == ethers::types::Chain::Mainnet.into() { - let hardfork: Hardfork = fork_block_number.into(); - env.cfg.spec_id = hardfork.into(); - self.hardfork = Some(hardfork); - } - Some(chain_id) - } else { - None - }; - - (fork_block_number, chain_id) - } else { - // pick the last block number but also ensure it's not pending anymore - ( - find_latest_fork_block(&provider) - .await - .expect("Failed to get fork block number"), - None, - ) - }; - - let block = provider - .get_block(BlockNumber::Number(fork_block_number.into())) - .await - .expect("Failed to get fork block"); - - let block = if let Some(block) = block { - block - } else { - if let Ok(latest_block) = provider.get_block_number().await { - let mut message = format!( - "Failed to get block for block number: {fork_block_number}\n\ -latest block number: {latest_block}" - ); - // If the `eth_getBlockByNumber` call succeeds, but returns null instead of - // the block, and the block number is less than equal the latest block, then - // the user is forking from a non-archive node with an older block number. - if fork_block_number <= latest_block.as_u64() { - message.push_str(&format!("\n{}", NON_ARCHIVE_NODE_WARNING)); - } - panic!("{}", message); - } - panic!("Failed to get block for block number: {fork_block_number}") - }; - - // we only use the gas limit value of the block if it is non-zero and the block gas - // limit is enabled, since there are networks where this is not used and is always - // `0x0` which would inevitably result in `OutOfGas` errors as soon as the evm is about to record gas, See also - let gas_limit = if self.disable_block_gas_limit || block.gas_limit.is_zero() { - u256_to_ru256(u64::MAX.into()) - } else { - u256_to_ru256(block.gas_limit) - }; - - env.block = BlockEnv { - number: rU256::from(fork_block_number), - timestamp: block.timestamp.into(), - difficulty: block.difficulty.into(), - // ensures prevrandao is set - prevrandao: Some(block.mix_hash.unwrap_or_default()).map(h256_to_b256), - gas_limit, - // Keep previous `coinbase` and `basefee` value - coinbase: env.block.coinbase, - basefee: env.block.basefee, - }; - - // apply changes such as difficulty -> prevrandao - apply_chain_and_block_specific_env_changes(&mut env, &block); - - // if not set explicitly we use the base fee of the latest block - if self.base_fee.is_none() { - if let Some(base_fee) = block.base_fee_per_gas { - self.base_fee = Some(base_fee); - env.block.basefee = u256_to_ru256(base_fee); - // this is the base fee of the current block, but we need the base fee of - // the next block - let next_block_base_fee = fees.get_next_block_base_fee_per_gas( - block.gas_used, - block.gas_limit, - block.base_fee_per_gas.unwrap_or_default(), - ); - // update next base fee - fees.set_base_fee(next_block_base_fee.into()); - } - } - - // use remote gas price - if self.gas_price.is_none() { - if let Ok(gas_price) = provider.get_gas_price().await { - self.gas_price = Some(gas_price); - fees.set_gas_price(gas_price); - } - } - - let block_hash = block.hash.unwrap_or_default(); - - let chain_id = if let Some(chain_id) = self.chain_id { - chain_id - } else { - let chain_id = if let Some(fork_chain_id) = fork_chain_id { - fork_chain_id - } else { - provider.get_chainid().await.unwrap() - } - .as_u64(); - - // need to update the dev signers and env with the chain id - self.set_chain_id(Some(chain_id)); - env.cfg.chain_id = rU256::from(chain_id); - env.tx.chain_id = chain_id.into(); - chain_id - }; - let override_chain_id = self.chain_id; - - let meta = BlockchainDbMeta::new(env.clone(), eth_rpc_url.clone()); - let block_chain_db = if self.fork_chain_id.is_some() { - BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) - } else { - BlockchainDb::new(meta, self.block_cache_path(fork_block_number)) - }; - - // This will spawn the background thread that will use the provider to fetch - // blockchain data from the other client - let backend = SharedBackend::spawn_backend_thread( - Arc::clone(&provider), - block_chain_db.clone(), - Some(fork_block_number.into()), - ); - - let db = Arc::new(tokio::sync::RwLock::new(ForkedDatabase::new( - backend, - block_chain_db, - ))); - let fork = ClientFork::new( - ClientForkConfig { - eth_rpc_url, - block_number: fork_block_number, - block_hash, - provider, - chain_id, - override_chain_id, - timestamp: block.timestamp.as_u64(), - base_fee: block.base_fee_per_gas, - timeout: self.fork_request_timeout, - retries: self.fork_request_retries, - backoff: self.fork_retry_backoff, - compute_units_per_second: self.compute_units_per_second, - total_difficulty: block.total_difficulty.unwrap_or_default(), - }, - Arc::clone(&db), - ); - - (db, Some(fork)) + self.setup_fork_db(eth_rpc_url, &mut env, &fees).await? } else { - (Arc::new(tokio::sync::RwLock::new(MemDb::default())), None) + (Arc::new(TokioRwLock::new(Box::::default())), None) }; // if provided use all settings of `genesis.json` if let Some(ref genesis) = self.genesis { - genesis.apply(&mut env); + env.cfg.chain_id = genesis.config.chain_id; + env.block.timestamp = U256::from(genesis.timestamp); + if let Some(base_fee) = genesis.base_fee_per_gas { + env.block.basefee = U256::from(base_fee); + } + if let Some(number) = genesis.number { + env.block.number = U256::from(number); + } + env.block.coinbase = genesis.coinbase; } let genesis = GenesisConfig { timestamp: self.get_genesis_timestamp(), - balance: self.genesis_balance.into(), + 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(), }; @@ -997,42 +1045,382 @@ latest block number: {latest_block}" Arc::new(RwLock::new(env)), genesis, fees, - fork, + Arc::new(RwLock::new(fork)), self.enable_steps_tracing, + self.print_logs, + self.odyssey, self.prune_history, + self.max_persisted_states, self.transaction_block_keeper, self.block_time, + self.cache_path.clone(), + Arc::new(TokioRwLock::new(self.clone())), ) - .await; + .await?; - if let Some(ref state) = self.init_state { + // Writes the default create2 deployer to the backend, + // if the option is not disabled and we are not forking. + if !self.disable_default_create2_deployer && self.eth_rpc_url.is_none() { backend - .get_db() - .write() + .set_create2_deployer(DEFAULT_CREATE2_DEPLOYER) .await - .load_state(state.clone()) - .expect("Failed to load init state"); + .wrap_err("failed to create default create2 deployer")?; } - backend + if let Some(state) = self.init_state.clone() { + backend.load_state(state).await.wrap_err("failed to load init state")?; + } + + Ok(backend) + } + + /// Configures everything related to forking based on the passed `eth_rpc_url`: + /// - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](TokioRwLock) and + /// [ClientFork] wrapped in an [Option] which can be used in a [Backend](mem::Backend) to + /// fork from. + /// - modifying some parameters of the passed `env` + /// - mutating some members of `self` + pub async fn setup_fork_db( + &mut self, + eth_rpc_url: String, + env: &mut EnvWithHandlerCfg, + fees: &FeeManager, + ) -> Result<(Arc>>, Option)> { + let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await?; + let db: Arc>> = Arc::new(TokioRwLock::new(Box::new(db))); + let fork = ClientFork::new(config, Arc::clone(&db)); + Ok((db, Some(fork))) + } + + /// Configures everything related to forking based on the passed `eth_rpc_url`: + /// - returning a tuple of a [ForkedDatabase] and [ClientForkConfig] which can be used to build + /// a [ClientFork] to fork from. + /// - modifying some parameters of the passed `env` + /// - mutating some members of `self` + pub async fn setup_fork_db_config( + &mut self, + eth_rpc_url: String, + env: &mut EnvWithHandlerCfg, + fees: &FeeManager, + ) -> Result<(ForkedDatabase, ClientForkConfig)> { + // TODO make provider agnostic + let provider = Arc::new( + ProviderBuilder::new(ð_rpc_url) + .timeout(self.fork_request_timeout) + .initial_backoff(self.fork_retry_backoff.as_millis() as u64) + .compute_units_per_second(self.compute_units_per_second) + .max_retry(self.fork_request_retries) + .initial_backoff(1000) + .headers(self.fork_headers.clone()) + .build() + .wrap_err("failed to establish provider to fork url")?, + ); + + 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.wrap_err( + "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() { + // Auto-adjust hardfork if not specified, but only if we're forking mainnet. + let chain_id = + provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")?; + if alloy_chains::NamedChain::Mainnet == chain_id { + let hardfork: EthereumHardfork = fork_block_number.into(); + env.handler_cfg.spec_id = hardfork.into(); + self.hardfork = Some(ChainHardfork::Ethereum(hardfork)); + } + Some(U256::from(chain_id)) + } else { + None + }; + + (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 + .wrap_err("failed to get fork block number")?; + (bn, None, None) + }; + + let block = provider + .get_block(BlockNumberOrTag::Number(fork_block_number).into(), false.into()) + .await + .wrap_err("failed to get fork block")?; + + let block = if let Some(block) = block { + block + } else { + if let Ok(latest_block) = provider.get_block_number().await { + let mut message = format!( + "Failed to get block for block number: {fork_block_number}\n\ +latest block number: {latest_block}" + ); + // If the `eth_getBlockByNumber` call succeeds, but returns null instead of + // the block, and the block number is less than equal the latest block, then + // the user is forking from a non-archive node with an older block number. + if fork_block_number <= latest_block { + message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}")); + } + eyre::bail!("{message}"); + } + eyre::bail!("failed to get block for block number: {fork_block_number}") + }; + + let gas_limit = self.fork_gas_limit(&block); + self.gas_limit = Some(gas_limit); + + env.block = BlockEnv { + number: U256::from(fork_block_number), + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + // ensures prevrandao is set + prevrandao: Some(block.header.mix_hash.unwrap_or_default()), + gas_limit: U256::from(gas_limit), + // Keep previous `coinbase` and `basefee` value + coinbase: env.block.coinbase, + basefee: env.block.basefee, + ..Default::default() + }; + + // if not set explicitly we use the base fee of the latest block + if self.base_fee.is_none() { + if let Some(base_fee) = block.header.base_fee_per_gas { + self.base_fee = Some(base_fee); + env.block.basefee = U256::from(base_fee); + // this is the base fee of the current block, but we need the base fee of + // the next block + let next_block_base_fee = fees.get_next_block_base_fee_per_gas( + block.header.gas_used as u128, + gas_limit, + block.header.base_fee_per_gas.unwrap_or_default(), + ); + + // update next base fee + fees.set_base_fee(next_block_base_fee); + } + if let (Some(blob_excess_gas), Some(blob_gas_used)) = + (block.header.excess_blob_gas, block.header.blob_gas_used) + { + env.block.blob_excess_gas_and_price = + Some(BlobExcessGasAndPrice::new(blob_excess_gas, false)); + let next_block_blob_excess_gas = fees + .get_next_block_blob_excess_gas(blob_excess_gas as u128, blob_gas_used as u128); + fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_blob_excess_gas, + false, + )); + } + } + + // use remote gas price + if self.gas_price.is_none() { + if let Ok(gas_price) = provider.get_gas_price().await { + self.gas_price = Some(gas_price); + fees.set_gas_price(gas_price); + } + } + + let block_hash = block.header.hash; + + let chain_id = if let Some(chain_id) = self.chain_id { + chain_id + } else { + let chain_id = if let Some(fork_chain_id) = fork_chain_id { + fork_chain_id.to() + } else { + provider.get_chain_id().await.wrap_err("failed to fetch network chain ID")? + }; + + // need to update the dev signers and env with the chain id + self.set_chain_id(Some(chain_id)); + env.cfg.chain_id = chain_id; + env.tx.chain_id = chain_id.into(); + chain_id + }; + let override_chain_id = self.chain_id; + // apply changes such as difficulty -> prevrandao and chain specifics for current chain id + apply_chain_and_block_specific_env_changes::(env, &block); + + let meta = BlockchainDbMeta::new(*env.env.clone(), eth_rpc_url.clone()); + let block_chain_db = if self.fork_chain_id.is_some() { + BlockchainDb::new_skip_check(meta, self.block_cache_path(fork_block_number)) + } else { + BlockchainDb::new(meta, self.block_cache_path(fork_block_number)) + }; + + // This will spawn the background thread that will use the provider to fetch + // blockchain data from the other client + let backend = SharedBackend::spawn_backend_thread( + Arc::clone(&provider), + block_chain_db.clone(), + Some(fork_block_number.into()), + ); + + let config = ClientForkConfig { + 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, + timestamp: block.header.timestamp, + base_fee: block.header.base_fee_per_gas.map(|g| g as u128), + timeout: self.fork_request_timeout, + retries: self.fork_request_retries, + backoff: self.fork_retry_backoff, + compute_units_per_second: self.compute_units_per_second, + total_difficulty: block.header.total_difficulty.unwrap_or_default(), + blob_gas_used: block.header.blob_gas_used.map(|g| g as u128), + blob_excess_gas_and_price: env.block.blob_excess_gas_and_price.clone(), + force_transactions, + }; + + let mut db = ForkedDatabase::new(backend, block_chain_db); + + // need to insert the forked block's hash + db.insert_block_hash(U256::from(config.block_number), config.block_hash); + + Ok((db, config)) + } + + /// we only use the gas limit value of the block if it is non-zero and the block gas + /// limit is enabled, since there are networks where this is not used and is always + /// `0x0` which would inevitably result in `OutOfGas` errors as soon as the evm is about to record gas, See also + pub(crate) fn fork_gas_limit( + &self, + block: &Block, + ) -> u128 { + if !self.disable_block_gas_limit { + if let Some(gas_limit) = self.gas_limit { + return gas_limit; + } else if block.header.gas_limit() > 0 { + return block.header.gas_limit() as u128; + } + } + + u64::MAX as u128 + } + + /// Returns the gas limit for a non forked anvil instance + /// + /// Checks the config for the `disable_block_gas_limit` flag + pub(crate) fn gas_limit(&self) -> u128 { + if self.disable_block_gas_limit { + return u64::MAX as u128; + } + + self.gas_limit.unwrap_or(DEFAULT_GAS_LIMIT) + } +} + +/// 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_else(|| 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(), + alloy_rpc_types::BlockTransactionsKind::Full, + ) + .await? + .ok_or_else(|| eyre::eyre!("failed to get fork block by number"))?; + + // Filter out transactions that are after the fork transaction + let filtered_transactions = transaction_block + .transactions + .as_transactions() + .ok_or_else(|| eyre::eyre!("failed to get transactions from full fork block"))? + .iter() + .take_while_inclusive(|&transaction| transaction.tx_hash() != transaction_hash.0) + .collect::>(); + + // Convert the transactions to PoolTransactions + let force_transactions = filtered_transactions + .iter() + .map(|&transaction| PoolTransaction::try_from(transaction.clone())) + .collect::, _>>() + .map_err(|e| eyre::eyre!("Err converting to pool transactions {e}"))?; + 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), + } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +/// 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, pub max_memory_history: Option, } -// === impl PruneStateHistoryConfig === - impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported pub fn is_state_history_supported(&self) -> bool { !self.enabled || self.max_memory_history.is_some() } - /// Returns tru if this setting was enabled. + /// Returns true if this setting was enabled. pub fn is_config_enabled(&self) -> bool { self.enabled } @@ -1043,7 +1431,7 @@ impl PruneStateHistoryConfig { } /// Can create dev accounts -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct AccountGenerator { chain_id: u64, amount: usize, @@ -1093,18 +1481,17 @@ impl AccountGenerator { } impl AccountGenerator { - pub fn gen(&self) -> Vec> { + pub fn gen(&self) -> Vec { let builder = MnemonicBuilder::::default().phrase(self.phrase.as_str()); - // use the + // use the derivation path let derivation_path = self.get_derivation_path(); 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(); - let wallet = builder.build().unwrap().with_chain_id(self.chain_id); + builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap(); + let wallet = builder.build().unwrap().with_chain_id(Some(self.chain_id)); wallets.push(wallet) } wallets @@ -1125,15 +1512,17 @@ pub fn anvil_tmp_dir() -> Option { /// /// This fetches the "latest" block and checks whether the `Block` is fully populated (`hash` field /// is present). This prevents edge cases where anvil forks the "latest" block but `eth_getBlockByNumber` still returns a pending block, -async fn find_latest_fork_block(provider: M) -> Result { - let mut num = provider.get_block_number().await?.as_u64(); +async fn find_latest_fork_block>( + provider: P, +) -> Result { + let mut num = provider.get_block_number().await?; // walk back from the head of the chain, but at most 2 blocks, which should be more than enough // leeway for _ in 0..2 { - if let Some(block) = provider.get_block(num).await? { - if block.hash.is_some() { - break + if let Some(block) = provider.get_block(num.into(), false.into()).await? { + if !block.header.hash.is_zero() { + break; } } // block not actually finalized, so we try the block before diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 19a28b4a5ac54..8a69a070cacc7 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,17 +1,20 @@ +use super::{ + backend::mem::{state, BlockRequest, State}, + sign::build_typed_transaction, +}; use crate::{ eth::{ - backend, backend::{ + self, db::SerializableState, mem::{MIN_CREATE_GAS, MIN_TRANSACTION_GAS}, notifications::NewBlockNotifications, validate::TransactionValidator, }, error::{ - decode_revert_reason, BlockchainError, FeeHistoryError, InvalidTransactionError, - Result, ToRpcResponseResult, + BlockchainError, FeeHistoryError, InvalidTransactionError, Result, ToRpcResponseResult, }, - fees::{FeeDetails, FeeHistoryCache}, + fees::{FeeDetails, FeeHistoryCache, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, miner::FixedBlockTimeMiner, pool::{ @@ -20,57 +23,72 @@ use crate::{ }, Pool, }, - sign, - sign::Signer, + sign::{self, Signer}, }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, - revm::primitives::Output, + revm::primitives::{BlobExcessGasAndPrice, Output}, ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, }; +use alloy_consensus::{transaction::eip4844::TxEip4844Variant, Account}; +use alloy_dyn_abi::TypedData; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{ + eip2718::Decodable2718, AnyRpcBlock, AnyRpcTransaction, BlockResponse, Ethereum, NetworkWallet, + TransactionBuilder, TransactionResponse, +}; +use alloy_primitives::{ + map::{HashMap, HashSet}, + Address, Bytes, PrimitiveSignature as Signature, TxHash, TxKind, B256, B64, U256, U64, +}; +use alloy_provider::utils::{ + eip1559_default_estimator, EIP1559_FEE_ESTIMATION_PAST_BLOCKS, + EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE, +}; +use alloy_rpc_types::{ + 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, AccessListResult, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, +}; +use alloy_serde::WithOtherFields; +use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ block::BlockInfo, - proof::AccountProof, - state::StateOverride, transaction::{ - EthTransactionRequest, LegacyTransaction, PendingTransaction, TransactionKind, - TypedTransaction, TypedTransactionRequest, + transaction_request_to_typed, PendingTransaction, ReceiptResponse, TypedTransaction, + TypedTransactionRequest, }, + wallet::{WalletCapabilities, WalletError}, EthRequest, }, - types::{EvmMineOptions, Forking, Index, NodeEnvironment, NodeForkConfig, NodeInfo, Work}, + types::{ReorgOptions, TransactionData, Work}, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; -use ethers::{ - abi::ethereum_types::H64, - prelude::{DefaultFrame, TxpoolInspect}, - providers::ProviderError, - types::{ - transaction::{ - eip2930::{AccessList, AccessListWithGasUsed}, - eip712::TypedData, - }, - Address, Block, BlockId, BlockNumber, Bytes, FeeHistory, Filter, FilteredParams, - GethDebugTracingOptions, GethTrace, Log, Trace, Transaction, TransactionReceipt, TxHash, - TxpoolContent, TxpoolInspectSummary, TxpoolStatus, H256, U256, U64, - }, - utils::rlp, -}; -use foundry_common::ProviderBuilder; +use foundry_common::provider::ProviderBuilder; use foundry_evm::{ - executor::{backend::DatabaseError, DatabaseRef}, + backend::DatabaseError, + decode::RevertDecoder, revm::{ + db::DatabaseRef, interpreter::{return_ok, return_revert, InstructionResult}, primitives::BlockEnv, }, }; -use futures::channel::mpsc::Receiver; +use futures::channel::{mpsc::Receiver, oneshot}; use parking_lot::RwLock; -use std::{sync::Arc, time::Duration}; -use tracing::{trace, warn}; - -use super::{backend::mem::BlockRequest, sign::build_typed_transaction}; +use revm::primitives::Bytecode; +use std::{future::Future, sync::Arc, time::Duration}; /// The client version: `anvil/v{major}.{minor}.{patch}` pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); @@ -84,7 +102,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 @@ -106,10 +124,10 @@ pub struct EthApi { transaction_order: Arc>, /// Whether we're listening for RPC calls net_listening: bool, + /// The instance ID. Changes on every reset. + instance_id: Arc>, } -// === impl Eth RPC API === - impl EthApi { /// Creates a new instance #[allow(clippy::too_many_arguments)] @@ -136,15 +154,19 @@ impl EthApi { filters, net_listening: true, transaction_order: Arc::new(RwLock::new(transactions_order)), + instance_id: Arc::new(RwLock::new(B256::random())), } } - /// Executes the [EthRequest] and returns an RPC [RpcResponse] + /// Executes the [EthRequest] and returns an RPC [ResponseResult]. pub async fn execute(&self, request: EthRequest) -> ResponseResult { trace!(target: "rpc::api", "executing eth request"); - match request { + let response = match request.clone() { EthRequest::Web3ClientVersion(()) => self.client_version().to_rpc_result(), EthRequest::Web3Sha3(content) => self.sha3(content).to_rpc_result(), + EthRequest::EthGetAccount(addr, block) => { + self.get_account(addr, block).await.to_rpc_result() + } EthRequest::EthGetBalance(addr, block) => { self.balance(addr, block).await.to_rpc_result() } @@ -157,10 +179,11 @@ impl EthApi { EthRequest::EthChainId(_) => self.eth_chain_id().to_rpc_result(), EthRequest::EthNetworkId(_) => self.network_id().to_rpc_result(), EthRequest::NetListening(_) => self.net_listening().to_rpc_result(), - EthRequest::EthGasPrice(_) => self.gas_price().to_rpc_result(), + EthRequest::EthGasPrice(_) => self.eth_gas_price().to_rpc_result(), EthRequest::EthMaxPriorityFeePerGas(_) => { self.gas_max_priority_fee_per_gas().to_rpc_result() } + EthRequest::EthBlobBaseFee(_) => self.blob_base_fee().to_rpc_result(), EthRequest::EthAccounts(_) => self.accounts().to_rpc_result(), EthRequest::EthBlockNumber(_) => self.block_number().to_rpc_result(), EthRequest::EthGetStorageAt(addr, slot, block) => { @@ -202,6 +225,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() } @@ -223,8 +249,17 @@ impl EthApi { EthRequest::EthCreateAccessList(call, block) => { self.create_access_list(call, block).await.to_rpc_result() } - EthRequest::EthEstimateGas(call, block) => { - self.estimate_gas(call, block).await.to_rpc_result() + EthRequest::EthEstimateGas(call, block, overrides) => { + self.estimate_gas(call, block, overrides).await.to_rpc_result() + } + EthRequest::EthGetRawTransactionByHash(hash) => { + self.raw_transaction(hash).await.to_rpc_result() + } + EthRequest::EthGetRawTransactionByBlockHashAndIndex(hash, index) => { + self.raw_transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() + } + EthRequest::EthGetRawTransactionByBlockNumberAndIndex(num, index) => { + self.raw_transaction_by_block_number_and_index(num, index).await.to_rpc_result() } EthRequest::EthGetTransactionByBlockHashAndIndex(hash, index) => { self.transaction_by_block_hash_and_index(hash, index).await.to_rpc_result() @@ -235,6 +270,9 @@ impl EthApi { EthRequest::EthGetTransactionReceipt(tx) => { self.transaction_receipt(tx).await.to_rpc_result() } + EthRequest::EthGetBlockReceipts(number) => { + self.block_receipts(number).await.to_rpc_result() + } EthRequest::EthGetUncleByBlockHashAndIndex(hash, index) => { self.uncle_by_block_hash_and_index(hash, index).await.to_rpc_result() } @@ -253,7 +291,10 @@ impl EthApi { EthRequest::EthFeeHistory(count, newest, reward_percentiles) => { self.fee_history(count, newest, reward_percentiles).await.to_rpc_result() } - + // non eth-standard rpc calls + EthRequest::DebugGetRawTransaction(hash) => { + self.raw_transaction(hash).await.to_rpc_result() + } // non eth-standard rpc calls EthRequest::DebugTraceTransaction(tx, opts) => { self.debug_trace_transaction(tx, opts).await.to_rpc_result() @@ -264,6 +305,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() } @@ -283,9 +325,13 @@ impl EthApi { EthRequest::SetIntervalMining(interval) => { self.anvil_set_interval_mining(interval).to_rpc_result() } + EthRequest::GetIntervalMining(()) => self.anvil_get_interval_mining().to_rpc_result(), EthRequest::DropTransaction(tx) => { self.anvil_drop_transaction(tx).await.to_rpc_result() } + EthRequest::DropAllTransactions() => { + self.anvil_drop_all_transactions().await.to_rpc_result() + } EthRequest::Reset(fork) => { self.anvil_reset(fork.and_then(|p| p.params)).await.to_rpc_result() } @@ -302,6 +348,7 @@ impl EthApi { self.anvil_set_storage_at(addr, slot, val).await.to_rpc_result() } EthRequest::SetCoinbase(addr) => self.anvil_set_coinbase(addr).await.to_rpc_result(), + EthRequest::SetChainId(id) => self.anvil_set_chain_id(id).await.to_rpc_result(), EthRequest::SetLogging(log) => self.anvil_set_logging(log).await.to_rpc_result(), EthRequest::SetMinGasPrice(gas) => { self.anvil_set_min_gas_price(gas).await.to_rpc_result() @@ -309,23 +356,33 @@ impl EthApi { EthRequest::SetNextBlockBaseFeePerGas(gas) => { self.anvil_set_next_block_base_fee_per_gas(gas).await.to_rpc_result() } - EthRequest::DumpState(_) => self.anvil_dump_state().await.to_rpc_result(), + EthRequest::DumpState(preserve_historical_states) => self + .anvil_dump_state(preserve_historical_states.and_then(|s| s.params)) + .await + .to_rpc_result(), EthRequest::LoadState(buf) => self.anvil_load_state(buf).await.to_rpc_result(), EthRequest::NodeInfo(_) => self.anvil_node_info().await.to_rpc_result(), + EthRequest::AnvilMetadata(_) => self.anvil_metadata().await.to_rpc_result(), EthRequest::EvmSnapshot(_) => self.evm_snapshot().await.to_rpc_result(), EthRequest::EvmRevert(id) => self.evm_revert(id).await.to_rpc_result(), EthRequest::EvmIncreaseTime(time) => self.evm_increase_time(time).await.to_rpc_result(), EthRequest::EvmSetNextBlockTimeStamp(time) => { - match u64::try_from(time).map_err(BlockchainError::UintConversion) { - Ok(time) => self.evm_set_next_block_timestamp(time).to_rpc_result(), - err @ Err(_) => err.to_rpc_result(), + if time >= U256::from(u64::MAX) { + return ResponseResult::Error(RpcError::invalid_params( + "The timestamp is too big", + )) } + let time = time.to::(); + self.evm_set_next_block_timestamp(time).to_rpc_result() } EthRequest::EvmSetTime(timestamp) => { - match u64::try_from(timestamp).map_err(BlockchainError::UintConversion) { - Ok(timestamp) => self.evm_set_time(timestamp).to_rpc_result(), - err @ Err(_) => err.to_rpc_result(), + if timestamp >= U256::from(u64::MAX) { + return ResponseResult::Error(RpcError::invalid_params( + "The timestamp is too big", + )) } + let time = timestamp.to::(); + self.evm_set_time(time).to_rpc_result() } EthRequest::EvmSetBlockGasLimit(gas_limit) => { self.evm_set_block_gas_limit(gas_limit).to_rpc_result() @@ -393,7 +450,30 @@ impl EthApi { EthRequest::OtsGetContractCreator(address) => { self.ots_get_contract_creator(address).await.to_rpc_result() } + EthRequest::RemovePoolTransactions(address) => { + self.anvil_remove_pool_transactions(address).await.to_rpc_result() + } + EthRequest::Reorg(reorg_options) => { + self.anvil_reorg(reorg_options).await.to_rpc_result() + } + EthRequest::Rollback(depth) => self.anvil_rollback(depth).await.to_rpc_result(), + EthRequest::WalletGetCapabilities(()) => self.get_capabilities().to_rpc_result(), + EthRequest::WalletSendTransaction(tx) => { + self.wallet_send_transaction(*tx).await.to_rpc_result() + } + EthRequest::AnvilAddCapability(addr) => self.anvil_add_capability(addr).to_rpc_result(), + EthRequest::AnvilSetExecutor(executor_pk) => { + self.anvil_set_executor(executor_pk).to_rpc_result() + } + }; + + if let ResponseResult::Error(err) = &response { + node_info!("\nRPC request failed:"); + node_info!(" Request: {:?}", request); + node_info!(" Error: {}\n", err); } + + response } fn sign_request( @@ -401,20 +481,27 @@ impl EthApi { from: &Address, request: TypedTransactionRequest, ) -> Result { - for signer in self.signers.iter() { - if signer.accounts().contains(from) { - let signature = signer.sign_transaction(request.clone(), from)?; - return build_typed_transaction(request, signature) + match request { + TypedTransactionRequest::Deposit(_) => { + let nil_signature = Signature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + false, + ); + return build_typed_transaction(request, nil_signature) + } + _ => { + for signer in self.signers.iter() { + if signer.accounts().contains(from) { + let signature = signer.sign_transaction(request.clone(), from)?; + return build_typed_transaction(request, signature) + } + } } } Err(BlockchainError::NoSignerAvailable) } - /// Queries the current gas limit - fn current_gas_limit(&self) -> Result { - Ok(self.backend.gas_limit()) - } - async fn block_request(&self, block_number: Option) -> Result { let block_request = match block_number { Some(BlockId::Number(BlockNumber::Pending)) => { @@ -423,12 +510,22 @@ impl EthApi { } _ => { let number = self.backend.ensure_block_number(block_number).await?; - BlockRequest::Number(number.into()) + BlockRequest::Number(number) } }; Ok(block_request) } + async fn inner_raw_transaction(&self, hash: B256) -> Result> { + match self.pool.get_transaction(hash) { + Some(tx) => Ok(Some(tx.transaction.encoded_2718().into())), + None => match self.backend.transaction_by_hash(hash).await? { + Some(tx) => Ok(Some(tx.inner.inner.encoded_2718().into())), + None => Ok(None), + }, + } + } + /// Returns the current client version. /// /// Handler for ETH RPC call: `web3_clientVersion` @@ -442,8 +539,8 @@ impl EthApi { /// Handler for ETH RPC call: `web3_sha3` pub fn sha3(&self, bytes: Bytes) -> Result { node_info!("web3_sha3"); - let hash = ethers::utils::keccak256(bytes.as_ref()); - Ok(ethers::utils::hex::encode(&hash[..])) + let hash = alloy_primitives::keccak256(bytes.as_ref()); + Ok(alloy_primitives::hex::encode_prefixed(&hash[..])) } /// Returns protocol version encoded as a string (quotes are necessary). @@ -459,7 +556,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_hashrate` pub fn hashrate(&self) -> Result { node_info!("eth_hashrate"); - Ok(U256::zero()) + Ok(U256::ZERO) } /// Returns the client coinbase address. @@ -485,7 +582,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_chainId` pub fn eth_chain_id(&self) -> Result> { node_info!("eth_chainId"); - Ok(Some(self.backend.chain_id().as_u64().into())) + Ok(Some(self.backend.chain_id().to::())) } /// Returns the same as `chain_id` @@ -493,7 +590,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_networkId` pub fn network_id(&self) -> Result> { node_info!("eth_networkId"); - let chain_id = self.backend.chain_id().as_u64(); + let chain_id = self.backend.chain_id().to::(); Ok(Some(format!("{chain_id}"))) } @@ -506,8 +603,27 @@ impl EthApi { } /// Returns the current gas price - pub fn gas_price(&self) -> Result { - Ok(self.backend.gas_price()) + fn eth_gas_price(&self) -> Result { + node_info!("eth_gasPrice"); + Ok(U256::from(self.gas_price())) + } + + /// Returns the current gas price + pub fn gas_price(&self) -> u128 { + if self.backend.is_eip1559() { + if self.backend.is_min_priority_fee_enforced() { + (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) + } else { + self.backend.base_fee() as u128 + } + } else { + self.backend.fees().raw_gas_price() + } + } + + /// Returns the excess blob gas and current blob gas price + pub fn excess_blob_gas_and_price(&self) -> Result> { + Ok(self.backend.excess_blob_gas_and_price()) } /// Returns a fee per gas that is an estimate of how much you can pay as a priority fee, or @@ -515,12 +631,19 @@ impl EthApi { /// /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn gas_max_priority_fee_per_gas(&self) -> Result { - Ok(self.backend.max_priority_fee_per_gas()) + self.max_priority_fee_per_gas() + } + + /// Returns the base fee per blob required to send a EIP-4844 tx. + /// + /// Handler for ETH RPC call: `eth_blobBaseFee` + pub fn blob_base_fee(&self) -> Result { + Ok(U256::from(self.backend.fees().base_fee_per_blob_gas())) } /// Returns the block gas limit pub fn gas_limit(&self) -> U256 { - self.backend.gas_limit() + U256::from(self.backend.gas_limit()) } /// Returns the accounts list @@ -528,11 +651,19 @@ impl EthApi { /// Handler for ETH RPC call: `eth_accounts` pub fn accounts(&self) -> Result> { node_info!("eth_accounts"); - let mut accounts = Vec::new(); + let mut unique = HashSet::new(); + let mut accounts: Vec
= Vec::new(); for signer in self.signers.iter() { - accounts.append(&mut signer.accounts()); + accounts.extend(signer.accounts().into_iter().filter(|acc| unique.insert(*acc))); } - Ok(accounts) + accounts.extend( + self.backend + .cheats() + .impersonated_accounts() + .into_iter() + .filter(|acc| unique.insert(*acc)), + ); + Ok(accounts.into_iter().collect()) } /// Returns the number of most recent block. @@ -540,7 +671,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_blockNumber` pub fn block_number(&self) -> Result { node_info!("eth_blockNumber"); - Ok(self.backend.best_number().as_u64().into()) + Ok(U256::from(self.backend.best_number())) } /// Returns balance of the given account. @@ -551,10 +682,10 @@ impl EthApi { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { - return Ok(fork.get_balance(address, number.as_u64()).await?) + if fork.predates_fork(number) { + return Ok(fork.get_balance(address, number).await?) } } } @@ -562,6 +693,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` @@ -570,17 +724,17 @@ impl EthApi { address: Address, index: U256, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_getStorageAt"); 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 BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { - return Ok(fork - .storage_at(address, index, Some(BlockNumber::Number(*number))) - .await?) + if fork.predates_fork(number) { + return Ok(B256::from( + fork.storage_at(address, index, Some(BlockNumber::Number(number))).await?, + )); } } } @@ -591,7 +745,7 @@ impl EthApi { /// Returns block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash(&self, hash: H256) -> Result>> { + pub async fn block_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash(hash).await } @@ -599,7 +753,7 @@ impl EthApi { /// Returns a _full_ block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash_full(&self, hash: H256) -> Result>> { + pub async fn block_by_hash_full(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash_full(hash).await } @@ -607,10 +761,10 @@ impl EthApi { /// Returns block with given number. /// /// Handler for ETH RPC call: `eth_getBlockByNumber` - pub async fn block_by_number(&self, number: BlockNumber) -> Result>> { + pub async fn block_by_number(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { - return Ok(Some(self.pending_block().await)) + return Ok(Some(self.pending_block().await)); } self.backend.block_by_number(number).await @@ -619,13 +773,10 @@ impl EthApi { /// Returns a _full_ block with given number /// /// Handler for ETH RPC call: `eth_getBlockByNumber` - pub async fn block_by_number_full( - &self, - number: BlockNumber, - ) -> Result>> { + pub async fn block_by_number_full(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { - return Ok(self.pending_block_full().await) + return Ok(self.pending_block_full().await); } self.backend.block_by_number_full(number).await } @@ -642,16 +793,21 @@ impl EthApi { block_number: Option, ) -> Result { node_info!("eth_getTransactionCount"); - self.get_transaction_count(address, block_number).await + self.get_transaction_count(address, block_number).await.map(U256::from) } /// Returns the number of transactions in a block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockTransactionCountByHash` - pub async fn block_transaction_count_by_hash(&self, hash: H256) -> Result> { + pub async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockTransactionCountByHash"); let block = self.backend.block_by_hash(hash).await?; - Ok(block.map(|b| b.transactions.len().into())) + let txs = block.map(|b| match b.transactions() { + BlockTransactions::Full(txs) => U256::from(txs.len()), + BlockTransactions::Hashes(txs) => U256::from(txs.len()), + BlockTransactions::Uncle => U256::from(0), + }); + Ok(txs) } /// Returns the number of transactions in a block with given block number. @@ -665,20 +821,25 @@ impl EthApi { let block_request = self.block_request(Some(block_number.into())).await?; if let BlockRequest::Pending(txs) = block_request { let block = self.backend.pending_block(txs).await; - return Ok(Some(block.transactions.len().into())) + return Ok(Some(U256::from(block.transactions.len()))); } let block = self.backend.block_by_number(block_number).await?; - Ok(block.map(|b| b.transactions.len().into())) + let txs = block.map(|b| match b.transactions() { + BlockTransactions::Full(txs) => U256::from(txs.len()), + BlockTransactions::Hashes(txs) => U256::from(txs.len()), + BlockTransactions::Uncle => U256::from(0), + }); + Ok(txs) } /// Returns the number of uncles in a block with given hash. /// /// Handler for ETH RPC call: `eth_getUncleCountByBlockHash` - pub async fn block_uncles_count_by_hash(&self, hash: H256) -> Result { + pub async fn block_uncles_count_by_hash(&self, hash: B256) -> Result { node_info!("eth_getUncleCountByBlockHash"); let block = self.backend.block_by_hash(hash).await?.ok_or(BlockchainError::BlockNotFound)?; - Ok(block.uncles.len().into()) + Ok(U256::from(block.uncles.len())) } /// Returns the number of uncles in a block with given block number. @@ -691,7 +852,7 @@ impl EthApi { .block_by_number(block_number) .await? .ok_or(BlockchainError::BlockNotFound)?; - Ok(block.uncles.len().into()) + Ok(U256::from(block.uncles.len())) } /// Returns the code at given address at given time (block number). @@ -701,10 +862,10 @@ impl EthApi { node_info!("eth_getCode"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { - return Ok(fork.get_code(address, number.as_u64()).await?) + if fork.predates_fork(number) { + return Ok(fork.get_code(address, number).await?) } } } @@ -718,18 +879,18 @@ impl EthApi { pub async fn get_proof( &self, address: Address, - keys: Vec, + keys: Vec, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_getProof"); let block_request = self.block_request(block_number).await?; - if let BlockRequest::Number(number) = &block_request { + // If we're in forking mode, or still on the forked block (no blocks mined yet) then we can + // delegate the call. + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - // if we're in forking mode, or still on the forked block (no blocks mined yet) then - // we can delegate the call - if fork.predates_fork_inclusive(number.as_u64()) { - return Ok(fork.get_proof(address, keys, Some((*number).into())).await?) + if fork.predates_fork_inclusive(number) { + return Ok(fork.get_proof(address, keys, Some(number.into())).await?) } } } @@ -769,6 +930,7 @@ impl EthApi { node_info!("eth_signTypedData_v4"); let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; let signature = signer.sign_typed_data(address, data).await?; + let signature = alloy_primitives::hex::encode(signature.as_bytes()); Ok(format!("0x{signature}")) } @@ -778,46 +940,65 @@ impl EthApi { pub async fn sign(&self, address: Address, content: impl AsRef<[u8]>) -> Result { node_info!("eth_sign"); let signer = self.get_signer(address).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = signer.sign(address, content.as_ref()).await?; + let signature = + alloy_primitives::hex::encode(signer.sign(address, content.as_ref()).await?.as_bytes()); Ok(format!("0x{signature}")) } /// Signs a transaction /// /// Handler for ETH RPC call: `eth_signTransaction` - pub async fn sign_transaction(&self, request: EthTransactionRequest) -> Result { + pub async fn sign_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { node_info!("eth_signTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.get(0).cloned().ok_or(BlockchainError::NoSignerAvailable) + self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) })?; let (nonce, _) = self.request_nonce(&request, from).await?; + if request.gas.is_none() { + // estimate if not provided + if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + request.gas = Some(gas.to()); + } + } + let request = self.build_typed_tx_request(request, nonce)?; - let signer = self.get_signer(from).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = signer.sign_transaction(request, &from)?; - Ok(format!("0x{signature}")) + let signed_transaction = self.sign_request(&from, request)?.encoded_2718(); + Ok(alloy_primitives::hex::encode_prefixed(signed_transaction)) } /// Sends a transaction /// /// Handler for ETH RPC call: `eth_sendTransaction` - pub async fn send_transaction(&self, request: EthTransactionRequest) -> Result { + pub async fn send_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { node_info!("eth_sendTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { - self.accounts()?.get(0).cloned().ok_or(BlockchainError::NoSignerAvailable) + self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) })?; - let (nonce, on_chain_nonce) = self.request_nonce(&request, from).await?; + if request.gas.is_none() { + // estimate if not provided + if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { + request.gas = Some(gas.to()); + } + } + let request = self.build_typed_tx_request(request, nonce)?; // 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"); @@ -827,12 +1008,11 @@ impl EthApi { self.ensure_typed_transaction_supported(&transaction)?; PendingTransaction::new(transaction)? }; - // pre-validate self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce.as_u64(), from)]; + let provides = vec![to_marker(nonce, from)]; debug_assert!(requires != provides); self.add_pending_transaction(pending_transaction, requires, provides) @@ -843,31 +1023,15 @@ impl EthApi { /// Handler for ETH RPC call: `eth_sendRawTransaction` pub async fn send_raw_transaction(&self, tx: Bytes) -> Result { node_info!("eth_sendRawTransaction"); - let data = tx.as_ref(); + let mut data = tx.as_ref(); if data.is_empty() { - return Err(BlockchainError::EmptyRawTransactionData) + return Err(BlockchainError::EmptyRawTransactionData); } - let transaction = if data[0] > 0x7f { - // legacy transaction - match rlp::decode::(data) { - Ok(transaction) => TypedTransaction::Legacy(transaction), - Err(_) => return Err(BlockchainError::FailedToDecodeSignedTransaction), - } - } else { - // the [TypedTransaction] requires a valid rlp input, - // but EIP-1559 prepends a version byte, so we need to encode the data first to get a - // valid rlp and then rlp decode impl of `TypedTransaction` will remove and check the - // version byte - let extend = rlp::encode(&data); - let tx = match rlp::decode::(&extend[..]) { - Ok(transaction) => transaction, - Err(_) => return Err(BlockchainError::FailedToDecodeSignedTransaction), - }; - self.ensure_typed_transaction_supported(&tx)?; + let transaction = TypedTransaction::decode_2718(&mut data) + .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; - tx - }; + self.ensure_typed_transaction_supported(&transaction)?; let pending_transaction = PendingTransaction::new(transaction)?; @@ -876,13 +1040,13 @@ impl EthApi { let on_chain_nonce = self.backend.current_nonce(*pending_transaction.sender()).await?; let from = *pending_transaction.sender(); - let nonce = *pending_transaction.transaction.nonce(); + let nonce = pending_transaction.transaction.nonce(); let requires = required_marker(nonce, on_chain_nonce, from); let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = PoolTransaction { requires, - provides: vec![to_marker(nonce.as_u64(), *pending_transaction.sender())], + provides: vec![to_marker(nonce, *pending_transaction.sender())], pending_transaction, priority, }; @@ -897,20 +1061,20 @@ impl EthApi { /// Handler for ETH RPC call: `eth_call` pub async fn call( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, overrides: Option, ) -> Result { node_info!("eth_call"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { + if fork.predates_fork(number) { if overrides.is_some() { return Err(BlockchainError::StateOverrideError( "not available on past forked blocks".to_string(), - )) + )); } return Ok(fork.call(&request, Some(number.into())).await?) } @@ -921,14 +1085,19 @@ impl EthApi { request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); - - let (exit, out, gas, _) = - self.backend.call(request, fees, Some(block_request), overrides).await?; - trace!(target : "node", "Call status {:?}, gas {}", exit, gas); - - ensure_return_ok(exit, &out) + // this can be blocking for a bit, especially in forking mode + // + self.on_blocking_task(|this| async move { + let (exit, out, gas, _) = + this.backend.call(request, fees, Some(block_request), overrides).await?; + trace!(target : "node", "Call status {:?}, gas {}", exit, gas); + + ensure_return_ok(exit, &out) + }) + .await } /// This method creates an EIP2930 type accessList based on a given Transaction. The accessList @@ -946,15 +1115,15 @@ impl EthApi { /// Handler for ETH RPC call: `eth_createAccessList` pub async fn create_access_list( &self, - mut request: EthTransactionRequest, + mut request: WithOtherFields, block_number: Option, - ) -> Result { + ) -> Result { node_info!("eth_createAccessList"); let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { + if fork.predates_fork(number) { return Ok(fork.create_access_list(&request, Some(number.into())).await?) } } @@ -971,7 +1140,7 @@ impl EthApi { ensure_return_ok(exit, &out)?; // execute again but with access list set - request.access_list = Some(access_list.0.clone()); + request.access_list = Some(access_list.clone()); let (exit, out, gas_used, _) = self.backend.call_with_state( &state, @@ -981,9 +1150,10 @@ impl EthApi { )?; ensure_return_ok(exit, &out)?; - Ok(AccessListWithGasUsed { + Ok(AccessListResult { access_list: AccessList(access_list.0), - gas_used: gas_used.into(), + gas_used: U256::from(gas_used), + error: None, }) }) .await? @@ -995,12 +1165,18 @@ impl EthApi { /// Handler for ETH RPC call: `eth_estimateGas` pub async fn estimate_gas( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, + overrides: Option, ) -> Result { node_info!("eth_estimateGas"); - self.do_estimate_gas(request, block_number.or_else(|| Some(BlockNumber::Pending.into()))) - .await + self.do_estimate_gas( + request, + block_number.or_else(|| Some(BlockNumber::Pending.into())), + overrides, + ) + .await + .map(U256::from) } /// Get transaction by its hash. @@ -1009,7 +1185,7 @@ impl EthApi { /// this will also scan the mempool for a matching pending transaction /// /// Handler for ETH RPC call: `eth_getTransactionByHash` - pub async fn transaction_by_hash(&self, hash: H256) -> Result> { + pub async fn transaction_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getTransactionByHash"); let mut tx = self.pool.get_transaction(hash).map(|pending| { let from = *pending.sender(); @@ -1037,9 +1213,9 @@ impl EthApi { /// Handler for ETH RPC call: `eth_getTransactionByBlockHashAndIndex` pub async fn transaction_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: Index, - ) -> Result> { + ) -> Result> { node_info!("eth_getTransactionByBlockHashAndIndex"); self.backend.transaction_by_block_hash_and_index(hash, index).await } @@ -1051,7 +1227,7 @@ impl EthApi { &self, block: BlockNumber, idx: Index, - ) -> Result> { + ) -> Result> { node_info!("eth_getTransactionByBlockNumberAndIndex"); self.backend.transaction_by_block_number_and_index(block, idx).await } @@ -1059,25 +1235,34 @@ impl EthApi { /// Returns transaction receipt by transaction hash. /// /// Handler for ETH RPC call: `eth_getTransactionReceipt` - pub async fn transaction_receipt(&self, hash: H256) -> Result> { + pub async fn transaction_receipt(&self, hash: B256) -> Result> { node_info!("eth_getTransactionReceipt"); let tx = self.pool.get_transaction(hash); if tx.is_some() { - return Ok(None) + return Ok(None); } self.backend.transaction_receipt(hash).await } + /// Returns block receipts by block number. + /// + /// Handler for ETH RPC call: `eth_getBlockReceipts` + pub async fn block_receipts(&self, number: BlockId) -> Result>> { + node_info!("eth_getBlockReceipts"); + self.backend.block_receipts(number).await + } + /// Returns an uncles at given block and index. /// /// Handler for ETH RPC call: `eth_getUncleByBlockHashAndIndex` pub async fn uncle_by_block_hash_and_index( &self, - block_hash: H256, + block_hash: B256, idx: Index, - ) -> Result>> { + ) -> Result> { node_info!("eth_getUncleByBlockHashAndIndex"); - let number = self.backend.ensure_block_number(Some(BlockId::Hash(block_hash))).await?; + let number = + self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { return Ok(fork.uncle_by_block_hash_and_index(block_hash, idx.into()).await?) @@ -1094,7 +1279,7 @@ impl EthApi { &self, block_number: BlockNumber, idx: Index, - ) -> Result>> { + ) -> Result> { node_info!("eth_getUncleByBlockNumberAndIndex"); let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; if let Some(fork) = self.get_fork() { @@ -1133,7 +1318,7 @@ impl EthApi { /// Used for submitting a proof-of-work solution. /// /// Handler for ETH RPC call: `eth_submitWork` - pub fn submit_work(&self, _: H64, _: H256, _: H256) -> Result { + pub fn submit_work(&self, _: B64, _: B256, _: B256) -> Result { node_info!("eth_submitWork"); Err(BlockchainError::RpcUnimplemented) } @@ -1141,12 +1326,12 @@ impl EthApi { /// Used for submitting mining hashrate. /// /// Handler for ETH RPC call: `eth_submitHashrate` - pub fn submit_hashrate(&self, _: U256, _: H256) -> Result { + pub fn submit_hashrate(&self, _: U256, _: B256) -> Result { node_info!("eth_submitHashrate"); Err(BlockchainError::RpcUnimplemented) } - /// Introduced in EIP-1159 for getting information on the appropriate priority fee to use. + /// Introduced in EIP-1559 for getting information on the appropriate priority fee to use. /// /// Handler for ETH RPC call: `eth_feeHistory` pub async fn fee_history( @@ -1158,13 +1343,13 @@ impl EthApi { node_info!("eth_feeHistory"); // max number of blocks in the requested range - let current = self.backend.best_number().as_u64(); + let current = self.backend.best_number(); let slots_in_an_epoch = 32u64; let number = match newest_block { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, - BlockNumber::Number(n) => n.as_u64(), + BlockNumber::Number(n) => n, BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), }; @@ -1174,89 +1359,75 @@ impl EthApi { // if we're still at the forked block we don't have any history and can't compute it // efficiently, instead we fetch it from the fork if fork.predates_fork_inclusive(number) { - return Ok(fork - .fee_history( - block_count, - BlockNumber::Number(number.into()), - &reward_percentiles, - ) - .await?) + return fork + .fee_history(block_count.to(), BlockNumber::Number(number), &reward_percentiles) + .await + .map_err(BlockchainError::AlloyForkProvider); } } const MAX_BLOCK_COUNT: u64 = 1024u64; - let range_limit = U256::from(MAX_BLOCK_COUNT); - let block_count = - if block_count > range_limit { range_limit.as_u64() } else { block_count.as_u64() }; + let block_count = block_count.to::().min(MAX_BLOCK_COUNT); // highest and lowest block num in the requested range let highest = number; let lowest = highest.saturating_sub(block_count.saturating_sub(1)); // only support ranges that are in cache range - if lowest < self.backend.best_number().as_u64().saturating_sub(self.fee_history_limit) { - return Err(FeeHistoryError::InvalidBlockRange.into()) + if lowest < self.backend.best_number().saturating_sub(self.fee_history_limit) { + return Err(FeeHistoryError::InvalidBlockRange.into()); } - let fee_history = self.fee_history_cache.lock(); - let mut response = FeeHistory { - oldest_block: U256::from(lowest), + oldest_block: lowest, base_fee_per_gas: Vec::new(), gas_used_ratio: Vec::new(), - reward: Default::default(), + reward: Some(Default::default()), + base_fee_per_blob_gas: Default::default(), + blob_gas_used_ratio: Default::default(), }; - let mut rewards = Vec::new(); - // iter over the requested block range - for n in lowest..=highest { - // - if let Some(block) = fee_history.get(&n) { - response.base_fee_per_gas.push(U256::from(block.base_fee)); - response.gas_used_ratio.push(block.gas_used_ratio); - - // requested percentiles - if !reward_percentiles.is_empty() { - let mut block_rewards = Vec::new(); - let resolution_per_percentile: f64 = 2.0; - for p in &reward_percentiles { - let p = p.clamp(0.0, 100.0); - let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; - let reward = if let Some(r) = block.rewards.get(index as usize) { - U256::from(*r) - } else { - U256::zero() - }; - block_rewards.push(reward); + + { + let fee_history = self.fee_history_cache.lock(); + + // iter over the requested block range + for n in lowest..=highest { + // + if let Some(block) = fee_history.get(&n) { + response.base_fee_per_gas.push(block.base_fee); + response.base_fee_per_blob_gas.push(block.base_fee_per_blob_gas.unwrap_or(0)); + response.blob_gas_used_ratio.push(block.blob_gas_used_ratio); + response.gas_used_ratio.push(block.gas_used_ratio); + + // requested percentiles + if !reward_percentiles.is_empty() { + let mut block_rewards = Vec::new(); + let resolution_per_percentile: f64 = 2.0; + for p in &reward_percentiles { + let p = p.clamp(0.0, 100.0); + let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; + let reward = block.rewards.get(index as usize).map_or(0, |r| *r); + block_rewards.push(reward); + } + rewards.push(block_rewards); } - rewards.push(block_rewards); } } } - response.reward = rewards; + response.reward = Some(rewards); - // calculate next base fee - if let (Some(last_gas_used), Some(last_fee_per_gas)) = - (response.gas_used_ratio.last(), response.base_fee_per_gas.last()) - { - let elasticity = self.backend.elasticity(); - let last_fee_per_gas = last_fee_per_gas.as_u64() as f64; - if last_gas_used > &0.5 { - // increase base gas - let increase = ((last_gas_used - 0.5) * 2f64) * elasticity; - let new_base_fee = (last_fee_per_gas + (last_fee_per_gas * increase)) as u64; - response.base_fee_per_gas.push(U256::from(new_base_fee)); - } else if last_gas_used < &0.5 { - // decrease gas - let increase = ((0.5 - last_gas_used) * 2f64) * elasticity; - let new_base_fee = (last_fee_per_gas - (last_fee_per_gas * increase)) as u64; - response.base_fee_per_gas.push(U256::from(new_base_fee)); - } else { - // same base gas - response.base_fee_per_gas.push(U256::from(last_fee_per_gas as u64)); - } - } + // add the next block's base fee to the response + // The spec states that `base_fee_per_gas` "[..] includes the next block after the + // newest of the returned range, because this value can be derived from the + // newest block" + response.base_fee_per_gas.push(self.backend.fees().base_fee() as u128); + + // Same goes for the `base_fee_per_blob_gas`: + // > [..] includes the next block after the newest of the returned range, because this + // > value can be derived from the newest block. + response.base_fee_per_blob_gas.push(self.backend.fees().base_fee_per_blob_gas()); Ok(response) } @@ -1269,7 +1440,22 @@ impl EthApi { /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn max_priority_fee_per_gas(&self) -> Result { node_info!("eth_maxPriorityFeePerGas"); - Ok(self.backend.max_priority_fee_per_gas()) + Ok(U256::from(self.lowest_suggestion_tip())) + } + + /// Returns the suggested fee cap. + /// + /// Returns at least [MIN_SUGGESTED_PRIORITY_FEE] + fn lowest_suggestion_tip(&self) -> u128 { + let block_number = self.backend.best_number(); + let latest_cached_block = self.fee_history_cache.lock().get(&block_number).cloned(); + + match latest_cached_block { + Some(block) => block.rewards.iter().copied().min(), + None => self.fee_history_cache.lock().values().flat_map(|b| b.rewards.clone()).min(), + } + .map(|fee| fee.max(MIN_SUGGESTED_PRIORITY_FEE)) + .unwrap_or(MIN_SUGGESTED_PRIORITY_FEE) } /// Creates a filter object, based on filter options, to notify when the state changes (logs). @@ -1337,19 +1523,53 @@ impl EthApi { Ok(self.filters.uninstall_filter(id).await.is_some()) } + /// Returns EIP-2718 encoded raw transaction + /// + /// Handler for RPC call: `debug_getRawTransaction` + pub async fn raw_transaction(&self, hash: B256) -> Result> { + node_info!("debug_getRawTransaction"); + self.inner_raw_transaction(hash).await + } + + /// Returns EIP-2718 encoded raw transaction by block hash and index + /// + /// Handler for RPC call: `eth_getRawTransactionByBlockHashAndIndex` + pub async fn raw_transaction_by_block_hash_and_index( + &self, + block_hash: B256, + index: Index, + ) -> Result> { + node_info!("eth_getRawTransactionByBlockHashAndIndex"); + match self.backend.transaction_by_block_hash_and_index(block_hash, index).await? { + Some(tx) => self.inner_raw_transaction(tx.tx_hash()).await, + None => Ok(None), + } + } + + /// Returns EIP-2718 encoded raw transaction by block number and index + /// + /// Handler for RPC call: `eth_getRawTransactionByBlockNumberAndIndex` + pub async fn raw_transaction_by_block_number_and_index( + &self, + block_number: BlockNumber, + index: Index, + ) -> Result> { + node_info!("eth_getRawTransactionByBlockNumberAndIndex"); + match self.backend.transaction_by_block_number_and_index(block_number, index).await? { + Some(tx) => self.inner_raw_transaction(tx.tx_hash()).await, + None => Ok(None), + } + } + /// Returns traces for the transaction hash for geth's tracing endpoint /// /// Handler for RPC call: `debug_traceTransaction` pub async fn debug_trace_transaction( &self, - tx_hash: H256, + tx_hash: B256, opts: GethDebugTracingOptions, ) -> Result { node_info!("debug_traceTransaction"); - if opts.tracer.is_some() { - return Err(RpcError::invalid_params("non-default tracer not supported yet").into()) - } - self.backend.debug_trace_transaction(tx_hash, opts).await } @@ -1358,29 +1578,29 @@ impl EthApi { /// Handler for RPC call: `debug_traceCall` pub async fn debug_trace_call( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, - opts: GethDebugTracingOptions, - ) -> Result { + opts: GethDebugTracingCallOptions, + ) -> Result { node_info!("debug_traceCall"); - if opts.tracer.is_some() { - return Err(RpcError::invalid_params("non-default tracer not supported yet").into()) - } let block_request = self.block_request(block_number).await?; let fees = FeeDetails::new( request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); - 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 /// /// Handler for RPC call: `trace_transaction` - pub async fn trace_transaction(&self, tx_hash: H256) -> Result> { + pub async fn trace_transaction(&self, tx_hash: B256) -> Result> { node_info!("trace_transaction"); self.backend.trace_transaction(tx_hash).await } @@ -1388,10 +1608,21 @@ impl EthApi { /// Returns traces for the transaction hash via parity's tracing endpoint /// /// Handler for RPC call: `trace_block` - pub async fn trace_block(&self, block: BlockNumber) -> Result> { + pub async fn trace_block(&self, block: BlockNumber) -> Result> { node_info!("trace_block"); self.backend.trace_block(block).await } + + /// Returns filtered traces over blocks + /// + /// Handler for RPC call: `trace_filter` + pub async fn trace_filter( + &self, + filter: TraceFilter, + ) -> Result> { + node_info!("trace_filter"); + self.backend.trace_filter(filter).await + } } // == impl EthApi anvil endpoints == @@ -1402,7 +1633,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(()) } @@ -1411,7 +1642,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(()) } @@ -1420,7 +1651,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(()) } @@ -1432,6 +1663,14 @@ impl EthApi { Ok(self.miner.is_auto_mine()) } + /// Returns the value of mining interval, if set. + /// + /// Handler for ETH RPC call: `anvil_getIntervalMining`. + pub fn anvil_get_interval_mining(&self) -> Result> { + node_info!("anvil_getIntervalMining"); + Ok(self.miner.get_interval()) + } + /// Enables or disables, based on the single boolean argument, the automatic mining of new /// blocks with each new transaction submitted to the network. /// @@ -1440,7 +1679,7 @@ impl EthApi { node_info!("evm_setAutomine"); if self.miner.is_auto_mine() { if enable_automine { - return Ok(()) + return Ok(()); } self.miner.set_mining_mode(MiningMode::None); } else if enable_automine { @@ -1456,20 +1695,20 @@ impl EthApi { /// Handler for ETH RPC call: `anvil_mine` pub async fn anvil_mine(&self, num_blocks: Option, interval: Option) -> Result<()> { node_info!("anvil_mine"); - let interval = interval.map(|i| i.as_u64()); - let blocks = num_blocks.unwrap_or_else(U256::one); - if blocks == U256::zero() { - return Ok(()) + let interval = interval.map(|i| i.to::()); + let blocks = num_blocks.unwrap_or(U256::from(1)); + if blocks.is_zero() { + return Ok(()); } // mine all the blocks - for _ in 0..blocks.as_u64() { - self.mine_one().await; - + for _ in 0..blocks.to::() { // If we have an interval, jump forwards in time to the "next" timestamp if let Some(interval) = interval { self.backend.time().increase_time(interval); } + + self.mine_one().await; } Ok(()) @@ -1497,9 +1736,18 @@ impl EthApi { /// Removes transactions from the pool /// /// Handler for RPC call: `anvil_dropTransaction` - pub async fn anvil_drop_transaction(&self, tx_hash: H256) -> Result> { + pub async fn anvil_drop_transaction(&self, tx_hash: B256) -> Result> { node_info!("anvil_dropTransaction"); - Ok(self.pool.drop_transaction(tx_hash).map(|tx| *tx.hash())) + Ok(self.pool.drop_transaction(tx_hash).map(|tx| tx.hash())) + } + + /// Removes all transactions from the pool + /// + /// Handler for RPC call: `anvil_dropAllTransactions` + pub async fn anvil_drop_all_transactions(&self) -> Result<()> { + node_info!("anvil_dropAllTransactions"); + self.pool.clear(); + Ok(()) } /// Reset the fork to a fresh forked state, and optionally update the fork config. @@ -1510,12 +1758,20 @@ impl EthApi { pub async fn anvil_reset(&self, forking: Option) -> Result<()> { node_info!("anvil_reset"); if let Some(forking) = forking { + // if we're resetting the fork we need to reset the instance id + self.reset_instance_id(); self.backend.reset_fork(forking).await } else { Err(BlockchainError::RpcUnimplemented) } } + pub async fn anvil_set_chain_id(&self, chain_id: u64) -> Result<()> { + node_info!("anvil_setChainId"); + self.backend.set_chain_id(chain_id); + Ok(()) + } + /// Modifies the balance of an account. /// /// Handler for RPC call: `anvil_setBalance` @@ -1550,7 +1806,7 @@ impl EthApi { &self, address: Address, slot: U256, - val: H256, + val: B256, ) -> Result { node_info!("anvil_setStorageAt"); self.backend.set_storage_at(address, slot, val).await?; @@ -1575,9 +1831,9 @@ impl EthApi { return Err(RpcError::invalid_params( "anvil_setMinGasPrice is not supported when EIP-1559 is active", ) - .into()) + .into()); } - self.backend.set_gas_price(gas); + self.backend.set_gas_price(gas.to()); Ok(()) } @@ -1590,9 +1846,9 @@ impl EthApi { return Err(RpcError::invalid_params( "anvil_setNextBlockBaseFeePerGas is only supported when EIP-1559 is active", ) - .into()) + .into()); } - self.backend.set_base_fee(basefee); + self.backend.set_base_fee(basefee.to()); Ok(()) } @@ -1605,18 +1861,24 @@ impl EthApi { Ok(()) } - /// Create a bufer that represents all state on the chain, which can be loaded to separate + /// Create a buffer that represents all state on the chain, which can be loaded to separate /// process by calling `anvil_loadState` /// /// Handler for RPC call: `anvil_dumpState` - pub async fn anvil_dump_state(&self) -> Result { + pub async fn anvil_dump_state( + &self, + preserve_historical_states: Option, + ) -> Result { node_info!("anvil_dumpState"); - self.backend.dump_state().await + self.backend.dump_state(preserve_historical_states.unwrap_or(false)).await } /// Returns the current state - pub async fn serialized_state(&self) -> Result { - self.backend.serialized_state().await + pub async fn serialized_state( + &self, + preserve_historical_states: bool, + ) -> Result { + self.backend.serialized_state(preserve_historical_states).await } /// Append chain state buffer to current chain. Will overwrite any conflicting addresses or @@ -1625,7 +1887,7 @@ impl EthApi { /// Handler for RPC call: `anvil_loadState` pub async fn anvil_load_state(&self, buf: Bytes) -> Result { node_info!("anvil_loadState"); - self.backend.load_state(buf).await + self.backend.load_state_bytes(buf).await } /// Retrieves the Anvil node configuration params. @@ -1637,21 +1899,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: 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.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(), - chain_id: self.backend.chain_id(), + base_fee: self.backend.base_fee() as u128, + chain_id: self.backend.chain_id().to::(), gas_limit: self.backend.gas_limit(), - gas_price: self.backend.gas_price(), + gas_price: self.gas_price(), }, fork_config: fork_config .map(|fork| { @@ -1667,12 +1930,188 @@ impl EthApi { }) } + /// Retrieves metadata about the Anvil instance. + /// + /// Handler for RPC call: `anvil_metadata` + pub async fn anvil_metadata(&self) -> Result { + node_info!("anvil_metadata"); + let fork_config = self.backend.get_fork(); + + Ok(Metadata { + client_version: CLIENT_VERSION.to_string(), + chain_id: self.backend.chain_id().to::(), + latest_block_hash: self.backend.best_hash(), + latest_block_number: self.backend.best_number(), + instance_id: *self.instance_id.read(), + forked_network: fork_config.map(|cfg| ForkedNetwork { + chain_id: cfg.chain_id(), + fork_block_number: cfg.block_number(), + fork_block_hash: cfg.block_hash(), + }), + snapshots: self.backend.list_state_snapshots(), + }) + } + + pub async fn anvil_remove_pool_transactions(&self, address: Address) -> Result<()> { + node_info!("anvil_removePoolTransactions"); + self.pool.remove_transactions_by_address(address); + Ok(()) + } + + /// Reorg the chain to a specific depth and mine new blocks back to the canonical height. + /// + /// e.g depth = 3 + /// A -> B -> C -> D -> E + /// A -> B -> C' -> D' -> E' + /// + /// Depth specifies the height to reorg the chain back to. Depth must not exceed the current + /// chain height, i.e. can't reorg past the genesis block. + /// + /// Optionally supply a list of transaction and block pairs that will populate the reorged + /// blocks. The maximum block number of the pairs must not exceed the specified depth. + /// + /// Handler for RPC call: `anvil_reorg` + pub async fn anvil_reorg(&self, options: ReorgOptions) -> Result<()> { + node_info!("anvil_reorg"); + let depth = options.depth; + let tx_block_pairs = options.tx_block_pairs; + + // Check reorg depth doesn't exceed current chain height + let current_height = self.backend.best_number(); + let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( + RpcError::invalid_params(format!( + "Reorg depth must not exceed current chain height: current height {current_height}, depth {depth}" + )), + ))?; + + // Get the common ancestor block + let common_block = + self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; + + // Convert the transaction requests to pool transactions if they exist, otherwise use empty + // hashmap + let block_pool_txs = if tx_block_pairs.is_empty() { + HashMap::default() + } else { + let mut pairs = tx_block_pairs; + + // Check the maximum block supplied number will not exceed the reorged chain height + if let Some((_, num)) = pairs.iter().find(|(_, num)| *num >= depth) { + return Err(BlockchainError::RpcError(RpcError::invalid_params(format!( + "Block number for reorg tx will exceed the reorged chain height. Block number {num} must not exceed (depth-1) {}", + depth-1 + )))); + } + + // Sort by block number to make it easier to manage new nonces + pairs.sort_by_key(|a| a.1); + + // Manage nonces for each signer + // address -> cumulative nonce + let mut nonces: HashMap = HashMap::default(); + + let mut txs: HashMap>> = HashMap::default(); + for pair in pairs { + let (tx_data, block_index) = pair; + + let mut tx_req = match tx_data { + TransactionData::JSON(req) => WithOtherFields::new(req), + TransactionData::Raw(bytes) => { + let mut data = bytes.as_ref(); + let decoded = TypedTransaction::decode_2718(&mut data) + .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; + let request = + TransactionRequest::try_from(decoded.clone()).map_err(|_| { + BlockchainError::RpcError(RpcError::invalid_params( + "Failed to convert raw transaction", + )) + })?; + WithOtherFields::new(request) + } + }; + + let from = tx_req.from.map(Ok).unwrap_or_else(|| { + self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) + })?; + + // Get the nonce at the common block + let curr_nonce = nonces.entry(from).or_insert( + self.get_transaction_count(from, Some(common_block.header.number.into())) + .await?, + ); + + // Estimate gas + if tx_req.gas.is_none() { + if let Ok(gas) = self.estimate_gas(tx_req.clone(), None, None).await { + tx_req.gas = Some(gas.to()); + } + } + + // Build typed transaction request + let typed = self.build_typed_tx_request(tx_req, *curr_nonce)?; + + // Increment nonce + *curr_nonce += 1; + + // Handle signer and convert to pending transaction + let pending = if self.is_impersonated(from) { + let bypass_signature = self.impersonated_signature(&typed); + let transaction = sign::build_typed_transaction(typed, bypass_signature)?; + self.ensure_typed_transaction_supported(&transaction)?; + PendingTransaction::with_impersonated(transaction, from) + } else { + let transaction = self.sign_request(&from, typed)?; + self.ensure_typed_transaction_supported(&transaction)?; + PendingTransaction::new(transaction)? + }; + + let pooled = PoolTransaction::new(pending); + txs.entry(block_index).or_default().push(Arc::new(pooled)); + } + + txs + }; + + self.backend.reorg(depth, block_pool_txs, common_block).await?; + Ok(()) + } + + /// Rollback the chain to a specific depth. + /// + /// e.g depth = 3 + /// A -> B -> C -> D -> E + /// A -> B + /// + /// Depth specifies the height to rollback the chain back to. Depth must not exceed the current + /// chain height, i.e. can't rollback past the genesis block. + /// + /// Handler for RPC call: `anvil_rollback` + pub async fn anvil_rollback(&self, depth: Option) -> Result<()> { + node_info!("anvil_rollback"); + let depth = depth.unwrap_or(1); + + // Check reorg depth doesn't exceed current chain height + let current_height = self.backend.best_number(); + let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( + RpcError::invalid_params(format!( + "Rollback depth must not exceed current chain height: current height {current_height}, depth {depth}" + )), + ))?; + + // Get the common ancestor block + let common_block = + self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; + + self.backend.rollback(common_block).await?; + Ok(()) + } + /// Snapshot the state of the blockchain at the current block. /// /// Handler for RPC call: `evm_snapshot` pub async fn evm_snapshot(&self) -> Result { node_info!("evm_snapshot"); - Ok(self.backend.create_snapshot().await) + Ok(self.backend.create_state_snapshot().await) } /// Revert the state of the blockchain to a previous snapshot. @@ -1681,7 +2120,7 @@ impl EthApi { /// Handler for RPC call: `evm_revert` pub async fn evm_revert(&self, id: U256) -> Result { node_info!("evm_revert"); - self.backend.revert_snapshot(id).await + self.backend.revert_state_snapshot(id).await } /// Jump forward in time by the given amount of time, in seconds. @@ -1719,7 +2158,7 @@ impl EthApi { /// Handler for RPC call: `evm_setBlockGasLimit` pub fn evm_set_block_gas_limit(&self, gas_limit: U256) -> Result { node_info!("evm_setBlockGasLimit"); - self.backend.set_gas_limit(gas_limit); + self.backend.set_gas_limit(gas_limit.to()); Ok(true) } @@ -1746,7 +2185,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?; @@ -1763,28 +2202,38 @@ 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?; let mut blocks = Vec::with_capacity(mined_blocks as usize); - let latest = self.backend.best_number().as_u64(); + let latest = self.backend.best_number(); for offset in (0..mined_blocks).rev() { let block_num = latest - offset; if let Some(mut block) = - self.backend.block_by_number_full(BlockNumber::Number(block_num.into())).await? + self.backend.block_by_number_full(BlockNumber::Number(block_num)).await? { - for tx in block.transactions.iter_mut() { - if let Some(receipt) = self.backend.mined_transaction_receipt(tx.hash) { + let block_txs = match block.transactions_mut() { + BlockTransactions::Full(txs) => txs, + BlockTransactions::Hashes(_) | BlockTransactions::Uncle => unreachable!(), + }; + for tx in block_txs.iter_mut() { + if let Some(receipt) = self.backend.mined_transaction_receipt(tx.tx_hash()) { if let Some(output) = receipt.out { // insert revert reason if failure - if receipt.inner.status.unwrap_or_default().as_u64() == 0 { - if let Some(reason) = decode_revert_reason(&output) { + if !receipt + .inner + .inner + .as_receipt_with_bloom() + .receipt + .status + .coerce_status() + { + if let Some(reason) = + RevertDecoder::new().maybe_decode(&output, None) + { tx.other.insert( "revertReason".to_string(), serde_json::to_value(reason).expect("Infallible"), @@ -1798,6 +2247,7 @@ impl EthApi { } } } + block.transactions = BlockTransactions::Full(block_txs.to_vec()); blocks.push(block); } } @@ -1821,16 +2271,16 @@ impl EthApi { node_info!("anvil_setRpcUrl"); if let Some(fork) = self.backend.get_fork() { let mut config = fork.config.write(); - let interval = config.provider.get_interval(); + // let interval = config.provider.get_interval(); let new_provider = Arc::new( - ProviderBuilder::new(&url) - .max_retry(10) - .initial_backoff(1000) - .build() - .map_err(|_| { - ProviderError::CustomError(format!("Failed to parse invalid url {url}")) - })? - .interval(interval), + ProviderBuilder::new(&url).max_retry(10).initial_backoff(1000).build().map_err( + |_| { + TransportErrorKind::custom_str( + format!("Failed to parse invalid url {url}").as_str(), + ) + }, + // TODO: Add interval + )?, // .interval(interval), ); config.provider = new_provider; trace!(target: "backend", "Updated fork rpc from \"{}\" to \"{}\"", config.eth_rpc_url, url); @@ -1853,7 +2303,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_sendUnsignedTransaction` pub async fn eth_send_unsigned_transaction( &self, - request: EthTransactionRequest, + request: WithOtherFields, ) -> Result { node_info!("eth_sendUnsignedTransaction"); // either use the impersonated account of the request's `from` field @@ -1863,7 +2313,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)?; @@ -1874,7 +2324,7 @@ impl EthApi { self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce.as_u64(), from)]; + let provides = vec![to_marker(nonce, from)]; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -1901,7 +2351,7 @@ impl EthApi { fn convert(tx: Arc) -> TxpoolInspectSummary { let tx = &tx.pending_transaction.transaction; - let to = tx.to().copied(); + let to = tx.to(); let gas_price = tx.gas_price(); let value = tx.value(); let gas = tx.gas_limit(); @@ -1933,13 +2383,13 @@ impl EthApi { /// See [here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) for more details /// /// Handler for ETH RPC call: `txpool_inspect` - pub async fn txpool_content(&self) -> Result { + pub async fn txpool_content(&self) -> Result> { node_info!("txpool_content"); - let mut content = TxpoolContent::default(); - fn convert(tx: Arc) -> Transaction { + let mut content = TxpoolContent::::default(); + fn convert(tx: Arc) -> Result { let from = *tx.pending_transaction.sender(); let mut tx = transaction_build( - Some(*tx.hash()), + Some(tx.hash()), tx.pending_transaction.transaction.clone(), None, None, @@ -1949,35 +2399,184 @@ impl EthApi { // we set the from field here explicitly to the set sender of the pending transaction, // in case the transaction is impersonated. tx.from = from; - tx + + Ok(tx) } for pending in self.pool.ready_transactions() { let entry = content.pending.entry(*pending.pending_transaction.sender()).or_default(); let key = pending.pending_transaction.nonce().to_string(); - entry.insert(key, convert(pending)); + entry.insert(key, convert(pending)?); } for queued in self.pool.pending_transactions() { let entry = content.pending.entry(*queued.pending_transaction.sender()).or_default(); let key = queued.pending_transaction.nonce().to_string(); - entry.insert(key, convert(queued)); + entry.insert(key, convert(queued)?); } Ok(content) } } -// === impl EthApi utility functions === +// ===== impl Wallet endppoints ===== +impl EthApi { + /// Get the capabilities of the wallet. + /// + /// See also [EIP-5792][eip-5792]. + /// + /// [eip-5792]: https://eips.ethereum.org/EIPS/eip-5792 + pub fn get_capabilities(&self) -> Result { + node_info!("wallet_getCapabilities"); + Ok(self.backend.get_capabilities()) + } + + pub async fn wallet_send_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { + node_info!("wallet_sendTransaction"); + + // Validate the request + // reject transactions that have a non-zero value to prevent draining the executor. + if request.value.is_some_and(|val| val > U256::ZERO) { + return Err(WalletError::ValueNotZero.into()) + } + + // reject transactions that have from set, as this will be the executor. + if request.from.is_some() { + return Err(WalletError::FromSet.into()); + } + + // reject transaction requests that have nonce set, as this is managed by the executor. + if request.nonce.is_some() { + return Err(WalletError::NonceSet.into()); + } + + let capabilities = self.backend.get_capabilities(); + let valid_delegations: &[Address] = capabilities + .get(self.chain_id()) + .map(|caps| caps.delegation.addresses.as_ref()) + .unwrap_or_default(); + + if let Some(authorizations) = &request.authorization_list { + if authorizations.iter().any(|auth| !valid_delegations.contains(&auth.address)) { + return Err(WalletError::InvalidAuthorization.into()); + } + } + + // validate the destination address + match (request.authorization_list.is_some(), request.to) { + // if this is an eip-1559 tx, ensure that it is an account that delegates to a + // whitelisted address + (false, Some(TxKind::Call(addr))) => { + let acc = self.backend.get_account(addr).await?; + + let delegated_address = acc + .code + .map(|code| match code { + Bytecode::Eip7702(c) => c.address(), + _ => Address::ZERO, + }) + .unwrap_or_default(); + + // not a whitelisted address, or not an eip-7702 bytecode + if delegated_address == Address::ZERO || + !valid_delegations.contains(&delegated_address) + { + return Err(WalletError::IllegalDestination.into()); + } + } + // if it's an eip-7702 tx, let it through + (true, _) => (), + // create tx's disallowed + _ => return Err(WalletError::IllegalDestination.into()), + } + + let wallet = self.backend.executor_wallet().ok_or(WalletError::InternalError)?; + + let from = NetworkWallet::::default_signer_address(&wallet); + + let nonce = self.get_transaction_count(from, Some(BlockId::latest())).await?; + + request.nonce = Some(nonce); + + let chain_id = self.chain_id(); + + request.chain_id = Some(chain_id); + + request.from = Some(from); + + let gas_limit_fut = self.estimate_gas(request.clone(), Some(BlockId::latest()), None); + + let fees_fut = self.fee_history( + U256::from(EIP1559_FEE_ESTIMATION_PAST_BLOCKS), + BlockNumber::Latest, + vec![EIP1559_FEE_ESTIMATION_REWARD_PERCENTILE], + ); + + let (gas_limit, fees) = tokio::join!(gas_limit_fut, fees_fut); + + let gas_limit = gas_limit?; + let fees = fees?; + + request.gas = Some(gas_limit.to()); + + let base_fee = fees.latest_block_base_fee().unwrap_or_default(); + + let estimation = eip1559_default_estimator(base_fee, &fees.reward.unwrap_or_default()); + + request.max_fee_per_gas = Some(estimation.max_fee_per_gas); + request.max_priority_fee_per_gas = Some(estimation.max_priority_fee_per_gas); + request.gas_price = None; + + let envelope = request.build(&wallet).await.map_err(|_| WalletError::InternalError)?; + + self.send_raw_transaction(envelope.encoded_2718().into()).await + } + + /// Add an address to the delegation capability of wallet. + /// + /// This entails that the executor will now be able to sponsor transactions to this address. + pub fn anvil_add_capability(&self, address: Address) -> Result<()> { + node_info!("anvil_addCapability"); + self.backend.add_capability(address); + Ok(()) + } + + pub fn anvil_set_executor(&self, executor_pk: String) -> Result
{ + node_info!("anvil_setExecutor"); + self.backend.set_executor(executor_pk) + } +} impl EthApi { + /// Executes the future on a new blocking task. + async fn on_blocking_task(&self, c: C) -> Result + where + C: FnOnce(Self) -> F, + F: Future> + Send + 'static, + R: Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let this = self.clone(); + let f = c(this); + tokio::task::spawn_blocking(move || { + tokio::runtime::Handle::current().block_on(async move { + let res = f.await; + let _ = tx.send(res); + }) + }); + rx.await.map_err(|_| BlockchainError::Internal("blocking task panicked".to_string()))? + } + /// 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; } @@ -2000,46 +2599,61 @@ impl EthApi { async fn do_estimate_gas( &self, - request: EthTransactionRequest, + request: WithOtherFields, block_number: Option, - ) -> Result { + overrides: Option, + ) -> Result { let block_request = self.block_request(block_number).await?; // check if the number predates the fork, if in fork mode - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork(number.as_u64()) { + if fork.predates_fork(number) { + if overrides.is_some() { + return Err(BlockchainError::StateOverrideError( + "not available on past forked blocks".to_string(), + )); + } return Ok(fork.estimate_gas(&request, Some(number.into())).await?) } } } self.backend - .with_database_at(Some(block_request), |state, block| { - self.do_estimate_gas_with_state(request, state, block) + .with_database_at(Some(block_request), |mut state, block| { + if let Some(overrides) = overrides { + state = Box::new(state::apply_state_override( + overrides.into_iter().collect(), + state, + )?); + } + self.do_estimate_gas_with_state(request, &state, block) }) .await? } /// Estimates the gas usage of the `request` with the state. /// - /// This will execute the [EthTransactionRequest] and find the best gas limit via binary search - fn do_estimate_gas_with_state( + /// This will execute the transaction request and find the best gas limit via binary search. + fn do_estimate_gas_with_state( &self, - mut request: EthTransactionRequest, - state: D, + mut request: WithOtherFields, + state: &dyn DatabaseRef, block_env: BlockEnv, - ) -> Result - where - D: DatabaseRef, - { - // if the request is a simple transfer we can optimize - let likely_transfer = - request.data.as_ref().map(|data| data.as_ref().is_empty()).unwrap_or(true); - if likely_transfer { - if let Some(to) = request.to { - if let Ok(target_code) = self.backend.get_code_with_state(&state, to) { + ) -> Result { + // If the request is a simple native token transfer we can optimize + // We assume it's a transfer if we have no input data. + let to = request.to.as_ref().and_then(TxKind::to); + + // check certain fields to see if the request could be a simple transfer + let maybe_transfer = request.input.input().is_none() && + request.access_list.is_none() && + request.blob_versioned_hashes.is_none(); + + if maybe_transfer { + if let Some(to) = to { + if let Ok(target_code) = self.backend.get_code_with_state(&state, *to) { if target_code.as_ref().is_empty() { - return Ok(MIN_TRANSACTION_GAS) + return Ok(MIN_TRANSACTION_GAS); } } } @@ -2049,108 +2663,70 @@ impl EthApi { request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); // get the highest possible gas limit, either the request's set value or the currently // configured gas limit - let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit.into()); - - // check with the funds of the sender - if let Some(from) = request.from { - let gas_price = fees.gas_price.unwrap_or_default(); - if gas_price > U256::zero() { - let mut available_funds = self.backend.get_balance_with_state(&state, from)?; + let mut highest_gas_limit = + request.gas.map_or(block_env.gas_limit.to::(), |g| g as u128); + + let gas_price = fees.gas_price.unwrap_or_default(); + // If we have non-zero gas price, cap gas limit by sender balance + if gas_price > 0 { + if let Some(from) = request.from { + let mut available_funds = self.backend.get_balance_with_state(state, from)?; if let Some(value) = request.value { if value > available_funds { - return Err(InvalidTransactionError::InsufficientFunds.into()) + return Err(InvalidTransactionError::InsufficientFunds.into()); } // safe: value < available_funds available_funds -= value; } // amount of gas the sender can afford with the `gas_price` - let allowance = available_funds.checked_div(gas_price).unwrap_or_default(); - if highest_gas_limit > allowance { - trace!(target: "node", "eth_estimateGas capped by limited user funds"); - highest_gas_limit = allowance; - } + let allowance = + available_funds.checked_div(U256::from(gas_price)).unwrap_or_default(); + highest_gas_limit = std::cmp::min(highest_gas_limit, allowance.saturating_to()); } } - // if the provided gas limit is less than computed cap, use that - let gas_limit = std::cmp::min(request.gas.unwrap_or(highest_gas_limit), highest_gas_limit); let mut call_to_estimate = request.clone(); - call_to_estimate.gas = Some(gas_limit); + call_to_estimate.gas = Some(highest_gas_limit as u64); // execute the call without writing to db let ethres = self.backend.call_with_state(&state, call_to_estimate, fees.clone(), block_env.clone()); - // Exceptional case: init used too much gas, we need to increase the gas limit and try - // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) = - ethres - { - // if price or limit was included in the request then we can execute the request - // again with the block's gas limit to check if revert is gas related or not - if request.gas.is_some() || request.gas_price.is_some() { - return Err(map_out_of_gas_err( - request, - state, - self.backend.clone(), - block_env, - fees, - gas_limit, - )) + let gas_used = match ethres.try_into()? { + GasEstimationCallResult::Success(gas) => Ok(gas), + GasEstimationCallResult::OutOfGas => { + Err(InvalidTransactionError::BasicOutOfGas(highest_gas_limit).into()) } - } - - let (exit, out, gas, _) = ethres?; - match exit { - return_ok!() => { - // succeeded - } - InstructionResult::OutOfGas | InstructionResult::OutOfFund => { - return Err(InvalidTransactionError::BasicOutOfGas(gas_limit).into()) - } - // need to check if the revert was due to lack of gas or unrelated reason - return_revert!() => { - // if price or limit was included in the request then we can execute the request - // again with the max gas limit to check if revert is gas related or not - return if request.gas.is_some() || request.gas_price.is_some() { - Err(map_out_of_gas_err( - request, - state, - self.backend.clone(), - block_env, - fees, - gas_limit, - )) - } else { - // the transaction did fail due to lack of gas from the user - Err(InvalidTransactionError::Revert(Some(convert_transact_out(&out))).into()) - } + GasEstimationCallResult::Revert(output) => { + Err(InvalidTransactionError::Revert(output).into()) } - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)) + GasEstimationCallResult::EvmError(err) => { + warn!(target: "node", "estimation failed due to {:?}", err); + Err(BlockchainError::EvmError(err)) } - } + }?; // at this point we know the call succeeded but want to find the _best_ (lowest) gas the // transaction succeeds with. we find this by doing a binary search over the // possible range NOTE: this is the gas the transaction used, which is less than the // transaction requires to succeed - let gas: U256 = gas.into(); + // Get the starting lowest gas needed depending on the transaction kind. - let mut lowest_gas_limit = determine_base_gas_by_kind(request.clone()); + let mut lowest_gas_limit = determine_base_gas_by_kind(&request); // pick a point that's close to the estimated gas - let mut mid_gas_limit = std::cmp::min(gas * 3, (highest_gas_limit + lowest_gas_limit) / 2); + let mut mid_gas_limit = + std::cmp::min(gas_used * 3, (highest_gas_limit + lowest_gas_limit) / 2); // Binary search for the ideal gas limit - while (highest_gas_limit - lowest_gas_limit) > U256::one() { - request.gas = Some(mid_gas_limit); + while (highest_gas_limit - lowest_gas_limit) > 1 { + request.gas = Some(mid_gas_limit as u64); let ethres = self.backend.call_with_state( &state, request.clone(), @@ -2158,50 +2734,25 @@ impl EthApi { block_env.clone(), ); - // Exceptional case: init used too much gas, we need to increase the gas limit and try - // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh( - _, - ))) = ethres - { - // increase the lowest gas limit - lowest_gas_limit = mid_gas_limit; - - // new midpoint - mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; - continue - } - - match ethres { - Ok((exit, _, _gas, _)) => match exit { + match ethres.try_into()? { + GasEstimationCallResult::Success(_) => { // If the transaction succeeded, we can set a ceiling for the highest gas limit // at the current midpoint, as spending any more gas would // make no sense (as the TX would still succeed). - return_ok!() => { - highest_gas_limit = mid_gas_limit; - } - // If the transaction failed due to lack of gas, we can set a floor for the - // lowest gas limit at the current midpoint, as spending any - // less gas would make no sense (as the TX would still revert due to lack of - // gas). - InstructionResult::Revert | - InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { - lowest_gas_limit = mid_gas_limit; - } - // The tx failed for some other reason. - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)) - } - }, - // We've already checked for the exceptional GasTooHigh case above, so this is a - // real error. - Err(reason) => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(reason) + highest_gas_limit = mid_gas_limit; } - } + GasEstimationCallResult::OutOfGas | + GasEstimationCallResult::Revert(_) | + GasEstimationCallResult::EvmError(_) => { + // If the transaction failed, we can set a floor for the lowest gas limit at the + // current midpoint, as spending any less gas would make no + // sense (as the TX would still revert due to lack of gas). + // + // We don't care about the reason here, as we known that transaction is correct + // as it succeeded earlier + lowest_gas_limit = mid_gas_limit; + } + }; // new midpoint mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; } @@ -2223,13 +2774,24 @@ impl EthApi { /// Returns the chain ID used for transaction pub fn chain_id(&self) -> u64 { - self.backend.chain_id().as_u64() + self.backend.chain_id().to::() } - pub fn get_fork(&self) -> Option<&ClientFork> { + /// Returns the configured fork, if any. + pub fn get_fork(&self) -> Option { self.backend.get_fork() } + /// Returns the current instance's ID. + pub fn instance_id(&self) -> B256 { + *self.instance_id.read() + } + + /// Resets the instance ID. + pub fn reset_instance_id(&self) { + *self.instance_id.write() = B256::random(); + } + /// Returns the first signer that can sign for the given address #[allow(clippy::borrowed_box)] pub fn get_signer(&self, address: Address) -> Option<&Box> { @@ -2266,19 +2828,19 @@ impl EthApi { } /// Returns the pending block with tx hashes - async fn pending_block(&self) -> Block { + async fn pending_block(&self) -> AnyRpcBlock { let transactions = self.pool.ready_transactions().collect::>(); let info = self.backend.pending_block(transactions).await; self.backend.convert_block(info.block) } /// Returns the full pending block with `Transaction` objects - async fn pending_block_full(&self) -> Option> { + async fn pending_block_full(&self) -> Option { let transactions = self.pool.ready_transactions().collect::>(); let BlockInfo { block, transactions, receipts: _ } = self.backend.pending_block(transactions).await; - let ethers_block = self.backend.convert_block(block.clone()); + let mut partial_block = self.backend.convert_block(block.clone()); let mut block_transactions = Vec::with_capacity(block.transactions.len()); let base_fee = self.backend.base_fee(); @@ -2296,27 +2858,30 @@ impl EthApi { block_transactions.push(tx); } - Some(ethers_block.into_full_block(block_transactions)) + partial_block.transactions = BlockTransactions::from(block_transactions); + + Some(partial_block) } fn build_typed_tx_request( &self, - request: EthTransactionRequest, - nonce: U256, + request: WithOtherFields, + nonce: u64, ) -> Result { - let chain_id = request.chain_id.map(|c| c.as_u64()).unwrap_or_else(|| self.chain_id()); + let chain_id = request.chain_id.unwrap_or_else(|| self.chain_id()); let max_fee_per_gas = request.max_fee_per_gas; + let max_fee_per_blob_gas = request.max_fee_per_blob_gas; let gas_price = request.gas_price; - let gas_limit = request.gas.map(Ok).unwrap_or_else(|| self.current_gas_limit())?; + let gas_limit = request.gas.unwrap_or_else(|| self.backend.gas_limit()); - let request = match request.into_typed_request() { + let request = match transaction_request_to_typed(request) { Some(TypedTransactionRequest::Legacy(mut m)) => { m.nonce = nonce; m.chain_id = Some(chain_id); m.gas_limit = gas_limit; if gas_price.is_none() { - m.gas_price = self.gas_price().unwrap_or_default(); + m.gas_price = self.gas_price(); } TypedTransactionRequest::Legacy(m) } @@ -2325,7 +2890,7 @@ impl EthApi { m.chain_id = chain_id; m.gas_limit = gas_limit; if gas_price.is_none() { - m.gas_price = self.gas_price().unwrap_or_default(); + m.gas_price = self.gas_price(); } TypedTransactionRequest::EIP2930(m) } @@ -2334,11 +2899,40 @@ impl EthApi { m.chain_id = chain_id; m.gas_limit = gas_limit; if max_fee_per_gas.is_none() { - m.max_fee_per_gas = self.gas_price().unwrap_or_default(); + m.max_fee_per_gas = self.gas_price(); } TypedTransactionRequest::EIP1559(m) } - _ => return Err(BlockchainError::FailedToDecodeTransaction), + Some(TypedTransactionRequest::EIP4844(m)) => { + TypedTransactionRequest::EIP4844(match m { + // We only accept the TxEip4844 variant which has the sidecar. + TxEip4844Variant::TxEip4844WithSidecar(mut m) => { + m.tx.nonce = nonce; + m.tx.chain_id = chain_id; + m.tx.gas_limit = gas_limit; + if max_fee_per_gas.is_none() { + m.tx.max_fee_per_gas = self.gas_price(); + } + if max_fee_per_blob_gas.is_none() { + m.tx.max_fee_per_blob_gas = self + .excess_blob_gas_and_price() + .unwrap_or_default() + .map_or(0, |g| g.blob_gasprice) + } + TxEip4844Variant::TxEip4844WithSidecar(m) + } + // It is not valid to receive a TxEip4844 without a sidecar, therefore + // we must reject it. + TxEip4844Variant::TxEip4844(_) => { + return Err(BlockchainError::FailedToDecodeTransaction) + } + }) + } + Some(TypedTransactionRequest::Deposit(mut m)) => { + m.gas_limit = gas_limit; + TypedTransactionRequest::Deposit(m) + } + None => return Err(BlockchainError::FailedToDecodeTransaction), }; Ok(request) } @@ -2348,25 +2942,44 @@ 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), + false, + ), + TypedTransactionRequest::EIP2930(_) | + TypedTransactionRequest::EIP1559(_) | + TypedTransactionRequest::EIP4844(_) | + TypedTransactionRequest::Deposit(_) => Signature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + false, + ), + } + } + /// Returns the nonce of the `address` depending on the `block_number` async fn get_transaction_count( &self, address: Address, block_number: Option, - ) -> Result { + ) -> Result { let block_request = self.block_request(block_number).await?; - if let BlockRequest::Number(number) = &block_request { + if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { - if fork.predates_fork_inclusive(number.as_u64()) { - return Ok(fork.get_nonce(address, number.as_u64()).await?) + if fork.predates_fork(number) { + return Ok(fork.get_nonce(address, number).await?) } } } - let nonce = self.backend.get_nonce(address, Some(block_request)).await?; - - Ok(nonce) + self.backend.get_nonce(address, block_request).await } /// Returns the nonce for this request @@ -2378,9 +2991,9 @@ impl EthApi { /// This will also check the tx pool for pending transactions from the sender. async fn request_nonce( &self, - request: &EthTransactionRequest, + request: &TransactionRequest, from: Address, - ) -> Result<(U256, U256)> { + ) -> Result<(u64, u64)> { let highest_nonce = self.get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))).await?; let nonce = request.nonce.unwrap_or(highest_nonce); @@ -2405,7 +3018,7 @@ impl EthApi { } /// Returns the current state root - pub async fn state_root(&self) -> Option { + pub async fn state_root(&self) -> Option { self.backend.get_db().read().await.maybe_state_root() } @@ -2414,18 +3027,21 @@ impl EthApi { match &tx { TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(), TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(), + TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(), + TypedTransaction::EIP7702(_) => self.backend.ensure_eip7702_active(), + TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(), TypedTransaction::Legacy(_) => Ok(()), } } } -fn required_marker(provided_nonce: U256, on_chain_nonce: U256, from: Address) -> Vec { +fn required_marker(provided_nonce: u64, on_chain_nonce: u64, from: Address) -> Vec { if provided_nonce == on_chain_nonce { - return Vec::new() + return Vec::new(); } - let prev_nonce = provided_nonce.saturating_sub(U256::one()); + let prev_nonce = provided_nonce.saturating_sub(1); if on_chain_nonce <= prev_nonce { - vec![to_marker(prev_nonce.as_u64(), from)] + vec![to_marker(prev_nonce, from)] } else { Vec::new() } @@ -2444,63 +3060,31 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result Ok(out), - return_revert!() => Err(InvalidTransactionError::Revert(Some(out)).into()), + return_revert!() => Err(InvalidTransactionError::Revert(Some(out.0.into())).into()), reason => Err(BlockchainError::EvmError(reason)), } } -/// Executes the requests again after an out of gas error to check if the error is gas related or -/// not -#[inline] -fn map_out_of_gas_err( - mut request: EthTransactionRequest, - state: D, - backend: Arc, - block_env: BlockEnv, - fees: FeeDetails, - gas_limit: U256, -) -> BlockchainError -where - D: DatabaseRef, -{ - request.gas = Some(backend.gas_limit()); - let (exit, out, _, _) = match backend.call_with_state(&state, request, fees, block_env) { - Ok(res) => res, - Err(err) => return err, - }; - match exit { - return_ok!() => { - // transaction succeeded by manually increasing the gas limit to - // highest, which means the caller lacks funds to pay for the tx - InvalidTransactionError::BasicOutOfGas(gas_limit).into() - } - return_revert!() => { - // reverted again after bumping the limit - InvalidTransactionError::Revert(Some(convert_transact_out(&out))).into() - } - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - BlockchainError::EvmError(reason) - } - } -} - /// Determines the minimum gas needed for a transaction depending on the transaction kind. -#[inline] -fn determine_base_gas_by_kind(request: EthTransactionRequest) -> U256 { - match request.into_typed_request() { +fn determine_base_gas_by_kind(request: &WithOtherFields) -> u128 { + match transaction_request_to_typed(request.clone()) { Some(request) => match request { - TypedTransactionRequest::Legacy(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TypedTransactionRequest::Legacy(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, + }, + TypedTransactionRequest::EIP1559(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, - TypedTransactionRequest::EIP1559(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TypedTransactionRequest::EIP2930(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, - TypedTransactionRequest::EIP2930(req) => match req.kind { - TransactionKind::Call(_) => MIN_TRANSACTION_GAS, - TransactionKind::Create => MIN_CREATE_GAS, + TypedTransactionRequest::EIP4844(_) => MIN_TRANSACTION_GAS, + TypedTransactionRequest::Deposit(req) => match req.to { + TxKind::Call(_) => MIN_TRANSACTION_GAS, + TxKind::Create => MIN_CREATE_GAS, }, }, // Tighten the gas limit upwards if we don't know the transaction type to avoid deployments @@ -2508,3 +3092,68 @@ fn determine_base_gas_by_kind(request: EthTransactionRequest) -> U256 { _ => MIN_CREATE_GAS, } } + +/// Keeps result of a call to revm EVM used for gas estimation +enum GasEstimationCallResult { + Success(u128), + OutOfGas, + Revert(Option), + EvmError(InstructionResult), +} + +/// Converts the result of a call to revm EVM into a [`GasEstimationCallResult`]. +impl TryFrom, u128, State)>> for GasEstimationCallResult { + type Error = BlockchainError; + + fn try_from(res: Result<(InstructionResult, Option, u128, State)>) -> Result { + match res { + // Exceptional case: init used too much gas, treated as out of gas error + Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) => { + Ok(Self::OutOfGas) + } + Err(err) => Err(err), + Ok((exit, output, gas, _)) => match exit { + return_ok!() | InstructionResult::CallOrCreate => Ok(Self::Success(gas)), + + InstructionResult::Revert => Ok(Self::Revert(output.map(|o| o.into_data()))), + + InstructionResult::OutOfGas | + InstructionResult::MemoryOOG | + InstructionResult::MemoryLimitOOG | + InstructionResult::PrecompileOOG | + InstructionResult::InvalidOperandOOG => Ok(Self::OutOfGas), + + InstructionResult::OpcodeNotFound | + InstructionResult::CallNotAllowedInsideStatic | + InstructionResult::StateChangeDuringStaticCall | + InstructionResult::InvalidExtDelegateCallTarget | + InstructionResult::InvalidEXTCALLTarget | + InstructionResult::InvalidFEOpcode | + InstructionResult::InvalidJump | + InstructionResult::NotActivated | + InstructionResult::StackUnderflow | + InstructionResult::StackOverflow | + InstructionResult::OutOfOffset | + InstructionResult::CreateCollision | + InstructionResult::OverflowPayment | + InstructionResult::PrecompileError | + InstructionResult::NonceOverflow | + InstructionResult::CreateContractSizeLimit | + InstructionResult::CreateContractStartingWithEF | + InstructionResult::CreateInitCodeSizeLimit | + InstructionResult::FatalExternalError | + InstructionResult::OutOfFunds | + InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), + + // Handle Revm EOF InstructionResults: Not supported yet + InstructionResult::ReturnContractInNotInitEOF | + InstructionResult::EOFOpcodeDisabledInLegacy | + 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 336f4672e8976..32115cf41c13f 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -1,23 +1,18 @@ //! Support for "cheat codes" / bypass functions -use anvil_core::eth::transaction::IMPERSONATED_SIGNATURE; -use ethers::types::{Address, Signature}; -use foundry_evm::hashbrown::HashSet; +use alloy_primitives::{map::AddressHashSet, Address}; use parking_lot::RwLock; use std::sync::Arc; -use tracing::trace; /// Manages user modifications that may affect the node's behavior /// /// Contains the state of executed, non-eth standard cheat code RPC -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct CheatsManager { /// shareable state state: Arc>, } -// === impl CheatsManager === - impl CheatsManager { /// Sets the account to impersonate /// @@ -27,6 +22,9 @@ impl CheatsManager { pub fn impersonate(&self, addr: Address) -> bool { trace!(target: "cheats", "Start impersonating {:?}", addr); let mut state = self.state.write(); + // When somebody **explicitly** impersonates an account we need to store it so we are able + // to return it from `eth_accounts`. That's why we do not simply call `is_impersonated()` + // which does not check that list when auto impersonation is enabled. if state.impersonated_accounts.contains(&addr) { // need to check if already impersonated, so we don't overwrite the code return true @@ -49,36 +47,24 @@ 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) { trace!(target: "cheats", "Auto impersonation set to {:?}", enabled); self.state.write().auto_impersonate_accounts = enabled } + + /// Returns all accounts that are currently being impersonated. + pub fn impersonated_accounts(&self) -> AddressHashSet { + self.state.read().impersonated_accounts.clone() + } } /// Container type for all the state variables -#[derive(Debug, Clone)] +#[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, + pub impersonated_accounts: AddressHashSet, /// 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 6967b79d13f8c..55159f4d3460c 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -1,72 +1,88 @@ //! Helper types for working with [revm](foundry_evm::revm) -use crate::{mem::state::trie_hash_db, revm::primitives::AccountInfo, U256}; -use anvil_core::eth::trie::KeccakHasher; -use ethers::{ - prelude::{Address, Bytes}, - types::H256, - utils::keccak256, +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::{MaybeImpersonatedTransaction, TransactionInfo, TypedReceipt, TypedTransaction}, }; use foundry_common::errors::FsPathError; use foundry_evm::{ - executor::{ - backend::{snapshot::StateSnapshot, DatabaseError, DatabaseResult, MemDb}, - DatabaseRef, + backend::{ + BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, + StateSnapshot, }, revm::{ - db::{CacheDB, DbAccount}, - primitives::{Bytecode, B160, B256, KECCAK_EMPTY, U256 as rU256}, + db::{CacheDB, DatabaseRef, DbAccount}, + primitives::{BlockEnv, Bytecode, HashMap, KECCAK_EMPTY}, Database, DatabaseCommit, }, - HashMap, }; -use hash_db::HashDB; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; use std::{collections::BTreeMap, fmt, path::Path}; -/// Type alias for the `HashDB` representation of the Database -pub type AsHashDB = Box>>; +/// Helper trait get access to the full state data of the database +pub trait MaybeFullDatabase: DatabaseRef { + /// Returns a reference to the database as a `dyn DatabaseRef`. + // TODO: Required until trait upcasting is stabilized: + fn as_dyn(&self) -> &dyn DatabaseRef; -/// Helper trait get access to the data in `HashDb` form -#[auto_impl::auto_impl(Box)] -pub trait MaybeHashDatabase: DatabaseRef { - /// Return the DB as read-only hashdb and the root key - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - None - } - /// Return the storage DB as read-only hashdb and the storage root of the account - fn maybe_account_db(&self, _addr: Address) -> Option<(AsHashDB, H256)> { + fn maybe_as_full_db(&self) -> Option<&HashMap> { None } - /// Clear the state and move it into a new `StateSnapshot` - fn clear_into_snapshot(&mut self) -> StateSnapshot; + /// Clear the state and move it into a new `StateSnapshot`. + fn clear_into_state_snapshot(&mut self) -> StateSnapshot; + + /// Read the state snapshot. + /// + /// This clones all the states and returns a new `StateSnapshot`. + fn read_as_state_snapshot(&self) -> StateSnapshot; /// Clears the entire database fn clear(&mut self); - /// Reverses `clear_into_snapshot` by initializing the db's state with the snapshot - fn init_from_snapshot(&mut self, snapshot: StateSnapshot); + /// Reverses `clear_into_snapshot` by initializing the db's state with the state snapshot. + fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot); } -impl<'a, T: 'a + MaybeHashDatabase + ?Sized> MaybeHashDatabase for &'a T +impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T where &'a T: DatabaseRef, { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - T::maybe_as_hash_db(self) + fn as_dyn(&self) -> &dyn DatabaseRef { + T::as_dyn(self) } - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, H256)> { - T::maybe_account_db(self, addr) + + fn maybe_as_full_db(&self) -> Option<&HashMap> { + T::maybe_as_full_db(self) } - fn clear_into_snapshot(&mut self) -> StateSnapshot { + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { + unreachable!("never called for DatabaseRef") + } + + fn read_as_state_snapshot(&self) -> StateSnapshot { unreachable!("never called for DatabaseRef") } fn clear(&mut self) {} - fn init_from_snapshot(&mut self, _snapshot: StateSnapshot) {} + fn init_from_state_snapshot(&mut self, _state_snapshot: StateSnapshot) {} +} + +/// Helper trait to reset the DB if it's forked +pub trait MaybeForkedDatabase { + fn maybe_reset(&mut self, _url: Option, block_number: BlockId) -> Result<(), String>; + + fn maybe_flush_cache(&self) -> Result<(), String>; + + fn maybe_inner(&self) -> Result<&BlockchainDb, String>; } /// This bundles all required revm traits @@ -74,7 +90,8 @@ pub trait Db: DatabaseRef + Database + DatabaseCommit - + MaybeHashDatabase + + MaybeFullDatabase + + MaybeForkedDatabase + fmt::Debug + Send + Sync @@ -84,7 +101,7 @@ pub trait Db: /// Sets the nonce of the given address fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<()> { - let mut info = self.basic(address.into())?.unwrap_or_default(); + let mut info = self.basic(address)?.unwrap_or_default(); info.nonce = nonce; self.insert_account(address, info); Ok(()) @@ -92,39 +109,46 @@ pub trait Db: /// Sets the balance of the given address fn set_balance(&mut self, address: Address, balance: U256) -> DatabaseResult<()> { - let mut info = self.basic(address.into())?.unwrap_or_default(); - info.balance = balance.into(); + let mut info = self.basic(address)?.unwrap_or_default(); + info.balance = balance; self.insert_account(address, info); Ok(()) } /// Sets the balance of the given address fn set_code(&mut self, address: Address, code: Bytes) -> DatabaseResult<()> { - let mut info = self.basic(address.into())?.unwrap_or_default(); + let mut info = self.basic(address)?.unwrap_or_default(); let code_hash = if code.as_ref().is_empty() { KECCAK_EMPTY } else { B256::from_slice(&keccak256(code.as_ref())[..]) }; info.code_hash = code_hash; - info.code = Some(Bytecode::new_raw(code.0).to_checked()); + info.code = Some(Bytecode::new_raw(alloy_primitives::Bytes(code.0))); self.insert_account(address, info); Ok(()) } /// Sets the balance of the given address - fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()>; + fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()>; /// inserts a blockhash for the given number - fn insert_block_hash(&mut self, number: U256, hash: H256); + fn insert_block_hash(&mut self, number: U256, hash: B256); /// Write all chain data to serialized bytes buffer - fn dump_state(&self) -> DatabaseResult>; + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + blocks: Vec, + transactions: Vec, + historical_states: Option, + ) -> DatabaseResult>; /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { for (addr, account) in state.accounts.into_iter() { - let old_account_nonce = DatabaseRef::basic(self, addr.into()) + let old_account_nonce = DatabaseRef::basic_ref(self, addr) .ok() .and_then(|acc| acc.map(|acc| acc.nonce)) .unwrap_or_default(); @@ -135,12 +159,12 @@ pub trait Db: self.insert_account( addr, AccountInfo { - balance: account.balance.into(), + balance: account.balance, code_hash: KECCAK_EMPTY, // will be set automatically code: if account.code.0.is_empty() { None } else { - Some(Bytecode::new_raw(account.code.0).to_checked()) + Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0))) }, nonce, }, @@ -153,16 +177,16 @@ pub trait Db: Ok(true) } - /// Creates a new snapshot - fn snapshot(&mut self) -> U256; + /// Creates a new state snapshot. + fn snapshot_state(&mut self) -> U256; - /// Reverts a snapshot + /// Reverts a state snapshot. /// - /// Returns `true` if the snapshot was reverted - fn revert(&mut self, snapshot: U256) -> bool; + /// Returns `true` if the state snapshot was reverted. + fn revert_state(&mut self, state_snapshot: U256, action: RevertStateSnapshotAction) -> bool; /// Returns the state root if possible to compute - fn maybe_state_root(&self) -> Option { + fn maybe_state_root(&self) -> Option { None } @@ -170,32 +194,46 @@ pub trait Db: fn current_state(&self) -> StateDb; } +impl dyn Db { + // TODO: Required until trait upcasting is stabilized: + pub fn as_dbref(&self) -> &dyn DatabaseRef { + self.as_dyn() + } +} + /// Convenience impl only used to use any `Db` on the fly as the db layer for revm's CacheDB /// This is useful to create blocks without actually writing to the `Db`, but rather in the cache of /// the `CacheDB` see also /// [Backend::pending_block()](crate::eth::backend::mem::Backend::pending_block()) impl + Send + Sync + Clone + fmt::Debug> Db for CacheDB { fn insert_account(&mut self, address: Address, account: AccountInfo) { - self.insert_account_info(address.into(), account) + self.insert_account_info(address, account) } - fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()> { - self.insert_account_storage(address.into(), slot.into(), val.into()) + fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> { + self.insert_account_storage(address, slot.into(), val.into()) } - fn insert_block_hash(&mut self, number: U256, hash: H256) { - self.block_hashes.insert(number.into(), hash.into()); + fn insert_block_hash(&mut self, number: U256, hash: B256) { + self.block_hashes.insert(number, hash); } - fn dump_state(&self) -> DatabaseResult> { + fn dump_state( + &self, + _at: BlockEnv, + _best_number: U64, + _blocks: Vec, + _transaction: Vec, + _historical_states: Option, + ) -> DatabaseResult> { Ok(None) } - fn snapshot(&mut self) -> U256 { - U256::zero() + fn snapshot_state(&mut self) -> U256 { + U256::ZERO } - fn revert(&mut self, _snapshot: U256) -> bool { + fn revert_state(&mut self, _state_snapshot: U256, _action: RevertStateSnapshotAction) -> bool { false } @@ -204,14 +242,19 @@ impl + Send + Sync + Clone + fmt::Debug> D } } -impl> MaybeHashDatabase for CacheDB { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - Some(trie_hash_db(&self.accounts)) +impl> MaybeFullDatabase for CacheDB { + fn as_dyn(&self) -> &dyn DatabaseRef { + self } - fn clear_into_snapshot(&mut self) -> StateSnapshot { + + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.accounts) + } + + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { let db_accounts = std::mem::take(&mut self.accounts); - let mut accounts = HashMap::new(); - let mut account_storage = HashMap::new(); + let mut accounts = HashMap::default(); + let mut account_storage = HashMap::default(); for (addr, mut acc) in db_accounts { account_storage.insert(addr, std::mem::take(&mut acc.storage)); @@ -223,12 +266,28 @@ impl> MaybeHashDatabase for CacheDB { StateSnapshot { accounts, storage: account_storage, block_hashes } } + fn read_as_state_snapshot(&self) -> StateSnapshot { + let db_accounts = self.accounts.clone(); + let mut accounts = HashMap::default(); + let mut account_storage = HashMap::default(); + + for (addr, acc) in db_accounts { + account_storage.insert(addr, acc.storage.clone()); + let mut info = acc.info; + info.code = self.contracts.get(&info.code_hash).cloned(); + accounts.insert(addr, info); + } + + let block_hashes = self.block_hashes.clone(); + StateSnapshot { accounts, storage: account_storage, block_hashes } + } + fn clear(&mut self) { - self.clear_into_snapshot(); + self.clear_into_state_snapshot(); } - fn init_from_snapshot(&mut self, snapshot: StateSnapshot) { - let StateSnapshot { accounts, mut storage, block_hashes } = snapshot; + fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { + let StateSnapshot { accounts, mut storage, block_hashes } = state_snapshot; for (addr, mut acc) in accounts { if let Some(code) = acc.code.take() { @@ -247,65 +306,100 @@ impl> MaybeHashDatabase for CacheDB { } } -/// Represents a state at certain point -pub struct StateDb(pub(crate) Box); +impl> MaybeForkedDatabase for CacheDB { + fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + Err("not supported".to_string()) + } + + fn maybe_flush_cache(&self) -> Result<(), String> { + Err("not supported".to_string()) + } -// === impl StateDB === + fn maybe_inner(&self) -> Result<&BlockchainDb, String> { + Err("not supported".to_string()) + } +} + +/// Represents a state at certain point +pub struct StateDb(pub(crate) Box); impl StateDb { - pub fn new(db: impl MaybeHashDatabase + Send + Sync + 'static) -> Self { + pub fn new(db: impl MaybeFullDatabase + Send + Sync + 'static) -> Self { Self(Box::new(db)) } + + pub fn serialize_state(&mut self) -> StateSnapshot { + // Using read_as_snapshot makes sures we don't clear the historical state from the current + // instance. + self.read_as_state_snapshot() + } } impl DatabaseRef for StateDb { type Error = DatabaseError; - fn basic(&self, address: B160) -> DatabaseResult> { - self.0.basic(address) + fn basic_ref(&self, address: Address) -> DatabaseResult> { + self.0.basic_ref(address) } - fn code_by_hash(&self, code_hash: B256) -> DatabaseResult { - self.0.code_by_hash(code_hash) + fn code_by_hash_ref(&self, code_hash: B256) -> DatabaseResult { + self.0.code_by_hash_ref(code_hash) } - fn storage(&self, address: B160, index: rU256) -> DatabaseResult { - self.0.storage(address, index) + fn storage_ref(&self, address: Address, index: U256) -> DatabaseResult { + self.0.storage_ref(address, index) } - fn block_hash(&self, number: rU256) -> DatabaseResult { - self.0.block_hash(number) + fn block_hash_ref(&self, number: u64) -> DatabaseResult { + self.0.block_hash_ref(number) } } -impl MaybeHashDatabase for StateDb { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - self.0.maybe_as_hash_db() +impl MaybeFullDatabase for StateDb { + fn as_dyn(&self) -> &dyn DatabaseRef { + self.0.as_dyn() } - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, H256)> { - self.0.maybe_account_db(addr) + fn maybe_as_full_db(&self) -> Option<&HashMap> { + self.0.maybe_as_full_db() } - fn clear_into_snapshot(&mut self) -> StateSnapshot { - self.0.clear_into_snapshot() + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { + self.0.clear_into_state_snapshot() + } + + fn read_as_state_snapshot(&self) -> StateSnapshot { + self.0.read_as_state_snapshot() } fn clear(&mut self) { self.0.clear() } - fn init_from_snapshot(&mut self, snapshot: StateSnapshot) { - self.0.init_from_snapshot(snapshot) + fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { + self.0.init_from_state_snapshot(state_snapshot) } } -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SerializableState { + /// The block number of the state + /// + /// Note: This is an Option for backwards compatibility: + pub block: Option, pub accounts: BTreeMap, + /// The best block number of the state, can be different from block number (Arbitrum chain). + pub best_block_number: Option, + #[serde(default)] + pub blocks: Vec, + #[serde(default)] + pub transactions: Vec, + /// Historical states of accounts and storage at particular block hashes. + /// + /// Note: This is an Option for backwards compatibility. + #[serde(default)] + pub historical_states: Option, } -// === impl SerializableState === - impl SerializableState { /// Loads the `Genesis` object from the given json file path pub fn load(path: impl AsRef) -> Result { @@ -323,10 +417,199 @@ impl SerializableState { } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SerializableAccountRecord { pub nonce: u64, pub balance: U256, pub code: Bytes, - pub storage: BTreeMap, + + #[serde(deserialize_with = "deserialize_btree")] + pub storage: BTreeMap, +} + +fn deserialize_btree<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + struct BTreeVisitor; + + impl<'de> Visitor<'de> for BTreeVisitor { + type Value = BTreeMap; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a mapping of hex encoded storage slots to hex encoded state data") + } + + fn visit_map(self, mut mapping: M) -> Result, M::Error> + where + M: MapAccess<'de>, + { + let mut btree = BTreeMap::new(); + while let Some((key, value)) = mapping.next_entry::()? { + btree.insert(B256::from(key), B256::from(value)); + } + + Ok(btree) + } + } + + deserializer.deserialize_map(BTreeVisitor) +} + +/// Defines a backwards-compatible enum for transactions. +/// This is essential for maintaining compatibility with state dumps +/// created before the changes introduced in PR #8411. +/// +/// The enum can represent either a `TypedTransaction` or a `MaybeImpersonatedTransaction`, +/// depending on the data being deserialized. This flexibility ensures that older state +/// dumps can still be loaded correctly, even after the changes in #8411. +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum SerializableTransactionType { + TypedTransaction(TypedTransaction), + MaybeImpersonatedTransaction(MaybeImpersonatedTransaction), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SerializableBlock { + pub header: Header, + pub transactions: Vec, + pub ommers: Vec
, +} + +impl From for SerializableBlock { + fn from(block: Block) -> Self { + Self { + header: block.header, + transactions: block.transactions.into_iter().map(Into::into).collect(), + ommers: block.ommers.into_iter().collect(), + } + } +} + +impl From for Block { + fn from(block: SerializableBlock) -> Self { + Self { + header: block.header, + transactions: block.transactions.into_iter().map(Into::into).collect(), + ommers: block.ommers.into_iter().collect(), + } + } +} + +impl From for SerializableTransactionType { + fn from(transaction: MaybeImpersonatedTransaction) -> Self { + Self::MaybeImpersonatedTransaction(transaction) + } +} + +impl From for MaybeImpersonatedTransaction { + fn from(transaction: SerializableTransactionType) -> Self { + match transaction { + SerializableTransactionType::TypedTransaction(tx) => Self::new(tx), + SerializableTransactionType::MaybeImpersonatedTransaction(tx) => tx, + } + } +} + +#[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, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Default)] +pub struct SerializableHistoricalStates(Vec<(B256, StateSnapshot)>); + +impl SerializableHistoricalStates { + pub const fn new(states: Vec<(B256, StateSnapshot)>) -> Self { + Self(states) + } +} + +impl IntoIterator for SerializableHistoricalStates { + type Item = (B256, StateSnapshot); + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_deser_block() { + let block = r#"{ + "header": { + "parentHash": "0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "miner": "0x0000000000000000000000000000000000000000", + "stateRoot": "0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1", + "transactionsRoot": "0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988", + "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x0", + "number": "0x2", + "gasLimit": "0x1c9c380", + "gasUsed": "0x5208", + "timestamp": "0x66cdc823", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x342a1c58", + "blobGasUsed": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x" + }, + "transactions": [ + { + "EIP1559": { + "chainId": "0x7a69", + "nonce": "0x0", + "gas": "0x5209", + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "to": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "value": "0x0", + "accessList": [], + "input": "0x", + "r": "0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0", + "s": "0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd", + "yParity": "0x0", + "hash": "0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515" + } + } + ], + "ommers": [] + } + "#; + + let _block: SerializableBlock = serde_json::from_str(block).unwrap(); + } } diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index 7198e160cd0ab..c07bfab785e24 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -4,36 +4,37 @@ use crate::{ error::InvalidTransactionError, pool::transactions::PoolTransaction, }, + inject_precompiles, mem::inspector::Inspector, + PrecompileFactory, }; +use alloy_consensus::{constants::EMPTY_WITHDRAWALS, Receipt, ReceiptWithBloom}; +use alloy_eips::{eip2718::Encodable2718, eip7685::EMPTY_REQUESTS_HASH}; +use alloy_primitives::{Bloom, BloomInput, Log, B256}; use anvil_core::eth::{ - block::{Block, BlockInfo, Header, PartialHeader}, - receipt::{EIP1559Receipt, EIP2930Receipt, EIP658Receipt, Log, TypedReceipt}, - transaction::{PendingTransaction, TransactionInfo, TypedTransaction}, + block::{Block, BlockInfo, PartialHeader}, + transaction::{ + DepositReceipt, PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction, + }, trie, }; -use ethers::{ - abi::ethereum_types::BloomInput, - types::{Bloom, H256, U256}, - utils::rlp, -}; use foundry_evm::{ - executor::backend::DatabaseError, - revm, + backend::DatabaseError, revm::{ interpreter::InstructionResult, - primitives::{BlockEnv, CfgEnv, EVMError, Env, ExecutionResult, Output, SpecId}, - }, - trace::{node::CallTraceNode, CallTraceArena}, - utils::{ - b160_to_h160, eval_to_instruction_result, h160_to_b160, halt_to_instruction_result, - ru256_to_u256, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, Output, + SpecId, + }, }, + traces::CallTraceNode, + utils::odyssey_handler_register, }; +use revm::db::WrapDatabaseRef; use std::sync::Arc; -use tracing::{trace, warn}; /// Represents an executed transaction (transacted on the DB) +#[derive(Debug)] pub struct ExecutedTransaction { transaction: Arc, exit_reason: InstructionResult, @@ -41,49 +42,47 @@ pub struct ExecutedTransaction { gas_used: u64, logs: Vec, traces: Vec, + nonce: u64, } // == impl ExecutedTransaction == impl ExecutedTransaction { /// Creates the receipt for the transaction - fn create_receipt(&self) -> TypedReceipt { - let used_gas: U256 = self.gas_used.into(); - let mut bloom = Bloom::default(); - logs_bloom(self.logs.clone(), &mut bloom); + fn create_receipt(&self, cumulative_gas_used: &mut u64) -> TypedReceipt { let logs = self.logs.clone(); + *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); // successful return see [Return] let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8); + let receipt_with_bloom: ReceiptWithBloom = Receipt { + status: (status_code == 1).into(), + cumulative_gas_used: *cumulative_gas_used, + logs, + } + .into(); + match &self.transaction.pending_transaction.transaction.transaction { - TypedTransaction::Legacy(_) => TypedReceipt::Legacy(EIP658Receipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, - }), - TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(EIP2930Receipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, - }), - TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(EIP1559Receipt { - status_code, - gas_used: used_gas, - logs_bloom: bloom, - logs, + TypedTransaction::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), + TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), + TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), + TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedTransaction::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), + TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: Some(tx.nonce), + deposit_receipt_version: Some(1), }), } } } /// Represents the outcome of mining a new block -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ExecutedTransactions { /// The block created after executing the `included` transactions pub block: BlockInfo, - /// All transactions included in the + /// All transactions included in the block pub included: Vec>, /// All transactions that were invalid at the point of their execution and were not included in /// the block @@ -91,51 +90,72 @@ pub struct ExecutedTransactions { } /// An executor for a series of transactions -pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator> { +pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> { /// where to insert the transactions pub db: &'a mut Db, /// type used to validate before inclusion - pub validator: Validator, + pub validator: &'a V, /// all pending transactions pub pending: std::vec::IntoIter>, pub block_env: BlockEnv, - pub cfg_env: CfgEnv, - pub parent_hash: H256, + /// The configuration environment and spec id + pub cfg_env: CfgEnvWithHandlerCfg, + pub parent_hash: B256, /// Cumulative gas used by all executed transactions - pub gas_used: U256, + pub gas_used: u64, + /// Cumulative blob gas used by all executed transactions + pub blob_gas_used: u64, pub enable_steps_tracing: bool, + pub odyssey: bool, + pub print_logs: bool, + /// Precompiles to inject to the EVM. + pub precompile_factory: Option>, } -impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'a, DB, Validator> { +impl TransactionExecutor<'_, DB, V> { /// Executes all transactions and puts them in a new block with the provided `timestamp` pub fn execute(mut self) -> ExecutedTransactions { let mut transactions = Vec::new(); let mut transaction_infos = Vec::new(); let mut receipts = Vec::new(); let mut bloom = Bloom::default(); - let mut cumulative_gas_used = U256::zero(); + let mut cumulative_gas_used = 0u64; let mut invalid = Vec::new(); let mut included = Vec::new(); - let gas_limit = self.block_env.gas_limit; + let gas_limit = self.block_env.gas_limit.to::(); let parent_hash = self.parent_hash; - let block_number = self.block_env.number; + let block_number = self.block_env.number.to::(); let difficulty = self.block_env.difficulty; let beneficiary = self.block_env.coinbase; - let timestamp = ru256_to_u256(self.block_env.timestamp).as_u64(); - let base_fee = if (self.cfg_env.spec_id as u8) >= (SpecId::LONDON as u8) { - Some(self.block_env.basefee) + let timestamp = self.block_env.timestamp.to::(); + let base_fee = if self.cfg_env.handler_cfg.spec_id.is_enabled_in(SpecId::LONDON) { + Some(self.block_env.basefee.to::()) } else { None }; + let is_shanghai = self.cfg_env.handler_cfg.spec_id >= SpecId::SHANGHAI; + let is_cancun = self.cfg_env.handler_cfg.spec_id >= SpecId::CANCUN; + let is_prague = self.cfg_env.handler_cfg.spec_id >= SpecId::PRAGUE; + let excess_blob_gas = if is_cancun { self.block_env.get_blob_excess_gas() } else { None }; + let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; + for tx in self.into_iter() { let tx = match tx { TransactionExecutionOutcome::Executed(tx) => { included.push(tx.transaction.clone()); tx } - TransactionExecutionOutcome::Exhausted(_) => continue, + TransactionExecutionOutcome::Exhausted(tx) => { + trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction"); + continue + } + TransactionExecutionOutcome::BlobGasExhausted(tx) => { + trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction"); + continue + } TransactionExecutionOutcome::Invalid(tx, _) => { + trace!(target: "backend", ?tx, "skipping invalid transaction"); invalid.push(tx); continue } @@ -146,34 +166,43 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' continue } }; - let receipt = tx.create_receipt(); - cumulative_gas_used = cumulative_gas_used.saturating_add(receipt.gas_used()); + if is_cancun { + let tx_blob_gas = tx + .transaction + .pending_transaction + .transaction + .transaction + .blob_gas() + .unwrap_or(0); + cumulative_blob_gas_used = + Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas)); + } + let receipt = tx.create_receipt(&mut cumulative_gas_used); + let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; - logs_bloom(logs.clone(), &mut bloom); + build_logs_bloom(logs.clone(), &mut bloom); - let contract_address = if let Some(Output::Create(_, contract_address)) = out { - trace!(target: "backend", "New contract deployed: at {:?}", contract_address); - contract_address - } else { - None - }; + let contract_address = out.as_ref().and_then(|out| { + if let Output::Create(_, contract_address) = out { + trace!(target: "backend", "New contract deployed: at {:?}", contract_address); + *contract_address + } else { + None + } + }); - let transaction_index = transaction_infos.len() as u32; + let transaction_index = transaction_infos.len() as u64; let info = TransactionInfo { - transaction_hash: *transaction.hash(), + transaction_hash: transaction.hash(), transaction_index, from: *transaction.pending_transaction.sender(), - to: transaction.pending_transaction.transaction.to().copied(), - contract_address: contract_address.map(b160_to_h160), - logs, - logs_bloom: *receipt.logs_bloom(), - traces: CallTraceArena { arena: traces }, + to: transaction.pending_transaction.transaction.to(), + contract_address, + traces, exit, - out: match out { - Some(Output::Call(b)) => Some(b.into()), - Some(Output::Create(b, _)) => Some(b.into()), - _ => None, - }, + out: out.map(Output::into_data), + nonce: tx.nonce, + gas_used: tx.gas_used, }; transaction_infos.push(info); @@ -181,37 +210,49 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' transactions.push(transaction.pending_transaction.transaction.clone()); } - let ommers: Vec
= Vec::new(); - let receipts_root = trie::ordered_trie_root(receipts.iter().map(rlp::encode)); + let receipts_root = + trie::ordered_trie_root(receipts.iter().map(Encodable2718::encoded_2718)); let partial_header = PartialHeader { parent_hash, - beneficiary: b160_to_h160(beneficiary), + beneficiary, state_root: self.db.maybe_state_root().unwrap_or_default(), receipts_root, logs_bloom: bloom, - difficulty: difficulty.into(), - number: block_number.into(), - gas_limit: gas_limit.into(), + difficulty, + number: block_number, + gas_limit, gas_used: cumulative_gas_used, timestamp, extra_data: Default::default(), mix_hash: Default::default(), nonce: Default::default(), - base_fee: base_fee.map(|x| x.into()), + base_fee, + parent_beacon_block_root: is_cancun.then_some(Default::default()), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas, + withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), + requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), }; - let block = Block::new(partial_header, transactions.clone(), ommers); + let block = Block::new(partial_header, transactions.clone()); let block = BlockInfo { block, transactions: transaction_infos, receipts }; ExecutedTransactions { block, included, invalid } } - fn env_for(&self, tx: &PendingTransaction) -> Env { - Env { cfg: self.cfg_env.clone(), block: self.block_env.clone(), tx: tx.to_revm_tx_env() } + fn env_for(&self, tx: &PendingTransaction) -> EnvWithHandlerCfg { + let mut tx_env = tx.to_revm_tx_env(); + if self.cfg_env.handler_cfg.is_optimism { + tx_env.optimism.enveloped_tx = + Some(alloy_rlp::encode(&tx.transaction.transaction).into()); + } + + EnvWithHandlerCfg::new_with_cfg_env(self.cfg_env.clone(), self.block_env.clone(), tx_env) } } /// Represents the result of a single transaction execution attempt +#[derive(Debug)] pub enum TransactionExecutionOutcome { /// Transaction successfully executed Executed(ExecutedTransaction), @@ -219,29 +260,38 @@ pub enum TransactionExecutionOutcome { Invalid(Arc, InvalidTransactionError), /// Execution skipped because could exceed gas limit Exhausted(Arc), + /// Execution skipped because it exceeded the blob gas limit + BlobGasExhausted(Arc), /// When an error occurred during execution DatabaseError(Arc, DatabaseError), } -impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator - for &'b mut TransactionExecutor<'a, DB, Validator> -{ +impl Iterator for &mut TransactionExecutor<'_, DB, V> { type Item = TransactionExecutionOutcome; fn next(&mut self) -> Option { let transaction = self.pending.next()?; let sender = *transaction.pending_transaction.sender(); - let account = match self.db.basic(h160_to_b160(sender)).map(|acc| acc.unwrap_or_default()) { + let account = match self.db.basic(sender).map(|acc| acc.unwrap_or_default()) { Ok(account) => account, Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)), }; let env = self.env_for(&transaction.pending_transaction); - // check that we comply with the block's gas limit - let max_gas = self.gas_used.saturating_add(U256::from(env.tx.gas_limit)); - if max_gas > env.block.gas_limit.into() { + + // check that we comply with the block's gas limit, if not disabled + let max_gas = self.gas_used.saturating_add(env.tx.gas_limit); + if !env.cfg.disable_block_gas_limit && max_gas > env.block.gas_limit.to::() { return Some(TransactionExecutionOutcome::Exhausted(transaction)) } + // check that we comply with the block's blob gas limit + let max_blob_gas = self.blob_gas_used.saturating_add( + transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0), + ); + if max_blob_gas > alloy_eips::eip4844::MAX_DATA_GAS_PER_BLOCK { + return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)) + } + // validate before executing if let Err(err) = self.validator.validate_pool_transaction_for( &transaction.pending_transaction, @@ -252,33 +302,45 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator return Some(TransactionExecutionOutcome::Invalid(transaction, err)) } - let mut evm = revm::EVM::new(); - evm.env = env; - evm.database(&mut self.db); + let nonce = account.nonce; // records all call and step traces let mut inspector = Inspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } + if self.print_logs { + inspector = inspector.with_log_collector(); + } - trace!(target: "backend", "[{:?}] executing", transaction.hash()); - // transact and commit the transaction - let exec_result = match evm.inspect_commit(&mut inspector) { - Ok(exec_result) => exec_result, - Err(err) => { - warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); - match err { - EVMError::Database(err) => { - return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)) - } - EVMError::Transaction(err) => { - return Some(TransactionExecutionOutcome::Invalid(transaction, err.into())) - } - // This will correspond to prevrandao not set, and it should never happen. - // If it does, it's a bug. - e => { - panic!("Failed to execute transaction. This is a bug.\n {:?}", e) + let exec_result = { + let mut evm = new_evm_with_inspector(&mut *self.db, env, &mut inspector, self.odyssey); + if let Some(factory) = &self.precompile_factory { + inject_precompiles(&mut evm, factory.precompiles()); + } + + trace!(target: "backend", "[{:?}] executing", transaction.hash()); + // transact and commit the transaction + match evm.transact_commit() { + Ok(exec_result) => exec_result, + Err(err) => { + warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); + match err { + EVMError::Database(err) => { + return Some(TransactionExecutionOutcome::DatabaseError( + transaction, + err, + )) + } + EVMError::Transaction(err) => { + return Some(TransactionExecutionOutcome::Invalid( + transaction, + err.into(), + )) + } + // This will correspond to prevrandao not set, and it should never happen. + // If it does, it's a bug. + e => panic!("failed to execute transaction: {e}"), } } } @@ -287,14 +349,12 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator let (exit_reason, gas_used, out, logs) = match exec_result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), Some(logs)) + (reason.into(), gas_used, Some(output), Some(logs)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), }; if exit_reason == InstructionResult::OutOfGas { @@ -304,7 +364,13 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out); - self.gas_used.saturating_add(U256::from(gas_used)); + // Track the total gas used for total gas per block checks + self.gas_used = self.gas_used.saturating_add(gas_used); + + // Track the total blob gas used for total blob gas per blob checks + if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() { + self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas); + } trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used); @@ -313,8 +379,9 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator exit_reason, out, gas_used, - logs: logs.unwrap_or_default().into_iter().map(Into::into).collect(), - traces: inspector.tracer.unwrap_or_default().traces.arena, + logs: logs.unwrap_or_default(), + traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(), + nonce, }; Some(TransactionExecutionOutcome::Executed(tx)) @@ -322,11 +389,45 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator } /// Inserts all logs into the bloom -fn logs_bloom(logs: Vec, bloom: &mut Bloom) { +fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { for log in logs { bloom.accrue(BloomInput::Raw(&log.address[..])); - for topic in log.topics { + for topic in log.topics() { bloom.accrue(BloomInput::Raw(&topic[..])); } } } + +/// Creates a database with given database and inspector, optionally enabling odyssey features. +pub fn new_evm_with_inspector( + db: DB, + env: EnvWithHandlerCfg, + inspector: &mut dyn revm::Inspector, + odyssey: bool, +) -> revm::Evm<'_, &mut dyn revm::Inspector, DB> { + let EnvWithHandlerCfg { env, handler_cfg } = env; + + let mut handler = revm::Handler::new(handler_cfg); + + handler.append_handler_register_plain(revm::inspector_handle_register); + if odyssey { + handler.append_handler_register_plain(odyssey_handler_register); + } + + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + + revm::Evm::new(context, handler) +} + +/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. +pub fn new_evm_with_inspector_ref<'a, DB>( + db: DB, + env: EnvWithHandlerCfg, + inspector: &mut dyn revm::Inspector>, + odyssey: bool, +) -> revm::Evm<'a, &mut dyn revm::Inspector>, WrapDatabaseRef> +where + DB: revm::DatabaseRef, +{ + new_evm_with_inspector(WrapDatabaseRef(db), env, inspector, odyssey) +} diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 0445573405d85..64852c802ae1e 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -1,31 +1,43 @@ //! Support for forking off another client -use crate::eth::{backend::mem::fork_db::ForkedDatabase, error::BlockchainError}; -use anvil_core::eth::{proof::AccountProof, transaction::EthTransactionRequest}; -use ethers::{ - prelude::BlockNumber, - providers::{Middleware, ProviderError}, - types::{ - transaction::eip2930::AccessListWithGasUsed, Address, Block, BlockId, Bytes, FeeHistory, - Filter, GethDebugTracingOptions, GethTrace, Log, Trace, Transaction, TransactionReceipt, - TxHash, H256, U256, +use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction}; +use alloy_consensus::Account; +use alloy_eips::eip2930::AccessListResult; +use alloy_network::{AnyRpcBlock, AnyRpcTransaction, BlockResponse, TransactionResponse}; +use alloy_primitives::{ + map::{FbHashMap, HashMap}, + Address, Bytes, StorageValue, B256, U256, +}; +use alloy_provider::{ + ext::{DebugApi, TraceApi}, + Provider, +}; +use alloy_rpc_types::{ + request::TransactionRequest, + trace::{ + geth::{GethDebugTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace as Trace, }, + BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, + FeeHistory, Filter, Log, }; -use foundry_common::{ProviderBuilder, RetryProvider}; -use foundry_evm::utils::u256_to_h256_be; +use alloy_serde::WithOtherFields; +use alloy_transport::TransportError; +use anvil_core::eth::transaction::{convert_to_anvil_receipt, ReceiptResponse}; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use parking_lot::{ lock_api::{RwLockReadGuard, RwLockWriteGuard}, RawRwLock, RwLock, }; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use revm::primitives::BlobExcessGasAndPrice; +use std::{sync::Arc, time::Duration}; use tokio::sync::RwLock as AsyncRwLock; -use tracing::trace; /// Represents a fork of a remote client /// /// This type contains a subset of the [`EthApi`](crate::eth::EthApi) functions but will exclusively /// fetch the requested data from the remote client, if it wasn't already fetched. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ClientFork { /// Contains the cached data pub storage: Arc>, @@ -34,14 +46,12 @@ pub struct ClientFork { // endpoints pub config: Arc>, /// This also holds a handle to the underlying database - pub database: Arc>, + pub database: Arc>>, } -// === impl ClientFork === - impl ClientFork { /// Creates a new instance of the fork - pub fn new(config: ClientForkConfig, database: Arc>) -> Self { + pub fn new(config: ClientForkConfig, database: Arc>>) -> Self { Self { storage: Default::default(), config: Arc::new(RwLock::new(config)), database } } @@ -56,7 +66,7 @@ impl ClientFork { self.database .write() .await - .reset(url.clone(), block_number) + .maybe_reset(url.clone(), block_number) .map_err(BlockchainError::Internal)?; } @@ -64,30 +74,36 @@ impl ClientFork { self.config.write().update_url(url)?; let override_chain_id = self.config.read().override_chain_id; let chain_id = if let Some(chain_id) = override_chain_id { - chain_id.into() + chain_id } else { - self.provider().get_chainid().await? + self.provider().get_chain_id().await? }; - self.config.write().chain_id = chain_id.as_u64(); + self.config.write().chain_id = chain_id; } let provider = self.provider(); - let block = - provider.get_block(block_number).await?.ok_or(BlockchainError::BlockNotFound)?; - let block_hash = block.hash.ok_or(BlockchainError::BlockNotFound)?; - let timestamp = block.timestamp.as_u64(); - let base_fee = block.base_fee_per_gas; - let total_difficulty = block.total_difficulty.unwrap_or_default(); - + let block = provider + .get_block(block_number, false.into()) + .await? + .ok_or(BlockchainError::BlockNotFound)?; + let block_hash = block.header.hash; + let timestamp = block.header.timestamp; + let base_fee = block.header.base_fee_per_gas; + let total_difficulty = block.header.total_difficulty.unwrap_or_default(); + + let number = block.header.number; self.config.write().update_block( - block.number.ok_or(BlockchainError::BlockNotFound)?.as_u64(), + number, block_hash, timestamp, - base_fee, + base_fee.map(|g| g as u128), total_difficulty, ); self.clear_cached_storage(); + + self.database.write().await.insert_block_hash(U256::from(number), block_hash); + Ok(()) } @@ -114,15 +130,20 @@ 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 } - pub fn base_fee(&self) -> Option { + pub fn base_fee(&self) -> Option { self.config.read().base_fee } - pub fn block_hash(&self) -> H256 { + pub fn block_hash(&self) -> B256 { self.config.read().block_hash } @@ -149,90 +170,54 @@ impl ClientFork { /// Returns the fee history `eth_feeHistory` pub async fn fee_history( &self, - block_count: U256, + block_count: u64, newest_block: BlockNumber, reward_percentiles: &[f64], - ) -> Result { - self.provider().fee_history(block_count, newest_block, reward_percentiles).await + ) -> Result { + self.provider().get_fee_history(block_count, newest_block, reward_percentiles).await } /// Sends `eth_getProof` pub async fn get_proof( &self, address: Address, - keys: Vec, + keys: Vec, block_number: Option, - ) -> Result { - self.provider().get_proof(address, keys, block_number).await + ) -> Result { + self.provider().get_proof(address, keys).block_id(block_number.unwrap_or_default()).await } /// Sends `eth_call` pub async fn call( &self, - request: &EthTransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let request = Arc::new(request.clone()); + ) -> Result { let block = block.unwrap_or(BlockNumber::Latest); + let res = self.provider().call(request).block(block.into()).await?; - if let BlockNumber::Number(num) = block { - // check if this request was already been sent - let key = (request.clone(), num.as_u64()); - if let Some(res) = self.storage_read().eth_call.get(&key).cloned() { - return Ok(res) - } - } - - let tx = ethers::utils::serialize(request.as_ref()); - let block_value = ethers::utils::serialize(&block); - let res: Bytes = self.provider().request("eth_call", [tx, block_value]).await?; - - if let BlockNumber::Number(num) = block { - // cache result - let mut storage = self.storage_write(); - storage.eth_call.insert((request, num.as_u64()), res.clone()); - } Ok(res) } /// Sends `eth_call` pub async fn estimate_gas( &self, - request: &EthTransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let request = Arc::new(request.clone()); - let block = block.unwrap_or(BlockNumber::Latest); - - if let BlockNumber::Number(num) = block { - // check if this request was already been sent - let key = (request.clone(), num.as_u64()); - if let Some(res) = self.storage_read().eth_gas_estimations.get(&key).cloned() { - return Ok(res) - } - } - let tx = ethers::utils::serialize(request.as_ref()); - let block_value = ethers::utils::serialize(&block); - let res = self.provider().request("eth_estimateGas", [tx, block_value]).await?; - - if let BlockNumber::Number(num) = block { - // cache result - let mut storage = self.storage_write(); - storage.eth_gas_estimations.insert((request, num.as_u64()), res); - } + ) -> Result { + let block = block.unwrap_or_default(); + let res = self.provider().estimate_gas(request).block(block.into()).await?; - Ok(res) + Ok(res as u128) } /// Sends `eth_createAccessList` pub async fn create_access_list( &self, - request: &EthTransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let tx = ethers::utils::serialize(request); - let block = ethers::utils::serialize(&block.unwrap_or(BlockNumber::Latest)); - self.provider().request("eth_createAccessList", [tx, block]).await + ) -> Result { + self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await } pub async fn storage_at( @@ -240,14 +225,16 @@ impl ClientFork { address: Address, index: U256, number: Option, - ) -> Result { - let index = u256_to_h256_be(index); - self.provider().get_storage_at(address, index, number.map(Into::into)).await + ) -> Result { + self.provider() + .get_storage_at(address, index) + .block_id(number.unwrap_or_default().into()) + .await } - pub async fn logs(&self, filter: &Filter) -> Result, ProviderError> { + pub async fn logs(&self, filter: &Filter) -> Result, TransportError> { if let Some(logs) = self.storage_read().logs.get(filter).cloned() { - return Ok(logs) + return Ok(logs); } let logs = self.provider().get_logs(filter).await?; @@ -261,15 +248,18 @@ impl ClientFork { &self, address: Address, blocknumber: u64, - ) -> Result { + ) -> Result { trace!(target: "backend::fork", "get_code={:?}", address); if let Some(code) = self.storage_read().code_at.get(&(address, blocknumber)).cloned() { - return Ok(code) + return Ok(code); } - let code = self.provider().get_code(address, Some(blocknumber.into())).await?; + let block_id = BlockId::number(blocknumber); + + let code = self.provider().get_code_at(address).block_id(block_id).await?; + let mut storage = self.storage_write(); - storage.code_at.insert((address, blocknumber), code.clone()); + storage.code_at.insert((address, blocknumber), code.clone().0.into()); Ok(code) } @@ -278,28 +268,44 @@ impl ClientFork { &self, address: Address, blocknumber: u64, - ) -> Result { + ) -> Result { trace!(target: "backend::fork", "get_balance={:?}", address); - self.provider().get_balance(address, Some(blocknumber.into())).await + self.provider().get_balance(address).block_id(blocknumber.into()).await } - pub async fn get_nonce( + pub async fn get_nonce(&self, address: Address, block: u64) -> Result { + trace!(target: "backend::fork", "get_nonce={:?}", address); + 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_nonce={:?}", address); - self.provider().get_transaction_count(address, Some(blocknumber.into())).await + ) -> Result { + trace!(target: "backend::fork", "get_account={:?}", address); + self.provider().get_account(address).block_id(blocknumber.into()).await } pub async fn transaction_by_block_number_and_index( &self, number: u64, index: usize, - ) -> Result, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { - if let Some(tx_hash) = block.transactions.get(index) { - return self.transaction_by_hash(*tx_hash).await + match block.transactions() { + BlockTransactions::Full(txs) => { + if let Some(tx) = txs.get(index) { + return Ok(Some(tx.clone())); + } + } + BlockTransactions::Hashes(hashes) => { + if let Some(tx_hash) = hashes.get(index) { + return self.transaction_by_hash(*tx_hash).await; + } + } + // TODO(evalir): Is it possible to reach this case? Should we support it + BlockTransactions::Uncle => panic!("Uncles not supported"), } } Ok(None) @@ -307,12 +313,23 @@ impl ClientFork { pub async fn transaction_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: usize, - ) -> Result, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { - if let Some(tx_hash) = block.transactions.get(index) { - return self.transaction_by_hash(*tx_hash).await + match block.transactions() { + BlockTransactions::Full(txs) => { + if let Some(tx) = txs.get(index) { + return Ok(Some(tx.clone())); + } + } + BlockTransactions::Hashes(hashes) => { + if let Some(tx_hash) = hashes.get(index) { + return self.transaction_by_hash(*tx_hash).await; + } + } + // TODO(evalir): Is it possible to reach this case? Should we support it + BlockTransactions::Uncle => panic!("Uncles not supported"), } } Ok(None) @@ -320,27 +337,28 @@ impl ClientFork { pub async fn transaction_by_hash( &self, - hash: H256, - ) -> Result, ProviderError> { + hash: B256, + ) -> Result, TransportError> { trace!(target: "backend::fork", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.storage_read().transactions.get(&hash).cloned() { - return Ok(tx) + return Ok(tx); } - if let Some(tx) = self.provider().get_transaction(hash).await? { + let tx = self.provider().get_transaction_by_hash(hash).await?; + if let Some(tx) = tx.clone() { let mut storage = self.storage_write(); - storage.transactions.insert(hash, tx.clone()); - return Ok(Some(tx)) + storage.transactions.insert(hash, tx); } - Ok(None) + Ok(tx) } - pub async fn trace_transaction(&self, hash: H256) -> Result, ProviderError> { + pub async fn trace_transaction(&self, hash: B256) -> Result, TransportError> { if let Some(traces) = self.storage_read().transaction_traces.get(&hash).cloned() { - return Ok(traces) + return Ok(traces); } - let traces = self.provider().trace_transaction(hash).await?; + let traces = self.provider().trace_transaction(hash).await?.into_iter().collect::>(); + let mut storage = self.storage_write(); storage.transaction_traces.insert(hash, traces.clone()); @@ -349,26 +367,29 @@ impl ClientFork { pub async fn debug_trace_transaction( &self, - hash: H256, + hash: B256, opts: GethDebugTracingOptions, - ) -> Result { + ) -> Result { if let Some(traces) = self.storage_read().geth_transaction_traces.get(&hash).cloned() { - return Ok(traces) + return Ok(traces); } let trace = self.provider().debug_trace_transaction(hash, opts).await?; + let mut storage = self.storage_write(); storage.geth_transaction_traces.insert(hash, trace.clone()); Ok(trace) } - pub async fn trace_block(&self, number: u64) -> Result, ProviderError> { + pub async fn trace_block(&self, number: u64) -> Result, TransportError> { if let Some(traces) = self.storage_read().block_traces.get(&number).cloned() { - return Ok(traces) + return Ok(traces); } - let traces = self.provider().trace_block(number.into()).await?; + let traces = + self.provider().trace_block(number.into()).await?.into_iter().collect::>(); + let mut storage = self.storage_write(); storage.block_traces.insert(number, traces.clone()); @@ -377,35 +398,76 @@ impl ClientFork { pub async fn transaction_receipt( &self, - hash: H256, - ) -> Result, ProviderError> { + hash: B256, + ) -> Result, BlockchainError> { if let Some(receipt) = self.storage_read().transaction_receipts.get(&hash).cloned() { - return Ok(Some(receipt)) + return Ok(Some(receipt)); } if let Some(receipt) = self.provider().get_transaction_receipt(hash).await? { + let receipt = + convert_to_anvil_receipt(receipt).ok_or(BlockchainError::FailedToDecodeReceipt)?; let mut storage = self.storage_write(); storage.transaction_receipts.insert(hash, receipt.clone()); - return Ok(Some(receipt)) + return Ok(Some(receipt)); } Ok(None) } - pub async fn block_by_hash(&self, hash: H256) -> Result>, ProviderError> { - if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { - return Ok(Some(block)) + pub async fn block_receipts( + &self, + number: u64, + ) -> Result>, BlockchainError> { + if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() { + return Ok(receipts); } - let block = self.fetch_full_block(hash).await?.map(Into::into); - Ok(block) + + // TODO Needs to be removed. + // Since alloy doesn't indicate in the result whether the block exists, + // this is being temporarily implemented in anvil. + if self.predates_fork_inclusive(number) { + let receipts = self.provider().get_block_receipts(BlockId::from(number)).await?; + let receipts = receipts + .map(|r| { + r.into_iter() + .map(|r| { + convert_to_anvil_receipt(r) + .ok_or(BlockchainError::FailedToDecodeReceipt) + }) + .collect::, _>>() + }) + .transpose()?; + + if let Some(receipts) = receipts.clone() { + let mut storage = self.storage_write(); + storage.block_receipts.insert(number, receipts); + } + + return Ok(receipts); + } + + Ok(None) + } + + pub async fn block_by_hash(&self, hash: B256) -> Result, TransportError> { + if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() { + block.transactions.convert_to_hashes(); + return Ok(Some(block)); + } + + Ok(self.fetch_full_block(hash).await?.map(|mut b| { + b.transactions.convert_to_hashes(); + b + })) } pub async fn block_by_hash_full( &self, - hash: H256, - ) -> Result>, ProviderError> { + hash: B256, + ) -> Result, TransportError> { if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { - return Ok(Some(self.convert_to_full_block(block))) + return Ok(Some(self.convert_to_full_block(block))); } self.fetch_full_block(hash).await } @@ -413,25 +475,28 @@ impl ClientFork { pub async fn block_by_number( &self, block_number: u64, - ) -> Result>, ProviderError> { - if let Some(block) = self + ) -> Result, TransportError> { + if let Some(mut block) = self .storage_read() .hashes .get(&block_number) - .copied() - .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) + .and_then(|hash| self.storage_read().blocks.get(hash).cloned()) { - return Ok(Some(block)) + block.transactions.convert_to_hashes(); + return Ok(Some(block)); } - let block = self.fetch_full_block(block_number).await?.map(Into::into); + let mut block = self.fetch_full_block(block_number).await?; + if let Some(block) = &mut block { + block.transactions.convert_to_hashes(); + } Ok(block) } pub async fn block_by_number_full( &self, block_number: u64, - ) -> Result>, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self .storage_read() .hashes @@ -439,7 +504,7 @@ impl ClientFork { .copied() .and_then(|hash| self.storage_read().blocks.get(&hash).cloned()) { - return Ok(Some(self.convert_to_full_block(block))) + return Ok(Some(self.convert_to_full_block(block))); } self.fetch_full_block(block_number).await @@ -448,16 +513,20 @@ impl ClientFork { async fn fetch_full_block( &self, block_id: impl Into, - ) -> Result>, ProviderError> { - if let Some(block) = self.provider().get_block_with_txs(block_id.into()).await? { - let hash = block.hash.unwrap(); - let block_number = block.number.unwrap().as_u64(); + ) -> Result, TransportError> { + if let Some(block) = self.provider().get_block(block_id.into(), true.into()).await? { + let hash = block.header.hash; + let block_number = block.header.number; let mut storage = self.storage_write(); // also insert all transactions - storage.transactions.extend(block.transactions.iter().map(|tx| (tx.hash, tx.clone()))); + let block_txs = match block.transactions() { + BlockTransactions::Full(txs) => txs.to_owned(), + _ => vec![], + }; + storage.transactions.extend(block_txs.iter().map(|tx| (tx.tx_hash(), tx.clone()))); storage.hashes.insert(block_number, hash); - storage.blocks.insert(hash, block.clone().into()); - return Ok(Some(block)) + storage.blocks.insert(hash, block.clone()); + return Ok(Some(block)); } Ok(None) @@ -465,11 +534,11 @@ impl ClientFork { pub async fn uncle_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: usize, - ) -> Result>, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { - return self.uncles_by_block_and_index(block, index).await + return self.uncles_by_block_and_index(block, index).await; } Ok(None) } @@ -478,31 +547,31 @@ impl ClientFork { &self, number: u64, index: usize, - ) -> Result>, ProviderError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { - return self.uncles_by_block_and_index(block, index).await + return self.uncles_by_block_and_index(block, index).await; } Ok(None) } async fn uncles_by_block_and_index( &self, - block: Block, + block: AnyRpcBlock, index: usize, - ) -> Result>, ProviderError> { - let block_hash = block - .hash - .ok_or_else(|| ProviderError::CustomError("missing block-hash".to_string()))?; + ) -> Result, TransportError> { + let block_hash = block.header.hash; + let block_number = block.header.number; if let Some(uncles) = self.storage_read().uncles.get(&block_hash) { - return Ok(uncles.get(index).cloned()) + return Ok(uncles.get(index).cloned()); } let mut uncles = Vec::with_capacity(block.uncles.len()); for (uncle_idx, _) in block.uncles.iter().enumerate() { - let uncle = match self.provider().get_uncle(block_hash, uncle_idx.into()).await? { - Some(u) => u, - None => return Ok(None), - }; + let uncle = + match self.provider().get_uncle(block_number.into(), uncle_idx as u64).await? { + Some(u) => u, + None => return Ok(None), + }; uncles.push(uncle); } self.storage_write().uncles.insert(block_hash, uncles.clone()); @@ -510,24 +579,37 @@ impl ClientFork { } /// Converts a block of hashes into a full block - fn convert_to_full_block(&self, block: Block) -> Block { + fn convert_to_full_block(&self, mut block: AnyRpcBlock) -> AnyRpcBlock { let storage = self.storage.read(); - let mut transactions = Vec::with_capacity(block.transactions.len()); - for tx in block.transactions.iter() { - if let Some(tx) = storage.transactions.get(tx).cloned() { + let block_txs_len = match block.transactions { + BlockTransactions::Full(ref txs) => txs.len(), + BlockTransactions::Hashes(ref hashes) => hashes.len(), + // TODO: Should this be supported at all? + BlockTransactions::Uncle => 0, + }; + let mut transactions = Vec::with_capacity(block_txs_len); + for tx in block.transactions.hashes() { + if let Some(tx) = storage.transactions.get(&tx).cloned() { transactions.push(tx); } } - block.into_full_block(transactions) + // TODO: fix once blocks have generic transactions + block.inner.transactions = BlockTransactions::Full(transactions); + + block } } /// Contains all fork metadata -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ClientForkConfig { pub eth_rpc_url: String, + /// The block number of the forked block pub block_number: u64, - pub block_hash: H256, + /// 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, @@ -535,7 +617,11 @@ pub struct ClientForkConfig { /// The timestamp for the forked block pub timestamp: u64, /// The basefee of the forked block - pub base_fee: Option, + pub base_fee: Option, + /// Blob gas used of the forked block + pub blob_gas_used: Option, + /// Blob excess gas and price of the forked block + pub blob_excess_gas_and_price: Option, /// request timeout pub timeout: Duration, /// request retries for spurious networks @@ -546,10 +632,10 @@ 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 === - impl ClientForkConfig { /// Updates the provider URL /// @@ -557,17 +643,16 @@ impl ClientForkConfig { /// /// This will fail if no new provider could be established (erroneous URL) fn update_url(&mut self, url: String) -> Result<(), BlockchainError> { - let interval = self.provider.get_interval(); + // let interval = self.provider.get_interval(); 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() - .map_err(|_| BlockchainError::InvalidUrl(url.clone()))? - .interval(interval), + .map_err(|_| BlockchainError::InvalidUrl(url.clone()))?, // .interval(interval), ); trace!(target: "fork", "Updated rpc url {}", url); self.eth_rpc_url = url; @@ -577,9 +662,9 @@ impl ClientForkConfig { pub fn update_block( &mut self, block_number: u64, - block_hash: H256, + block_hash: B256, timestamp: u64, - base_fee: Option, + base_fee: Option, total_difficulty: U256, ) { self.block_number = block_number; @@ -592,24 +677,23 @@ impl ClientForkConfig { } /// Contains cached state fetched to serve EthApi requests -#[derive(Debug, Clone, Default)] +/// +/// This is used as a cache so repeated requests to the same data are not sent to the remote client +#[derive(Clone, Debug, Default)] pub struct ForkedStorage { - pub uncles: HashMap>>, - pub blocks: HashMap>, - pub hashes: HashMap, - pub transactions: HashMap, - pub transaction_receipts: HashMap, - pub transaction_traces: HashMap>, + pub uncles: FbHashMap<32, Vec>, + pub blocks: FbHashMap<32, AnyRpcBlock>, + pub hashes: HashMap, + pub transactions: FbHashMap<32, AnyRpcTransaction>, + pub transaction_receipts: FbHashMap<32, ReceiptResponse>, + pub transaction_traces: FbHashMap<32, Vec>, pub logs: HashMap>, - pub geth_transaction_traces: HashMap, + pub geth_transaction_traces: FbHashMap<32, GethTrace>, pub block_traces: HashMap>, - pub eth_gas_estimations: HashMap<(Arc, u64), U256>, - pub eth_call: HashMap<(Arc, u64), Bytes>, + pub block_receipts: HashMap>, pub code_at: HashMap<(Address, u64), Bytes>, } -// === impl ForkedStorage === - impl ForkedStorage { /// Clears all data pub fn clear(&mut self) { diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index 4f1c0f92c3a96..e5e76ff491e82 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -1,27 +1,16 @@ //! Genesis settings -use crate::{ - eth::backend::db::{Db, MaybeHashDatabase}, - genesis::Genesis, -}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - types::{Address, H256}, -}; +use crate::eth::backend::db::Db; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::{Address, U256}; use foundry_evm::{ - executor::{ - backend::{snapshot::StateSnapshot, DatabaseError, DatabaseResult}, - DatabaseRef, - }, - revm::primitives::{AccountInfo, Bytecode, B160, B256, KECCAK_EMPTY, U256}, - utils::b160_to_h160, + backend::DatabaseResult, + revm::primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, }; -use parking_lot::Mutex; -use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLockWriteGuard; /// Genesis settings -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct GenesisConfig { /// The initial timestamp for the genesis block pub timestamp: u64, @@ -29,17 +18,10 @@ 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, } -// === impl GenesisConfig === - impl GenesisConfig { /// Returns fresh `AccountInfo`s for the configured `accounts` pub fn account_infos(&self) -> impl Iterator + '_ { @@ -58,92 +40,31 @@ impl GenesisConfig { /// If an initial `genesis.json` was provided, this applies the account alloc to the db pub fn apply_genesis_json_alloc( &self, - mut db: RwLockWriteGuard<'_, dyn Db>, + mut db: RwLockWriteGuard<'_, Box>, ) -> DatabaseResult<()> { if let Some(ref genesis) = self.genesis_init { - for (addr, mut acc) in genesis.alloc.accounts.clone() { + for (addr, mut acc) in genesis.alloc.clone() { let storage = std::mem::take(&mut acc.storage); // insert all accounts - db.insert_account(addr, acc.into()); + db.insert_account(addr, self.genesis_to_account_info(&acc)); // insert all storage values - for (k, v) in storage.iter() { - db.set_storage_at(addr, k.into_uint(), v.into_uint())?; + for (k, v) in storage.unwrap_or_default().iter() { + db.set_storage_at(addr, *k, *v)?; } } } Ok(()) } - /// 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(&self, address: B160) -> DatabaseResult> { - if let Some(acc) = self.accounts.get(&address.into()).cloned() { - return Ok(Some(acc)) - } - self.db.basic(address) - } - - fn code_by_hash(&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()) + /// Converts a [`GenesisAccount`] to an [`AccountInfo`] + fn genesis_to_account_info(&self, acc: &GenesisAccount) -> AccountInfo { + let GenesisAccount { code, balance, nonce, .. } = acc.clone(); + let code = code.map(Bytecode::new_raw); + AccountInfo { + balance, + nonce: nonce.unwrap_or_default(), + code_hash: code.as_ref().map(|code| code.hash_slow()).unwrap_or(KECCAK_EMPTY), + code, } - self.db.code_by_hash(code_hash) - } - - fn storage(&self, address: B160, index: U256) -> DatabaseResult { - if let Some(acc) = self - .genesis - .as_ref() - .and_then(|genesis| genesis.alloc.accounts.get(&b160_to_h160(address))) - { - let value = - acc.storage.get(&H256::from_uint(&index.into())).copied().unwrap_or_default(); - return Ok(value.into_uint().into()) - } - self.db.storage(address, index) - } - - fn block_hash(&self, number: U256) -> DatabaseResult { - self.db.block_hash(number) - } -} - -impl<'a> MaybeHashDatabase 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/info.rs b/crates/anvil/src/eth/backend/info.rs index 8d4e3601b30d4..0f539f9373dcb 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -1,8 +1,9 @@ //! Handler that can get current storage related data use crate::mem::Backend; -use anvil_core::eth::{block::Block, receipt::TypedReceipt}; -use ethers::types::{Block as EthersBlock, TxHash, H256}; +use alloy_network::AnyRpcBlock; +use alloy_primitives::B256; +use anvil_core::eth::{block::Block, transaction::TypedReceipt}; use std::{fmt, sync::Arc}; /// A type that can fetch data related to the ethereum storage. @@ -15,8 +16,6 @@ pub struct StorageInfo { backend: Arc, } -// === impl StorageInfo === - impl StorageInfo { pub(crate) fn new(backend: Arc) -> Self { Self { backend } @@ -33,17 +32,17 @@ impl StorageInfo { } /// Returns the receipts of the block with the given hash - pub fn receipts(&self, hash: H256) -> Option> { + pub fn receipts(&self, hash: B256) -> Option> { self.backend.mined_receipts(hash) } /// Returns the block with the given hash - pub fn block(&self, hash: H256) -> Option { + pub fn block(&self, hash: B256) -> Option { self.backend.get_block_by_hash(hash) } /// Returns the block with the given hash in the format of the ethereum API - pub fn eth_block(&self, hash: H256) -> Option> { + pub fn eth_block(&self, hash: B256) -> Option { let block = self.block(hash)?; Some(self.backend.convert_block(block)) } diff --git a/crates/anvil/src/eth/backend/mem/cache.rs b/crates/anvil/src/eth/backend/mem/cache.rs index 0703cb78fb5ec..d4b2779da32b6 100644 --- a/crates/anvil/src/eth/backend/mem/cache.rs +++ b/crates/anvil/src/eth/backend/mem/cache.rs @@ -1,12 +1,11 @@ use crate::config::anvil_tmp_dir; -use ethers::prelude::H256; -use foundry_evm::executor::backend::snapshot::StateSnapshot; +use alloy_primitives::B256; +use foundry_evm::backend::StateSnapshot; use std::{ io, path::{Path, PathBuf}, }; use tempfile::TempDir; -use tracing::{error, trace}; /// On disk state cache /// @@ -19,8 +18,12 @@ pub struct DiskStateCache { } impl DiskStateCache { + /// Specify the path where to create the tempdir in + pub fn with_path(self, temp_path: PathBuf) -> Self { + Self { temp_path: Some(temp_path), temp_dir: None } + } /// Returns the cache file for the given hash - fn with_cache_file(&mut self, hash: H256, f: F) -> Option + fn with_cache_file(&mut self, hash: B256, f: F) -> Option where F: FnOnce(PathBuf) -> R, { @@ -40,11 +43,11 @@ impl DiskStateCache { self.temp_dir = Some(temp_dir); } Err(err) => { - error!(target: "backend", ?err, "failed to create disk state cache dir"); + error!(target: "backend", %err, "failed to create disk state cache dir"); } } } - if let Some(ref temp_dir) = self.temp_dir { + if let Some(temp_dir) = &self.temp_dir { let path = temp_dir.path().join(format!("{hash:?}.json")); Some(f(path)) } else { @@ -57,7 +60,7 @@ impl DiskStateCache { /// Note: this writes the state on a new spawned task /// /// Caution: this requires a running tokio Runtime. - pub fn write(&mut self, hash: H256, state: StateSnapshot) { + pub fn write(&mut self, hash: B256, state: StateSnapshot) { self.with_cache_file(hash, |file| { tokio::task::spawn(async move { match foundry_common::fs::write_json_file(&file, &state) { @@ -65,7 +68,7 @@ impl DiskStateCache { trace!(target: "backend", ?hash, "wrote state json file"); } Err(err) => { - error!(target: "backend", ?err, ?hash, "Failed to load state snapshot"); + error!(target: "backend", %err, ?hash, "Failed to load state snapshot"); } }; }); @@ -75,7 +78,7 @@ impl DiskStateCache { /// Loads the snapshot file for the given hash /// /// Returns None if it doesn't exist or deserialization failed - pub fn read(&mut self, hash: H256) -> Option { + pub fn read(&mut self, hash: B256) -> Option { self.with_cache_file(hash, |file| { match foundry_common::fs::read_json_file::(&file) { Ok(state) => { @@ -83,7 +86,7 @@ impl DiskStateCache { Some(state) } Err(err) => { - error!(target: "backend", ?err, ?hash, "Failed to load state snapshot"); + error!(target: "backend", %err, ?hash, "Failed to load state snapshot"); None } } @@ -92,10 +95,10 @@ impl DiskStateCache { } /// Removes the cache file for the given hash, if it exists - pub fn remove(&mut self, hash: H256) { + pub fn remove(&mut self, hash: B256) { self.with_cache_file(hash, |file| { foundry_common::fs::remove_file(file).map_err(|err| { - error!(target: "backend", ?err, ?hash, "Failed to remove state snapshot"); + error!(target: "backend", %err, %hash, "Failed to remove state snapshot"); }) }); } @@ -103,7 +106,7 @@ impl DiskStateCache { impl Default for DiskStateCache { fn default() -> Self { - DiskStateCache { temp_path: anvil_tmp_dir(), temp_dir: None } + Self { temp_path: anvil_tmp_dir(), temp_dir: None } } } diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 593fae025a7e5..be5c3bcd7b32e 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -1,37 +1,46 @@ use crate::{ eth::backend::db::{ - Db, MaybeHashDatabase, SerializableAccountRecord, SerializableState, StateDb, + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, + SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }, revm::primitives::AccountInfo, - Address, U256, }; -use ethers::prelude::H256; -pub use foundry_evm::executor::fork::database::ForkedDatabase; +use alloy_primitives::{map::HashMap, Address, B256, U256, U64}; +use alloy_rpc_types::BlockId; use foundry_evm::{ - executor::{ - backend::{snapshot::StateSnapshot, DatabaseResult}, - fork::database::ForkDbSnapshot, + backend::{ + BlockchainDb, DatabaseError, DatabaseResult, RevertStateSnapshotAction, StateSnapshot, }, - revm::Database, + fork::database::ForkDbStateSnapshot, + revm::{primitives::BlockEnv, Database}, }; +use revm::{db::DbAccount, DatabaseRef}; + +pub use foundry_evm::fork::database::ForkedDatabase; -/// Implement the helper for the fork database impl Db for ForkedDatabase { fn insert_account(&mut self, address: Address, account: AccountInfo) { self.database_mut().insert_account(address, account) } - fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()> { + fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> { // this ensures the account is loaded first - let _ = Database::basic(self, address.into())?; + let _ = Database::basic(self, address)?; self.database_mut().set_storage_at(address, slot, val) } - fn insert_block_hash(&mut self, number: U256, hash: H256) { - self.inner().block_hashes().write().insert(number.into(), hash.into()); + fn insert_block_hash(&mut self, number: U256, hash: B256) { + self.inner().block_hashes().write().insert(number, hash); } - fn dump_state(&self) -> DatabaseResult> { + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + blocks: Vec, + transactions: Vec, + historical_states: Option, + ) -> DatabaseResult> { let mut db = self.database().clone(); let accounts = self .database() @@ -43,41 +52,51 @@ impl Db for ForkedDatabase { code } else { db.code_by_hash(v.info.code_hash)? - } - .to_checked(); + }; Ok(( - k.into(), + k, SerializableAccountRecord { nonce: v.info.nonce, - balance: v.info.balance.into(), - code: code.bytes()[..code.len()].to_vec().into(), - storage: v - .storage - .into_iter() - .map(|kv| (kv.0.into(), kv.1.into())) - .collect(), + balance: v.info.balance, + code: code.original_bytes(), + storage: v.storage.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), }, )) }) .collect::>()?; - Ok(Some(SerializableState { accounts })) + Ok(Some(SerializableState { + block: Some(at), + accounts, + best_block_number: Some(best_number), + blocks, + transactions, + historical_states, + })) } - fn snapshot(&mut self) -> U256 { - self.insert_snapshot() + fn snapshot_state(&mut self) -> U256 { + self.insert_state_snapshot() } - fn revert(&mut self, id: U256) -> bool { - self.revert_snapshot(id) + fn revert_state(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { + self.revert_state_snapshot(id, action) } fn current_state(&self) -> StateDb { - StateDb::new(self.create_snapshot()) + StateDb::new(self.create_state_snapshot()) } } -impl MaybeHashDatabase for ForkedDatabase { - fn clear_into_snapshot(&mut self) -> StateSnapshot { +impl MaybeFullDatabase for ForkedDatabase { + fn as_dyn(&self) -> &dyn DatabaseRef { + self + } + + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.database().accounts) + } + + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { let db = self.inner().db(); let accounts = std::mem::take(&mut *db.accounts.write()); let storage = std::mem::take(&mut *db.storage.write()); @@ -85,30 +104,66 @@ impl MaybeHashDatabase for ForkedDatabase { StateSnapshot { accounts, storage, block_hashes } } + fn read_as_state_snapshot(&self) -> StateSnapshot { + let db = self.inner().db(); + let accounts = db.accounts.read().clone(); + let storage = db.storage.read().clone(); + let block_hashes = db.block_hashes.read().clone(); + StateSnapshot { accounts, storage, block_hashes } + } + fn clear(&mut self) { self.flush_cache(); - self.clear_into_snapshot(); + self.clear_into_state_snapshot(); } - fn init_from_snapshot(&mut self, snapshot: StateSnapshot) { + fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { let db = self.inner().db(); - let StateSnapshot { accounts, storage, block_hashes } = snapshot; + let StateSnapshot { accounts, storage, block_hashes } = state_snapshot; *db.accounts.write() = accounts; *db.storage.write() = storage; *db.block_hashes.write() = block_hashes; } } -impl MaybeHashDatabase for ForkDbSnapshot { - fn clear_into_snapshot(&mut self) -> StateSnapshot { - std::mem::take(&mut self.snapshot) + +impl MaybeFullDatabase for ForkDbStateSnapshot { + fn as_dyn(&self) -> &dyn DatabaseRef { + self + } + + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.local.accounts) + } + + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { + std::mem::take(&mut self.state_snapshot) + } + + fn read_as_state_snapshot(&self) -> StateSnapshot { + self.state_snapshot.clone() } fn clear(&mut self) { - std::mem::take(&mut self.snapshot); + std::mem::take(&mut self.state_snapshot); self.local.clear() } - fn init_from_snapshot(&mut self, snapshot: StateSnapshot) { - self.snapshot = snapshot; + fn init_from_state_snapshot(&mut self, state_snapshot: StateSnapshot) { + self.state_snapshot = state_snapshot; + } +} + +impl MaybeForkedDatabase for ForkedDatabase { + fn maybe_reset(&mut self, url: Option, block_number: BlockId) -> Result<(), String> { + self.reset(url, block_number) + } + + fn maybe_flush_cache(&self) -> Result<(), String> { + self.flush_cache(); + Ok(()) + } + + fn maybe_inner(&self) -> Result<&BlockchainDb, String> { + Ok(self.inner()) } } 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 18b0c83690ef7..9e34448ad3110 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -2,35 +2,41 @@ use crate::{ eth::backend::db::{ - AsHashDB, Db, MaybeHashDatabase, SerializableAccountRecord, SerializableState, StateDb, + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, + SerializableHistoricalStates, SerializableState, SerializableTransaction, StateDb, }, - mem::state::{state_merkle_trie_root, trie_hash_db}, - revm::primitives::AccountInfo, - Address, U256, + mem::state::state_root, + revm::{db::DbAccount, primitives::AccountInfo}, }; -use ethers::prelude::H256; -use foundry_evm::utils::h160_to_b160; -use tracing::{trace, warn}; +use alloy_primitives::{map::HashMap, Address, B256, U256, U64}; +use alloy_rpc_types::BlockId; +use foundry_evm::backend::{BlockchainDb, DatabaseResult, StateSnapshot}; // reexport for convenience -use crate::mem::state::storage_trie_db; -use foundry_evm::executor::backend::{snapshot::StateSnapshot, DatabaseResult}; -pub use foundry_evm::executor::{backend::MemDb, DatabaseRef}; +pub use foundry_evm::{backend::MemDb, revm::db::DatabaseRef}; +use foundry_evm::{backend::RevertStateSnapshotAction, revm::primitives::BlockEnv}; impl Db for MemDb { fn insert_account(&mut self, address: Address, account: AccountInfo) { - self.inner.insert_account_info(address.into(), account) + self.inner.insert_account_info(address, account) } - fn set_storage_at(&mut self, address: Address, slot: U256, val: U256) -> DatabaseResult<()> { - self.inner.insert_account_storage(address.into(), slot.into(), val.into()) + fn set_storage_at(&mut self, address: Address, slot: B256, val: B256) -> DatabaseResult<()> { + self.inner.insert_account_storage(address, slot.into(), val.into()) } - fn insert_block_hash(&mut self, number: U256, hash: H256) { - self.inner.block_hashes.insert(number.into(), hash.into()); + fn insert_block_hash(&mut self, number: U256, hash: B256) { + self.inner.block_hashes.insert(number, hash); } - fn dump_state(&self) -> DatabaseResult> { + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + blocks: Vec, + transactions: Vec, + historical_states: Option, + ) -> DatabaseResult> { let accounts = self .inner .accounts @@ -40,93 +46,108 @@ impl Db for MemDb { let code = if let Some(code) = v.info.code { code } else { - self.inner.code_by_hash(v.info.code_hash)? - } - .to_checked(); + self.inner.code_by_hash_ref(v.info.code_hash)? + }; Ok(( - k.into(), + k, SerializableAccountRecord { nonce: v.info.nonce, - balance: v.info.balance.into(), - code: code.bytes()[..code.len()].to_vec().into(), - storage: v.storage.into_iter().map(|k| (k.0.into(), k.1.into())).collect(), + balance: v.info.balance, + code: code.original_bytes(), + storage: v.storage.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), }, )) }) .collect::>()?; - Ok(Some(SerializableState { accounts })) + Ok(Some(SerializableState { + block: Some(at), + accounts, + best_block_number: Some(best_number), + blocks, + transactions, + historical_states, + })) } /// Creates a new snapshot - fn snapshot(&mut self) -> U256 { - let id = self.snapshots.insert(self.inner.clone()); - trace!(target: "backend::memdb", "Created new snapshot {}", id); + fn snapshot_state(&mut self) -> U256 { + let id = self.state_snapshots.insert(self.inner.clone()); + trace!(target: "backend::memdb", "Created new state snapshot {}", id); id } - fn revert(&mut self, id: U256) -> bool { - if let Some(snapshot) = self.snapshots.remove(id) { - self.inner = snapshot; - trace!(target: "backend::memdb", "Reverted snapshot {}", id); + fn revert_state(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { + if let Some(state_snapshot) = self.state_snapshots.remove(id) { + if action.is_keep() { + self.state_snapshots.insert_at(state_snapshot.clone(), id); + } + self.inner = state_snapshot; + trace!(target: "backend::memdb", "Reverted state snapshot {}", id); true } else { - warn!(target: "backend::memdb", "No snapshot to revert for {}", id); + warn!(target: "backend::memdb", "No state snapshot to revert for {}", id); false } } - fn maybe_state_root(&self) -> Option { - Some(state_merkle_trie_root(&self.inner.accounts)) + fn maybe_state_root(&self) -> Option { + Some(state_root(&self.inner.accounts)) } fn current_state(&self) -> StateDb { - StateDb::new(MemDb { inner: self.inner.clone(), ..Default::default() }) + StateDb::new(Self { inner: self.inner.clone(), ..Default::default() }) } } -impl MaybeHashDatabase for MemDb { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, H256)> { - Some(trie_hash_db(&self.inner.accounts)) +impl MaybeFullDatabase for MemDb { + fn as_dyn(&self) -> &dyn DatabaseRef { + self } - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, H256)> { - if let Some(acc) = self.inner.accounts.get(&h160_to_b160(addr)) { - Some(storage_trie_db(&acc.storage)) - } else { - Some(storage_trie_db(&Default::default())) - } + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.inner.accounts) + } + + fn clear_into_state_snapshot(&mut self) -> StateSnapshot { + self.inner.clear_into_state_snapshot() } - fn clear_into_snapshot(&mut self) -> StateSnapshot { - self.inner.clear_into_snapshot() + fn read_as_state_snapshot(&self) -> StateSnapshot { + self.inner.read_as_state_snapshot() } fn clear(&mut self) { self.inner.clear(); } - fn init_from_snapshot(&mut self, snapshot: StateSnapshot) { - self.inner.init_from_snapshot(snapshot) + fn init_from_state_snapshot(&mut self, snapshot: StateSnapshot) { + self.inner.init_from_state_snapshot(snapshot) + } +} + +impl MaybeForkedDatabase for MemDb { + fn maybe_reset(&mut self, _url: Option, _block_number: BlockId) -> Result<(), String> { + Err("not supported".to_string()) + } + + fn maybe_flush_cache(&self) -> Result<(), String> { + Err("not supported".to_string()) + } + + fn maybe_inner(&self) -> Result<&BlockchainDb, String> { + Err("not supported".to_string()) } } #[cfg(test)] mod tests { - use crate::{ - eth::backend::db::{Db, SerializableAccountRecord, SerializableState}, - revm::primitives::AccountInfo, - Address, - }; - use bytes::Bytes; - use ethers::types::U256; - use foundry_evm::{ - executor::{backend::MemDb, DatabaseRef}, - revm::primitives::{Bytecode, KECCAK_EMPTY, U256 as rU256}, - }; + use super::*; + use alloy_primitives::Bytes; + use foundry_evm::revm::primitives::{Bytecode, KECCAK_EMPTY}; use std::{collections::BTreeMap, str::FromStr}; - // verifies that all substantial aspects of a loaded account remain the state after an account + // verifies that all substantial aspects of a loaded account remain the same after an account // is dumped and reloaded #[test] fn test_dump_reload_cycle() { @@ -135,36 +156,36 @@ mod tests { let mut dump_db = MemDb::default(); - let contract_code: Bytecode = - Bytecode::new_raw(Bytes::from("fake contract code")).to_checked(); - + let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")); dump_db.insert_account( test_addr, AccountInfo { - balance: rU256::from(123456), + balance: U256::from(123456), code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, }, ); + dump_db + .set_storage_at(test_addr, U256::from(1234567).into(), U256::from(1).into()) + .unwrap(); - dump_db.set_storage_at(test_addr, "0x1234567".into(), "0x1".into()).unwrap(); - - let state = dump_db.dump_state().unwrap().unwrap(); + // blocks dumping/loading tested in storage.rs + let state = dump_db + .dump_state(Default::default(), U64::ZERO, Vec::new(), Vec::new(), Default::default()) + .unwrap() + .unwrap(); let mut load_db = MemDb::default(); load_db.load_state(state).unwrap(); - let loaded_account = load_db.basic(test_addr.into()).unwrap().unwrap(); + let loaded_account = load_db.basic_ref(test_addr).unwrap().unwrap(); - assert_eq!(loaded_account.balance, rU256::from(123456)); - assert_eq!(load_db.code_by_hash(loaded_account.code_hash).unwrap(), contract_code); + assert_eq!(loaded_account.balance, U256::from(123456)); + assert_eq!(load_db.code_by_hash_ref(loaded_account.code_hash).unwrap(), contract_code); assert_eq!(loaded_account.nonce, 1234); - assert_eq!( - load_db.storage(test_addr.into(), Into::::into("0x1234567").into()).unwrap(), - Into::::into("0x1").into() - ); + assert_eq!(load_db.storage_ref(test_addr, U256::from(1234567)).unwrap(), U256::from(1)); } // verifies that multiple accounts can be loaded at a time, and storage is merged within those @@ -176,23 +197,22 @@ mod tests { let test_addr2: Address = Address::from_str("0x70997970c51812dc3a010c7d01b50e0d17dc79c8").unwrap(); - let contract_code: Bytecode = - Bytecode::new_raw(Bytes::from("fake contract code")).to_checked(); + let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")); let mut db = MemDb::default(); db.insert_account( test_addr, AccountInfo { - balance: rU256::from(123456), + balance: U256::from(123456), code_hash: KECCAK_EMPTY, code: Some(contract_code.clone()), nonce: 1234, }, ); - db.set_storage_at(test_addr, "0x1234567".into(), "0x1".into()).unwrap(); - db.set_storage_at(test_addr, "0x1234568".into(), "0x2".into()).unwrap(); + db.set_storage_at(test_addr, U256::from(1234567).into(), U256::from(1).into()).unwrap(); + db.set_storage_at(test_addr, U256::from(1234568).into(), U256::from(2).into()).unwrap(); let mut new_state = SerializableState::default(); @@ -207,12 +227,12 @@ mod tests { ); let mut new_storage = BTreeMap::default(); - new_storage.insert("0x1234568".into(), "0x5".into()); + new_storage.insert(U256::from(1234568).into(), U256::from(5).into()); new_state.accounts.insert( test_addr, SerializableAccountRecord { - balance: 100100.into(), + balance: U256::from(100100), code: contract_code.bytes()[..contract_code.len()].to_vec().into(), nonce: 100, storage: new_storage, @@ -221,21 +241,15 @@ mod tests { db.load_state(new_state).unwrap(); - let loaded_account = db.basic(test_addr.into()).unwrap().unwrap(); - let loaded_account2 = db.basic(test_addr2.into()).unwrap().unwrap(); + let loaded_account = db.basic_ref(test_addr).unwrap().unwrap(); + let loaded_account2 = db.basic_ref(test_addr2).unwrap().unwrap(); assert_eq!(loaded_account2.nonce, 1); - assert_eq!(loaded_account.balance, rU256::from(100100)); - assert_eq!(db.code_by_hash(loaded_account.code_hash).unwrap(), contract_code); + assert_eq!(loaded_account.balance, U256::from(100100)); + assert_eq!(db.code_by_hash_ref(loaded_account.code_hash).unwrap(), contract_code); assert_eq!(loaded_account.nonce, 1234); - assert_eq!( - db.storage(test_addr.into(), Into::::into("0x1234567").into()).unwrap(), - Into::::into("0x1").into() - ); - assert_eq!( - db.storage(test_addr.into(), Into::::into("0x1234568").into()).unwrap(), - Into::::into("0x5").into() - ); + assert_eq!(db.storage_ref(test_addr, U256::from(1234567)).unwrap(), U256::from(1)); + assert_eq!(db.storage_ref(test_addr, U256::from(1234568)).unwrap(), U256::from(5)); } } diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 805f01e3c3bc1..e590d57e35bbb 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,208 +1,175 @@ //! Anvil specific [`revm::Inspector`] implementation -use crate::{eth::macros::node_info, revm::Database}; -use bytes::Bytes; -use ethers::types::Log; +use crate::revm::Database; +use alloy_primitives::{Address, Log}; use foundry_evm::{ call_inspectors, decode::decode_console_logs, - executor::inspector::{LogCollector, Tracer}, - revm, + inspectors::{LogCollector, TracingInspector}, revm::{ - inspectors::GasInspector, - interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::{B160, B256}, - EVMData, + interpreter::{ + CallInputs, CallOutcome, CreateInputs, CreateOutcome, EOFCreateInputs, Interpreter, + }, + primitives::U256, + EvmContext, }, + traces::TracingInspectorConfig, }; -use std::{cell::RefCell, rc::Rc}; /// The [`revm::Inspector`] used when transacting in the evm -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Inspector { - pub gas: Option>>, - pub tracer: Option, + pub tracer: Option, /// collects all `console.sol` logs - pub log_collector: LogCollector, + pub log_collector: Option, } -// === impl Inspector === - impl Inspector { /// Called after the inspecting the evm /// /// 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`] pub fn with_tracing(mut self) -> Self { - self.tracer = Some(Default::default()); + self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().set_steps(false))); self } - /// Enables steps recording for `Tracer` and attaches `GasInspector` to it - /// If `Tracer` wasn't configured before, configures it automatically + pub fn with_tracing_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 { - if self.tracer.is_none() { - self = self.with_tracing() - } - let gas_inspector = Rc::new(RefCell::new(GasInspector::default())); - self.gas = Some(gas_inspector.clone()); - self.tracer = self.tracer.map(|tracer| tracer.with_steps_recording(gas_inspector)); + self.tracer = Some(TracingInspector::new(TracingInspectorConfig::all().with_state_diffs())); + 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 { - fn initialize_interp( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> InstructionResult { - call_inspectors!( - inspector, - [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], - { inspector.initialize_interp(interp, data, is_static) } - ); - InstructionResult::Continue + fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + call_inspectors!([&mut self.tracer], |inspector| { + inspector.initialize_interp(interp, ecx); + }); } - fn step( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> InstructionResult { - call_inspectors!( - inspector, - [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], - { - inspector.step(interp, data, is_static); - } - ); - InstructionResult::Continue + fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + call_inspectors!([&mut self.tracer], |inspector| { + inspector.step(interp, ecx); + }); } - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &B160, - topics: &[B256], - data: &Bytes, - ) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.tracer, - Some(&mut self.log_collector) - ], - { - inspector.log(evm_data, address, topics, data); - } - ); + fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + call_inspectors!([&mut self.tracer], |inspector| { + inspector.step_end(interp, ecx); + }); } - fn step_end( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - eval: InstructionResult, - ) -> InstructionResult { - call_inspectors!( - inspector, - [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], - { - inspector.step_end(interp, data, is_static, eval); - } - ); - eval + 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); + }); } - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.tracer, - Some(&mut self.log_collector) - ], - { - inspector.call(data, call, is_static); - } + #[ret] + [&mut self.tracer, &mut self.log_collector], + |inspector| inspector.call(ecx, inputs).map(Some), ); - - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } fn call_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( - inspector, - [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], - { - inspector.call_end(data, inputs, remaining_gas, ret, out.clone(), is_static); - } - ); - (ret, remaining_gas, out) + outcome: CallOutcome, + ) -> CallOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.call_end(ecx, inputs, outcome); + } + + outcome } fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option, Gas, Bytes) { - call_inspectors!( - inspector, - [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], - { - inspector.create(data, call); + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> Option { + if let Some(tracer) = &mut self.tracer { + if let Some(out) = tracer.create(ecx, inputs) { + return Some(out); } - ); - - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + } + None } fn create_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, inputs: &CreateInputs, - status: InstructionResult, - address: Option, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option, Gas, Bytes) { - call_inspectors!( - inspector, - [&mut self.gas.as_deref().map(|gas| gas.borrow_mut()), &mut self.tracer], - { - inspector.create_end(data, inputs, status, address, gas, retdata.clone()); + outcome: CreateOutcome, + ) -> CreateOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.create_end(ecx, inputs, outcome); + } + + 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); } - ); - (status, address, gas, retdata) + } + 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 { + revm::Inspector::::selfdestruct(tracer, contract, target, value); + } } } /// Prints all the logs 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 98ba22c2eba25..f6cb5e993ef16 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,95 +1,113 @@ -//! In memory blockchain backend +//! In-memory blockchain backend. + +use self::state::trie_storage; +use super::executor::new_evm_with_inspector_ref; use crate::{ config::PruneStateHistoryConfig, eth::{ backend::{ cheats::CheatsManager, - db::{AsHashDB, Db, MaybeHashDatabase, SerializableState}, + db::{Db, MaybeFullDatabase, SerializableState}, executor::{ExecutedTransactions, TransactionExecutor}, fork::ClientFork, genesis::GenesisConfig, - mem::storage::MinedTransactionReceipt, + mem::{ + state::{storage_root, trie_accounts}, + storage::MinedTransactionReceipt, + }, notifications::{NewBlockNotification, NewBlockNotifications}, time::{utc_from_secs, TimeManager}, validate::TransactionValidator, }, error::{BlockchainError, ErrDetail, InvalidTransactionError}, - fees::{FeeDetails, FeeManager}, + fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, pool::transactions::PoolTransaction, util::get_precompiles_for, }, + inject_precompiles, mem::{ inspector::Inspector, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, - revm::{ - db::DatabaseRef, - primitives::{AccountInfo, U256 as rU256}, - }, + revm::{db::DatabaseRef, primitives::AccountInfo}, + ForkChoice, NodeConfig, PrecompileFactory, }; -use anvil_core::{ - eth::{ - block::{Block, BlockInfo, Header}, - proof::{AccountProof, BasicAccount, StorageProof}, - receipt::{EIP658Receipt, TypedReceipt}, - state::StateOverride, - transaction::{ - EthTransactionRequest, MaybeImpersonatedTransaction, PendingTransaction, - TransactionInfo, TypedTransaction, +use alloy_chains::NamedChain; +use alloy_consensus::{ + Account, Header, Receipt, ReceiptWithBloom, Signed, Transaction as TransactionTrait, TxEnvelope, +}; +use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; +use alloy_network::{ + AnyHeader, AnyRpcBlock, AnyRpcTransaction, AnyTxEnvelope, AnyTxType, EthereumWallet, + UnknownTxEnvelope, UnknownTypedTransaction, +}; +use alloy_primitives::{ + address, hex, keccak256, utils::Unit, Address, Bytes, TxHash, TxKind, B256, U256, U64, +}; +use alloy_rpc_types::{ + anvil::Forking, + request::TransactionRequest, + serde_helpers::JsonStorageKey, + state::StateOverride, + trace::{ + filter::TraceFilter, + geth::{ + GethDebugBuiltInTracerType, GethDebugTracerType, GethDebugTracingCallOptions, + GethDebugTracingOptions, GethTrace, NoopFrame, }, - trie::RefTrieDB, - utils::to_revm_access_list, + parity::LocalizedTransactionTrace, }, - types::{Forking, Index}, + AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, + FilteredParams, Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, }; -use anvil_rpc::error::RpcError; -use ethers::{ - abi::ethereum_types::BigEndianHash, - prelude::{BlockNumber, GethTraceFrame, TxHash, H256, U256, U64}, - types::{ - transaction::eip2930::AccessList, Address, Block as EthersBlock, BlockId, Bytes, - DefaultFrame, Filter, FilteredParams, GethDebugTracingOptions, GethTrace, Log, OtherFields, - Trace, Transaction, TransactionReceipt, H160, +use alloy_serde::{OtherFields, WithOtherFields}; +use alloy_signer_local::PrivateKeySigner; +use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; +use anvil_core::eth::{ + block::{Block, BlockInfo}, + transaction::{ + optimism::DepositTransaction, DepositReceipt, MaybeImpersonatedTransaction, + PendingTransaction, ReceiptResponse, TransactionInfo, TypedReceipt, TypedTransaction, }, - utils::{get_contract_address, hex, keccak256, rlp}, + wallet::{Capabilities, DelegationCapability, WalletCapabilities}, }; +use anvil_rpc::error::RpcError; +use chrono::Datelike; +use eyre::{Context, Result}; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use foundry_evm::{ - decode::{decode_custom_error_args, decode_revert}, - executor::{ - backend::{DatabaseError, DatabaseResult}, - inspector::AccessListTracer, - }, + backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, + constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, + decode::RevertDecoder, + inspectors::AccessListInspector, revm::{ - self, db::CacheDB, - interpreter::{return_ok, InstructionResult}, + interpreter::InstructionResult, primitives::{ - Account, BlockEnv, CreateScheme, EVMError, Env, ExecutionResult, Output, SpecId, - TransactTo, TxEnv, KECCAK_EMPTY, + BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, Output, SpecId, + TxEnv, KECCAK_EMPTY, }, }, - utils::{ - eval_to_instruction_result, h256_to_b256, halt_to_instruction_result, ru256_to_u256, - u256_to_h256_be, u256_to_ru256, - }, + traces::TracingInspectorConfig, }; use futures::channel::mpsc::{unbounded, UnboundedSender}; -use hash_db::HashDB; +use op_alloy_consensus::{TxDeposit, DEPOSIT_TX_TYPE_ID}; use parking_lot::{Mutex, RwLock}; +use revm::{ + db::WrapDatabaseRef, + primitives::{BlobExcessGasAndPrice, HashMap, OptimismFields, ResultAndState}, +}; use std::{ - collections::HashMap, + collections::BTreeMap, io::{Read, Write}, - ops::Deref, + path::PathBuf, sync::Arc, time::Duration, }; -use storage::{Blockchain, MinedTransaction}; +use storage::{Blockchain, MinedTransaction, DEFAULT_HISTORY_LIMIT}; use tokio::sync::RwLock as AsyncRwLock; -use tracing::{trace, warn}; -use trie_db::{Recorder, Trie}; - pub mod cache; pub mod fork_db; pub mod in_memory_db; @@ -98,24 +116,35 @@ pub mod state; pub mod storage; // Gas per transaction not creating a contract. -pub const MIN_TRANSACTION_GAS: U256 = U256([21_000, 0, 0, 0]); +pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. -pub const MIN_CREATE_GAS: U256 = U256([53_000, 0, 0, 0]); - -pub type State = foundry_evm::HashMap; +pub const MIN_CREATE_GAS: u128 = 53000; +// Executor +pub const EXECUTOR: Address = address!("6634F723546eCc92277e8a2F93d4f248bf1189ea"); +pub const EXECUTOR_PK: &str = "0x502d47e1421cb9abef497096728e69f07543232b93ef24de4998e18b5fd9ba0f"; +// P256 Batch Delegation Contract: https://odyssey-explorer.ithaca.xyz/address/0x35202a6E6317F3CC3a177EeEE562D3BcDA4a6FcC +pub const P256_DELEGATION_CONTRACT: Address = address!("35202a6e6317f3cc3a177eeee562d3bcda4a6fcc"); +// Runtime code of the P256 delegation contract +pub const P256_DELEGATION_RUNTIME_CODE: &[u8] = &hex!("60806040526004361015610018575b361561001657005b005b5f3560e01c806309c5eabe146100c75780630cb6aaf1146100c257806330f6a8e5146100bd5780635fce1927146100b8578063641cdfe2146100b357806376ba882d146100ae5780638d80ff0a146100a9578063972ce4bc146100a4578063a78fc2441461009f578063a82e44e01461009a5763b34893910361000e576108e1565b6108b5565b610786565b610646565b6105ba565b610529565b6103f8565b6103a2565b61034c565b6102c0565b61020b565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176100fc57604052565b6100cc565b6080810190811067ffffffffffffffff8211176100fc57604052565b60a0810190811067ffffffffffffffff8211176100fc57604052565b90601f8019910116810190811067ffffffffffffffff8211176100fc57604052565b6040519061016a608083610139565b565b67ffffffffffffffff81116100fc57601f01601f191660200190565b9291926101948261016c565b916101a26040519384610139565b8294818452818301116101be578281602093845f960137010152565b5f80fd5b9080601f830112156101be578160206101dd93359101610188565b90565b60206003198201126101be576004359067ffffffffffffffff82116101be576101dd916004016101c2565b346101be57610219366101e0565b3033036102295761001690610ae6565b636f6a1b8760e11b5f5260045ffd5b634e487b7160e01b5f52603260045260245ffd5b5f54811015610284575f8080526005919091027f290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e5630191565b610238565b8054821015610284575f52600560205f20910201905f90565b906040516102af816100e0565b602060018294805484520154910152565b346101be5760203660031901126101be576004355f548110156101be576102e69061024c565b5060ff815416600182015491610306600360ff60028401541692016102a2565b926040519215158352602083015260028110156103385760a09260209160408401528051606084015201516080820152f35b634e487b7160e01b5f52602160045260245ffd5b346101be575f3660031901126101be576020600254604051908152f35b6004359063ffffffff821682036101be57565b6064359063ffffffff821682036101be57565b6084359063ffffffff821682036101be57565b346101be5760203660031901126101be576103bb610369565b303303610229576103cb9061024c565b50805460ff19169055005b60609060231901126101be57602490565b60609060831901126101be57608490565b346101be5760803660031901126101be57610411610369565b60205f61041d366103d6565b60015461043161042c82610a0b565b600155565b60405184810191825260e086901b6001600160e01b031916602083015261046581602484015b03601f198101835282610139565b51902060ff61047660408401610a19565b161583146104fe576104b2601b925b85813591013590604051948594859094939260ff6060936080840197845216602083015260408201520152565b838052039060015afa156104f9575f51306001600160a01b03909116036104ea576104df6100169161024c565b50805460ff19169055565b638baa579f60e01b5f5260045ffd5b610a27565b6104b2601c92610485565b60409060031901126101be57600490565b6044359060028210156101be57565b346101be5760803660031901126101be5761054336610509565b61054b61051a565b606435903033036102295761059192610580610587926040519461056e86610101565b60018652602086015260408501610a32565b36906105f3565b6060820152610a3e565b5f545f1981019081116105b55760405163ffffffff919091168152602090f35b0390f35b6109f7565b6100166105c6366101e0565b610ae6565b60409060231901126101be57604051906105e4826100e0565b60243582526044356020830152565b91908260409103126101be5760405161060b816100e0565b6020808294803584520135910152565b6084359081151582036101be57565b60a4359081151582036101be57565b359081151582036101be57565b346101be5760a03660031901126101be5760043567ffffffffffffffff81116101be576106779036906004016101c2565b610680366105cb565b61068861037c565b61069061061b565b906002546106a56106a082610a0b565b600255565b6040516106bb8161045788602083019586610b6a565b51902091610747575b6106d06106d69161024c565b50610b7b565b906106e86106e48351151590565b1590565b610738576020820151801515908161072e575b5061071f576107129260606106e493015191610ce3565b6104ea5761001690610ae6565b632572e3a960e01b5f5260045ffd5b905042115f6106fb565b637dd286d760e11b5f5260045ffd5b905f61077361045761076760209460405192839187830160209181520190565b60405191828092610b58565b039060025afa156104f9575f51906106c4565b346101be5760e03660031901126101be576107a036610509565b6107a861051a565b6064359060205f6107b8366103e7565b6001546107c761042c82610a0b565b60408051808601928352883560208401528589013591830191909152606082018790526107f78160808401610457565b51902060ff61080860408401610a19565b161583146108aa5760408051918252601b602083015282359082015290830135606082015280608081015b838052039060015afa156104f9575f51306001600160a01b03909116036104ea5761087a926105806105879261086761015b565b6001815294602086015260408501610a32565b6105b161089361088a5f54610ad8565b63ffffffff1690565b60405163ffffffff90911681529081906020820190565b610833601c92610485565b346101be575f3660031901126101be576020600154604051908152f35b359061ffff821682036101be57565b346101be5760c03660031901126101be5760043567ffffffffffffffff81116101be576109129036906004016101c2565b61091b366105cb565b906064359167ffffffffffffffff83116101be5760a060031984360301126101be576040516109498161011d565b836004013567ffffffffffffffff81116101be5761096d90600436918701016101c2565b8152602484013567ffffffffffffffff81116101be57840193366023860112156101be5760846109db916109ae610016973690602460048201359101610188565b60208501526109bf604482016108d2565b60408501526109d0606482016108d2565b606085015201610639565b60808201526109e861038f565b916109f161062a565b93610bc3565b634e487b7160e01b5f52601160045260245ffd5b5f1981146105b55760010190565b3560ff811681036101be5790565b6040513d5f823e3d90fd5b60028210156103385752565b5f54680100000000000000008110156100fc57806001610a6192015f555f610289565b610ac557610a7e82511515829060ff801983541691151516179055565b6020820151600182015560028101604083015160028110156103385761016a9360039260609260ff8019835416911617905501519101906020600191805184550151910155565b634e487b7160e01b5f525f60045260245ffd5b5f198101919082116105b557565b80519060205b828110610af857505050565b808201805160f81c600182015160601c91601581015160358201519384915f9493845f14610b4257505050506001146101be575b15610b3a5701605501610aec565b3d5f803e3d5ffd5b5f95508594506055019130811502175af1610b2c565b805191908290602001825e015f815290565b6020906101dd939281520190610b58565b90604051610b8881610101565b6060610bbe6003839560ff8154161515855260018101546020860152610bb860ff60028301541660408701610a32565b016102a2565b910152565b93909192600254610bd66106a082610a0b565b604051610bec8161045789602083019586610b6a565b51902091610c50575b6106d0610c019161024c565b91610c0f6106e48451151590565b6107385760208301518015159081610c46575b5061071f57610c399360606106e494015192610e0d565b6104ea5761016a90610ae6565b905042115f610c22565b905f610c7061045761076760209460405192839187830160209181520190565b039060025afa156104f9575f5190610bf5565b3d15610cad573d90610c948261016c565b91610ca26040519384610139565b82523d5f602084013e565b606090565b8051601f101561028457603f0190565b8051602010156102845760400190565b908151811015610284570160200190565b5f9291839260208251920151906020815191015191604051936020850195865260408501526060840152608083015260a082015260a08152610d2660c082610139565b519060145afa610d34610c83565b81610d74575b81610d43575090565b600160f81b91506001600160f81b031990610d6f90610d6190610cb2565b516001600160f81b03191690565b161490565b80516020149150610d3a565b60405190610d8f604083610139565b6015825274113a3cb832911d113bb2b130baba34371733b2ba1160591b6020830152565b9061016a6001610de3936040519485916c1131b430b63632b733b2911d1160991b6020840152602d830190610b58565b601160f91b815203601e19810185520183610139565b610e069060209392610b58565b9081520190565b92919281516025815110908115610f0a575b50610ef957610e2c610d80565b90610e596106e460208501938451610e53610e4c606089015161ffff1690565b61ffff1690565b91610f9b565b610f01576106e4610e8d610e88610457610e83610ea1956040519283916020830160209181520190565b611012565b610db3565b8351610e53610e4c604088015161ffff1690565b610ef9575f610eb96020925160405191828092610b58565b039060025afa156104f9575f610ee360209261076783519151610457604051938492888401610df9565b039060025afa156104f9576101dd915f51610ce3565b505050505f90565b50505050505f90565b610f2b9150610f1e610d616106e492610cc2565b6080850151151590610f31565b5f610e1f565b906001600160f81b0319600160f81b831601610f955780610f85575b610f8057601f60fb1b600160fb1b821601610f69575b50600190565b600160fc1b90811614610f7c575f610f63565b5f90565b505f90565b50600160fa1b8181161415610f4d565b50505f90565b80519282515f5b858110610fb457505050505050600190565b8083018084116105b5578281101561100757610fe56001600160f81b0319610fdc8488610cd2565b51169187610cd2565b516001600160f81b03191603610ffd57600101610fa2565b5050505050505f90565b505050505050505f90565b80516060929181611021575050565b9092506003600284010460021b604051937f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f527f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f603f52602085019282860191602083019460208284010190600460038351955f85525b0191603f8351818160121c16515f538181600c1c1651600153818160061c165160025316516003535f5181520190878210156110db5760049060039061109a565b5095505f93600393604092520160405206600204809303613d3d60f01b81525203825256fea26469706673582212200ba93b78f286a25ece47e9403c47be9862f9b8b70ba1a95098667b90c47308b064736f6c634300081a0033"); +// Experimental ERC20 +pub const EXP_ERC20_CONTRACT: Address = address!("238c8CD93ee9F8c7Edf395548eF60c0d2e46665E"); +// Runtime code of the experimental ERC20 contract +pub const EXP_ERC20_RUNTIME_CODE: &[u8] = &hex!("60806040526004361015610010575b005b5f3560e01c806306fdde03146106f7578063095ea7b31461068c57806318160ddd1461066757806323b872dd146105a15780632bb7c5951461050e578063313ce567146104f35780633644e5151461045557806340c10f191461043057806370a08231146103fe5780637ecebe00146103cc57806395d89b4114610366578063a9059cbb146102ea578063ad0c8fdd146102ad578063d505accf146100fb5763dd62ed3e0361000e57346100f75760403660031901126100f7576100d261075c565b6100da610772565b602052637f5e9f20600c525f5260206034600c2054604051908152f35b5f80fd5b346100f75760e03660031901126100f75761011461075c565b61011c610772565b6084359160643560443560ff851685036100f757610138610788565b60208101906e04578706572696d656e74455243323608c1b8252519020908242116102a0576040519360018060a01b03169460018060a01b03169565383775081901600e52855f5260c06020600c20958654957f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252602082019586528660408301967fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc688528b6060850198468a528c608087019330855260a08820602e527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9885252528688525260a082015220604e526042602c205f5260ff1660205260a43560405260c43560605260208060805f60015afa93853d5103610293577f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92594602094019055856303faf4f960a51b176040526034602c2055a3005b63ddafbaef5f526004601cfd5b631a15a3cc5f526004601cfd5b5f3660031901126100f7576103e834023481046103e814341517156102d65761000e90336107ac565b634e487b7160e01b5f52601160045260245ffd5b346100f75760403660031901126100f75761030361075c565b602435906387a211a2600c52335f526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c335f51602061080d5f395f51905f52602080a3602060405160018152f35b63f4d678b85f526004601cfd5b346100f7575f3660031901126100f757604051604081019080821067ffffffffffffffff8311176103b8576103b491604052600381526204558560ec1b602082015260405191829182610732565b0390f35b634e487b7160e01b5f52604160045260245ffd5b346100f75760203660031901126100f7576103e561075c565b6338377508600c525f52602080600c2054604051908152f35b346100f75760203660031901126100f75761041761075c565b6387a211a2600c525f52602080600c2054604051908152f35b346100f75760403660031901126100f75761000e61044c61075c565b602435906107ac565b346100f7575f3660031901126100f757602060a0610471610788565b828101906e04578706572696d656e74455243323608c1b8252519020604051907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8252838201527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6604082015246606082015230608082015220604051908152f35b346100f7575f3660031901126100f757602060405160128152f35b346100f75760203660031901126100f7576004356387a211a2600c52335f526020600c2090815490818111610359575f80806103e88487839688039055806805345cdf77eb68f44c54036805345cdf77eb68f44c5580835282335f51602061080d5f395f51905f52602083a304818115610598575b3390f11561058d57005b6040513d5f823e3d90fd5b506108fc610583565b346100f75760603660031901126100f7576105ba61075c565b6105c2610772565b604435908260601b33602052637f5e9f208117600c526034600c20908154918219610643575b506387a211a2915017600c526020600c2080548084116103595783900390555f526020600c20818154019055602052600c5160601c9060018060a01b03165f51602061080d5f395f51905f52602080a3602060405160018152f35b82851161065a57846387a211a293039055856105e8565b6313be252b5f526004601cfd5b346100f7575f3660031901126100f75760206805345cdf77eb68f44c54604051908152f35b346100f75760403660031901126100f7576106a561075c565b60243590602052637f5e9f20600c52335f52806034600c20555f52602c5160601c337f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560205fa3602060405160018152f35b346100f7575f3660031901126100f7576103b4610712610788565b6e04578706572696d656e74455243323608c1b6020820152604051918291825b602060409281835280519182918282860152018484015e5f828201840152601f01601f1916010190565b600435906001600160a01b03821682036100f757565b602435906001600160a01b03821682036100f757565b604051906040820182811067ffffffffffffffff8211176103b857604052600f8252565b6805345cdf77eb68f44c548281019081106107ff576805345cdf77eb68f44c556387a211a2600c525f526020600c20818154019055602052600c5160601c5f5f51602061080d5f395f51905f52602080a3565b63e5cfe9575f526004601cfdfeddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220fbe302881d9891005ba1448ba48547cc1cb17dea1a5c4011dfcb035de325bb1d64736f6c634300081b0033"); + +pub type State = foundry_evm::utils::StateChangeset; /// A block request, which includes the Pool Transactions if it's Pending #[derive(Debug)] pub enum BlockRequest { Pending(Vec>), - Number(U64), + Number(u64), } impl BlockRequest { pub fn block_number(&self) -> BlockNumber { - match self { - BlockRequest::Pending(_) => BlockNumber::Pending, - BlockRequest::Number(n) => BlockNumber::Number(*n), + match *self { + Self::Pending(_) => BlockNumber::Pending, + Self::Number(n) => BlockNumber::Number(n), } } } @@ -129,7 +158,7 @@ pub struct Backend { /// the evm during its execution. /// /// At time of writing, there are two different types of `Db`: - /// - [`MemDb`](crate::mem::MemDb): everything is stored in memory + /// - [`MemDb`](crate::mem::in_memory_db::MemDb): everything is stored in memory /// - [`ForkDb`](crate::mem::fork_db::ForkedDatabase): forks off a remote client, missing /// data is retrieved via RPC-calls /// @@ -138,77 +167,149 @@ pub struct Backend { /// which the write-lock is active depends on whether the `ForkDb` can provide all requested /// data from memory or whether it has to retrieve it via RPC calls first. This means that it /// potentially blocks for some time, even taking into account the rate limits of RPC - /// endpoints. Therefor the `Db` is guarded by a `tokio::sync::RwLock` here so calls that + /// endpoints. Therefore the `Db` is guarded by a `tokio::sync::RwLock` here so calls that /// need to read from it, while it's currently written to, don't block. E.g. a new block is - /// currently mined and a new [`Self::set_storage()`] request is being executed. - db: Arc>, - /// stores all block related data in memory + /// currently mined and a new [`Self::set_storage_at()`] request is being executed. + db: Arc>>, + /// stores all block related data in memory. blockchain: Blockchain, - /// Historic states of previous blocks + /// Historic states of previous blocks. states: Arc>, - /// env data of the chain - env: Arc>, - /// this is set if this is currently forked off another client - fork: Option, - /// provides time related info, like timestamp + /// Env data of the chain + env: Arc>, + /// This is set if this is currently forked off another client. + fork: Arc>>, + /// Provides time related info, like timestamp. time: TimeManager, - /// Contains state of custom overrides + /// Contains state of custom overrides. cheats: CheatsManager, - /// contains fee data + /// Contains fee data. fees: FeeManager, - /// initialised genesis + /// Initialised genesis. genesis: GenesisConfig, - /// listeners for new blocks that get notified when a new block was imported + /// Listeners for new blocks that get notified when a new block was imported. new_block_listeners: Arc>>>, - /// keeps track of active snapshots at a specific block - active_snapshots: Arc>>, + /// Keeps track of active state snapshots at a specific block. + active_state_snapshots: Arc>>, enable_steps_tracing: bool, + print_logs: bool, + odyssey: bool, /// How to keep history state prune_state_history_config: PruneStateHistoryConfig, /// max number of blocks with transactions in memory transaction_block_keeper: Option, + node_config: Arc>, + /// Slots in an epoch + slots_in_an_epoch: u64, + /// Precompiles to inject to the EVM. + precompile_factory: Option>, + /// Prevent race conditions during mining + mining: Arc>, + // === wallet === // + capabilities: Arc>, + executor_wallet: Arc>>, } impl Backend { /// Initialises the balance of the given accounts #[allow(clippy::too_many_arguments)] pub async fn with_genesis( - db: Arc>, - env: Arc>, + db: Arc>>, + env: Arc>, genesis: GenesisConfig, fees: FeeManager, - fork: Option, + fork: Arc>>, enable_steps_tracing: bool, + print_logs: bool, + odyssey: bool, prune_state_history_config: PruneStateHistoryConfig, + max_persisted_states: Option, transaction_block_keeper: Option, automine_block_time: Option, - ) -> Self { + cache_path: Option, + node_config: Arc>, + ) -> Result { // if this is a fork then adjust the blockchain storage - let blockchain = if let Some(ref fork) = fork { + let blockchain = if let Some(fork) = fork.read().as_ref() { trace!(target: "backend", "using forked blockchain at {}", fork.block_number()); Blockchain::forked(fork.block_number(), fork.block_hash(), fork.total_difficulty()) } else { + let env = env.read(); Blockchain::new( - &env.read(), + &env, + env.handler_cfg.spec_id, fees.is_eip1559().then(|| fees.base_fee()), genesis.timestamp, ) }; - let start_timestamp = - if let Some(fork) = fork.as_ref() { fork.timestamp() } else { genesis.timestamp }; + let start_timestamp = if let Some(fork) = fork.read().as_ref() { + fork.timestamp() + } else { + genesis.timestamp + }; - let states = if prune_state_history_config.is_config_enabled() { + let mut states = if prune_state_history_config.is_config_enabled() { // if prune state history is enabled, configure the state cache only for memory prune_state_history_config .max_memory_history - .map(InMemoryBlockStates::new) + .map(|limit| InMemoryBlockStates::new(limit, 0)) .unwrap_or_default() .memory_only() + } else if max_persisted_states.is_some() { + max_persisted_states + .map(|limit| InMemoryBlockStates::new(DEFAULT_HISTORY_LIMIT, limit)) + .unwrap_or_default() } else { Default::default() }; + if let Some(cache_path) = cache_path { + states = states.disk_path(cache_path); + } + + let (slots_in_an_epoch, precompile_factory) = { + let cfg = node_config.read().await; + (cfg.slots_in_an_epoch, cfg.precompile_factory.clone()) + }; + + let (capabilities, executor_wallet) = if odyssey { + // Insert account that sponsors the delegated txs. And deploy P256 delegation contract. + let mut db = db.write().await; + + let _ = db.set_code( + P256_DELEGATION_CONTRACT, + Bytes::from_static(P256_DELEGATION_RUNTIME_CODE), + ); + + // Insert EXP ERC20 contract + let _ = db.set_code(EXP_ERC20_CONTRACT, Bytes::from_static(EXP_ERC20_RUNTIME_CODE)); + + let init_balance = Unit::ETHER.wei().saturating_mul(U256::from(10_000)); // 10K ETH + + // Add ETH + let _ = db.set_balance(EXP_ERC20_CONTRACT, init_balance); + let _ = db.set_balance(EXECUTOR, init_balance); + + let mut capabilities = WalletCapabilities::default(); + + let chain_id = env.read().cfg.chain_id; + capabilities.insert( + chain_id, + Capabilities { + delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, + }, + ); + + let signer: PrivateKeySigner = EXECUTOR_PK.parse().unwrap(); + + let executor_wallet = EthereumWallet::new(signer); + + (capabilities, Some(executor_wallet)) + } else { + (WalletCapabilities::default(), None) + }; + let backend = Self { db, blockchain, @@ -220,10 +321,18 @@ impl Backend { new_block_listeners: Default::default(), fees, genesis, - active_snapshots: Arc::new(Mutex::new(Default::default())), + active_state_snapshots: Arc::new(Mutex::new(Default::default())), enable_steps_tracing, + print_logs, + odyssey, prune_state_history_config, transaction_block_keeper, + node_config, + slots_in_an_epoch, + precompile_factory, + mining: Arc::new(tokio::sync::Mutex::new(())), + capabilities: Arc::new(RwLock::new(capabilities)), + executor_wallet: Arc::new(RwLock::new(executor_wallet)), }; if let Some(interval_block_time) = automine_block_time { @@ -231,8 +340,24 @@ impl Backend { } // Note: this can only fail in forking mode, in which case we can't recover - backend.apply_genesis().await.expect("Failed to create genesis"); - backend + backend.apply_genesis().await.wrap_err("failed to create genesis")?; + Ok(backend) + } + + /// Writes the CREATE2 deployer code directly to the database at the address provided. + pub async fn set_create2_deployer(&self, address: Address) -> DatabaseResult<()> { + self.set_code(address, Bytes::from_static(DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE)).await?; + + Ok(()) + } + + /// Get the capabilities of the wallet. + /// + /// Currently the only capability is [`DelegationCapability`]. + /// + /// [`DelegationCapability`]: anvil_core::eth::wallet::DelegationCapability + pub(crate) fn get_capabilities(&self) -> WalletCapabilities { + self.capabilities.read().clone() } /// Updates memory limits that should be more strict when auto-mine is enabled @@ -240,13 +365,38 @@ impl Backend { self.states.write().update_interval_mine_block_time(block_time) } + pub(crate) fn executor_wallet(&self) -> Option { + self.executor_wallet.read().clone() + } + + /// Adds an address to the [`DelegationCapability`] of the wallet. + pub(crate) fn add_capability(&self, address: Address) { + let chain_id = self.env.read().cfg.chain_id; + let mut capabilities = self.capabilities.write(); + let mut capability = capabilities.get(chain_id).cloned().unwrap_or_default(); + capability.delegation.addresses.push(address); + capabilities.insert(chain_id, capability); + } + + pub(crate) fn set_executor(&self, executor_pk: String) -> Result { + let signer: PrivateKeySigner = + executor_pk.parse().map_err(|_| RpcError::invalid_params("Invalid private key"))?; + + let executor = signer.address(); + let wallet = EthereumWallet::new(signer); + + *self.executor_wallet.write() = Some(wallet); + + Ok(executor) + } + /// Applies the configured genesis settings /// /// This will fund, create the genesis accounts - async fn apply_genesis(&self) -> DatabaseResult<()> { + async fn apply_genesis(&self) -> Result<(), DatabaseError> { trace!(target: "backend", "setting genesis balances"); - if self.fork.is_some() { + if self.fork.read().is_some() { // fetch all account first let mut genesis_accounts_futures = Vec::with_capacity(self.genesis.accounts.len()); for address in self.genesis.accounts.iter().copied() { @@ -256,7 +406,7 @@ impl Backend { // accounts concurrently by spawning the job to a new task genesis_accounts_futures.push(tokio::task::spawn(async move { let db = db.read().await; - let info = db.basic(address.into())?.unwrap_or_default(); + let info = db.basic_ref(address)?.unwrap_or_default(); Ok::<_, DatabaseError>((address, info)) })); } @@ -265,25 +415,20 @@ 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??; + 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; for (account, info) in self.genesis.account_infos() { db.insert_account(account, info); } + + // insert the new genesis hash to the database so it's available for the next block in + // the evm + db.insert_block_hash(U256::from(self.best_number()), self.best_hash()); } let db = self.db.write().await; @@ -295,55 +440,82 @@ impl Backend { /// Sets the account to impersonate /// /// Returns `true` if the account is already impersonated - pub async fn impersonate(&self, addr: Address) -> DatabaseResult { - if self.cheats.is_impersonated(addr) { - return Ok(true) + pub fn impersonate(&self, addr: Address) -> bool { + if self.cheats.impersonated_accounts().contains(&addr) { + 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); } /// Returns the configured fork, if any - pub fn get_fork(&self) -> Option<&ClientFork> { - self.fork.as_ref() + pub fn get_fork(&self) -> Option { + self.fork.read().clone() } /// Returns the database - pub fn get_db(&self) -> &Arc> { + pub fn get_db(&self) -> &Arc>> { &self.db } /// Returns the `AccountInfo` from the database pub async fn get_account(&self, address: Address) -> DatabaseResult { - Ok(self.db.read().await.basic(address.into())?.unwrap_or_default()) + Ok(self.db.read().await.basic_ref(address)?.unwrap_or_default()) } /// Whether we're forked off some remote client pub fn is_fork(&self) -> bool { - self.fork.is_some() + self.fork.read().is_some() } pub fn precompiles(&self) -> Vec
{ - get_precompiles_for(self.env().read().cfg.spec_id) + get_precompiles_for(self.env.read().handler_cfg.spec_id) } /// Resets the fork to a fresh state pub async fn reset_fork(&self, forking: Forking) -> Result<(), BlockchainError> { + if !self.is_fork() { + if let Some(eth_rpc_url) = forking.clone().json_rpc_url { + let mut env = self.env.read().clone(); + + let (db, config) = { + let mut node_config = self.node_config.write().await; + + // we want to force the correct base fee for the next block during + // `setup_fork_db_config` + node_config.base_fee.take(); + + node_config.setup_fork_db_config(eth_rpc_url, &mut env, &self.fees).await? + }; + + *self.db.write().await = Box::new(db); + + let fork = ClientFork::new(config, Arc::clone(&self.db)); + + *self.env.write() = env; + *self.fork.write() = Some(fork); + } else { + return Err(RpcError::invalid_params( + "Forking not enabled and RPC URL not provided to start forking", + ) + .into()); + } + } + if let Some(fork) = self.get_fork() { let block_number = forking.block_number.map(BlockNumber::from).unwrap_or(BlockNumber::Latest); @@ -356,27 +528,53 @@ impl Backend { .ok_or(BlockchainError::BlockNotFound)?; // update all settings related to the forked block { - let mut env = self.env.write(); - env.cfg.chain_id = rU256::from(fork.chain_id()); - - env.block = BlockEnv { - number: rU256::from(fork_block_number), - timestamp: fork_block.timestamp.into(), - gas_limit: fork_block.gas_limit.into(), - difficulty: fork_block.difficulty.into(), - prevrandao: fork_block.mix_hash.map(h256_to_b256), - // Keep previous `coinbase` and `basefee` value - coinbase: env.block.coinbase, - basefee: env.block.basefee, - }; + if let Some(fork_url) = forking.json_rpc_url { + // Set the fork block number + let mut node_config = self.node_config.write().await; + node_config.fork_choice = Some(ForkChoice::Block(fork_block_number)); + + let mut env = self.env.read().clone(); + let (forked_db, client_fork_config) = + node_config.setup_fork_db_config(fork_url, &mut env, &self.fees).await?; + + *self.db.write().await = Box::new(forked_db); + let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db)); + *self.fork.write() = Some(fork); + *self.env.write() = env; + } else { + let gas_limit = self.node_config.read().await.fork_gas_limit(&fork_block); + let mut env = self.env.write(); + + env.cfg.chain_id = fork.chain_id(); + env.block = BlockEnv { + number: U256::from(fork_block_number), + timestamp: U256::from(fork_block.header.timestamp), + gas_limit: U256::from(gas_limit), + difficulty: fork_block.header.difficulty, + prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), + // Keep previous `coinbase` and `basefee` value + coinbase: env.block.coinbase, + basefee: env.block.basefee, + ..env.block.clone() + }; - self.time.reset(ru256_to_u256(env.block.timestamp).as_u64()); - self.fees.set_base_fee(env.block.basefee.into()); + // this is the base fee of the current block, but we need the base fee of + // the next block + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + fork_block.header.gas_used as u128, + gas_limit, + fork_block.header.base_fee_per_gas.unwrap_or_default(), + ); + + self.fees.set_base_fee(next_block_base_fee); + } + + // reset the time to the timestamp of the forked block + self.time.reset(fork_block.header.timestamp); // also reset the total difficulty self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); } - // reset storage *self.blockchain.storage.write() = BlockchainStorage::forked( fork.block_number(), @@ -384,23 +582,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 { @@ -424,50 +608,53 @@ impl Backend { } /// The env data of the blockchain - pub fn env(&self) -> &Arc> { + pub fn env(&self) -> &Arc> { &self.env } /// Returns the current best hash of the chain - pub fn best_hash(&self) -> H256 { + pub fn best_hash(&self) -> B256 { self.blockchain.storage.read().best_hash } /// Returns the current best number of the chain - pub fn best_number(&self) -> U64 { - let num: u64 = self.env.read().block.number.try_into().unwrap_or(u64::MAX); - num.into() + pub fn best_number(&self) -> u64 { + self.blockchain.storage.read().best_number.try_into().unwrap_or(u64::MAX) } /// Sets the block number pub fn set_block_number(&self, number: U256) { let mut env = self.env.write(); - env.block.number = number.into(); + env.block.number = number; } /// Returns the client coinbase address. pub fn coinbase(&self) -> Address { - self.env.read().block.coinbase.into() + self.env.read().block.coinbase } /// Returns the client coinbase address. pub fn chain_id(&self) -> U256 { - self.env.read().cfg.chain_id.into() + U256::from(self.env.read().cfg.chain_id) + } + + pub fn set_chain_id(&self, chain_id: u64) { + self.env.write().cfg.chain_id = chain_id; } /// Returns balance of the given account. pub async fn current_balance(&self, address: Address) -> DatabaseResult { - Ok(self.get_account(address).await?.balance.into()) + Ok(self.get_account(address).await?.balance) } /// Returns balance of the given account. - pub async fn current_nonce(&self, address: Address) -> DatabaseResult { - Ok(self.get_account(address).await?.nonce.into()) + pub async fn current_nonce(&self, address: Address) -> DatabaseResult { + Ok(self.get_account(address).await?.nonce) } /// Sets the coinbase address pub fn set_coinbase(&self, address: Address) { - self.env.write().block.coinbase = address.into(); + self.env.write().block.coinbase = address; } /// Sets the nonce of the given address @@ -482,7 +669,7 @@ impl Backend { /// Sets the code of the given address pub async fn set_code(&self, address: Address, code: Bytes) -> DatabaseResult<()> { - self.db.write().await.set_code(address, code) + self.db.write().await.set_code(address, code.0.into()) } /// Sets the value for the given slot of the given address @@ -490,14 +677,14 @@ impl Backend { &self, address: Address, slot: U256, - val: H256, + val: B256, ) -> DatabaseResult<()> { - self.db.write().await.set_storage_at(address, slot, val.into_uint()) + self.db.write().await.set_storage_at(address, slot.into(), val) } /// Returns the configured specid pub fn spec_id(&self) -> SpecId { - self.env.read().cfg.spec_id + self.env.read().handler_cfg.spec_id } /// Returns true for post London @@ -515,10 +702,25 @@ impl Backend { (self.spec_id() as u8) >= (SpecId::BERLIN as u8) } + /// Returns true for post Cancun + pub fn is_eip4844(&self) -> bool { + (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 + } + /// Returns an error if EIP1559 is not active (pre Berlin) pub fn ensure_eip1559_active(&self) -> Result<(), BlockchainError> { if self.is_eip1559() { - return Ok(()) + return Ok(()); } Err(BlockchainError::EIP1559TransactionUnsupportedAtHardfork) } @@ -526,43 +728,64 @@ impl Backend { /// Returns an error if EIP1559 is not active (pre muirGlacier) pub fn ensure_eip2930_active(&self) -> Result<(), BlockchainError> { if self.is_eip2930() { - return Ok(()) + return Ok(()); } Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork) } + pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> { + if self.is_eip4844() { + return Ok(()); + } + 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() { + return Ok(()) + } + Err(BlockchainError::DepositTransactionUnsupported) + } + /// Returns the block gas limit - pub fn gas_limit(&self) -> U256 { - self.env().read().block.gas_limit.into() + pub fn gas_limit(&self) -> u64 { + self.env.read().block.gas_limit.saturating_to() } /// Sets the block gas limit - pub fn set_gas_limit(&self, gas_limit: U256) { - self.env().write().block.gas_limit = gas_limit.into(); + pub fn set_gas_limit(&self, gas_limit: u64) { + self.env.write().block.gas_limit = U256::from(gas_limit); } /// Returns the current base fee - pub fn base_fee(&self) -> U256 { + pub fn base_fee(&self) -> u64 { self.fees.base_fee() } - /// Sets the current basefee - pub fn set_base_fee(&self, basefee: U256) { - self.fees.set_base_fee(basefee) + /// Returns whether the minimum suggested priority fee is enforced + pub fn is_min_priority_fee_enforced(&self) -> bool { + self.fees.is_min_priority_fee_enforced() } - /// Returns the current gas price - pub fn gas_price(&self) -> U256 { - self.fees.gas_price() + pub fn excess_blob_gas_and_price(&self) -> Option { + self.fees.excess_blob_gas_and_price() } - /// Returns the suggested fee cap - pub fn max_priority_fee_per_gas(&self) -> U256 { - self.fees.max_priority_fee_per_gas() + /// Sets the current basefee + pub fn set_base_fee(&self, basefee: u64) { + self.fees.set_base_fee(basefee) } /// Sets the gas price - pub fn set_gas_price(&self, price: U256) { + pub fn set_gas_price(&self, price: u128) { self.fees.set_gas_price(price) } @@ -578,30 +801,30 @@ impl Backend { self.blockchain.storage.read().total_difficulty } - /// Creates a new `evm_snapshot` at the current height + /// Creates a new `evm_snapshot` at the current height. /// - /// Returns the id of the snapshot created - pub async fn create_snapshot(&self) -> U256 { - let num = self.best_number().as_u64(); + /// Returns the id of the snapshot created. + pub async fn create_state_snapshot(&self) -> U256 { + let num = self.best_number(); let hash = self.best_hash(); - let id = self.db.write().await.snapshot(); + let id = self.db.write().await.snapshot_state(); trace!(target: "backend", "creating snapshot {} at {}", id, num); - self.active_snapshots.lock().insert(id, (num, hash)); + self.active_state_snapshots.lock().insert(id, (num, hash)); id } - /// Reverts the state to the snapshot identified by the given `id`. - pub async fn revert_snapshot(&self, id: U256) -> Result { - let block = { self.active_snapshots.lock().remove(&id) }; + /// Reverts the state to the state snapshot identified by the given `id`. + pub async fn revert_state_snapshot(&self, id: U256) -> Result { + let block = { self.active_state_snapshots.lock().remove(&id) }; if let Some((num, hash)) = block { let best_block_hash = { // revert the storage that's newer than the snapshot - let current_height = self.best_number().as_u64(); + let current_height = self.best_number(); let mut storage = self.blockchain.storage.write(); for n in ((num + 1)..=current_height).rev() { trace!(target: "backend", "reverting block {}", n); - let n: U64 = n.into(); + let n = U64::from(n); if let Some(hash) = storage.hashes.remove(&n) { if let Some(block) = storage.blocks.remove(&hash) { for tx in block.transactions { @@ -611,25 +834,59 @@ impl Backend { } } - storage.best_number = num.into(); + storage.best_number = U64::from(num); storage.best_hash = hash; hash }; let block = self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?; - // Note: In [`TimeManager::compute_next_timestamp`] we ensure that the next timestamp is - // always increasing by at least one. By subtracting 1 here, this is mitigated. - let reset_time = block.timestamp.as_u64().saturating_sub(1); + let reset_time = block.header.timestamp; self.time.reset(reset_time); - self.set_block_number(num.into()); + + let mut env = self.env.write(); + env.block = BlockEnv { + number: U256::from(num), + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + // ensures prevrandao is set + prevrandao: Some(block.header.mix_hash.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), + // Keep previous `coinbase` and `basefee` value + coinbase: env.block.coinbase, + basefee: env.block.basefee, + ..Default::default() + }; } - Ok(self.db.write().await.revert(id)) + Ok(self.db.write().await.revert_state(id, RevertStateSnapshotAction::RevertRemove)) + } + + pub fn list_state_snapshots(&self) -> BTreeMap { + self.active_state_snapshots.lock().clone().into_iter().collect() } /// Get the current state. - pub async fn serialized_state(&self) -> Result { - let state = self.db.read().await.dump_state()?; + pub async fn serialized_state( + &self, + preserve_historical_states: bool, + ) -> Result { + 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 transactions = self.blockchain.storage.read().serialized_transactions(); + let historical_states = if preserve_historical_states { + Some(self.states.write().serialized_states()) + } else { + None + }; + + let state = self.db.read().await.dump_state( + at, + best_number, + blocks, + transactions, + historical_states, + )?; state.ok_or_else(|| { RpcError::invalid_params("Dumping state not supported with the current configuration") .into() @@ -637,8 +894,11 @@ impl Backend { } /// Write all chain data to serialized bytes buffer - pub async fn dump_state(&self) -> Result { - let state = self.serialized_state().await?; + pub async fn dump_state( + &self, + preserve_historical_states: bool, + ) -> Result { + let state = self.serialized_state(preserve_historical_states).await?; let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); encoder .write_all(&serde_json::to_vec(&state).unwrap_or_default()) @@ -646,8 +906,74 @@ impl Backend { Ok(encoder.finish().unwrap_or_default().into()) } + /// Apply [SerializableState] data to the backend storage. + pub async fn load_state(&self, state: SerializableState) -> Result { + // load the blocks and transactions into the storage + self.blockchain.storage.write().load_blocks(state.blocks.clone()); + self.blockchain.storage.write().load_transactions(state.transactions.clone()); + // reset the block env + if let Some(block) = state.block.clone() { + self.env.write().block = block.clone(); + + // Set the current best block number. + // Defaults to block number for compatibility with existing state files. + let fork_num_and_hash = self.get_fork().map(|f| (f.block_number(), f.block_hash())); + + if let Some((number, hash)) = fork_num_and_hash { + let best_number = state.best_block_number.unwrap_or(block.number.to::()); + trace!(target: "backend", state_block_number=?best_number, fork_block_number=?number); + // If the state.block_number is greater than the fork block number, set best number + // to the state block number. + // Ref: https://github.com/foundry-rs/foundry/issues/9539 + if best_number.to::() > number { + self.blockchain.storage.write().best_number = best_number; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else( + || { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + }, + )?; + self.blockchain.storage.write().best_hash = best_hash; + } else { + // If loading state file on a fork, set best number to the fork block number. + // Ref: https://github.com/foundry-rs/foundry/pull/9215#issue-2618681838 + self.blockchain.storage.write().best_number = U64::from(number); + self.blockchain.storage.write().best_hash = hash; + } + } else { + let best_number = state.best_block_number.unwrap_or(block.number.to::()); + self.blockchain.storage.write().best_number = best_number; + + // Set the current best block hash; + let best_hash = + self.blockchain.storage.read().hash(best_number.into()).ok_or_else(|| { + BlockchainError::RpcError(RpcError::internal_error_with(format!( + "Best hash not found for best number {best_number}", + ))) + })?; + + self.blockchain.storage.write().best_hash = best_hash; + } + } + + if !self.db.write().await.load_state(state.clone())? { + return Err(RpcError::invalid_params( + "Loading state not supported with the current configuration", + ) + .into()); + } + + if let Some(historical_states) = state.historical_states { + self.states.write().load_states(historical_states); + } + + Ok(true) + } + /// Deserialize and add all chain data to the backend storage - pub async fn load_state(&self, buf: Bytes) -> Result { + pub async fn load_state_bytes(&self, buf: Bytes) -> Result { let orig_buf = &buf.0[..]; let mut decoder = GzDecoder::new(orig_buf); let mut decoded_data = Vec::new(); @@ -662,26 +988,40 @@ impl Backend { }) .map_err(|_| BlockchainError::FailedToDecodeStateDump)?; - if !self.db.write().await.load_state(state)? { - Err(RpcError::invalid_params( - "Loading state not supported with the current configuration", - ) - .into()) - } else { - Ok(true) - } + self.load_state(state).await } /// Returns the environment for the next block - fn next_env(&self) -> Env { + fn next_env(&self) -> EnvWithHandlerCfg { let mut env = self.env.read().clone(); // increase block number for this block - env.block.number = env.block.number.saturating_add(rU256::from(1)); - env.block.basefee = self.base_fee().into(); - env.block.timestamp = rU256::from(self.time.current_call_timestamp()); + env.block.number = env.block.number.saturating_add(U256::from(1)); + env.block.basefee = U256::from(self.base_fee()); + env.block.timestamp = U256::from(self.time.current_call_timestamp()); env } + /// Creates an EVM instance with optionally injected precompiles. + #[allow(clippy::type_complexity)] + fn new_evm_with_inspector_ref<'i, 'db>( + &self, + db: &'db dyn DatabaseRef, + env: EnvWithHandlerCfg, + inspector: &'i mut dyn revm::Inspector< + WrapDatabaseRef<&'db dyn DatabaseRef>, + >, + ) -> revm::Evm< + '_, + &'i mut dyn revm::Inspector>>, + WrapDatabaseRef<&'db dyn DatabaseRef>, + > { + let mut evm = new_evm_with_inspector_ref(db, env, inspector, self.odyssey); + if let Some(factory) = &self.precompile_factory { + inject_precompiles(&mut evm, factory.precompiles()); + } + evm + } + /// executes the transactions without writing to the underlying database pub async fn inspect_tx( &self, @@ -692,31 +1032,27 @@ impl Backend { > { let mut env = self.next_env(); env.tx = tx.pending_transaction.to_revm_tx_env(); - let db = self.db.read().await; - let mut inspector = Inspector::default(); - let mut evm = revm::EVM::new(); - evm.env = env; - evm.database(&*db); - let result_and_state = match evm.inspect_ref(&mut inspector) { - Ok(res) => res, - Err(e) => return Err(e.into()), - }; - let state = result_and_state.state; - let state: revm::primitives::HashMap = - state.into_iter().map(|kv| (kv.0.into(), kv.1)).collect(); - let (exit_reason, gas_used, out, logs) = match result_and_state.result { + if env.handler_cfg.is_optimism { + env.tx.optimism.enveloped_tx = + Some(alloy_rlp::encode(&tx.pending_transaction.transaction.transaction).into()); + } + + let db = self.db.read().await; + let mut inspector = self.build_inspector(); + let mut evm = self.new_evm_with_inspector_ref(db.as_dyn(), env, &mut inspector); + let ResultAndState { result, state } = evm.transact()?; + let (exit_reason, gas_used, out, logs) = match result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), Some(logs)) + (reason.into(), gas_used, Some(output), Some(logs)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output)), None) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), }; + drop(evm); inspector.print_logs(); Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) @@ -738,7 +1074,7 @@ impl Backend { f: F, ) -> T where - F: FnOnce(Box, BlockInfo) -> T, + F: FnOnce(Box, BlockInfo) -> T, { let db = self.db.read().await; let env = self.next_env(); @@ -747,15 +1083,20 @@ impl Backend { let storage = self.blockchain.storage.read(); + let cfg_env = CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg); let executor = TransactionExecutor { db: &mut cache_db, validator: self, pending: pool_transactions.into_iter(), block_env: env.block.clone(), - cfg_env: env.cfg, + cfg_env, parent_hash: storage.best_hash, - gas_used: U256::zero(), + 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(), + odyssey: self.odyssey, }; // create a new pending block @@ -778,23 +1119,37 @@ impl Backend { &self, pool_transactions: Vec>, ) -> MinedBlockOutcome { + let _mining_guard = self.mining.lock().await; trace!(target: "backend", "creating new block with {} transactions", pool_transactions.len()); let (outcome, header, block_hash) = { let current_base_fee = self.base_fee(); + let current_excess_blob_gas_and_price = self.excess_blob_gas_and_price(); - let mut env = self.env().read().clone(); + let mut env = self.env.read().clone(); - 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; } + let block_number = + self.blockchain.storage.read().best_number.saturating_add(U64::from(1)); + // increase block number for this block - env.block.number = env.block.number.saturating_add(rU256::from(1)); - env.block.basefee = current_base_fee.into(); - env.block.timestamp = rU256::from(self.time.next_timestamp()); + if is_arbitrum(env.cfg.chain_id) { + // Temporary set `env.block.number` to `block_number` for Arbitrum chains. + env.block.number = block_number.to(); + } else { + env.block.number = env.block.number.saturating_add(U256::from(1)); + } + + env.block.basefee = U256::from(current_base_fee); + env.block.blob_excess_gas_and_price = current_excess_blob_gas_and_price; + + // pick a random value for prevrandao + env.block.prevrandao = Some(B256::random()); let best_hash = self.blockchain.storage.read().best_hash; @@ -806,21 +1161,31 @@ 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, + db: &mut **db, validator: self, pending: pool_transactions.into_iter(), block_env: env.block.clone(), - cfg_env: env.cfg.clone(), + cfg_env: CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg), parent_hash: best_hash, - gas_used: U256::zero(), + gas_used: 0, + blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + print_logs: self.print_logs, + odyssey: self.odyssey, + precompile_factory: self.precompile_factory.clone(), }; let executed_tx = executor.execute(); // we also need to update the new blockhash in the db itself - let block_hash = executed_tx.block.block.header.hash(); - db.insert_block_hash(executed_tx.block.block.header.number, block_hash); + let block_hash = executed_tx.block.block.header.hash_slow(); + db.insert_block_hash(U256::from(executed_tx.block.block.header.number), block_hash); (executed_tx, block_hash) }; @@ -830,7 +1195,6 @@ impl Backend { let BlockInfo { block, transactions, receipts } = block; let header = block.header.clone(); - let block_number: U64 = ru256_to_u256(env.block.number).as_u64().into(); trace!( target: "backend", @@ -839,7 +1203,6 @@ impl Backend { transactions.len(), transactions.iter().map(|tx| tx.transaction_hash).collect::>() ); - let mut storage = self.blockchain.storage.write(); // update block metadata storage.best_number = block_number; @@ -858,82 +1221,41 @@ impl Backend { // insert all transactions for (info, receipt) in transactions.into_iter().zip(receipts) { // log some tx info - { - node_info!(" Transaction: {:?}", info.transaction_hash); - if let Some(ref contract) = info.contract_address { - node_info!(" Contract created: {:?}", contract); - } - node_info!(" Gas used: {}", receipt.gas_used()); - match info.exit { - return_ok!() => (), - InstructionResult::OutOfFund => { - node_info!(" Error: reverted due to running out of funds"); - } - InstructionResult::CallTooDeep => { - node_info!(" Error: reverted with call too deep"); - } - InstructionResult::Revert => { - if let Some(ref r) = info.out { - if let Ok(reason) = decode_revert(r.as_ref(), None, None) { - node_info!(" Error: reverted with '{}'", reason); - } else { - match decode_custom_error_args(r, 5) { - // assuming max 5 args - Some(token) => { - node_info!( - " Error: reverted with custom error: {:?}", - token - ); - } - None => { - node_info!( - " Error: reverted with custom error: {}", - hex::encode(r) - ); - } - } - } - } else { - node_info!(" Error: reverted without a reason"); - } - } - InstructionResult::OutOfGas => { - node_info!(" Error: ran out of gas"); - } - reason => { - node_info!(" Error: failed due to {:?}", reason); - } - } - node_info!(""); + node_info!(" Transaction: {:?}", info.transaction_hash); + if let Some(contract) = &info.contract_address { + node_info!(" Contract created: {contract}"); + } + node_info!(" Gas used: {}", receipt.cumulative_gas_used()); + if !info.exit.is_ok() { + let r = RevertDecoder::new().decode( + info.out.as_ref().map(|b| &b[..]).unwrap_or_default(), + Some(info.exit), + ); + node_info!(" Error: reverted with: {r}"); } + node_info!(""); let mined_tx = MinedTransaction { info, receipt, block_hash, - block_number: block_number.as_u64(), + block_number: block_number.to::(), }; storage.transactions.insert(mined_tx.info.transaction_hash, mined_tx); } + // remove old transactions that exceed the transaction block keeper if let Some(transaction_block_keeper) = self.transaction_block_keeper { if storage.blocks.len() > transaction_block_keeper { - let n: U64 = block_number - .as_u64() - .saturating_sub(transaction_block_keeper.try_into().unwrap()) - .into(); - if let Some(hash) = storage.hashes.get(&n) { - if let Some(block) = storage.blocks.get(hash) { - for tx in block.clone().transactions { - let _ = storage.transactions.remove(&tx.hash()); - } - } - } + let to_clear = block_number + .to::() + .saturating_sub(transaction_block_keeper.try_into().unwrap_or(u64::MAX)); + storage.remove_block_transactions_by_number(to_clear) } } // we intentionally set the difficulty to `0` for newer blocks - env.block.difficulty = rU256::from(0); + env.block.difficulty = U256::from(0); // update env with new values *self.env.write() = env; @@ -942,46 +1264,59 @@ impl Backend { node_info!(" Block Number: {}", block_number); node_info!(" Block Hash: {:?}", block_hash); - node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822()); + if timestamp.year() > 9999 { + // rf2822 panics with more than 4 digits + node_info!(" Block Time: {:?}\n", timestamp.to_rfc3339()); + } else { + node_info!(" Block Time: {:?}\n", timestamp.to_rfc2822()); + } let outcome = MinedBlockOutcome { block_number, included, invalid }; (outcome, header, block_hash) }; let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - header.gas_used, - header.gas_limit, + header.gas_used as u128, + header.gas_limit as u128, header.base_fee_per_gas.unwrap_or_default(), ); + let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( + header.excess_blob_gas.map(|g| g as u128).unwrap_or_default(), + header.blob_gas_used.map(|g| g as u128).unwrap_or_default(), + ); + + // update next base fee + self.fees.set_base_fee(next_block_base_fee); + self.fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( + next_block_excess_blob_gas, + false, + )); // notify all listeners self.notify_on_new_block(header, block_hash); - // update next base fee - self.fees.set_base_fee(next_block_base_fee.into()); - outcome } - /// Executes the `EthTransactionRequest` without writing to the DB + /// Executes the [TransactionRequest] without writing to the DB /// /// # Errors /// /// Returns an error if the `block_number` is greater than the current height pub async fn call( &self, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_request: Option, overrides: Option, - ) -> Result<(InstructionResult, Option, u64, State), BlockchainError> { + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { self.with_database_at(block_request, |state, block| { - let block_number = ru256_to_u256(block.number).as_u64(); + let block_number = block.number.to::(); let (exit, out, gas, state) = match overrides { - None => self.call_with_state(state, request, fee_details, block), + None => self.call_with_state(state.as_dyn(), request, fee_details, block), Some(overrides) => { - let state = state::apply_state_override(overrides, state)?; - self.call_with_state(state, request, fee_details, block) + let state = state::apply_state_override(overrides.into_iter().collect(), state)?; + self.call_with_state(state.as_dyn(), request, fee_details, block) }, }?; trace!(target: "backend", "call return {:?} out: {:?} gas {} on block {}", exit, out, gas, block_number); @@ -989,47 +1324,98 @@ impl Backend { }).await? } + /// ## EVM settings + /// + /// This modifies certain EVM settings to mirror geth's `SkipAccountChecks` when transacting requests, see also: : + /// + /// - `disable_eip3607` is set to `true` + /// - `disable_base_fee` is set to `true` + /// - `nonce` is set to `None` fn build_call_env( &self, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Env { - let EthTransactionRequest { from, to, gas, value, data, nonce, access_list, .. } = request; + ) -> EnvWithHandlerCfg { + let WithOtherFields:: { + inner: + TransactionRequest { + from, + to, + gas, + value, + input, + access_list, + blob_versioned_hashes, + authorization_list, + // nonce is always ignored for calls + nonce: _, + sidecar: _, + chain_id: _, + transaction_type: _, + .. // Rest of the gas fees related fields are taken from `fee_details` + }, + .. + } = request; - let FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = fee_details; + let FeeDetails { + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + } = fee_details; - let gas_limit = gas.unwrap_or(block_env.gas_limit.into()); + let gas_limit = gas.unwrap_or(block_env.gas_limit.to()); let mut env = self.env.read().clone(); env.block = block_env; // we want to disable this in eth_call, since this is common practice used by other node // impls and providers env.cfg.disable_block_gas_limit = true; - if let Some(base) = max_fee_per_gas { - env.block.basefee = base.into(); - } + // The basefee should be ignored for calls against state for + // - eth_call + // - eth_estimateGas + // - eth_createAccessList + // - tracing + env.cfg.disable_base_fee = true; - let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| self.gas_price()); + let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| { + self.fees().raw_gas_price().saturating_add(MIN_SUGGESTED_PRIORITY_FEE) + }); let caller = from.unwrap_or_default(); + let to = to.as_ref().and_then(TxKind::to); + let blob_hashes = blob_versioned_hashes.unwrap_or_default(); + env.tx = + TxEnv { + caller, + gas_limit, + gas_price: U256::from(gas_price), + gas_priority_fee: max_priority_fee_per_gas.map(U256::from), + max_fee_per_blob_gas: max_fee_per_blob_gas + .or_else(|| { + if !blob_hashes.is_empty() { + env.block.get_blob_gasprice() + } else { + None + } + }) + .map(U256::from), + transact_to: match to { + Some(addr) => TxKind::Call(*addr), + None => TxKind::Create, + }, + value: value.unwrap_or_default(), + data: input.into_input().unwrap_or_default(), + chain_id: None, + // set nonce to None so that the correct nonce is chosen by the EVM + nonce: None, + access_list: access_list.unwrap_or_default().into(), + blob_hashes, + optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, + authorization_list: authorization_list.map(Into::into), + }; - env.tx = TxEnv { - caller: caller.into(), - gas_limit: gas_limit.as_u64(), - gas_price: gas_price.into(), - gas_priority_fee: max_priority_fee_per_gas.map(u256_to_ru256), - transact_to: match to { - Some(addr) => TransactTo::Call(addr.into()), - None => TransactTo::Create(CreateScheme::Create), - }, - value: value.unwrap_or_default().into(), - data: data.unwrap_or_default().to_vec().into(), - chain_id: None, - nonce: nonce.map(|n| n.as_u64()), - access_list: to_revm_access_list(access_list.unwrap_or_default()), - }; - - 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; @@ -1038,128 +1424,166 @@ impl Backend { env } - pub fn call_with_state( + /// Builds [`Inspector`] with the configured options + fn build_inspector(&self) -> Inspector { + let mut inspector = Inspector::default(); + + if self.print_logs { + inspector = inspector.with_log_collector(); + } + + inspector + } + + pub fn call_with_state( &self, - state: D, - request: EthTransactionRequest, + state: &dyn DatabaseRef, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Result<(InstructionResult, Option, u64, State), BlockchainError> - where - D: DatabaseRef, - { - let mut inspector = Inspector::default(); - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block_env); - evm.database(state); - let result_and_state = match evm.inspect_ref(&mut inspector) { - Ok(result_and_state) => result_and_state, - Err(e) => match e { - EVMError::Transaction(invalid_tx) => { - return Err(BlockchainError::InvalidTransaction(invalid_tx.into())) - } - EVMError::PrevrandaoNotSet => return Err(BlockchainError::PrevrandaoNotSet), - EVMError::Database(e) => return Err(BlockchainError::DatabaseError(e)), - }, - }; - let state = result_and_state.state; - let state: revm::primitives::HashMap = - state.into_iter().map(|kv| (kv.0.into(), kv.1)).collect(); - let (exit_reason, gas_used, out) = match result_and_state.result { + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { + let mut inspector = self.build_inspector(); + + let env = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state } = evm.transact()?; + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output)) + (reason.into(), gas_used, Some(output)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; + drop(evm); inspector.print_logs(); - Ok((exit_reason, out, gas_used, state)) + Ok((exit_reason, out, gas_used as u128, state)) } pub async fn call_with_tracing( &self, - request: EthTransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_request: Option, - opts: GethDebugTracingOptions, - ) -> 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; - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block); - evm.database(state); - let result_and_state = - match evm.inspect_ref(&mut inspector) { - Ok(result_and_state) => result_and_state, - Err(e) => return Err(e.into()), - }; - let (exit_reason, gas_used, out, ) = match result_and_state.result { + + let state = if let Some(overrides) = state_overrides { + Box::new(state::apply_state_override(overrides, state)?) + as Box + } else { + state + }; + + if let Some(tracer) = tracer { + return match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::CallTracer => { + let call_config = tracer_config + .into_call_config() + .map_err(|e| (RpcError::invalid_params(e.to_string())))?; + + let mut inspector = self.build_inspector().with_tracing_config( + TracingInspectorConfig::from_geth_call_config(&call_config), + ); + + let env = self.build_call_env(request, fee_details, block); + let mut evm = self.new_evm_with_inspector_ref( + state.as_dyn(), + 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 | + GethDebugBuiltInTracerType::FlatCallTracer => { + Err(RpcError::invalid_params("unsupported tracer type").into()) + } + }, + + GethDebugTracerType::JsTracer(_code) => { + Err(RpcError::invalid_params("unsupported tracer type").into()) + } + } + } + + // defaults to StructLog tracer used since no tracer is specified + let mut inspector = self + .build_inspector() + .with_tracing_config(TracingInspectorConfig::from_geth_config(&config)); + + let env = self.build_call_env(request, fee_details, block); + let mut evm = self.new_evm_with_inspector_ref(state.as_dyn(), env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), ) - }, - ExecutionResult::Revert { gas_used, output} => { + (reason.into(), gas_used, Some(output)) + } + ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) - }, - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None) - }, + } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; - let res = inspector.tracer.unwrap_or_default().traces.geth_trace(gas_used.into(), opts); - trace!(target: "backend", "trace call return {:?} out: {:?} gas {} on block {}", exit_reason, out, gas_used, block_number); + + drop(evm); + let tracing_inspector = inspector.tracer.expect("tracer disappeared"); + let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default(); + + trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); + + let res = tracing_inspector + .into_geth_builder() + .geth_traces(gas_used, return_value, config) + .into(); + Ok(res) }) .await? } - pub fn build_access_list_with_state( + pub fn build_access_list_with_state( &self, - state: D, - request: EthTransactionRequest, + state: &dyn DatabaseRef, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> - where - D: DatabaseRef, - { - let from = request.from.unwrap_or_default(); - let to = if let Some(to) = request.to { - to - } else { - let nonce = state.basic(from.into())?.unwrap_or_default().nonce; - get_contract_address(from, nonce) - }; - - let mut tracer = AccessListTracer::new( - AccessList(request.access_list.clone().unwrap_or_default()), - from, - to, - self.precompiles(), - ); - - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block_env); - evm.database(state); - let result_and_state = match evm.inspect_ref(&mut tracer) { - Ok(result_and_state) => result_and_state, - Err(e) => return Err(e.into()), - }; - let (exit_reason, gas_used, out) = match result_and_state.result { + ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> { + let mut inspector = + AccessListInspector::new(request.access_list.clone().unwrap_or_default()); + + let env = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output)) + (reason.into(), gas_used, Some(output)) } ExecutionResult::Revert { gas_used, output } => { (InstructionResult::Revert, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; - let access_list = tracer.access_list(); + drop(evm); + let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) } @@ -1181,14 +1605,14 @@ impl Backend { async fn logs_for_block( &self, filter: Filter, - hash: H256, + hash: B256, ) -> Result, BlockchainError> { if let Some(block) = self.blockchain.get_block_by_hash(&hash) { - return Ok(self.mined_logs_for_block(filter, block)) + return Ok(self.mined_logs_for_block(filter, block)); } if let Some(fork) = self.get_fork() { - return Ok(fork.logs(&filter).await?) + return Ok(fork.logs(&filter).await?); } Ok(Vec::new()) @@ -1198,56 +1622,43 @@ impl Backend { fn mined_logs_for_block(&self, filter: Filter, block: Block) -> Vec { let params = FilteredParams::new(Some(filter.clone())); let mut all_logs = Vec::new(); - let block_hash = block.header.hash(); + let block_hash = block.header.hash_slow(); let mut block_log_index = 0u32; - let transactions: Vec<_> = { - let storage = self.blockchain.storage.read(); - block - .transactions - .iter() - .filter_map(|tx| storage.transactions.get(&tx.hash()).map(|tx| tx.info.clone())) - .collect() - }; + let storage = self.blockchain.storage.read(); - for transaction in transactions { - let logs = transaction.logs.clone(); - let transaction_hash = transaction.transaction_hash; - - for (log_idx, log) in logs.into_iter().enumerate() { - let mut log = Log { - address: log.address, - topics: log.topics, - data: log.data, - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - transaction_log_index: None, - log_type: None, - removed: Some(false), - }; + for tx in block.transactions { + let Some(tx) = storage.transactions.get(&tx.hash()) else { + continue; + }; + let logs = tx.receipt.logs(); + let transaction_hash = tx.info.transaction_hash; + + for log in logs { let mut is_match: bool = true; - if filter.address.is_some() && filter.has_topics() { - if !params.filter_address(&log) || !params.filter_topics(&log) { + if !filter.address.is_empty() && filter.has_topics() { + if !params.filter_address(&log.address) || !params.filter_topics(log.topics()) { is_match = false; } - } else if filter.address.is_some() { - if !params.filter_address(&log) { + } else if !filter.address.is_empty() { + if !params.filter_address(&log.address) { is_match = false; } - } else if filter.has_topics() && !params.filter_topics(&log) { + } else if filter.has_topics() && !params.filter_topics(log.topics()) { is_match = false; } if is_match { - log.block_hash = Some(block_hash); - log.block_number = Some(block.header.number.as_u64().into()); - log.transaction_hash = Some(transaction_hash); - log.transaction_index = Some(transaction.transaction_index.into()); - log.log_index = Some(U256::from(block_log_index)); - log.transaction_log_index = Some(U256::from(log_idx)); + let log = Log { + inner: log.clone(), + block_hash: Some(block_hash), + block_number: Some(block.header.number), + block_timestamp: Some(block.header.timestamp), + transaction_hash: Some(transaction_hash), + transaction_index: Some(tx.info.transaction_index), + log_index: Some(block_log_index as u64), + removed: false, + }; all_logs.push(log); } block_log_index += 1; @@ -1275,7 +1686,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?; @@ -1300,31 +1711,28 @@ impl Backend { if let Some(hash) = filter.get_block_hash() { self.logs_for_block(filter, hash).await } else { - let best = self.best_number().as_u64(); + let best = self.best_number(); let to_block = self.convert_block_number(filter.block_option.get_to_block().copied()).min(best); let from_block = self.convert_block_number(filter.block_option.get_from_block().copied()); if from_block > best { // requested log range does not exist yet - return Ok(vec![]) + return Ok(vec![]); } self.logs_for_range(&filter, from_block, to_block).await } } - pub async fn block_by_hash( - &self, - hash: H256, - ) -> Result>, BlockchainError> { + pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.mined_block_by_hash(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { - return Ok(fork.block_by_hash(hash).await?) + return Ok(fork.block_by_hash(hash).await?); } Ok(None) @@ -1332,11 +1740,11 @@ impl Backend { pub async fn block_by_hash_full( &self, - hash: H256, - ) -> Result>, BlockchainError> { + hash: B256, + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.get_full_block(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1346,7 +1754,7 @@ impl Backend { Ok(None) } - fn mined_block_by_hash(&self, hash: H256) -> Option> { + fn mined_block_by_hash(&self, hash: B256) -> Option { let block = self.blockchain.get_block_by_hash(&hash)?; Some(self.convert_block(block)) } @@ -1354,15 +1762,18 @@ impl Backend { pub(crate) async fn mined_transactions_by_block_number( &self, number: BlockNumber, - ) -> Option> { + ) -> Option> { if let Some(block) = self.get_block(number) { - return self.mined_transactions_in_block(&block) + return self.mined_transactions_in_block(&block); } None } /// Returns all transactions given a block - pub(crate) fn mined_transactions_in_block(&self, block: &Block) -> Option> { + pub(crate) fn mined_transactions_in_block( + &self, + block: &Block, + ) -> Option> { let mut transactions = Vec::with_capacity(block.transactions.len()); let base_fee = block.header.base_fee_per_gas; let storage = self.blockchain.storage.read(); @@ -1379,10 +1790,10 @@ impl Backend { pub async fn block_by_number( &self, number: BlockNumber, - ) -> Result>, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.mined_block_by_number(number) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1398,10 +1809,10 @@ impl Backend { pub async fn block_by_number_full( &self, number: BlockNumber, - ) -> Result>, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.get_full_block(number) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1416,15 +1827,15 @@ impl Backend { pub fn get_block(&self, id: impl Into) -> Option { let hash = match id.into() { - BlockId::Hash(hash) => hash, + BlockId::Hash(hash) => hash.block_hash, BlockId::Number(number) => { let storage = self.blockchain.storage.read(); - let slots_in_an_epoch = U64::from(32u64); + let slots_in_an_epoch = U64::from(self.slots_in_an_epoch); match number { BlockNumber::Latest => storage.best_hash, BlockNumber::Earliest => storage.genesis_hash, BlockNumber::Pending => return None, - BlockNumber::Number(num) => *storage.hashes.get(&num)?, + BlockNumber::Number(num) => *storage.hashes.get(&U64::from(num))?, BlockNumber::Safe => { if storage.best_number > (slots_in_an_epoch) { *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch)))? @@ -1433,8 +1844,10 @@ impl Backend { } } BlockNumber::Finalized => { - if storage.best_number > (slots_in_an_epoch * 2) { - *storage.hashes.get(&(storage.best_number - (slots_in_an_epoch * 2)))? + if storage.best_number > (slots_in_an_epoch * U64::from(2)) { + *storage + .hashes + .get(&(storage.best_number - (slots_in_an_epoch * U64::from(2))))? } else { storage.genesis_hash } @@ -1445,73 +1858,58 @@ impl Backend { self.get_block_by_hash(hash) } - pub fn get_block_by_hash(&self, hash: H256) -> Option { + pub fn get_block_by_hash(&self, hash: B256) -> Option { self.blockchain.get_block_by_hash(&hash) } - pub fn mined_block_by_number(&self, number: BlockNumber) -> Option> { - Some(self.convert_block(self.get_block(number)?)) + pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { + let block = self.get_block(number)?; + let mut block = self.convert_block(block); + block.transactions.convert_to_hashes(); + Some(block) } - pub fn get_full_block(&self, id: impl Into) -> Option> { + pub fn get_full_block(&self, id: impl Into) -> Option { let block = self.get_block(id)?; let transactions = self.mined_transactions_in_block(&block)?; - let block = self.convert_block(block); - Some(block.into_full_block(transactions)) + let mut block = self.convert_block(block); + block.inner.transactions = BlockTransactions::Full(transactions); + + Some(block) } - /// Takes a block as it's stored internally and returns the eth api conform block format - pub fn convert_block(&self, block: Block) -> EthersBlock { - let size = U256::from(rlp::encode(&block).len() as u32); + /// Takes a block as it's stored internally and returns the eth api conform block format. + pub fn convert_block(&self, block: Block) -> AnyRpcBlock { + let size = U256::from(alloy_rlp::encode(&block).len() as u32); let Block { header, transactions, .. } = block; - let hash = header.hash(); - let Header { - parent_hash, - ommers_hash, - beneficiary, - state_root, - transactions_root, - receipts_root, - logs_bloom, - difficulty, - number, - gas_limit, - gas_used, - timestamp, - extra_data, - mix_hash, - nonce, - base_fee_per_gas, - } = header; - - EthersBlock { - hash: Some(hash), - parent_hash, - uncles_hash: ommers_hash, - author: Some(beneficiary), - state_root, - transactions_root, - receipts_root, - number: Some(number.as_u64().into()), - gas_used, - gas_limit, - extra_data, - logs_bloom: Some(logs_bloom), - timestamp: timestamp.into(), - difficulty, - total_difficulty: Some(self.total_difficulty()), - seal_fields: { vec![mix_hash.as_bytes().to_vec().into(), nonce.0.to_vec().into()] }, + let hash = header.hash_slow(); + let Header { number, withdrawals_root, .. } = header; + + let block = AlloyBlock { + header: AlloyHeader { + inner: AnyHeader::from(header), + hash, + total_difficulty: Some(self.total_difficulty()), + size: Some(size), + }, + transactions: alloy_rpc_types::BlockTransactions::Hashes( + transactions.into_iter().map(|tx| tx.hash()).collect(), + ), uncles: vec![], - transactions: transactions.into_iter().map(|tx| tx.hash()).collect(), - size: Some(size), - mix_hash: Some(mix_hash), - nonce: Some(nonce), - base_fee_per_gas, - other: Default::default(), - ..Default::default() + withdrawals: withdrawals_root.map(|_| Default::default()), + }; + + let mut block = WithOtherFields::new(block); + + // If Arbitrum, apply chain specifics to converted block. + if is_arbitrum(self.env.read().cfg.chain_id) { + // Set `l1BlockNumber` field. + block.other.insert("l1BlockNumber".to_string(), number.into()); } + + block } /// Converts the `BlockNumber` into a numeric value @@ -1523,23 +1921,22 @@ impl Backend { &self, block_id: Option, ) -> Result { - let current = self.best_number().as_u64(); - let slots_in_an_epoch = 32u64; + let current = self.best_number(); let requested = match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { - BlockId::Hash(hash) => self - .block_by_hash(hash) - .await? - .ok_or(BlockchainError::BlockNotFound)? - .number - .ok_or(BlockchainError::BlockNotFound)? - .as_u64(), + BlockId::Hash(hash) => { + self.block_by_hash(hash.block_hash) + .await? + .ok_or(BlockchainError::BlockNotFound)? + .header + .number + } BlockId::Number(num) => match num { - BlockNumber::Latest | BlockNumber::Pending => self.best_number().as_u64(), - BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num.as_u64(), - BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), + BlockNumber::Latest | BlockNumber::Pending => self.best_number(), + BlockNumber::Earliest => U64::ZERO.to::(), + BlockNumber::Number(num) => num, + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), }, }; @@ -1551,14 +1948,13 @@ impl Backend { } pub fn convert_block_number(&self, block: Option) -> u64 { - let current = self.best_number().as_u64(); - let slots_in_an_epoch = 32u64; + let current = self.best_number(); match block.unwrap_or(BlockNumber::Latest) { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num.as_u64(), - BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), + BlockNumber::Number(num) => num, + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), } } @@ -1569,7 +1965,7 @@ impl Backend { f: F, ) -> Result where - F: FnOnce(Box, BlockEnv) -> T, + F: FnOnce(Box, BlockEnv) -> T, { let block_number = match block_request { Some(BlockRequest::Pending(pool_transactions)) => { @@ -1577,74 +1973,56 @@ impl Backend { .with_pending_block(pool_transactions, |state, block| { let block = block.block; let block = BlockEnv { - number: block.header.number.into(), - coinbase: block.header.beneficiary.into(), - timestamp: rU256::from(block.header.timestamp), - difficulty: block.header.difficulty.into(), - prevrandao: Some(block.header.mix_hash.into()), - basefee: block.header.base_fee_per_gas.unwrap_or_default().into(), - gas_limit: block.header.gas_limit.into(), + number: U256::from(block.header.number), + coinbase: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + prevrandao: Some(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() }; f(state, block) }) .await; - return Ok(result) + return Ok(result); } Some(BlockRequest::Number(bn)) => Some(BlockNumber::Number(bn)), None => None, }; - let block_number: U256 = self.convert_block_number(block_number).into(); + let block_number: U256 = U256::from(self.convert_block_number(block_number)); - if block_number < self.env.read().block.number.into() { + if block_number < self.env.read().block.number { + if let Some((block_hash, block)) = self + .block_by_number(BlockNumber::Number(block_number.to::())) + .await? + .map(|block| (block.header.hash, block)) { - let mut states = self.states.write(); - - if let Some((state, block)) = self - .get_block(block_number.as_u64()) - .and_then(|block| Some((states.get(&block.header.hash())?, block))) - { + if let Some(state) = self.states.write().get(&block_hash) { let block = BlockEnv { - number: block.header.number.into(), - coinbase: block.header.beneficiary.into(), - timestamp: rU256::from(block.header.timestamp), - difficulty: block.header.difficulty.into(), - prevrandao: Some(block.header.mix_hash).map(Into::into), - basefee: block.header.base_fee_per_gas.unwrap_or_default().into(), - gas_limit: block.header.gas_limit.into(), + number: block_number, + coinbase: block.header.beneficiary, + timestamp: U256::from(block.header.timestamp), + difficulty: block.header.difficulty, + 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() }; - return Ok(f(Box::new(state), block)) - } - } - - // 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.into(); - block.timestamp = rU256::from(fork.timestamp()); - block.basefee = fork.base_fee().unwrap_or_default().into(); - - return Ok(f(Box::new(&gen_db), block)) + return Ok(f(Box::new(state), block)); } } warn!(target: "backend", "Not historic state found for block={}", block_number); return Err(BlockchainError::BlockOutOfRange( - ru256_to_u256(self.env.read().block.number).as_u64(), - block_number.as_u64(), - )) + self.env.read().block.number.to::(), + block_number.to::(), + )); } let db = self.db.read().await; - let block = self.env().read().block.clone(); - Ok(f(Box::new(&*db), block)) + let block = self.env.read().block.clone(); + Ok(f(Box::new(&**db), block)) } pub async fn storage_at( @@ -1652,11 +2030,11 @@ impl Backend { address: Address, index: U256, block_request: Option, - ) -> Result { + ) -> Result { self.with_database_at(block_request, |db, _| { trace!(target: "backend", "get storage for {:?} at {:?}", address, index); - let val = db.storage(address.into(), index.into())?; - Ok(u256_to_h256_be(ru256_to_u256(val))) + let val = db.storage_ref(address, index)?; + Ok(val.into()) }) .await? } @@ -1670,27 +2048,24 @@ impl Backend { address: Address, block_request: Option, ) -> Result { - self.with_database_at(block_request, |db, _| self.get_code_with_state(db, address)).await? + self.with_database_at(block_request, |db, _| self.get_code_with_state(&db, address)).await? } - pub fn get_code_with_state( + pub fn get_code_with_state( &self, - state: D, + state: &dyn DatabaseRef, address: Address, - ) -> Result - where - D: DatabaseRef, - { + ) -> Result { trace!(target: "backend", "get code for {:?}", address); - let account = state.basic(address.into())?.unwrap_or_default(); + let account = state.basic_ref(address)?.unwrap_or_default(); if account.code_hash == KECCAK_EMPTY { // if the code hash is `KECCAK_EMPTY`, we check no further - return Ok(Default::default()) + return Ok(Default::default()); } let code = if let Some(code) = account.code { code } else { - state.code_by_hash(account.code_hash)? + state.code_by_hash_ref(account.code_hash)? }; Ok(code.bytes()[..code.len()].to_vec().into()) } @@ -1707,6 +2082,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, @@ -1716,7 +2108,7 @@ impl Backend { D: DatabaseRef, { trace!(target: "backend", "get balance for {:?}", address); - Ok(state.basic(address.into())?.unwrap_or_default().balance.into()) + Ok(state.basic_ref(address)?.unwrap_or_default().balance) } /// Returns the nonce of the address @@ -1725,29 +2117,32 @@ impl Backend { pub async fn get_nonce( &self, address: Address, - block_request: Option, - ) -> Result { - if let Some(BlockRequest::Pending(pool_transactions)) = block_request.as_ref() { + block_request: BlockRequest, + ) -> Result { + if let BlockRequest::Pending(pool_transactions) = &block_request { if let Some(value) = get_pool_transactions_nonce(pool_transactions, address) { - return Ok(value) + return Ok(value); } } let final_block_request = match block_request { - Some(BlockRequest::Pending(_)) => Some(BlockRequest::Number(self.best_number())), - Some(BlockRequest::Number(bn)) => Some(BlockRequest::Number(bn)), - None => None, + BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()), + BlockRequest::Number(bn) => BlockRequest::Number(bn), }; - self.with_database_at(final_block_request, |db, _| { + + self.with_database_at(Some(final_block_request), |db, _| { trace!(target: "backend", "get nonce for {:?}", address); - Ok(db.basic(address.into())?.unwrap_or_default().nonce.into()) + Ok(db.basic_ref(address)?.unwrap_or_default().nonce) }) .await? } /// Returns the traces for the given transaction - pub async fn trace_transaction(&self, hash: H256) -> Result, BlockchainError> { + pub async fn trace_transaction( + &self, + hash: B256, + ) -> Result, BlockchainError> { if let Some(traces) = self.mined_parity_trace_transaction(hash) { - return Ok(traces) + return Ok(traces); } if let Some(fork) = self.get_fork() { @@ -1758,12 +2153,23 @@ impl Backend { } /// Returns the traces for the given transaction - pub(crate) fn mined_parity_trace_transaction(&self, hash: H256) -> Option> { + pub(crate) fn mined_parity_trace_transaction( + &self, + hash: B256, + ) -> Option> { self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.parity_traces()) } + /// Returns the traces for the given transaction + pub(crate) fn mined_transaction(&self, hash: B256) -> Option { + self.blockchain.storage.read().transactions.get(&hash).cloned() + } + /// Returns the traces for the given block - pub(crate) fn mined_parity_trace_block(&self, block: u64) -> Option> { + pub(crate) fn mined_parity_trace_block( + &self, + block: u64, + ) -> Option> { let block = self.get_block(block)?; let mut traces = vec![]; let storage = self.blockchain.storage.read(); @@ -1776,33 +2182,36 @@ impl Backend { /// Returns the traces for the given transaction pub async fn debug_trace_transaction( &self, - hash: H256, + hash: B256, opts: GethDebugTracingOptions, ) -> Result { - if let Some(traces) = self.mined_geth_trace_transaction(hash, opts.clone()) { - return Ok(GethTrace::Known(GethTraceFrame::Default(traces))) + if let Some(trace) = self.mined_geth_trace_transaction(hash, opts.clone()) { + return trace; } if let Some(fork) = self.get_fork() { return Ok(fork.debug_trace_transaction(hash, opts).await?) } - Ok(GethTrace::Known(GethTraceFrame::Default(Default::default()))) + Ok(GethTrace::Default(Default::default())) } fn mined_geth_trace_transaction( &self, - hash: H256, + hash: B256, opts: GethDebugTracingOptions, - ) -> Option { + ) -> Option> { self.blockchain.storage.read().transactions.get(&hash).map(|tx| tx.geth_trace(opts)) } /// Returns the traces for the given block - pub async fn trace_block(&self, block: BlockNumber) -> Result, BlockchainError> { + pub async fn trace_block( + &self, + block: BlockNumber, + ) -> Result, BlockchainError> { let number = self.convert_block_number(Some(block)); if let Some(traces) = self.mined_parity_trace_block(number) { - return Ok(traces) + return Ok(traces); } if let Some(fork) = self.get_fork() { @@ -1816,131 +2225,224 @@ impl Backend { pub async fn transaction_receipt( &self, - hash: H256, - ) -> Result, BlockchainError> { + hash: B256, + ) -> Result, BlockchainError> { if let Some(receipt) = self.mined_transaction_receipt(hash) { - return Ok(Some(receipt.inner)) + return Ok(Some(receipt.inner)); } if let Some(fork) = self.get_fork() { let receipt = fork.transaction_receipt(hash).await?; let number = self.convert_block_number( - receipt.clone().and_then(|r| r.block_number).map(|n| BlockNumber::from(n.as_u64())), + receipt.clone().and_then(|r| r.block_number).map(BlockNumber::from), ); if fork.predates_fork_inclusive(number) { - return Ok(receipt) + return Ok(receipt); } } 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| matcher.matches(&trace.trace)); + + // Apply after and count + let filtered_traces: Vec<_> = if let Some(after) = filter.after { + filtered_traces.skip(after as usize).collect() + } else { + filtered_traces.collect() + }; + + let filtered_traces: Vec<_> = if let Some(count) = filter.count { + filtered_traces.into_iter().take(count as usize).collect() + } else { + filtered_traces + }; + + Ok(filtered_traces) + } + /// Returns all receipts of the block - pub fn mined_receipts(&self, hash: H256) -> Option> { + pub fn mined_receipts(&self, hash: B256) -> Option> { let block = self.mined_block_by_hash(hash)?; let mut receipts = Vec::new(); let storage = self.blockchain.storage.read(); - for tx in block.transactions { + for tx in block.transactions.hashes() { let receipt = storage.transactions.get(&tx)?.receipt.clone(); receipts.push(receipt); } Some(receipts) } - /// Returns the transaction receipt for the given hash - pub(crate) fn mined_transaction_receipt(&self, hash: H256) -> Option { - let MinedTransaction { info, receipt, block_hash, .. } = - self.blockchain.get_transaction_by_hash(&hash)?; - - let EIP658Receipt { status_code, gas_used, logs_bloom, logs } = receipt.into(); - - let index = info.transaction_index as usize; - - let block = self.blockchain.get_block_by_hash(&block_hash)?; - - // TODO store cumulative gas used in receipt instead - let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); + /// Returns all transaction receipts of the block + pub fn mined_block_receipts(&self, id: impl Into) -> Option> { + let mut receipts = Vec::new(); + let block = self.get_block(id)?; - let mut cumulative_gas_used = U256::zero(); - for receipt in receipts.iter().take(index + 1) { - cumulative_gas_used = cumulative_gas_used.saturating_add(receipt.gas_used()); + for transaction in block.transactions { + let receipt = self.mined_transaction_receipt(transaction.hash())?; + receipts.push(receipt.inner); } - // cumulative_gas_used = cumulative_gas_used.saturating_sub(gas_used); + Some(receipts) + } - let mut cumulative_receipts = receipts; - cumulative_receipts.truncate(index + 1); + /// Returns the transaction receipt for the given hash + pub(crate) fn mined_transaction_receipt(&self, hash: B256) -> Option { + let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } = + self.blockchain.get_transaction_by_hash(&hash)?; + let index = info.transaction_index as usize; + let block = self.blockchain.get_block_by_hash(&block_hash)?; let transaction = block.transactions[index].clone(); - let transaction_type = transaction.transaction.r#type(); + // Cancun specific + let excess_blob_gas = block.header.excess_blob_gas; + let blob_gas_price = + alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas.unwrap_or_default()); + let blob_gas_used = transaction.blob_gas(); let effective_gas_price = match transaction.transaction { - TypedTransaction::Legacy(t) => t.gas_price, - TypedTransaction::EIP2930(t) => t.gas_price, + TypedTransaction::Legacy(t) => t.tx().gas_price, + TypedTransaction::EIP2930(t) => t.tx().gas_price, TypedTransaction::EIP1559(t) => block .header .base_fee_per_gas - .unwrap_or(self.base_fee()) - .checked_add(t.max_priority_fee_per_gas) - .unwrap_or_else(U256::max_value), + .map_or(self.base_fee() as u128, |g| g as u128) + .saturating_add(t.tx().max_priority_fee_per_gas), + TypedTransaction::EIP4844(t) => block + .header + .base_fee_per_gas + .map_or(self.base_fee() as u128, |g| g as u128) + .saturating_add(t.tx().tx().max_priority_fee_per_gas), + TypedTransaction::EIP7702(t) => block + .header + .base_fee_per_gas + .map_or(self.base_fee() as u128, |g| g as u128) + .saturating_add(t.tx().max_priority_fee_per_gas), + TypedTransaction::Deposit(_) => 0_u128, + }; + + let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); + let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::(); + + let receipt = tx_receipt.as_receipt_with_bloom().receipt.clone(); + let receipt = Receipt { + status: receipt.status, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .into_iter() + .enumerate() + .map(|(index, log)| alloy_rpc_types::Log { + inner: log, + block_hash: Some(block_hash), + block_number: Some(block.header.number), + block_timestamp: Some(block.header.timestamp), + transaction_hash: Some(info.transaction_hash), + transaction_index: Some(info.transaction_index), + log_index: Some((next_log_index + index) as u64), + removed: false, + }) + .collect(), + }; + let receipt_with_bloom = + ReceiptWithBloom { receipt, logs_bloom: tx_receipt.as_receipt_with_bloom().logs_bloom }; + + let inner = match tx_receipt { + TypedReceipt::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), + TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), + TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), + TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedReceipt::EIP7702(_) => TypedReceipt::EIP7702(receipt_with_bloom), + TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: r.deposit_nonce, + deposit_receipt_version: r.deposit_receipt_version, + }), }; let inner = TransactionReceipt { + inner, transaction_hash: info.transaction_hash, - transaction_index: info.transaction_index.into(), + transaction_index: Some(info.transaction_index), + block_number: Some(block.header.number), + gas_used: info.gas_used, + contract_address: info.contract_address, + effective_gas_price, block_hash: Some(block_hash), - block_number: Some(block.header.number.as_u64().into()), from: info.from, to: info.to, - cumulative_gas_used, - gas_used: Some(gas_used), - contract_address: info.contract_address, - logs: { - let mut pre_receipts_log_index = None; - if !cumulative_receipts.is_empty() { - cumulative_receipts.truncate(cumulative_receipts.len() - 1); - pre_receipts_log_index = - Some(cumulative_receipts.iter().map(|_r| logs.len() as u32).sum::()); - } - logs.iter() - .enumerate() - .map(|(i, log)| Log { - address: log.address, - topics: log.topics.clone(), - data: log.data.clone(), - block_hash: Some(block_hash), - block_number: Some(block.header.number.as_u64().into()), - transaction_hash: Some(info.transaction_hash), - transaction_index: Some(info.transaction_index.into()), - log_index: Some(U256::from( - (pre_receipts_log_index.unwrap_or(0)) + i as u32, - )), - transaction_log_index: Some(U256::from(i)), - log_type: None, - removed: Some(false), - }) - .collect() - }, - status: Some(status_code.into()), - root: None, - logs_bloom, - transaction_type: transaction_type.map(Into::into), - effective_gas_price: Some(effective_gas_price), - other: OtherFields::default(), + blob_gas_price: Some(blob_gas_price), + blob_gas_used, }; - Some(MinedTransactionReceipt { inner, out: info.out }) + Some(MinedTransactionReceipt { inner, out: info.out.map(|o| o.0.into()) }) + } + + /// Returns the blocks receipts for the given number + pub async fn block_receipts( + &self, + number: BlockId, + ) -> Result>, BlockchainError> { + if let Some(receipts) = self.mined_block_receipts(number) { + return Ok(Some(receipts)); + } + + if let Some(fork) = self.get_fork() { + let number = match self.ensure_block_number(Some(number)).await { + Err(_) => return Ok(None), + Ok(n) => n, + }; + + if fork.predates_fork_inclusive(number) { + let receipts = fork.block_receipts(number).await?; + + return Ok(receipts); + } + } + + Ok(None) } pub async fn transaction_by_block_number_and_index( &self, number: BlockNumber, index: Index, - ) -> Result, BlockchainError> { - if let Some(hash) = self.mined_block_by_number(number).and_then(|b| b.hash) { - return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)) + ) -> Result, BlockchainError> { + if let Some(block) = self.mined_block_by_number(number) { + return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index)); } if let Some(fork) = self.get_fork() { @@ -1955,11 +2457,11 @@ impl Backend { pub async fn transaction_by_block_hash_and_index( &self, - hash: H256, + hash: B256, index: Index, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainError> { if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { @@ -1969,11 +2471,11 @@ impl Backend { Ok(None) } - fn mined_transaction_by_block_hash_and_index( + pub fn mined_transaction_by_block_hash_and_index( &self, - block_hash: H256, + block_hash: B256, index: Index, - ) -> Option { + ) -> Option { let (info, block, tx) = { let storage = self.blockchain.storage.read(); let block = storage.blocks.get(&block_hash).cloned()?; @@ -1994,21 +2496,21 @@ impl Backend { pub async fn transaction_by_hash( &self, - hash: H256, - ) -> Result, BlockchainError> { + hash: B256, + ) -> Result, BlockchainError> { trace!(target: "backend", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { - return Ok(tx) + return Ok(tx); } if let Some(fork) = self.get_fork() { - return Ok(fork.transaction_by_hash(hash).await?) + return fork.transaction_by_hash(hash).await.map_err(BlockchainError::AlloyForkProvider) } Ok(None) } - fn mined_transaction_by_hash(&self, hash: H256) -> 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, .. } = @@ -2033,59 +2535,49 @@ impl Backend { pub async fn prove_account_at( &self, address: Address, - values: Vec, + keys: Vec, block_request: Option, ) -> Result { - let account_key = H256::from(keccak256(address.as_bytes())); let block_number = block_request.as_ref().map(|r| r.block_number()); self.with_database_at(block_request, |block_db, _| { trace!(target: "backend", "get proof for {:?} at {:?}", address, block_number); - let (db, root) = block_db.maybe_as_hash_db().ok_or(BlockchainError::DataUnavailable)?; + let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + let account = db.get(&address).cloned().unwrap_or_default(); - let data: &dyn HashDB<_, _> = db.deref(); - let mut recorder = Recorder::new(); - let trie = RefTrieDB::new(&data, &root.0) - .map_err(|err| BlockchainError::TrieError(err.to_string()))?; + let mut builder = HashBuilder::default() + .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); - let maybe_account: Option = { - let acc_decoder = |bytes: &[u8]| { - rlp::decode(bytes).unwrap_or_else(|_| { - panic!("prove_account_at, could not query trie for account={:?}", &address) - }) - }; - let query = (&mut recorder, acc_decoder); - trie.get_with(account_key.as_bytes(), query) - .map_err(|err| BlockchainError::TrieError(err.to_string()))? - }; - let account = maybe_account.unwrap_or_default(); + for (key, account) in trie_accounts(db) { + builder.add_leaf(key, &account); + } - let proof = - recorder.drain().into_iter().map(|r| r.data).map(Into::into).collect::>(); + let _ = builder.root(); - let account_db = - block_db.maybe_account_db(address).ok_or(BlockchainError::DataUnavailable)?; + let proof = builder + .take_proof_nodes() + .into_nodes_sorted() + .into_iter() + .map(|(_, v)| v) + .collect(); + let storage_proofs = prove_storage(&account.storage, &keys); let account_proof = AccountProof { address, - balance: account.balance, - nonce: account.nonce.as_u64().into(), - code_hash: account.code_hash, - storage_hash: account.storage_root, + balance: account.info.balance, + nonce: account.info.nonce, + code_hash: account.info.code_hash, + storage_hash: storage_root(&account.storage), account_proof: proof, - storage_proof: values + storage_proof: keys .into_iter() - .map(|storage_key| { - let key = H256::from(keccak256(storage_key)); - prove_storage(&account, &account_db.0, key).map( - |(storage_proof, storage_value)| StorageProof { - key, - value: storage_value.into_uint(), - proof: storage_proof.into_iter().map(Into::into).collect(), - }, - ) + .zip(storage_proofs) + .map(|(key, proof)| { + let storage_key: U256 = key.into(); + let value = account.storage.get(&storage_key).cloned().unwrap_or_default(); + StorageProof { key: JsonStorageKey::Hash(key), value, proof } }) - .collect::, _>>()?, + .collect(), }; Ok(account_proof) @@ -2102,7 +2594,7 @@ impl Backend { } /// Notifies all `new_block_listeners` about the new block - fn notify_on_new_block(&self, header: Header, hash: H256) { + fn notify_on_new_block(&self, header: Header, hash: B256) { // cleanup closed notification streams first, if the channel is closed we can remove the // sender half for the set self.new_block_listeners.lock().retain(|tx| !tx.is_closed()); @@ -2113,26 +2605,95 @@ impl Backend { .lock() .retain(|tx| tx.unbounded_send(notification.clone()).is_ok()); } + + /// Reorg the chain to a common height and execute blocks to build new chain. + /// + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. + /// + /// Finally, `do_mine_block` is called to create the new chain. + pub async fn reorg( + &self, + depth: u64, + tx_pairs: HashMap>>, + common_block: Block, + ) -> Result<(), BlockchainError> { + self.rollback(common_block).await?; + // Create the new reorged chain, filling the blocks with transactions if supplied + for i in 0..depth { + let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); + let outcome = self.do_mine_block(to_be_mined).await; + node_info!( + " Mined reorg block number {}. With {} valid txs and with invalid {} txs", + outcome.block_number, + outcome.included.len(), + outcome.invalid.len() + ); + } + + Ok(()) + } + + /// Rollback the chain to a common height. + /// + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. + pub async fn rollback(&self, common_block: Block) -> Result<(), BlockchainError> { + // Get the database at the common block + let common_state = { + let mut state = self.states.write(); + let state_db = state + .get(&common_block.header.hash_slow()) + .ok_or(BlockchainError::DataUnavailable)?; + let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + db_full.clone() + }; + + { + // Set state to common state + self.db.write().await.clear(); + for (address, acc) in common_state { + for (key, value) in acc.storage { + self.db.write().await.set_storage_at(address, key.into(), value.into())?; + } + self.db.write().await.insert_account(address, acc.info); + } + } + + { + // Unwind the storage back to the common ancestor + self.blockchain + .storage + .write() + .unwind_to(common_block.header.number, common_block.header.hash_slow()); + + // Set environment back to common block + let mut env = self.env.write(); + env.block.number = U256::from(common_block.header.number); + env.block.timestamp = U256::from(common_block.header.timestamp); + env.block.gas_limit = U256::from(common_block.header.gas_limit); + env.block.difficulty = common_block.header.difficulty; + env.block.prevrandao = Some(common_block.header.mix_hash); + + self.time.reset(env.block.timestamp.to::()); + } + Ok(()) + } } /// Get max nonce from transaction pool by address fn get_pool_transactions_nonce( pool_transactions: &[Arc], - address: ethers::types::H160, -) -> Option { - let highest_nonce_tx = pool_transactions + address: Address, +) -> Option { + if let Some(highest_nonce) = pool_transactions .iter() .filter(|tx| *tx.pending_transaction.sender() == address) - .reduce(|accum, item| { - let nonce = item.pending_transaction.nonce(); - if nonce.gt(accum.pending_transaction.nonce()) { - item - } else { - accum - } - }); - if let Some(highest_nonce_tx) = highest_nonce_tx { - return Some(highest_nonce_tx.pending_transaction.nonce().saturating_add(U256::one())) + .map(|tx| tx.pending_transaction.nonce()) + .max() + { + let tx_count = highest_nonce.saturating_add(1); + return Some(tx_count) } None } @@ -2153,53 +2714,54 @@ impl TransactionValidator for Backend { &self, pending: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; if let Some(tx_chain_id) = tx.chain_id() { let chain_id = self.chain_id(); - if chain_id != tx_chain_id.into() { + if chain_id.to::() != tx_chain_id { if let Some(legacy) = tx.as_legacy() { // - if env.cfg.spec_id >= SpecId::SPURIOUS_DRAGON && - !legacy.meets_eip155(chain_id.as_u64()) + if env.handler_cfg.spec_id >= SpecId::SPURIOUS_DRAGON && + legacy.tx().chain_id.is_none() { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); - return Err(InvalidTransactionError::IncompatibleEIP155) + return Err(InvalidTransactionError::IncompatibleEIP155); } } else { warn!(target: "backend", ?chain_id, ?tx_chain_id, "invalid chain id"); - return Err(InvalidTransactionError::InvalidChainId) + return Err(InvalidTransactionError::InvalidChainId); } } } - if tx.gas_limit() < MIN_TRANSACTION_GAS { + if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 { warn!(target: "backend", "[{:?}] gas too low", tx.hash()); - return Err(InvalidTransactionError::GasTooLow) + return Err(InvalidTransactionError::GasTooLow); } // Check gas limit, iff block gas limit is set. - if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.into() { + if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.to() { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { detail: String::from("tx.gas_limit > env.block.gas_limit"), - })) + })); } // check nonce - let nonce: u64 = - (*tx.nonce()).try_into().map_err(|_| InvalidTransactionError::NonceMaxValue)?; - if nonce < account.nonce { + let is_deposit_tx = + matches!(&pending.transaction.transaction, TypedTransaction::Deposit(_)); + let nonce = tx.nonce(); + if nonce < account.nonce && !is_deposit_tx { warn!(target: "backend", "[{:?}] nonce too low", tx.hash()); - return Err(InvalidTransactionError::NonceTooLow) + return Err(InvalidTransactionError::NonceTooLow); } - if (env.cfg.spec_id as u8) >= (SpecId::LONDON as u8) { - if tx.gas_price() < env.block.basefee.into() { + if (env.handler_cfg.spec_id as u8) >= (SpecId::LONDON as u8) { + if tx.gas_price() < env.block.basefee.to() && !is_deposit_tx { warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.block.basefee); - return Err(InvalidTransactionError::FeeCapTooLow) + return Err(InvalidTransactionError::FeeCapTooLow); } if let (Some(max_priority_fee_per_gas), Some(max_fee_per_gas)) = @@ -2207,24 +2769,75 @@ impl TransactionValidator for Backend { { if max_priority_fee_per_gas > max_fee_per_gas { warn!(target: "backend", "max priority fee per gas={}, too high, max fee per gas={}", max_priority_fee_per_gas, max_fee_per_gas); - return Err(InvalidTransactionError::TipAboveFeeCap) + return Err(InvalidTransactionError::TipAboveFeeCap); } } } + // EIP-4844 Cancun hard fork validation steps + if env.spec_id() >= SpecId::CANCUN && tx.transaction.is_eip4844() { + // Light checks first: see if the blob fee cap is too low. + if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas { + if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price { + if max_fee_per_blob_gas < blob_gas_and_price.blob_gasprice { + warn!(target: "backend", "max fee per blob gas={}, too low, block blob gas price={}", max_fee_per_blob_gas, blob_gas_and_price.blob_gasprice); + return Err(InvalidTransactionError::BlobFeeCapTooLow); + } + } + } + + // Heavy (blob validation) checks + let tx = match &tx.transaction { + TypedTransaction::EIP4844(tx) => tx.tx(), + _ => unreachable!(), + }; + + let blob_count = tx.tx().blob_versioned_hashes.len(); + + // Ensure there are blob hashes. + if blob_count == 0 { + return Err(InvalidTransactionError::NoBlobHashes) + } + + // Ensure the tx does not exceed the max blobs per block. + if blob_count > MAX_BLOBS_PER_BLOCK { + return Err(InvalidTransactionError::TooManyBlobs(blob_count)) + } + + // Check for any blob validation errors + if let Err(err) = tx.validate(env.cfg.kzg_settings.get()) { + return Err(InvalidTransactionError::BlobTransactionValidationError(err)) + } + } + let max_cost = tx.max_cost(); let value = tx.value(); - // check sufficient funds: `gas * price + value` - let req_funds = max_cost.checked_add(value).ok_or_else(|| { - warn!(target: "backend", "[{:?}] cost too high", - tx.hash()); - InvalidTransactionError::InsufficientFunds - })?; - if account.balance < req_funds.into() { - warn!(target: "backend", "[{:?}] insufficient allowance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); - return Err(InvalidTransactionError::InsufficientFunds) + match &tx.transaction { + TypedTransaction::Deposit(deposit_tx) => { + // Deposit transactions + // https://specs.optimism.io/protocol/deposits.html#execution + // 1. no gas cost check required since already have prepaid gas from L1 + // 2. increment account balance by deposited amount before checking for sufficient + // funds `tx.value <= existing account value + deposited value` + if value > account.balance + deposit_tx.mint { + warn!(target: "backend", "[{:?}] insufficient balance={}, required={} account={:?}", tx.hash(), account.balance + deposit_tx.mint, value, *pending.sender()); + return Err(InvalidTransactionError::InsufficientFunds); + } + } + _ => { + // check sufficient funds: `gas * price + value` + let req_funds = max_cost.checked_add(value.to()).ok_or_else(|| { + warn!(target: "backend", "[{:?}] cost too high", tx.hash()); + InvalidTransactionError::InsufficientFunds + })?; + if account.balance < U256::from(req_funds) { + warn!(target: "backend", "[{:?}] insufficient allowance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); + return Err(InvalidTransactionError::InsufficientFunds); + } + } } + Ok(()) } @@ -2232,100 +2845,196 @@ impl TransactionValidator for Backend { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError> { self.validate_pool_transaction_for(tx, account, env)?; - if tx.nonce().as_u64() > account.nonce { - return Err(InvalidTransactionError::NonceTooHigh) + if tx.nonce() > account.nonce { + return Err(InvalidTransactionError::NonceTooHigh); } Ok(()) } } -/// Creates a `Transaction` as it's expected for the `eth` RPC api from storage data +/// Creates a `AnyRpcTransaction` as it's expected for the `eth` RPC api from storage data #[allow(clippy::too_many_arguments)] pub fn transaction_build( - tx_hash: Option, + tx_hash: Option, eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, - base_fee: Option, -) -> Transaction { - let mut transaction: Transaction = eth_transaction.clone().into(); + base_fee: Option, +) -> AnyRpcTransaction { + if let TypedTransaction::Deposit(ref deposit_tx) = eth_transaction.transaction { + let DepositTransaction { + nonce, + source_hash, + from, + kind, + mint, + gas_limit, + is_system_tx, + input, + value, + } = deposit_tx.clone(); + + let dep_tx = TxDeposit { + source_hash, + input, + from, + mint: Some(mint.to()), + to: kind, + is_system_transaction: is_system_tx, + value, + gas_limit, + }; - if eth_transaction.is_dynamic_fee() { - if block.is_none() && info.is_none() { - // transaction is not mined yet, gas price is considered just `max_fee_per_gas` - transaction.gas_price = transaction.max_fee_per_gas; - } else { - // if transaction is already mined, gas price is considered base fee + priority fee: the - // effective gas price. - let base_fee = base_fee.unwrap_or(U256::zero()); - let max_priority_fee_per_gas = - transaction.max_priority_fee_per_gas.unwrap_or(U256::zero()); - transaction.gas_price = Some( - base_fee.checked_add(max_priority_fee_per_gas).unwrap_or_else(U256::max_value), - ); + let ser = serde_json::to_value(&dep_tx).expect("could not serialize TxDeposit"); + let maybe_deposit_fields = OtherFields::try_from(ser); + + match maybe_deposit_fields { + Ok(mut fields) => { + // Add zeroed signature fields for backwards compatibility + // https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type + fields.insert("v".to_string(), serde_json::to_value("0x0").unwrap()); + fields.insert("r".to_string(), serde_json::to_value(B256::ZERO).unwrap()); + fields.insert(String::from("s"), serde_json::to_value(B256::ZERO).unwrap()); + fields.insert( + String::from("nonce"), + serde_json::to_value(format!("0x{nonce}")).unwrap(), + ); + + let inner = UnknownTypedTransaction { + ty: AnyTxType(DEPOSIT_TX_TYPE_ID), + fields, + memo: Default::default(), + }; + + let envelope = AnyTxEnvelope::Unknown(UnknownTxEnvelope { + hash: eth_transaction.hash(), + inner, + }); + + let tx = Transaction { + inner: envelope, + block_hash: block + .as_ref() + .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), + block_number: block.as_ref().map(|block| block.header.number), + transaction_index: info.as_ref().map(|info| info.transaction_index), + effective_gas_price: None, + from, + }; + + return WithOtherFields::new(tx); + } + Err(_) => { + error!(target: "backend", "failed to serialize deposit transaction"); + } } - } else { - transaction.max_fee_per_gas = None; - transaction.max_priority_fee_per_gas = None; } - transaction.block_hash = - block.as_ref().map(|block| H256::from(keccak256(&rlp::encode(&block.header)))); + let mut transaction: Transaction = eth_transaction.clone().into(); - transaction.block_number = block.as_ref().map(|block| block.header.number.as_u64().into()); + let effective_gas_price = if !eth_transaction.is_dynamic_fee() { + transaction.effective_gas_price(base_fee) + } else if block.is_none() && info.is_none() { + // transaction is not mined yet, gas price is considered just `max_fee_per_gas` + transaction.max_fee_per_gas() + } else { + // if transaction is already mined, gas price is considered base fee + priority + // fee: the effective gas price. + let base_fee = base_fee.map_or(0u128, |g| g as u128); + let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas().unwrap_or(0); - transaction.transaction_index = info.as_ref().map(|status| status.transaction_index.into()); + base_fee.saturating_add(max_priority_fee_per_gas) + }; - // need to check if the signature of the transaction is impersonated, if so then we - // can't recover the sender, instead we use the sender from the executed transaction and set the - // impersonated hash. - if eth_transaction.is_impersonated() { - transaction.from = info.as_ref().map(|info| info.from).unwrap_or_default(); - transaction.hash = eth_transaction.impersonated_hash(transaction.from); - } else { - transaction.from = eth_transaction.recover().expect("can recover signed tx"); - } + transaction.effective_gas_price = Some(effective_gas_price); - // if a specific hash was provided we update the transaction's hash - // This is important for impersonated transactions since they all use the `BYPASS_SIGNATURE` - // which would result in different hashes - // Note: for impersonated transactions this only concerns pending transactions because there's - // no `info` yet. - if let Some(tx_hash) = tx_hash { - transaction.hash = tx_hash; - } + let envelope = transaction.inner; - transaction.to = info.as_ref().map_or(eth_transaction.to().cloned(), |status| status.to); + // if a specific hash was provided we update the transaction's hash + // This is important for impersonated transactions since they all use the + // `BYPASS_SIGNATURE` which would result in different hashes + // Note: for impersonated transactions this only concerns pending transactions because + // there's // no `info` yet. + let hash = tx_hash.unwrap_or(*envelope.tx_hash()); + + let envelope = match envelope { + TxEnvelope::Legacy(signed_tx) => { + let (t, sig, _) = signed_tx.into_parts(); + let new_signed = Signed::new_unchecked(t, sig, hash); + AnyTxEnvelope::Ethereum(TxEnvelope::Legacy(new_signed)) + } + TxEnvelope::Eip1559(signed_tx) => { + let (t, sig, _) = signed_tx.into_parts(); + let new_signed = Signed::new_unchecked(t, sig, hash); + AnyTxEnvelope::Ethereum(TxEnvelope::Eip1559(new_signed)) + } + TxEnvelope::Eip2930(signed_tx) => { + let (t, sig, _) = signed_tx.into_parts(); + let new_signed = Signed::new_unchecked(t, sig, hash); + AnyTxEnvelope::Ethereum(TxEnvelope::Eip2930(new_signed)) + } + TxEnvelope::Eip4844(signed_tx) => { + let (t, sig, _) = signed_tx.into_parts(); + let new_signed = Signed::new_unchecked(t, sig, hash); + AnyTxEnvelope::Ethereum(TxEnvelope::Eip4844(new_signed)) + } + TxEnvelope::Eip7702(signed_tx) => { + let (t, sig, _) = signed_tx.into_parts(); + let new_signed = Signed::new_unchecked(t, sig, hash); + AnyTxEnvelope::Ethereum(TxEnvelope::Eip7702(new_signed)) + } + }; - transaction + let tx = Transaction { + inner: envelope, + block_hash: block + .as_ref() + .map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))), + block_number: block.as_ref().map(|block| block.header.number), + transaction_index: info.as_ref().map(|info| info.transaction_index), + from: eth_transaction.recover().expect("can recover signed tx"), + // deprecated + effective_gas_price: Some(effective_gas_price), + }; + WithOtherFields::new(tx) } -/// Prove a storage key's existence or nonexistence in the account's storage -/// trie. +/// Prove a storage key's existence or nonexistence in the account's storage trie. +/// /// `storage_key` is the hash of the desired storage key, meaning /// this will only work correctly under a secure trie. /// `storage_key` == keccak(key) -pub fn prove_storage( - acc: &BasicAccount, - data: &AsHashDB, - storage_key: H256, -) -> Result<(Vec>, H256), BlockchainError> { - let data: &dyn HashDB<_, _> = data.deref(); - let mut recorder = Recorder::new(); - let trie = RefTrieDB::new(&data, &acc.storage_root.0) - .map_err(|err| BlockchainError::TrieError(err.to_string())) - .unwrap(); - - let item: U256 = { - let decode_value = |bytes: &[u8]| rlp::decode(bytes).expect("decoding db value failed"); - let query = (&mut recorder, decode_value); - trie.get_with(storage_key.as_bytes(), query) - .map_err(|err| BlockchainError::TrieError(err.to_string()))? - .unwrap_or_else(U256::zero) - }; +pub fn prove_storage(storage: &HashMap, keys: &[B256]) -> Vec> { + let keys: Vec<_> = keys.iter().map(|key| Nibbles::unpack(keccak256(key))).collect(); + + let mut builder = HashBuilder::default().with_proof_retainer(ProofRetainer::new(keys.clone())); + + for (key, value) in trie_storage(storage) { + builder.add_leaf(key, &value); + } + + let _ = builder.root(); - Ok((recorder.drain().into_iter().map(|r| r.data).collect(), BigEndianHash::from_uint(&item))) + let mut proofs = Vec::new(); + let all_proof_nodes = builder.take_proof_nodes(); + + for proof_key in keys { + // Iterate over all proof nodes and find the matching ones. + // The filtered results are guaranteed to be in order. + let matching_proof_nodes = + all_proof_nodes.matching_nodes_sorted(&proof_key).into_iter().map(|(_, node)| node); + proofs.push(matching_proof_nodes.collect()); + } + + proofs +} + +pub fn is_arbitrum(chain_id: u64) -> bool { + if let Ok(chain) = NamedChain::try_from(chain_id) { + return chain.is_arbitrum() + } + false } diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index b36f8bf8df923..9d66fac289dec 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -1,110 +1,73 @@ //! Support for generating the state root for memdb storage -use crate::eth::{backend::db::AsHashDB, error::BlockchainError}; -use anvil_core::eth::{state::StateOverride, trie::RefSecTrieDBMut}; -use bytes::Bytes; -use ethers::{ - abi::ethereum_types::BigEndianHash, - types::H256, - utils::{rlp, rlp::RlpStream}, -}; +use crate::eth::error::BlockchainError; +use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_rlp::Encodable; +use alloy_rpc_types::state::StateOverride; +use alloy_trie::{HashBuilder, Nibbles}; use foundry_evm::{ - executor::{backend::DatabaseError, DatabaseRef}, + backend::DatabaseError, revm::{ - db::{CacheDB, DbAccount}, - primitives::{AccountInfo, Bytecode, Log, B160, U256 as rU256}, + db::{CacheDB, DatabaseRef, DbAccount}, + primitives::{AccountInfo, Bytecode, HashMap}, }, - utils::{b160_to_h160, b256_to_h256, ru256_to_u256, u256_to_ru256}, - HashMap as Map, }; -use memory_db::HashKey; -use trie_db::TrieMut; -/// Returns the log hash for all `logs` -/// -/// The log hash is `keccak(rlp(logs[]))`, -pub fn log_rlp_hash(logs: Vec) -> H256 { - let mut stream = RlpStream::new(); - stream.begin_unbounded_list(); - for log in logs { - let topics = log.topics.into_iter().map(b256_to_h256).collect::>(); - stream.begin_list(3); - stream.append(&b160_to_h160(log.address)); - stream.append_list(&topics); - stream.append(&log.data); +pub fn build_root(values: impl IntoIterator)>) -> B256 { + let mut builder = HashBuilder::default(); + for (key, value) in values { + builder.add_leaf(key, value.as_ref()); } - stream.finalize_unbounded_list(); - let out = stream.out().freeze(); - - let out = ethers::utils::keccak256(out); - H256::from_slice(out.as_slice()) + builder.root() } -/// Returns storage trie of an account as `HashDB` -pub fn storage_trie_db(storage: &Map) -> (AsHashDB, H256) { - // Populate DB with full trie from entries. - let (db, root) = { - let mut db = , _>>::default(); - let mut root = Default::default(); - { - let mut trie = RefSecTrieDBMut::new(&mut db, &mut root); - for (k, v) in storage.iter().filter(|(_k, v)| *v != &rU256::from(0)) { - let mut temp: [u8; 32] = [0; 32]; - ru256_to_u256(*k).to_big_endian(&mut temp); - let key = H256::from(temp); - let value = rlp::encode(v); - trie.insert(key.as_bytes(), value.as_ref()).unwrap(); - } - } - (db, root) - }; - - (Box::new(db), H256::from(root)) +/// Builds state root from the given accounts +pub fn state_root(accounts: &HashMap) -> B256 { + build_root(trie_accounts(accounts)) } -/// Returns the account data as `HashDB` -pub fn trie_hash_db(accounts: &Map) -> (AsHashDB, H256) { - let accounts = trie_accounts(accounts); +/// Builds storage root from the given storage +pub fn storage_root(storage: &HashMap) -> B256 { + build_root(trie_storage(storage)) +} - // Populate DB with full trie from entries. - let (db, root) = { - let mut db = , _>>::default(); - let mut root = Default::default(); - { - let mut trie = RefSecTrieDBMut::new(&mut db, &mut root); - for (address, value) in accounts { - trie.insert(address.as_ref(), value.as_ref()).unwrap(); - } - } - (db, root) - }; +/// Builds iterator over stored key-value pairs ready for storage trie root calculation. +pub fn trie_storage(storage: &HashMap) -> Vec<(Nibbles, Vec)> { + let mut storage = storage + .iter() + .map(|(key, value)| { + let data = alloy_rlp::encode(value); + (Nibbles::unpack(keccak256(key.to_be_bytes::<32>())), data) + }) + .collect::>(); + storage.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); - (Box::new(db), H256::from(root)) + storage } -/// Returns all RLP-encoded Accounts -pub fn trie_accounts(accounts: &Map) -> Vec<(B160, Bytes)> { - accounts +/// Builds iterator over stored key-value pairs ready for account trie root calculation. +pub fn trie_accounts(accounts: &HashMap) -> Vec<(Nibbles, Vec)> { + let mut accounts = accounts .iter() .map(|(address, account)| { - let storage_root = trie_account_rlp(&account.info, &account.storage); - (*address, storage_root) + let data = trie_account_rlp(&account.info, &account.storage); + (Nibbles::unpack(keccak256(*address)), data) }) - .collect() -} + .collect::>(); + accounts.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); -pub fn state_merkle_trie_root(accounts: &Map) -> H256 { - trie_hash_db(accounts).1 + accounts } /// Returns the RLP for this account. -pub fn trie_account_rlp(info: &AccountInfo, storage: &Map) -> Bytes { - let mut stream = RlpStream::new_list(4); - stream.append(&info.nonce); - stream.append(&info.balance); - stream.append(&storage_trie_db(storage).1); - stream.append(&info.code_hash.as_bytes()); - stream.out().freeze() +pub fn trie_account_rlp(info: &AccountInfo, storage: &HashMap) -> Vec { + let mut out: Vec = Vec::new(); + let list: [&dyn Encodable; 4] = + [&info.nonce, &info.balance, &storage_root(storage), &info.code_hash]; + + alloy_rlp::encode_list::<_, dyn Encodable>(&list, &mut out); + + out } /// Applies the given state overrides to the state, returning a new CacheDB state @@ -117,7 +80,7 @@ where { let mut cache_db = CacheDB::new(state); for (account, account_overrides) in overrides.iter() { - let mut account_info = cache_db.basic((*account).into())?.unwrap_or_default(); + let mut account_info = cache_db.basic_ref(*account)?.unwrap_or_default(); if let Some(nonce) = account_overrides.nonce { account_info.nonce = nonce; @@ -126,10 +89,10 @@ where account_info.code = Some(Bytecode::new_raw(code.to_vec().into())); } if let Some(balance) = account_overrides.balance { - account_info.balance = balance.into(); + account_info.balance = balance; } - cache_db.insert_account_info((*account).into(), account_info); + cache_db.insert_account_info(*account, account_info); // We ensure that not both state and state_diff are set. // If state is set, we must mark the account as "NewlyCreated", so that the old storage @@ -143,22 +106,16 @@ where (None, None) => (), (Some(new_account_state), None) => { cache_db.replace_account_storage( - (*account).into(), + *account, new_account_state .iter() - .map(|(key, value)| { - (u256_to_ru256(key.into_uint()), u256_to_ru256(value.into_uint())) - }) + .map(|(key, value)| ((*key).into(), (*value).into())) .collect(), )?; } (None, Some(account_state_diff)) => { for (key, value) in account_state_diff.iter() { - cache_db.insert_account_storage( - (*account).into(), - key.into_uint().into(), - value.into_uint().into(), - )?; + cache_db.insert_account_storage(*account, (*key).into(), (*value).into())?; } } }; diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 17f65910c8144..276cba95c62e8 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -1,44 +1,62 @@ //! In-memory blockchain storage use crate::eth::{ backend::{ - db::{MaybeHashDatabase, StateDb}, + db::{ + MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates, + SerializableTransaction, StateDb, + }, mem::cache::DiskStateCache, }, + error::BlockchainError, pool::transactions::PoolTransaction, }; +use alloy_consensus::constants::EMPTY_WITHDRAWALS; +use alloy_eips::eip7685::EMPTY_REQUESTS_HASH; +use alloy_primitives::{ + map::{B256HashMap, HashMap}, + Bytes, B256, U256, U64, +}; +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}, - receipt::TypedReceipt, - transaction::{MaybeImpersonatedTransaction, TransactionInfo}, + transaction::{MaybeImpersonatedTransaction, ReceiptResponse, TransactionInfo, TypedReceipt}, }; -use ethers::{ - prelude::{BlockId, BlockNumber, DefaultFrame, Trace, H256, H256 as TxHash, U64}, - types::{ActionType, Bytes, GethDebugTracingOptions, TransactionReceipt, U256}, +use anvil_rpc::error::RpcError; +use foundry_evm::{ + backend::MemDb, + revm::primitives::Env, + traces::{ + CallKind, FourByteInspector, GethTraceBuilder, ParityTraceBuilder, TracingInspectorConfig, + }, }; -use foundry_evm::revm::{interpreter::InstructionResult, primitives::Env}; use parking_lot::RwLock; -use std::{ - collections::{HashMap, VecDeque}, - fmt, - sync::Arc, - time::Duration, -}; +use revm::primitives::SpecId; +use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration}; +// use yansi::Paint; // === various limits in number of blocks === -const DEFAULT_HISTORY_LIMIT: usize = 500; +pub const DEFAULT_HISTORY_LIMIT: usize = 500; const MIN_HISTORY_LIMIT: usize = 10; // 1hr of up-time at lowest 1s interval const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600; -// === impl DiskStateCache === - /// Represents the complete state of single block pub struct InMemoryBlockStates { /// The states at a certain block - states: HashMap, + states: B256HashMap, /// states which data is moved to disk - on_disk_states: HashMap, + on_disk_states: B256HashMap, /// How many states to store at most in_memory_limit: usize, /// minimum amount of states we keep in memory @@ -48,24 +66,22 @@ pub struct InMemoryBlockStates { /// Limiting the states will prevent disk blow up, especially in interval mining mode max_on_disk_limit: usize, /// the oldest states written to disk - oldest_on_disk: VecDeque, + oldest_on_disk: VecDeque, /// all states present, used to enforce `in_memory_limit` - present: VecDeque, + present: VecDeque, /// Stores old states on disk disk_cache: DiskStateCache, } -// === impl InMemoryBlockStates === - impl InMemoryBlockStates { /// Creates a new instance with limited slots - pub fn new(limit: usize) -> Self { + pub fn new(in_memory_limit: usize, on_disk_limit: usize) -> Self { Self { states: Default::default(), on_disk_states: Default::default(), - in_memory_limit: limit, - min_in_memory_limit: limit.min(MIN_HISTORY_LIMIT), - max_on_disk_limit: MAX_ON_DISK_HISTORY_LIMIT, + in_memory_limit, + min_in_memory_limit: in_memory_limit.min(MIN_HISTORY_LIMIT), + max_on_disk_limit: on_disk_limit, oldest_on_disk: Default::default(), present: Default::default(), disk_cache: Default::default(), @@ -78,6 +94,12 @@ impl InMemoryBlockStates { self } + /// Configures the path on disk where the states will cached. + pub fn disk_path(mut self, path: PathBuf) -> Self { + self.disk_cache = self.disk_cache.with_path(path); + self + } + /// This modifies the `limit` what to keep stored in memory. /// /// This will ensure the new limit adjusts based on the block time. @@ -108,7 +130,7 @@ impl InMemoryBlockStates { /// the number of states/blocks until we reached the `min_limit`. /// /// When a state that was previously written to disk is requested, it is simply read from disk. - pub fn insert(&mut self, hash: H256, state: StateDb) { + pub fn insert(&mut self, hash: B256, state: StateDb) { if !self.is_memory_only() && self.present.len() >= self.in_memory_limit { // once we hit the max limit we gradually decrease it self.in_memory_limit = @@ -133,8 +155,8 @@ impl InMemoryBlockStates { { // only write to disk if supported if !self.is_memory_only() { - let snapshot = state.0.clear_into_snapshot(); - self.disk_cache.write(hash, snapshot); + let state_snapshot = state.0.clear_into_state_snapshot(); + self.disk_cache.write(hash, state_snapshot); self.on_disk_states.insert(hash, state); self.oldest_on_disk.push_back(hash); } @@ -152,12 +174,12 @@ impl InMemoryBlockStates { } /// Returns the state for the given `hash` if present - pub fn get(&mut self, hash: &H256) -> Option<&StateDb> { + pub fn get(&mut self, hash: &B256) -> Option<&StateDb> { self.states.get(hash).or_else(|| { if let Some(state) = self.on_disk_states.get_mut(hash) { if let Some(cached) = self.disk_cache.read(*hash) { - state.init_from_snapshot(cached); - return Some(state) + state.init_from_state_snapshot(cached); + return Some(state); } } None @@ -178,6 +200,34 @@ impl InMemoryBlockStates { self.disk_cache.remove(on_disk) } } + + /// Serialize all states to a list of serializable historical states + pub fn serialized_states(&mut self) -> SerializableHistoricalStates { + // Get in-memory states + let mut states = self + .states + .iter_mut() + .map(|(hash, state)| (*hash, state.serialize_state())) + .collect::>(); + + // Get on-disk state snapshots + self.on_disk_states.iter().for_each(|(hash, _)| { + if let Some(state_snapshot) = self.disk_cache.read(*hash) { + states.push((*hash, state_snapshot)); + } + }); + + SerializableHistoricalStates::new(states) + } + + /// Load states from serialized data + pub fn load_states(&mut self, states: SerializableHistoricalStates) { + for (hash, state_snapshot) in states { + let mut state_db = StateDb::new(MemDb::default()); + state_db.init_from_state_snapshot(state_snapshot); + self.insert(hash, state_db); + } + } } impl fmt::Debug for InMemoryBlockStates { @@ -195,7 +245,7 @@ impl fmt::Debug for InMemoryBlockStates { impl Default for InMemoryBlockStates { fn default() -> Self { // enough in memory to store `DEFAULT_HISTORY_LIMIT` blocks in memory - Self::new(DEFAULT_HISTORY_LIMIT) + Self::new(DEFAULT_HISTORY_LIMIT, MAX_ON_DISK_HISTORY_LIMIT) } } @@ -203,42 +253,57 @@ impl Default for InMemoryBlockStates { #[derive(Clone)] pub struct BlockchainStorage { /// all stored blocks (block hash -> block) - pub blocks: HashMap, + pub blocks: B256HashMap, /// mapping from block number -> block hash - pub hashes: HashMap, + pub hashes: HashMap, /// The current best hash - pub best_hash: H256, + pub best_hash: B256, /// The current best block number pub best_number: U64, /// genesis hash of the chain - pub genesis_hash: H256, + pub genesis_hash: B256, /// Mapping from the transaction hash to a tuple containing the transaction as well as the /// transaction receipt - pub transactions: HashMap, + pub transactions: B256HashMap, /// The total difficulty of the chain until this block pub total_difficulty: U256, } impl BlockchainStorage { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { + pub fn new(env: &Env, spec_id: SpecId, base_fee: Option, timestamp: u64) -> Self { + let is_shanghai = spec_id >= SpecId::SHANGHAI; + let is_cancun = spec_id >= SpecId::CANCUN; + let is_prague = spec_id >= SpecId::PRAGUE; + // create a dummy genesis block let partial_header = PartialHeader { timestamp, base_fee, - gas_limit: env.block.gas_limit.into(), - beneficiary: env.block.coinbase.into(), - difficulty: env.block.difficulty.into(), + gas_limit: env.block.gas_limit.to::(), + beneficiary: env.block.coinbase, + difficulty: env.block.difficulty, + blob_gas_used: env.block.blob_excess_gas_and_price.as_ref().map(|_| 0), + excess_blob_gas: env.block.get_blob_excess_gas(), + + parent_beacon_block_root: is_cancun.then_some(Default::default()), + withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS), + requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH), ..Default::default() }; - let block = Block::new::(partial_header, vec![], vec![]); - let genesis_hash = block.header.hash(); + let block = Block::new::(partial_header, vec![]); + let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; - let best_number: U64 = 0u64.into(); + let best_number: U64 = U64::from(0u64); + let mut blocks = B256HashMap::default(); + blocks.insert(genesis_hash, block); + + let mut hashes = HashMap::default(); + hashes.insert(best_number, genesis_hash); Self { - blocks: HashMap::from([(genesis_hash, block)]), - hashes: HashMap::from([(best_number, genesis_hash)]), + blocks, + hashes, best_hash, best_number, genesis_hash, @@ -247,18 +312,38 @@ impl BlockchainStorage { } } - pub fn forked(block_number: u64, block_hash: H256, total_difficulty: U256) -> Self { - BlockchainStorage { - blocks: Default::default(), - hashes: HashMap::from([(block_number.into(), block_hash)]), + pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { + let mut hashes = HashMap::default(); + hashes.insert(U64::from(block_number), block_hash); + + Self { + blocks: B256HashMap::default(), + hashes, best_hash: block_hash, - best_number: block_number.into(), + best_number: U64::from(block_number), genesis_hash: Default::default(), transactions: Default::default(), total_difficulty, } } + /// Unwind the chain state back to the given block in storage. + /// + /// The block identified by `block_number` and `block_hash` is __non-inclusive__, i.e. it will + /// remain in the state. + pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) { + let best_num: u64 = self.best_number.try_into().unwrap_or(0); + for i in (block_number + 1)..=best_num { + if let Some(hash) = self.hashes.remove(&U64::from(i)) { + if let Some(block) = self.blocks.remove(&hash) { + self.remove_block_transactions_by_number(block.header.number); + } + } + } + self.best_hash = block_hash; + self.best_number = U64::from(block_number); + } + #[allow(unused)] pub fn empty() -> Self { Self { @@ -271,35 +356,79 @@ impl BlockchainStorage { total_difficulty: Default::default(), } } -} -// === impl BlockchainStorage === + /// Removes all stored transactions for the given block number + pub fn remove_block_transactions_by_number(&mut self, num: u64) { + if let Some(hash) = self.hashes.get(&(U64::from(num))).copied() { + self.remove_block_transactions(hash); + } + } + + /// Removes all stored transactions for the given block hash + pub fn remove_block_transactions(&mut self, block_hash: B256) { + if let Some(block) = self.blocks.get_mut(&block_hash) { + for tx in block.transactions.iter() { + self.transactions.remove(&tx.hash()); + } + block.transactions.clear(); + } + } +} impl BlockchainStorage { - /// Returns the hash for [BlockNumber] - pub fn hash(&self, number: BlockNumber) -> Option { + /// Returns the hash for [BlockNumberOrTag] + pub fn hash(&self, number: BlockNumberOrTag) -> Option { let slots_in_an_epoch = U64::from(32u64); match number { - BlockNumber::Latest => Some(self.best_hash), - BlockNumber::Earliest => Some(self.genesis_hash), - BlockNumber::Pending => None, - BlockNumber::Number(num) => self.hashes.get(&num).copied(), - BlockNumber::Safe => { + BlockNumberOrTag::Latest => Some(self.best_hash), + BlockNumberOrTag::Earliest => Some(self.genesis_hash), + BlockNumberOrTag::Pending => None, + BlockNumberOrTag::Number(num) => self.hashes.get(&U64::from(num)).copied(), + BlockNumberOrTag::Safe => { if self.best_number > (slots_in_an_epoch) { self.hashes.get(&(self.best_number - (slots_in_an_epoch))).copied() } else { Some(self.genesis_hash) // treat the genesis block as safe "by definition" } } - BlockNumber::Finalized => { - if self.best_number > (slots_in_an_epoch * 2) { - self.hashes.get(&(self.best_number - (slots_in_an_epoch * 2))).copied() + BlockNumberOrTag::Finalized => { + if self.best_number > (slots_in_an_epoch * U64::from(2)) { + self.hashes + .get(&(self.best_number - (slots_in_an_epoch * U64::from(2)))) + .copied() } else { Some(self.genesis_hash) } } } } + + pub fn serialized_blocks(&self) -> Vec { + 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() { + let block: Block = serializable_block.clone().into(); + let block_hash = block.header.hash_slow(); + let block_number = block.header.number; + self.blocks.insert(block_hash, block); + self.hashes.insert(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 @@ -309,15 +438,17 @@ pub struct Blockchain { pub storage: Arc>, } -// === impl BlockchainStorage === - impl Blockchain { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { - Self { storage: Arc::new(RwLock::new(BlockchainStorage::new(env, base_fee, timestamp))) } + pub fn new(env: &Env, spec_id: SpecId, base_fee: Option, timestamp: u64) -> Self { + Self { + storage: Arc::new(RwLock::new(BlockchainStorage::new( + env, spec_id, base_fee, timestamp, + ))), + } } - pub fn forked(block_number: u64, block_hash: H256, total_difficulty: U256) -> Self { + pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::forked( block_number, @@ -328,18 +459,18 @@ impl Blockchain { } /// returns the header hash of given block - pub fn hash(&self, id: BlockId) -> Option { + pub fn hash(&self, id: BlockId) -> Option { match id { - BlockId::Hash(h) => Some(h), + BlockId::Hash(h) => Some(h.block_hash), BlockId::Number(num) => self.storage.read().hash(num), } } - pub fn get_block_by_hash(&self, hash: &H256) -> Option { + pub fn get_block_by_hash(&self, hash: &B256) -> Option { self.storage.read().blocks.get(hash).cloned() } - pub fn get_transaction_by_hash(&self, hash: &H256) -> Option { + pub fn get_transaction_by_hash(&self, hash: &B256) -> Option { self.storage.read().transactions.get(hash).cloned() } @@ -350,7 +481,7 @@ impl Blockchain { } /// Represents the outcome of mining a new block -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MinedBlockOutcome { /// The block that was mined pub block_number: U64, @@ -362,59 +493,102 @@ pub struct MinedBlockOutcome { } /// Container type for a mined transaction -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MinedTransaction { pub info: TransactionInfo, pub receipt: TypedReceipt, - pub block_hash: H256, + pub block_hash: B256, pub block_number: u64, } -// === impl MinedTransaction === - impl MinedTransaction { /// Returns the traces of the transaction for `trace_transaction` - pub fn parity_traces(&self) -> Vec { - let mut traces = Vec::with_capacity(self.info.traces.arena.len()); - for (idx, node) in self.info.traces.arena.iter().cloned().enumerate() { - let action = node.parity_action(); - let result = node.parity_result(); - - let action_type = if node.status() == InstructionResult::SelfDestruct { - ActionType::Suicide - } else { - node.kind().into() - }; - - let trace = Trace { - action, - result: Some(result), - trace_address: self.info.trace_address(idx), - subtraces: node.children.len(), - transaction_position: Some(self.info.transaction_index as usize), - transaction_hash: Some(self.info.transaction_hash), - block_number: self.block_number, - block_hash: self.block_hash, - action_type, - error: None, - }; - traces.push(trace) - } + pub fn parity_traces(&self) -> Vec { + ParityTraceBuilder::new( + self.info.traces.clone(), + None, + TracingInspectorConfig::default_parity(), + ) + .into_localized_transaction_traces(RethTransactionInfo { + hash: Some(self.info.transaction_hash), + index: Some(self.info.transaction_index), + block_hash: Some(self.block_hash), + block_number: Some(self.block_number), + base_fee: None, + }) + } - traces + 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) -> DefaultFrame { - self.info.traces.geth_trace(self.receipt.gas_used(), opts) + pub fn geth_trace(&self, opts: GethDebugTracingOptions) -> Result { + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; + + if let Some(tracer) = tracer { + match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::FourByteTracer => { + let inspector = FourByteInspector::default(); + return Ok(FourByteFrame::from(inspector).into()); + } + GethDebugBuiltInTracerType::CallTracer => { + return match tracer_config.into_call_config() { + Ok(call_config) => Ok(GethTraceBuilder::new(self.info.traces.clone()) + .geth_call_traces(call_config, self.receipt.cumulative_gas_used()) + .into()), + Err(e) => Err(RpcError::invalid_params(e.to_string()).into()), + }; + } + GethDebugBuiltInTracerType::PreStateTracer | + GethDebugBuiltInTracerType::NoopTracer | + GethDebugBuiltInTracerType::MuxTracer | + GethDebugBuiltInTracerType::FlatCallTracer => {} + }, + GethDebugTracerType::JsTracer(_code) => {} + } + + return Ok(NoopFrame::default().into()); + } + + // default structlog tracer + Ok(GethTraceBuilder::new(self.info.traces.clone()) + .geth_traces( + self.receipt.cumulative_gas_used(), + self.info.out.clone().unwrap_or_default(), + config, + ) + .into()) } } /// Intermediary Anvil representation of a receipt -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct MinedTransactionReceipt { /// The actual json rpc receipt object - pub inner: TransactionReceipt, - /// Output data fo the transaction + pub inner: ReceiptResponse, + /// Output data for the transaction pub out: Option, } @@ -422,12 +596,14 @@ pub struct MinedTransactionReceipt { mod tests { use super::*; use crate::eth::backend::db::Db; - use ethers::{abi::ethereum_types::BigEndianHash, types::Address}; + use alloy_primitives::{hex, Address}; + use alloy_rlp::Decodable; + use anvil_core::eth::transaction::TypedTransaction; use foundry_evm::{ - executor::backend::MemDb, + backend::MemDb, revm::{ db::DatabaseRef, - primitives::{AccountInfo, U256 as rU256}, + primitives::{AccountInfo, U256}, }, }; @@ -438,60 +614,112 @@ mod tests { assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT * 3); } + #[test] + fn test_init_state_limits() { + let mut storage = InMemoryBlockStates::default(); + assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT); + assert_eq!(storage.min_in_memory_limit, MIN_HISTORY_LIMIT); + assert_eq!(storage.max_on_disk_limit, MAX_ON_DISK_HISTORY_LIMIT); + + storage = storage.memory_only(); + assert!(storage.is_memory_only()); + + storage = InMemoryBlockStates::new(1, 0); + assert!(storage.is_memory_only()); + assert_eq!(storage.in_memory_limit, 1); + assert_eq!(storage.min_in_memory_limit, 1); + assert_eq!(storage.max_on_disk_limit, 0); + + storage = InMemoryBlockStates::new(1, 2); + assert!(!storage.is_memory_only()); + assert_eq!(storage.in_memory_limit, 1); + assert_eq!(storage.min_in_memory_limit, 1); + assert_eq!(storage.max_on_disk_limit, 2); + } + #[tokio::test(flavor = "multi_thread")] async fn can_read_write_cached_state() { - let mut storage = InMemoryBlockStates::new(1); - let one = H256::from_uint(&U256::from(1)); - let two = H256::from_uint(&U256::from(2)); + let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT); + let one = B256::from(U256::from(1)); + let two = B256::from(U256::from(2)); let mut state = MemDb::default(); let addr = Address::random(); - let info = AccountInfo::from_balance(rU256::from(1337)); + let info = AccountInfo::from_balance(U256::from(1337)); state.insert_account(addr, info); storage.insert(one, StateDb::new(state)); storage.insert(two, StateDb::new(MemDb::default())); // wait for files to be flushed - tokio::time::sleep(std::time::Duration::from_secs(2)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; assert_eq!(storage.on_disk_states.len(), 1); - assert!(storage.on_disk_states.get(&one).is_some()); + assert!(storage.on_disk_states.contains_key(&one)); let loaded = storage.get(&one).unwrap(); - let acc = loaded.basic(addr.into()).unwrap().unwrap(); - assert_eq!(acc.balance, rU256::from(1337u64)); + let acc = loaded.basic_ref(addr).unwrap().unwrap(); + assert_eq!(acc.balance, U256::from(1337u64)); } #[tokio::test(flavor = "multi_thread")] async fn can_decrease_state_cache_size() { let limit = 15; - let mut storage = InMemoryBlockStates::new(limit); + let mut storage = InMemoryBlockStates::new(limit, MAX_ON_DISK_HISTORY_LIMIT); let num_states = 30; for idx in 0..num_states { let mut state = MemDb::default(); - let hash = H256::from_uint(&U256::from(idx)); - let addr = Address::from(hash); + let hash = B256::from(U256::from(idx)); + let addr = Address::from_word(hash); let balance = (idx * 2) as u64; - let info = AccountInfo::from_balance(rU256::from(balance)); + let info = AccountInfo::from_balance(U256::from(balance)); state.insert_account(addr, info); storage.insert(hash, StateDb::new(state)); } // wait for files to be flushed - tokio::time::sleep(std::time::Duration::from_secs(2)).await; + tokio::time::sleep(std::time::Duration::from_secs(1)).await; assert_eq!(storage.on_disk_states.len(), num_states - storage.min_in_memory_limit); assert_eq!(storage.present.len(), storage.min_in_memory_limit); for idx in 0..num_states { - let hash = H256::from_uint(&U256::from(idx)); - let addr = Address::from(hash); + let hash = B256::from(U256::from(idx)); + let addr = Address::from_word(hash); let loaded = storage.get(&hash).unwrap(); - let acc = loaded.basic(addr.into()).unwrap().unwrap(); + let acc = loaded.basic_ref(addr).unwrap().unwrap(); let balance = (idx * 2) as u64; - assert_eq!(acc.balance, rU256::from(balance)); + assert_eq!(acc.balance, U256::from(balance)); } } + + // verifies that blocks and transactions in BlockchainStorage remain the same when dumped and + // reloaded + #[test] + fn test_storage_dump_reload_cycle() { + let mut dump_storage = BlockchainStorage::empty(); + + let partial_header = PartialHeader { gas_limit: 123456, ..Default::default() }; + let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; + let tx: MaybeImpersonatedTransaction = + TypedTransaction::decode(&mut &bytes_first[..]).unwrap().into(); + let block = + Block::new::(partial_header.clone(), vec![tx.clone()]); + let block_hash = block.header.hash_slow(); + dump_storage.blocks.insert(block_hash, block); + + let serialized_blocks = dump_storage.serialized_blocks(); + let serialized_transactions = dump_storage.serialized_transactions(); + + let mut load_storage = BlockchainStorage::empty(); + + load_storage.load_blocks(serialized_blocks); + load_storage.load_transactions(serialized_transactions); + + let loaded_block = load_storage.blocks.get(&block_hash).unwrap(); + assert_eq!(loaded_block.header.gas_limit, { partial_header.gas_limit }); + let loaded_tx = loaded_block.transactions.first().unwrap(); + assert_eq!(loaded_tx, &tx); + } } diff --git a/crates/anvil/src/eth/backend/notifications.rs b/crates/anvil/src/eth/backend/notifications.rs index 7a5f428db7a41..795de0cca9a55 100644 --- a/crates/anvil/src/eth/backend/notifications.rs +++ b/crates/anvil/src/eth/backend/notifications.rs @@ -1,7 +1,7 @@ //! Notifications emitted from the backed -use anvil_core::eth::block::Header; -use ethers::types::H256; +use alloy_consensus::Header; +use alloy_primitives::B256; use futures::channel::mpsc::UnboundedReceiver; use std::sync::Arc; @@ -9,7 +9,7 @@ use std::sync::Arc; #[derive(Clone, Debug)] pub struct NewBlockNotification { /// Hash of the imported block - pub hash: H256, + pub hash: B256, /// block header pub header: Arc
, } diff --git a/crates/anvil/src/eth/backend/time.rs b/crates/anvil/src/eth/backend/time.rs index 87a4aad7b2ce2..3ae9524f0e5eb 100644 --- a/crates/anvil/src/eth/backend/time.rs +++ b/crates/anvil/src/eth/backend/time.rs @@ -1,19 +1,17 @@ //! Manages the block time -use chrono::{DateTime, NaiveDateTime, Utc}; +use crate::eth::error::BlockchainError; +use chrono::{DateTime, Utc}; use parking_lot::RwLock; use std::{sync::Arc, time::Duration}; -use tracing::trace; - -use crate::eth::error::BlockchainError; /// Returns the `Utc` datetime for the given seconds since unix epoch pub fn utc_from_secs(secs: u64) -> DateTime { - DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(secs as i64, 0).unwrap(), Utc) + DateTime::from_timestamp(secs as i64, 0).unwrap() } /// Manages block time -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct TimeManager { /// tracks the overall applied timestamp offset offset: Arc>, @@ -27,11 +25,9 @@ pub struct TimeManager { interval: Arc>>, } -// === impl TimeManager === - impl TimeManager { - pub fn new(start_timestamp: u64) -> TimeManager { - let time_manager = TimeManager { + pub fn new(start_timestamp: u64) -> Self { + let time_manager = Self { last_timestamp: Default::default(), offset: Default::default(), next_exact_timestamp: Default::default(), @@ -74,9 +70,9 @@ impl TimeManager { /// Fails if it's before (or at the same time) the last timestamp pub fn set_next_block_timestamp(&self, timestamp: u64) -> Result<(), BlockchainError> { trace!(target: "time", "override next timestamp {}", timestamp); - if timestamp <= *self.last_timestamp.read() { + if timestamp < *self.last_timestamp.read() { return Err(BlockchainError::TimestampError(format!( - "{timestamp} is lower than or equal to previous block's timestamp" + "{timestamp} is lower than previous block's timestamp" ))) } self.next_exact_timestamp.write().replace(timestamp); @@ -116,7 +112,7 @@ impl TimeManager { (current.saturating_add(self.offset()) as u64, false) }; // Ensures that the timestamp is always increasing - if next_timestamp <= last_timestamp { + if next_timestamp < last_timestamp { next_timestamp = last_timestamp + 1; } let next_offset = update_offset.then_some((next_timestamp as i128) - current); diff --git a/crates/anvil/src/eth/backend/validate.rs b/crates/anvil/src/eth/backend/validate.rs index 3ea666f15a156..eca3fd9e3bfe9 100644 --- a/crates/anvil/src/eth/backend/validate.rs +++ b/crates/anvil/src/eth/backend/validate.rs @@ -2,11 +2,10 @@ use crate::eth::error::{BlockchainError, InvalidTransactionError}; use anvil_core::eth::transaction::PendingTransaction; -use foundry_evm::revm::primitives::{AccountInfo, Env}; +use foundry_evm::revm::primitives::{AccountInfo, EnvWithHandlerCfg}; /// A trait for validating transactions #[async_trait::async_trait] -#[auto_impl::auto_impl(&, Box)] pub trait TransactionValidator { /// Validates the transaction's validity when it comes to nonce, payment /// @@ -22,7 +21,7 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account @@ -32,6 +31,6 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError>; } diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index 888f185d5647a..0c9723c40a65d 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -1,27 +1,28 @@ //! Aggregated error type for this module use crate::eth::pool::transactions::PoolTransaction; +use alloy_primitives::{Bytes, SignatureError}; +use alloy_rpc_types::BlockNumberOrTag; +use alloy_signer::Error as SignerError; +use alloy_transport::TransportError; +use anvil_core::eth::wallet::WalletError; use anvil_rpc::{ error::{ErrorCode, RpcError}, response::ResponseResult, }; -use ethers::{ - abi::AbiDecode, - providers::ProviderError, - signers::WalletError, - types::{Bytes, SignatureError, U256}, -}; -use foundry_common::SELECTOR_LEN; use foundry_evm::{ - executor::backend::DatabaseError, - revm::{self, interpreter::InstructionResult, primitives::EVMError}, + backend::DatabaseError, + decode::RevertDecoder, + revm::{ + interpreter::InstructionResult, + primitives::{EVMError, InvalidHeader}, + }, }; use serde::Serialize; -use tracing::error; pub(crate) type Result = std::result::Result; -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum BlockchainError { #[error(transparent)] Pool(#[from] PoolError), @@ -37,6 +38,8 @@ pub enum BlockchainError { FailedToDecodeSignedTransaction, #[error("Failed to decode transaction")] FailedToDecodeTransaction, + #[error("Failed to decode receipt")] + FailedToDecodeReceipt, #[error("Failed to decode state")] FailedToDecodeStateDump, #[error("Prevrandao not in th EVM's environment after merge")] @@ -44,7 +47,7 @@ pub enum BlockchainError { #[error(transparent)] SignatureError(#[from] SignatureError), #[error(transparent)] - WalletError(#[from] WalletError), + SignerError(#[from] SignerError), #[error("Rpc Endpoint not implemented")] RpcUnimplemented, #[error("Rpc error {0:?}")] @@ -54,7 +57,7 @@ pub enum BlockchainError { #[error(transparent)] FeeHistory(#[from] FeeHistoryError), #[error(transparent)] - ForkProvider(#[from] ProviderError), + AlloyForkProvider(#[from] TransportError), #[error("EVM error {0:?}")] EvmError(InstructionResult), #[error("Invalid url {0:?}")] @@ -81,29 +84,72 @@ pub enum BlockchainError { EIP1559TransactionUnsupportedAtHardfork, #[error("Access list received but is not supported by the current hardfork.\n\nYou can use it by running anvil with '--hardfork berlin' or later.")] 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("UnknownTransactionType not supported ")] + UnknownTransactionType, + #[error("Excess blob gas not set.")] + ExcessBlobGasNotSet, + #[error("{0}")] + Message(String), +} + +impl From for BlockchainError { + fn from(err: eyre::Report) -> Self { + Self::Message(err.to_string()) + } } impl From for BlockchainError { fn from(err: RpcError) -> Self { - BlockchainError::RpcError(err) + Self::RpcError(err) } } impl From> for BlockchainError where - T: Into, + T: Into, { fn from(err: EVMError) -> Self { match err { EVMError::Transaction(err) => InvalidTransactionError::from(err).into(), - EVMError::PrevrandaoNotSet => BlockchainError::PrevrandaoNotSet, + EVMError::Header(err) => match err { + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, + }, EVMError::Database(err) => err.into(), + EVMError::Precompile(err) => Self::Message(err), + EVMError::Custom(err) => Self::Message(err), + } + } +} + +impl From for BlockchainError { + fn from(value: WalletError) -> Self { + match value { + WalletError::ValueNotZero => Self::Message("tx value not zero".to_string()), + WalletError::FromSet => Self::Message("tx from field is set".to_string()), + WalletError::NonceSet => Self::Message("tx nonce is set".to_string()), + WalletError::InvalidAuthorization => { + Self::Message("invalid authorization address".to_string()) + } + WalletError::IllegalDestination => Self::Message( + "the destination of the transaction is not a delegated account".to_string(), + ), + WalletError::InternalError => Self::Message("internal error".to_string()), + WalletError::InvalidTransactionRequest => { + Self::Message("invalid tx request".to_string()) + } } } } /// Errors that can occur in the transaction pool -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum PoolError { #[error("Transaction with cyclic dependent transactions")] CyclicTransaction, @@ -115,10 +161,12 @@ pub enum PoolError { } /// Errors that can occur with `eth_feeHistory` -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum FeeHistoryError { - #[error("Requested block range is out of bounds")] + #[error("requested block range is out of bounds")] InvalidBlockRange, + #[error("could not find newest block number requested: {0}")] + BlockNotFound(BlockNumberOrTag), } #[derive(Debug)] @@ -127,7 +175,7 @@ pub struct ErrDetail { } /// An error due to invalid transaction -#[derive(thiserror::Error, Debug)] +#[derive(Debug, thiserror::Error)] pub enum InvalidTransactionError { /// returned if the nonce of a transaction is lower than the one present in the local chain. #[error("nonce too low")] @@ -168,7 +216,7 @@ pub enum InvalidTransactionError { FeeCapTooLow, /// Thrown during estimate if caller has insufficient funds to cover the tx. #[error("Out of gas: gas required exceeds allowance: {0:?}")] - BasicOutOfGas(U256), + BasicOutOfGas(u128), /// Thrown if executing a transaction failed during estimate/call #[error("execution reverted: {0:?}")] Revert(Option), @@ -181,57 +229,87 @@ pub enum InvalidTransactionError { /// Thrown when a legacy tx was signed for a different chain #[error("Incompatible EIP-155 transaction, signed for another chain")] IncompatibleEIP155, + /// Thrown when an access list is used before the berlin hard fork. + #[error("Access lists are not supported before the Berlin hardfork")] + AccessListNotSupported, + /// Thrown when the block's `blob_gas_price` is greater than tx-specified + /// `max_fee_per_blob_gas` after Cancun. + #[error("Block `blob_gas_price` is greater than tx-specified `max_fee_per_blob_gas`")] + BlobFeeCapTooLow, + /// Thrown when we receive a tx with `blob_versioned_hashes` and we're not on the Cancun hard + /// fork. + #[error("Block `blob_versioned_hashes` is not supported before the Cancun hardfork")] + BlobVersionedHashesNotSupported, + /// Thrown when `max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork. + #[error("`max_fee_per_blob_gas` is not supported for blocks before the Cancun hardfork.")] + MaxFeePerBlobGasNotSupported, + /// Thrown when there are no `blob_hashes` in the transaction, and it is an EIP-4844 tx. + #[error("`blob_hashes` are required for EIP-4844 transactions")] + NoBlobHashes, + #[error("too many blobs in one transaction, have: {0}")] + TooManyBlobs(usize), + /// Thrown when there's a blob validation error + #[error(transparent)] + BlobTransactionValidationError(#[from] alloy_consensus::BlobTransactionValidationError), + /// Thrown when Blob transaction is a create transaction. `to` must be present. + #[error("Blob transaction can't be a create transaction. `to` must be present.")] + BlobCreateTransaction, + /// Thrown when Blob transaction contains a versioned hash with an incorrect version. + #[error("Blob transaction contains a versioned hash with an incorrect version")] + BlobVersionNotSupported, + /// Thrown when there are no `blob_hashes` in the transaction. + #[error("There should be at least one blob in a Blob transaction.")] + EmptyBlobs, + /// Thrown when an access list is used before the berlin hard fork. + #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")] + AuthorizationListNotSupported, + /// Forwards error from the revm + #[error(transparent)] + Revm(revm::primitives::InvalidTransaction), } impl From for InvalidTransactionError { fn from(err: revm::primitives::InvalidTransaction) -> Self { use revm::primitives::InvalidTransaction; match err { - InvalidTransaction::InvalidChainId => InvalidTransactionError::InvalidChainId, - InvalidTransaction::GasMaxFeeGreaterThanPriorityFee => { - InvalidTransactionError::TipAboveFeeCap - } - InvalidTransaction::GasPriceLessThanBasefee => InvalidTransactionError::FeeCapTooLow, + InvalidTransaction::InvalidChainId => Self::InvalidChainId, + InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap, + InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow, InvalidTransaction::CallerGasLimitMoreThanBlock => { - InvalidTransactionError::GasTooHigh(ErrDetail { - detail: String::from("CallerGasLimitMoreThanBlock"), - }) + Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") }) } InvalidTransaction::CallGasCostMoreThanGasLimit => { - InvalidTransactionError::GasTooHigh(ErrDetail { - detail: String::from("CallGasCostMoreThanGasLimit"), - }) - } - InvalidTransaction::RejectCallerWithCode => InvalidTransactionError::SenderNoEOA, - InvalidTransaction::LackOfFundForGasLimit { .. } => { - InvalidTransactionError::InsufficientFunds - } - InvalidTransaction::OverflowPaymentInTransaction => { - InvalidTransactionError::GasUintOverflow + Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) } - InvalidTransaction::NonceOverflowInTransaction => { - InvalidTransactionError::NonceMaxValue + InvalidTransaction::RejectCallerWithCode => Self::SenderNoEOA, + InvalidTransaction::LackOfFundForMaxFee { .. } => Self::InsufficientFunds, + InvalidTransaction::OverflowPaymentInTransaction => Self::GasUintOverflow, + InvalidTransaction::NonceOverflowInTransaction => Self::NonceMaxValue, + InvalidTransaction::CreateInitCodeSizeLimit => Self::MaxInitCodeSizeExceeded, + InvalidTransaction::NonceTooHigh { .. } => Self::NonceTooHigh, + InvalidTransaction::NonceTooLow { .. } => Self::NonceTooLow, + InvalidTransaction::AccessListNotSupported => Self::AccessListNotSupported, + InvalidTransaction::BlobGasPriceGreaterThanMax => Self::BlobFeeCapTooLow, + InvalidTransaction::BlobVersionedHashesNotSupported => { + Self::BlobVersionedHashesNotSupported } - InvalidTransaction::CreateInitcodeSizeLimit => { - InvalidTransactionError::MaxInitCodeSizeExceeded + InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported, + InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction, + InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported, + InvalidTransaction::EmptyBlobs => Self::EmptyBlobs, + InvalidTransaction::TooManyBlobs { have } => Self::TooManyBlobs(have), + InvalidTransaction::AuthorizationListNotSupported => { + Self::AuthorizationListNotSupported } - InvalidTransaction::NonceTooHigh { .. } => InvalidTransactionError::NonceTooHigh, - InvalidTransaction::NonceTooLow { .. } => InvalidTransactionError::NonceTooLow, + InvalidTransaction::AuthorizationListInvalidFields | + InvalidTransaction::OptimismError(_) | + InvalidTransaction::EofCrateShouldHaveToAddress | + InvalidTransaction::EmptyAuthorizationList => Self::Revm(err), + InvalidTransaction::GasFloorMoreThanGasLimit => Self::Revm(err), } } } -/// Returns the revert reason from the `revm::TransactOut` data, if it's an abi encoded String. -/// -/// **Note:** it's assumed the `out` buffer starts with the call's signature -pub(crate) fn decode_revert_reason(out: impl AsRef<[u8]>) -> Option { - let out = out.as_ref(); - if out.len() < SELECTOR_LEN { - return None - } - String::decode(&out[SELECTOR_LEN..]).ok() -} - /// Helper trait to easily convert results to rpc results pub(crate) trait ToRpcResponseResult { fn to_rpc_result(self) -> ResponseResult; @@ -242,7 +320,7 @@ pub fn to_rpc_result(val: T) -> ResponseResult { match serde_json::to_value(val) { Ok(success) => ResponseResult::Success(success), Err(err) => { - error!("Failed serialize rpc response: {:?}", err); + error!(%err, "Failed serialize rpc response"); ResponseResult::error(RpcError::internal_error()) } } @@ -254,7 +332,7 @@ impl ToRpcResponseResult for Result { Ok(val) => to_rpc_result(val), Err(err) => match err { BlockchainError::Pool(err) => { - error!("txpool error: {:?}", err); + error!(%err, "txpool error"); match err { PoolError::CyclicTransaction => { RpcError::transaction_rejected("Cyclic transaction detected") @@ -277,7 +355,10 @@ impl ToRpcResponseResult for Result { InvalidTransactionError::Revert(data) => { // this mimics geth revert error let mut msg = "execution reverted".to_string(); - if let Some(reason) = data.as_ref().and_then(decode_revert_reason) { + if let Some(reason) = data + .as_ref() + .and_then(|data| RevertDecoder::new().maybe_decode(data, None)) + { msg = format!("{msg}: {reason}"); } RpcError { @@ -315,11 +396,14 @@ impl ToRpcResponseResult for Result { BlockchainError::FailedToDecodeTransaction => { RpcError::invalid_params("Failed to decode transaction") } + BlockchainError::FailedToDecodeReceipt => { + RpcError::invalid_params("Failed to decode receipt") + } BlockchainError::FailedToDecodeStateDump => { RpcError::invalid_params("Failed to decode state dump") } + BlockchainError::SignerError(err) => RpcError::invalid_params(err.to_string()), BlockchainError::SignatureError(err) => RpcError::invalid_params(err.to_string()), - BlockchainError::WalletError(err) => RpcError::invalid_params(err.to_string()), BlockchainError::RpcUnimplemented => { RpcError::internal_error_with("Not implemented") } @@ -328,9 +412,16 @@ impl ToRpcResponseResult for Result { BlockchainError::InvalidFeeInput => RpcError::invalid_params( "Invalid input: `max_priority_fee_per_gas` greater than `max_fee_per_gas`", ), - BlockchainError::ForkProvider(err) => { - error!("fork provider error: {:?}", err); - RpcError::internal_error_with(format!("Fork Error: {err:?}")) + BlockchainError::AlloyForkProvider(err) => { + error!(target: "backend", %err, "fork provider error"); + match err { + TransportError::ErrorResp(err) => RpcError { + code: ErrorCode::from(err.code), + message: err.message, + data: err.data.and_then(|data| serde_json::to_value(data).ok()), + }, + err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), + } } err @ BlockchainError::EvmError(_) => { RpcError::internal_error_with(err.to_string()) @@ -368,6 +459,22 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } + 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()) + } + err @ BlockchainError::ExcessBlobGasNotSet => { + RpcError::invalid_params(err.to_string()) + } + err @ BlockchainError::Message(_) => RpcError::internal_error_with(err.to_string()), + err @ BlockchainError::UnknownTransactionType => { + RpcError::invalid_params(err.to_string()) + } } .into(), } diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index 5b1e54679a4fc..bb405f62d1ef1 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -2,9 +2,14 @@ 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, + eip7840::BlobParams, +}; +use alloy_primitives::B256; use anvil_core::eth::transaction::TypedTransaction; -use ethers::types::{H256, U256}; -use foundry_evm::revm::primitives::SpecId; +use foundry_evm::revm::primitives::{BlobExcessGasAndPrice, SpecId}; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; use std::{ @@ -15,7 +20,6 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -use tracing::trace; /// Maximum number of entries in the fee history cache pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64; @@ -24,42 +28,54 @@ pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64; pub const INITIAL_BASE_FEE: u64 = 1_000_000_000; /// Initial default gas price for the first block -pub const INITIAL_GAS_PRICE: u64 = 1_875_000_000; +pub const INITIAL_GAS_PRICE: u128 = 1_875_000_000; /// Bounds the amount the base fee can change between blocks. -pub const BASE_FEE_CHANGE_DENOMINATOR: u64 = 8; +pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8; -/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2; +/// Minimum suggested priority fee +pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128; pub fn default_elasticity() -> f64 { - 1f64 / BASE_FEE_CHANGE_DENOMINATOR as f64 + 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64 } /// Stores the fee related information -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct FeeManager { /// Hardfork identifier spec_id: SpecId, /// Tracks the base fee for the next block post London /// /// This value will be updated after a new block was mined - base_fee: Arc>, + base_fee: Arc>, + /// Whether the minimum suggested priority fee is enforced + is_min_priority_fee_enforced: bool, + /// Tracks the excess blob gas, and the base fee, for the next block post Cancun + /// + /// This value will be updated after a new block was mined + blob_excess_gas_and_price: Arc>, /// The base price to use Pre London /// /// This will be constant value unless changed manually - gas_price: Arc>, + gas_price: Arc>, elasticity: Arc>, } -// === impl FeeManager === - impl FeeManager { - pub fn new(spec_id: SpecId, base_fee: U256, gas_price: U256) -> Self { + pub fn new( + spec_id: SpecId, + base_fee: u64, + is_min_priority_fee_enforced: bool, + gas_price: u128, + blob_excess_gas_and_price: BlobExcessGasAndPrice, + ) -> Self { Self { spec_id, base_fee: Arc::new(RwLock::new(base_fee)), + is_min_priority_fee_enforced, gas_price: Arc::new(RwLock::new(gas_price)), + blob_excess_gas_and_price: Arc::new(RwLock::new(blob_excess_gas_and_price)), elasticity: Arc::new(RwLock::new(default_elasticity())), } } @@ -73,96 +89,109 @@ impl FeeManager { (self.spec_id as u8) >= (SpecId::LONDON as u8) } - /// Calculates the current gas price - pub fn gas_price(&self) -> U256 { + pub fn is_eip4844(&self) -> bool { + (self.spec_id as u8) >= (SpecId::CANCUN as u8) + } + + /// Calculates the current blob gas price + pub fn blob_gas_price(&self) -> u128 { + if self.is_eip4844() { + self.base_fee_per_blob_gas() + } else { + 0 + } + } + + pub fn base_fee(&self) -> u64 { if self.is_eip1559() { - self.base_fee().saturating_add(self.suggested_priority_fee()) + *self.base_fee.read() } else { - *self.gas_price.read() + 0 } } - /// Suggested priority fee to add to the base fee - pub fn suggested_priority_fee(&self) -> U256 { - U256::from(1e9 as u64) + pub fn is_min_priority_fee_enforced(&self) -> bool { + self.is_min_priority_fee_enforced } - pub fn base_fee(&self) -> U256 { - if self.is_eip1559() { - *self.base_fee.read() + /// Raw base gas price + pub fn raw_gas_price(&self) -> u128 { + *self.gas_price.read() + } + + pub fn excess_blob_gas_and_price(&self) -> Option { + if self.is_eip4844() { + Some(self.blob_excess_gas_and_price.read().clone()) } else { - U256::zero() + None } } - /// Returns the suggested fee cap - /// - /// This mirrors geth's auto values for `SuggestGasTipCap` which is: `priority fee + 2x current - /// basefee`. - pub fn max_priority_fee_per_gas(&self) -> U256 { - self.suggested_priority_fee() + *self.base_fee.read() * 2 + pub fn base_fee_per_blob_gas(&self) -> u128 { + if self.is_eip4844() { + self.blob_excess_gas_and_price.read().blob_gasprice + } else { + 0 + } } /// Returns the current gas price - pub fn set_gas_price(&self, price: U256) { + pub fn set_gas_price(&self, price: u128) { let mut gas = self.gas_price.write(); *gas = price; } /// Returns the current base fee - pub fn set_base_fee(&self, fee: U256) { + pub fn set_base_fee(&self, fee: u64) { trace!(target: "backend::fees", "updated base fee {:?}", fee); let mut base = self.base_fee.write(); *base = fee; } + /// Sets the current blob excess gas and price + pub fn set_blob_excess_gas_and_price(&self, blob_excess_gas_and_price: BlobExcessGasAndPrice) { + trace!(target: "backend::fees", "updated blob base fee {:?}", blob_excess_gas_and_price); + let mut base = self.blob_excess_gas_and_price.write(); + *base = blob_excess_gas_and_price; + } + /// Calculates the base fee for the next block pub fn get_next_block_base_fee_per_gas( &self, - gas_used: U256, - gas_limit: U256, - last_fee_per_gas: U256, + gas_used: u128, + gas_limit: u128, + last_fee_per_gas: u64, ) -> u64 { // It's naturally impossible for base fee to be 0; // It means it was set by the user deliberately and therefore we treat it as a constant. // Therefore, we skip the base fee calculation altogether and we return 0. - if self.base_fee() == U256::zero() { + if self.base_fee() == 0 { return 0 } - calculate_next_block_base_fee( - gas_used.as_u64(), - gas_limit.as_u64(), - last_fee_per_gas.as_u64(), - ) + calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas) } -} - -/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec -pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { - let gas_target = gas_limit / EIP1559_ELASTICITY_MULTIPLIER; - if gas_used == gas_target { - return base_fee + /// Calculates the next block blob base fee, using the provided excess blob gas + pub fn get_next_block_blob_base_fee_per_gas(&self, excess_blob_gas: u128) -> u128 { + alloy_eips::eip4844::calc_blob_gasprice(excess_blob_gas as u64) } - if gas_used > gas_target { - let gas_used_delta = gas_used - gas_target; - let base_fee_delta = std::cmp::max( - 1, - base_fee as u128 * gas_used_delta as u128 / - gas_target as u128 / - BASE_FEE_CHANGE_DENOMINATOR as u128, - ); - base_fee + (base_fee_delta as u64) - } else { - let gas_used_delta = gas_target - gas_used; - let base_fee_per_gas_delta = base_fee as u128 * gas_used_delta as u128 / - gas_target as u128 / - BASE_FEE_CHANGE_DENOMINATOR as u128; - - base_fee.saturating_sub(base_fee_per_gas_delta as u64) + + /// Calculates the next block blob excess gas, using the provided parent blob gas used and + /// parent blob excess gas + pub fn get_next_block_blob_excess_gas( + &self, + blob_gas_used: u128, + blob_excess_gas: u128, + ) -> u64 { + alloy_eips::eip4844::calc_excess_blob_gas(blob_gas_used as u64, blob_excess_gas as u64) } } +/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec +pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u64) -> u64 { + calc_next_block_base_fee(gas_used as u64, gas_limit as u64, base_fee, BaseFeeParams::ethereum()) +} + /// An async service that takes care of the `FeeHistory` cache pub struct FeeHistoryService { /// incoming notifications about new blocks @@ -171,28 +200,17 @@ 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, } -// === impl FeeHistoryService === - 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 @@ -200,11 +218,17 @@ impl FeeHistoryService { self.fee_history_limit } + /// Inserts a new cache entry for the given block + pub(crate) fn insert_cache_entry_for_block(&self, hash: B256, header: &Header) { + 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: H256, - elasticity: f64, + 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 @@ -220,43 +244,61 @@ impl FeeHistoryService { }; let mut block_number: Option = None; - let base_fee = self.fees.base_fee(); + let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default(); + let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128); + let blob_gas_used = header.blob_gas_used.map(|g| g as u128); + let base_fee_per_blob_gas = header.blob_fee(BlobParams::cancun()); let mut item = FeeHistoryCacheItem { - base_fee: base_fee.as_u64(), + base_fee, gas_used_ratio: 0f64, + blob_gas_used_ratio: 0f64, rewards: Vec::new(), + excess_blob_gas, + base_fee_per_blob_gas, + blob_gas_used, }; let current_block = self.storage_info.block(hash); let current_receipts = self.storage_info.receipts(hash); if let (Some(block), Some(receipts)) = (current_block, current_receipts) { - block_number = Some(block.header.number.as_u64()); - - let gas_used = block.header.gas_used.as_u64() as f64; - let gas_limit = block.header.gas_limit.as_u64() as f64; + block_number = Some(block.header.number); - let gas_target = gas_limit / elasticity; - item.gas_used_ratio = gas_used / (gas_target * elasticity); + let gas_used = block.header.gas_used as f64; + let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64); + item.gas_used_ratio = gas_used / block.header.gas_limit as f64; + item.blob_gas_used_ratio = + blob_gas_used.map(|g| g / MAX_DATA_GAS_PER_BLOCK as f64).unwrap_or(0 as f64); // extract useful tx info (gas_used, effective_reward) - let mut transactions: Vec<(u64, u64)> = receipts + let mut transactions: Vec<(_, _)> = receipts .iter() .enumerate() .map(|(i, receipt)| { - let gas_used = receipt.gas_used().as_u64(); + let gas_used = receipt.cumulative_gas_used(); let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction) { Some(TypedTransaction::Legacy(t)) => { - t.gas_price.saturating_sub(base_fee).as_u64() + t.tx().gas_price.saturating_sub(base_fee) } Some(TypedTransaction::EIP2930(t)) => { - t.gas_price.saturating_sub(base_fee).as_u64() + t.tx().gas_price.saturating_sub(base_fee) } Some(TypedTransaction::EIP1559(t)) => t + .tx() .max_priority_fee_per_gas - .min(t.max_fee_per_gas.saturating_sub(base_fee)) - .as_u64(), + .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), + // TODO: This probably needs to be extended to extract 4844 info. + Some(TypedTransaction::EIP4844(t)) => t + .tx() + .tx() + .max_priority_fee_per_gas + .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)), + Some(TypedTransaction::EIP7702(t)) => t + .tx() + .max_priority_fee_per_gas + .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), + Some(TypedTransaction::Deposit(_)) => 0, None => 0, }; @@ -314,12 +356,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; - let elasticity = default_elasticity(); - // add the imported block. - let (result, block_number) = pin.create_cache_entry(hash, elasticity); - pin.insert_cache_entry(result, block_number) + pin.insert_cache_entry_for_block(notification.hash, notification.header.as_ref()); } Poll::Pending @@ -329,63 +367,90 @@ impl Future for FeeHistoryService { pub type FeeHistoryCache = Arc>>; /// A single item in the whole fee history cache -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct FeeHistoryCacheItem { - pub base_fee: u64, + pub base_fee: u128, pub gas_used_ratio: f64, - pub rewards: Vec, + pub base_fee_per_blob_gas: Option, + pub blob_gas_used_ratio: f64, + pub excess_blob_gas: Option, + pub blob_gas_used: Option, + pub rewards: Vec, } -#[derive(Default, Clone)] +#[derive(Clone, Default)] pub struct FeeDetails { - pub gas_price: Option, - pub max_fee_per_gas: Option, - pub max_priority_fee_per_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, } impl FeeDetails { /// All values zero pub fn zero() -> Self { Self { - gas_price: Some(U256::zero()), - max_fee_per_gas: Some(U256::zero()), - max_priority_fee_per_gas: Some(U256::zero()), + gas_price: Some(0), + max_fee_per_gas: Some(0), + max_priority_fee_per_gas: Some(0), + max_fee_per_blob_gas: None, } } /// If neither `gas_price` nor `max_fee_per_gas` is `Some`, this will set both to `0` pub fn or_zero_fees(self) -> Self { - let FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = self; + let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = + self; let no_fees = gas_price.is_none() && max_fee_per_gas.is_none(); - let gas_price = if no_fees { Some(U256::zero()) } else { gas_price }; - let max_fee_per_gas = if no_fees { Some(U256::zero()) } else { max_fee_per_gas }; + let gas_price = if no_fees { Some(0) } else { gas_price }; + let max_fee_per_gas = if no_fees { Some(0) } else { max_fee_per_gas }; + let max_fee_per_blob_gas = if no_fees { None } else { max_fee_per_blob_gas }; - Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas } + Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } } /// Turns this type into a tuple - pub fn split(self) -> (Option, Option, Option) { - let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = self; - (gas_price, max_fee_per_gas, max_priority_fee_per_gas) + pub fn split(self) -> (Option, Option, Option, Option) { + let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas } = + self; + (gas_price, max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) } /// Creates a new instance from the request's gas related values pub fn new( - request_gas_price: Option, - request_max_fee: Option, - request_priority: Option, - ) -> Result { - match (request_gas_price, request_max_fee, request_priority) { - (gas_price, None, None) => { + request_gas_price: Option, + request_max_fee: Option, + request_priority: Option, + max_fee_per_blob_gas: Option, + ) -> Result { + match (request_gas_price, request_max_fee, request_priority, max_fee_per_blob_gas) { + (gas_price, None, None, None) => { // Legacy request, all default to gas price. - Ok(FeeDetails { + Ok(Self { gas_price, max_fee_per_gas: gas_price, max_priority_fee_per_gas: gas_price, + max_fee_per_blob_gas: None, + }) + } + (_, max_fee, max_priority, None) => { + // eip-1559 + // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. + if let Some(max_priority) = max_priority { + let max_fee = max_fee.unwrap_or_default(); + if max_priority > max_fee { + return Err(BlockchainError::InvalidFeeInput) + } + } + Ok(Self { + gas_price: max_fee, + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: max_priority, + max_fee_per_blob_gas: None, }) } - (_, max_fee, max_priority) => { + (_, max_fee, max_priority, max_fee_per_blob_gas) => { // eip-1559 // Ensure `max_priority_fee_per_gas` is less or equal to `max_fee_per_gas`. if let Some(max_priority) = max_priority { @@ -394,10 +459,11 @@ impl FeeDetails { return Err(BlockchainError::InvalidFeeInput) } } - Ok(FeeDetails { + Ok(Self { gas_price: max_fee, max_fee_per_gas: max_fee, max_priority_fee_per_gas: max_priority, + max_fee_per_blob_gas, }) } } @@ -405,9 +471,9 @@ impl FeeDetails { } impl fmt::Debug for FeeDetails { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Fees {{ ")?; - write!(fmt, "gaPrice: {:?}, ", self.gas_price)?; + write!(fmt, "gas_price: {:?}, ", self.gas_price)?; write!(fmt, "max_fee_per_gas: {:?}, ", self.max_fee_per_gas)?; write!(fmt, "max_priority_fee_per_gas: {:?}, ", self.max_priority_fee_per_gas)?; write!(fmt, "}}")?; diff --git a/crates/anvil/src/eth/miner.rs b/crates/anvil/src/eth/miner.rs index 148361279b7aa..90c4550907fef 100644 --- a/crates/anvil/src/eth/miner.rs +++ b/crates/anvil/src/eth/miner.rs @@ -1,7 +1,7 @@ //! Mines transactions use crate::eth::pool::{transactions::PoolTransaction, Pool}; -use ethers::prelude::TxHash; +use alloy_primitives::TxHash; use futures::{ channel::mpsc::Receiver, stream::{Fuse, Stream, StreamExt}, @@ -12,13 +12,12 @@ use std::{ fmt, pin::Pin, sync::Arc, - task::{Context, Poll}, + task::{ready, Context, Poll}, time::Duration, }; -use tokio::time::Interval; -use tracing::trace; +use tokio::time::{Interval, MissedTickBehavior}; -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Miner { /// The mode this miner currently operates in mode: Arc>, @@ -26,14 +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 === - 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 @@ -47,9 +64,12 @@ impl Miner { matches!(*mode, MiningMode::Auto(_)) } - pub fn is_interval(&self) -> bool { + pub fn get_interval(&self) -> Option { let mode = self.mode.read(); - matches!(*mode, MiningMode::FixedBlockTime(_)) + if let MiningMode::FixedBlockTime(ref mm) = *mode { + return Some(mm.interval.period().as_secs()) + } + None } /// Sets the mining mode to operate in @@ -70,7 +90,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) + } } } @@ -80,8 +106,6 @@ pub struct MinerInner { waker: AtomicWaker, } -// === impl MinerInner === - impl MinerInner { /// Call the waker again fn wake(&self) { @@ -111,13 +135,14 @@ pub enum MiningMode { Auto(ReadyTransactionMiner), /// A miner that constructs a new block every `interval` tick FixedBlockTime(FixedBlockTimeMiner), -} -// === impl MiningMode === + /// A minner that uses both Auto and FixedBlockTime + Mixed(ReadyTransactionMiner, FixedBlockTimeMiner), +} impl MiningMode { pub fn instant(max_transactions: usize, listener: Receiver) -> Self { - MiningMode::Auto(ReadyTransactionMiner { + Self::Auto(ReadyTransactionMiner { max_transactions, has_pending_txs: None, rx: listener.fuse(), @@ -125,7 +150,14 @@ impl MiningMode { } pub fn interval(duration: Duration) -> Self { - MiningMode::FixedBlockTime(FixedBlockTimeMiner::new(duration)) + Self::FixedBlockTime(FixedBlockTimeMiner::new(duration)) + } + + pub fn mixed(max_transactions: usize, listener: Receiver, duration: Duration) -> Self { + Self::Mixed( + ReadyTransactionMiner { max_transactions, has_pending_txs: None, rx: listener.fuse() }, + FixedBlockTimeMiner::new(duration), + ) } /// polls the [Pool] and returns those transactions that should be put in a block, if any. @@ -135,9 +167,32 @@ impl MiningMode { cx: &mut Context<'_>, ) -> Poll>> { match self { - MiningMode::None => Poll::Pending, - MiningMode::Auto(miner) => miner.poll(pool, cx), - MiningMode::FixedBlockTime(miner) => miner.poll(pool, cx), + Self::None => Poll::Pending, + Self::Auto(miner) => miner.poll(pool, cx), + Self::FixedBlockTime(miner) => miner.poll(pool, cx), + Self::Mixed(auto, fixed) => { + let auto_txs = auto.poll(pool, cx); + let fixed_txs = fixed.poll(pool, cx); + + match (auto_txs, fixed_txs) { + // Both auto and fixed transactions are ready, combine them + (Poll::Ready(mut auto_txs), Poll::Ready(fixed_txs)) => { + for tx in fixed_txs { + // filter unique transactions + if auto_txs.iter().any(|auto_tx| auto_tx.hash() == tx.hash()) { + continue; + } + auto_txs.push(tx); + } + Poll::Ready(auto_txs) + } + // Only auto transactions are ready, return them + (Poll::Ready(auto_txs), Poll::Pending) => Poll::Ready(auto_txs), + // Only fixed transactions are ready or both are pending, + // return fixed transactions or pending status + (Poll::Pending, fixed_txs) => fixed_txs, + } + } } } } @@ -152,13 +207,15 @@ pub struct FixedBlockTimeMiner { interval: Interval, } -// === impl FixedBlockTimeMiner === - 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>> { @@ -180,14 +237,12 @@ impl Default for FixedBlockTimeMiner { pub struct ReadyTransactionMiner { /// how many transactions to mine per block max_transactions: usize, - /// stores whether there are pending transacions (if known) + /// stores whether there are pending transactions (if known) has_pending_txs: Option, /// Receives hashes of transactions that are ready rx: Fuse>, } -// === impl ReadyTransactionMiner === - impl ReadyTransactionMiner { fn poll(&mut self, pool: &Arc, cx: &mut Context<'_>) -> Poll>> { // drain the notification stream diff --git a/crates/anvil/src/eth/mod.rs b/crates/anvil/src/eth/mod.rs index a9acd1d9af4cd..393a9ff213306 100644 --- a/crates/anvil/src/eth/mod.rs +++ b/crates/anvil/src/eth/mod.rs @@ -1,5 +1,6 @@ pub mod api; pub mod otterscan; +pub mod sign; pub use api::EthApi; pub mod backend; @@ -10,5 +11,4 @@ pub mod fees; pub(crate) mod macros; pub mod miner; pub mod pool; -pub mod sign; pub mod util; diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index f5da4b287a78b..0392a938c304b 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -3,35 +3,103 @@ use crate::eth::{ macros::node_info, EthApi, }; - -use ethers::types::{ - Action, Address, Block, BlockId, BlockNumber, Bytes, Call, Create, CreateResult, Res, Reward, - Transaction, TxHash, H256, U256, U64, +use alloy_consensus::Transaction as TransactionTrait; +use alloy_network::{ + AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, BlockResponse, + TransactionResponse, +}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_rpc_types::{ + trace::{ + otterscan::{ + BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions, + OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, + }, + parity::{ + Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, + RewardAction, TraceOutput, + }, + }, + Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, }; use itertools::Itertools; -use super::types::{ - OtsBlockDetails, OtsBlockTransactions, OtsContractCreator, OtsInternalOperation, - OtsSearchTransactions, OtsTrace, -}; +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. +/// +/// Follows format specified in the [`ots_traceTransaction`](https://github.com/otterscan/otterscan/blob/main/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_* - /// https://github.com/otterscan/otterscan/blob/071d8c55202badf01804f6f8d53ef9311d4a9e47/src/useProvider.ts#L71 + /// Otterscan currently requires this endpoint, even though it's not part of the `ots_*`. + /// Ref: /// - /// As a faster alternative to eth_getBlockByNumber (by excluding uncle block + /// As a faster alternative to `eth_getBlockByNumber` (by excluding uncle block /// information), which is not relevant in the context of an anvil node pub async fn erigon_get_header_by_number( &self, number: BlockNumber, - ) -> Result>> { + ) -> Result> { node_info!("ots_getApiLevel"); self.backend.block_by_number(number).await } - /// As per the latest Otterscan source code, at least version 8 is needed - /// https://github.com/otterscan/otterscan/blob/071d8c55202badf01804f6f8d53ef9311d4a9e47/src/params.ts#L1C2-L1C2 + /// As per the latest Otterscan source code, at least version 8 is needed. + /// Ref: pub async fn ots_get_api_level(&self) -> Result { node_info!("ots_getApiLevel"); @@ -41,15 +109,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: H256, - ) -> Result> { + pub async fn ots_get_internal_operations(&self, hash: B256) -> Result> { node_info!("ots_getInternalOperations"); self.backend - .mined_parity_trace_transaction(hash) - .map(OtsInternalOperation::batch_build) + .mined_transaction(hash) + .map(|tx| tx.ots_internal_operations()) .ok_or_else(|| BlockchainError::DataUnavailable) } @@ -57,38 +122,40 @@ impl EthApi { pub async fn ots_has_code(&self, address: Address, block_number: BlockNumber) -> Result { node_info!("ots_hasCode"); let block_id = Some(BlockId::Number(block_number)); - Ok(self.get_code(address, block_id).await?.len() > 0) + Ok(!self.get_code(address, block_id).await?.is_empty()) } /// Trace a transaction and generate a trace call tree. - pub async fn ots_trace_transaction(&self, hash: H256) -> 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. - pub async fn ots_get_transaction_error(&self, hash: H256) -> Result> { + pub async fn ots_get_transaction_error(&self, hash: B256) -> Result { node_info!("ots_getTransactionError"); if let Some(receipt) = self.backend.mined_transaction_receipt(hash) { - if receipt.inner.status == Some(U64::zero()) { - return Ok(receipt.out) + 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())); } } - Ok(Default::default()) + Ok(Bytes::default()) } /// 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_full(number).await? { - let ots_block = OtsBlockDetails::build(block, &self.backend).await?; - + if let Some(block) = self.backend.block_by_number(number).await? { + let ots_block = self.build_ots_block_details(block).await?; Ok(ots_block) } else { Err(BlockchainError::BlockNotFound) @@ -98,12 +165,14 @@ 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: H256) -> 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_full(hash).await? { - let ots_block = OtsBlockDetails::build(block, &self.backend).await?; - + if let Some(block) = self.backend.block_by_hash(hash).await? { + let ots_block = self.build_ots_block_details(block).await?; Ok(ots_block) } else { Err(BlockchainError::BlockNotFound) @@ -117,11 +186,11 @@ impl EthApi { number: u64, page: usize, page_size: usize, - ) -> Result { + ) -> Result> { 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), } } @@ -132,47 +201,41 @@ impl EthApi { address: Address, block_number: u64, page_size: usize, - ) -> Result { + ) -> Result>> { node_info!("ots_searchTransactionsBefore"); - let best = self.backend.best_number().as_u64(); + let best = self.backend.best_number(); // we go from given block (defaulting to best) down to first block // considering only post-fork - let from = if block_number == 0 { best } else { block_number }; + let from = if block_number == 0 { best } else { block_number - 1 }; let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); - let first_page = from == best; + let first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; - dbg!(to, from); for n in (to..=from).rev() { - if n == to { - last_page = true; - } - if let Some(traces) = self.backend.mined_parity_trace_block(n) { let hashes = traces .into_iter() .rev() - .filter_map(|trace| match trace.action { - Action::Call(Call { from, to, .. }) if from == address || to == address => { - trace.transaction_hash - } - _ => None, - }) + .filter_map(|trace| mentions_address(trace, address)) .unique(); - res.extend(hashes); - if res.len() >= page_size { - break + break; } + + res.extend(hashes); + } + + if n == to { + last_page = true; } } - 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. @@ -181,25 +244,22 @@ impl EthApi { address: Address, block_number: u64, page_size: usize, - ) -> Result { + ) -> Result>> { node_info!("ots_searchTransactionsAfter"); - let best = self.backend.best_number().as_u64(); + let best = self.backend.best_number(); // we go from the first post-fork block, up to the tip - let from = if block_number == 0 { - self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1) - } else { - block_number - }; + let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1); + let from = if block_number == 0 { first_block } else { block_number + 1 }; let to = best; - let first_page = from == best; + let mut first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in from..=to { - if n == to { + if n == first_block { last_page = true; } @@ -207,29 +267,24 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| match trace.action { - Action::Call(Call { from, to, .. }) if from == address || to == address => { - trace.transaction_hash - } - Action::Create(Create { from, .. }) if from == address => { - trace.transaction_hash - } - Action::Reward(Reward { author, .. }) if author == address => { - trace.transaction_hash - } - _ => None, - }) + .filter_map(|trace| mentions_address(trace, address)) .unique(); - res.extend(hashes); - if res.len() >= page_size { - break + break; } + + res.extend(hashes); + } + + if n == to { + first_page = true; } } - OtsSearchTransactions::build(res, &self.backend, first_page, last_page).await + // Results are always sent in reverse chronological order, according to the Otterscan spec + res.reverse(); + 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 @@ -239,17 +294,17 @@ impl EthApi { &self, address: Address, nonce: U256, - ) -> Result> { + ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); - let to = self.backend.best_number().as_u64(); + let to = self.backend.best_number(); for n in (from..=to).rev() { if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await { for tx in txs { - if tx.nonce == nonce && tx.from == address { - return Ok(Some(tx)) + if U256::from(tx.nonce()) == nonce && tx.from == address { + return Ok(Some(tx.tx_hash())); } } } @@ -260,28 +315,25 @@ 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(); - let to = self.backend.best_number().as_u64(); + let to = self.backend.best_number(); // loop in reverse, since we want the latest deploy to the address for n in (from..=to).rev() { - if let Some(traces) = dbg!(self.backend.mined_parity_trace_block(n)) { + if let Some(traces) = self.backend.mined_parity_trace_block(n) { for trace in traces.into_iter().rev() { - match (trace.action, trace.result) { + match (trace.trace.action, trace.trace.result) { ( - Action::Create(Create { from, .. }), - Some(Res::Create(CreateResult { address, .. })), + 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, - })) + })); } _ => {} } @@ -291,4 +343,143 @@ 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/main/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: AnyRpcBlock, + ) -> Result>> { + if block.transactions.is_uncle() { + return Err(BlockchainError::DataUnavailable); + } + let receipts_futs = block + .transactions + .hashes() + .map(|hash| async move { 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 as u128) * receipt.effective_gas_price); + + let Block { header, uncles, transactions, withdrawals } = block.inner; + + let block = + OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals }; + + Ok(BlockDetails { + block, + 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/main/docs/custom-jsonrpc.md#ots_getblockdetails + pub async fn build_ots_block_tx( + &self, + mut block: AnyRpcBlock, + 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.iter().skip(page * page_size).take(page_size).cloned().collect(), + ), + BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( + txs.iter().skip(page * page_size).take(page_size).cloned().collect(), + ), + BlockTransactions::Uncle => unreachable!(), + }; + + let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash)); + + let receipts = join_all(receipt_futs.map(|r| async { + if let Ok(Some(r)) = r.await { + let block = self.block_by_number(r.block_number.unwrap().into()).await?; + let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; + let receipt = r.map_inner(OtsReceipt::from); + Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) + } else { + Err(BlockchainError::BlockNotFound) + } + })) + .await + .into_iter() + .collect::>>()?; + + let transaction_count = block.transactions().len(); + let fullblock = OtsBlock { block: block.inner, transaction_count }; + + let ots_block_txs = OtsBlockTransactions { fullblock, receipts }; + + Ok(ots_block_txs) + } + + 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 receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash)); + + let receipts = join_all(receipt_futs.map(|r| async { + if let Ok(Some(r)) = r.await { + let block = self.block_by_number(r.block_number.unwrap().into()).await?; + let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; + let receipt = r.map_inner(OtsReceipt::from); + Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) + } else { + Err(BlockchainError::BlockNotFound) + } + })) + .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 8389f117b5571..e5fdf85eed770 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 e1b9fd6039f6c..0000000000000 --- a/crates/anvil/src/eth/otterscan/types.rs +++ /dev/null @@ -1,331 +0,0 @@ -use ethers::types::{ - Action, Address, Block, Bytes, Call, CallType, Create, CreateResult, Res, Suicide, Trace, - Transaction, TransactionReceipt, H256, U256, -}; -use futures::future::join_all; -use serde::{de::DeserializeOwned, Serialize}; - -use crate::eth::{ - backend::mem::Backend, - error::{BlockchainError, Result}, -}; - -/// Patched Block struct, to include the additional `transactionCount` field expected by Otterscan -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase", bound = "TX: Serialize + DeserializeOwned")] -pub struct OtsBlock { - #[serde(flatten)] - pub block: Block, - pub transaction_count: usize, -} - -/// Block structure with additional details regarding fees and issuance -#[derive(Serialize, Debug)] -#[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, Serialize, Default)] -pub struct Issuance { - block_reward: U256, - uncle_reward: U256, - issuance: U256, -} - -/// Holds both transactions and receipts for a block -#[derive(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: TransactionReceipt, - timestamp: u64, -} - -/// Information about the creator address and transaction for a contract -#[derive(Serialize, Debug)] -pub struct OtsContractCreator { - pub hash: H256, - pub creator: Address, -} - -/// Paginated search results of an account's history -#[derive(Serialize, Debug)] -#[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 -#[derive(Serialize, Debug, PartialEq)] -#[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 -#[derive(Serialize, Debug, PartialEq)] -pub enum OtsInternalOperationType { - Transfer = 0, - SelfDestruct = 1, - Create = 2, - // The spec asks for a Create2 entry as well, but we don't have that info -} - -/// Otterscan's representation of a trace -#[derive(Serialize, Debug, PartialEq)] -pub struct OtsTrace { - pub r#type: OtsTraceType, - pub depth: usize, - pub from: Address, - pub to: Address, - pub value: U256, - pub input: Bytes, -} - -/// The type of call being described by an Otterscan trace. Only CALL, STATICCALL and DELEGATECALL -/// are represented -#[derive(Serialize, Debug, PartialEq)] -#[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 bandwith, 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 { - let receipts_futs = block - .transactions - .iter() - .map(|tx| async { backend.transaction_receipt(tx.hash).await }); - - // fetch all receipts - let receipts: Vec = 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(U256::zero(), |acc, receipt| { - acc + receipt.gas_used.unwrap() * (receipt.effective_gas_price.unwrap()) - }); - - Ok(Self { - block: block.into(), - 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 { - let transaction_count = block.transactions.len(); - - Self { block, transaction_count } - } -} - -impl OtsBlockTransactions { - /// Fetches all receipts for the blocks's transactions, as required by the [`ots_getBlockTransactions`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails) endpoint spec, and returns the final response object. - pub async fn build( - mut block: Block, - backend: &Backend, - page: usize, - page_size: usize, - ) -> Result { - block.transactions = - block.transactions.into_iter().skip(page * page_size).take(page_size).collect(); - - let receipt_futs = block - .transactions - .iter() - .map(|tx| async { backend.transaction_receipt(tx.hash).await }); - - let receipts: Vec = 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 }) - } -} - -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: Vec) -> Vec { - traces - .iter() - .filter_map(|trace| { - match (trace.action.clone(), trace.result.clone()) { - (Action::Call(Call { from, to, value, .. }), _) if !value.is_zero() => { - Some(Self { r#type: OtsInternalOperationType::Transfer, from, to, value }) - } - ( - Action::Create(Create { from, value, .. }), - Some(Res::Create(CreateResult { address, .. })), - ) => Some(Self { - r#type: OtsInternalOperationType::Create, - from, - to: address, - value, - }), - (Action::Suicide(Suicide { address, .. }), _) => { - // this assumes a suicide trace always has a parent trace - let (from, value) = - Self::find_suicide_caller(&traces, &trace.trace_address).unwrap(); - - Some(Self { - r#type: OtsInternalOperationType::SelfDestruct, - from, - to: address, - value, - }) - } - _ => None, - } - }) - .collect() - } - - /// finds the trace that parents a given trace_address - fn find_suicide_caller( - traces: &Vec, - suicide_address: &Vec, - ) -> Option<(Address, U256)> { - traces.iter().find(|t| t.trace_address == suicide_address[..suicide_address.len() - 1]).map( - |t| match t.action { - Action::Call(Call { from, value, .. }) => (from, value), - - Action::Create(Create { from, value, .. }) => (from, value), - - // we assume here a suicice trace can never be parented by another suicide trace - Action::Suicide(_) => Self::find_suicide_caller(traces, &t.trace_address).unwrap(), - - Action::Reward(_) => unreachable!(), - }, - ) - } -} - -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.action { - Action::Call(call) => { - if let Ok(ots_type) = call.call_type.try_into() { - Some(OtsTrace { - r#type: ots_type, - depth: trace.trace_address.len(), - from: call.from, - to: call.to, - value: call.value, - input: call.input, - }) - } else { - None - } - } - Action::Create(_) => None, - Action::Suicide(_) => None, - Action::Reward(_) => None, - }) - .collect() - } -} - -impl TryFrom for OtsTraceType { - type Error = (); - - fn try_from(value: CallType) -> std::result::Result { - match value { - CallType::Call => Ok(OtsTraceType::Call), - CallType::StaticCall => Ok(OtsTraceType::StaticCall), - CallType::DelegateCall => Ok(OtsTraceType::DelegateCall), - _ => Err(()), - } - } -} diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index ee43d8556eda8..544d7eac9df37 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -20,7 +20,7 @@ //! used to determine whether it can be included in a block (transaction is ready) or whether it //! still _requires_ other transactions to be mined first (transaction is pending). //! A transaction is associated with the nonce of the account it's sent from. A unique identifying -//! marker for a transaction is therefor the pair `(nonce + account)`. An incoming transaction with +//! marker for a transaction is therefore the pair `(nonce + account)`. An incoming transaction with //! a `nonce > nonce on chain` will _require_ `(nonce -1, account)` first, before it is ready to be //! included in a block. //! @@ -36,15 +36,12 @@ use crate::{ }, mem::storage::MinedBlockOutcome, }; +use alloy_primitives::{Address, TxHash, U64}; +use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; -use ethers::{ - prelude::TxpoolStatus, - types::{TxHash, U64}, -}; use futures::channel::mpsc::{channel, Receiver, Sender}; use parking_lot::{Mutex, RwLock}; use std::{collections::VecDeque, fmt, sync::Arc}; -use tracing::{debug, trace, warn}; pub mod transactions; @@ -78,8 +75,8 @@ impl Pool { /// Returns the number of tx that are ready and queued for further execution pub fn txpool_status(&self) -> TxpoolStatus { // Note: naming differs here compared to geth's `TxpoolStatus` - let pending = self.ready_transactions().count().into(); - let queued = self.inner.read().pending_transactions.len().into(); + let pending: u64 = self.ready_transactions().count().try_into().unwrap_or(0); + let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0); TxpoolStatus { pending, queued } } @@ -90,7 +87,7 @@ impl Pool { let MinedBlockOutcome { block_number, included, invalid } = outcome; // remove invalid transactions from the pool - self.remove_invalid(invalid.into_iter().map(|tx| *tx.hash()).collect()); + self.remove_invalid(invalid.into_iter().map(|tx| tx.hash()).collect()); // prune all the markers the mined transactions provide let res = self @@ -144,6 +141,11 @@ impl Pool { self.inner.write().remove_invalid(tx_hashes) } + /// Remove transactions by sender + pub fn remove_transactions_by_address(&self, sender: Address) -> Vec> { + self.inner.write().remove_transactions_by_address(sender) + } + /// Removes a single transaction from the pool /// /// This is similar to `[Pool::remove_invalid()]` but for a single transaction. @@ -164,6 +166,12 @@ impl Pool { dropped } + /// Removes all transactions from the pool + pub fn clear(&self) { + let mut pool = self.inner.write(); + pool.clear(); + } + /// notifies all listeners about the transaction fn notify_listener(&self, hash: TxHash) { let mut listener = self.transaction_listener.lock(); @@ -209,6 +217,12 @@ impl PoolInner { self.ready_transactions.get_transactions() } + /// Clears + fn clear(&mut self) { + self.ready_transactions.clear(); + self.pending_transactions.clear(); + } + /// checks both pools for the matching transaction /// /// Returns `None` if the transaction does not exist in the pool @@ -221,13 +235,31 @@ impl PoolInner { ) } + /// Returns an iterator over all transactions in the pool filtered by the sender + pub fn transactions_by_sender( + &self, + sender: Address, + ) -> impl Iterator> + '_ { + let pending_txs = self + .pending_transactions + .transactions() + .filter(move |tx| tx.pending_transaction.sender().eq(&sender)); + + let ready_txs = self + .ready_transactions + .get_transactions() + .filter(move |tx| tx.pending_transaction.sender().eq(&sender)); + + pending_txs.chain(ready_txs) + } + /// Returns true if this pool already contains the transaction fn contains(&self, tx_hash: &TxHash) -> bool { self.pending_transactions.contains(tx_hash) || self.ready_transactions.contains(tx_hash) } fn add_transaction(&mut self, tx: PoolTransaction) -> Result { - if self.contains(tx.hash()) { + if self.contains(&tx.hash()) { warn!(target: "txpool", "[{:?}] Already imported", tx.hash()); return Err(PoolError::AlreadyImported(Box::new(tx))) } @@ -237,7 +269,7 @@ impl PoolInner { // If all markers are not satisfied import to future if !tx.is_ready() { - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); self.pending_transactions.add_transaction(tx)?; return Ok(AddedTransaction::Pending { hash }) } @@ -249,7 +281,7 @@ impl PoolInner { &mut self, tx: PendingPoolTransaction, ) -> Result { - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); trace!(target: "txpool", "adding ready transaction [{:?}]", hash); let mut ready = ReadyTransaction::new(hash); @@ -264,7 +296,7 @@ impl PoolInner { self.pending_transactions.mark_and_unlock(¤t_tx.transaction.provides), ); - let current_hash = *current_tx.transaction.hash(); + let current_hash = current_tx.transaction.hash(); // try to add the transaction to the ready pool match self.ready_transactions.add_transaction(current_tx) { Ok(replaced_transactions) => { @@ -317,7 +349,7 @@ impl PoolInner { let mut promoted = vec![]; let mut failed = vec![]; for tx in imports { - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); match self.add_ready_transaction(tx) { Ok(res) => promoted.push(res), Err(e) => { @@ -345,6 +377,25 @@ impl PoolInner { removed } + + /// Remove transactions by sender address + pub fn remove_transactions_by_address(&mut self, sender: Address) -> Vec> { + let tx_hashes = + self.transactions_by_sender(sender).map(move |tx| tx.hash()).collect::>(); + + if tx_hashes.is_empty() { + return vec![] + } + + trace!(target: "txpool", "Removing transactions: {:?}", tx_hashes); + + let mut removed = self.ready_transactions.remove_with_markers(tx_hashes.clone(), None); + removed.extend(self.pending_transactions.remove(tx_hashes)); + + trace!(target: "txpool", "Removed transactions: {:?}", removed); + + removed + } } /// Represents the outcome of a prune @@ -358,7 +409,7 @@ pub struct PruneResult { } impl fmt::Debug for PruneResult { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "PruneResult {{ ")?; write!( fmt, @@ -376,7 +427,7 @@ impl fmt::Debug for PruneResult { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ReadyTransaction { /// the hash of the submitted transaction hash: TxHash, @@ -399,7 +450,7 @@ impl ReadyTransaction { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum AddedTransaction { /// transaction was successfully added and being processed Ready(ReadyTransaction), @@ -413,8 +464,8 @@ pub enum AddedTransaction { impl AddedTransaction { pub fn hash(&self) -> &TxHash { match self { - AddedTransaction::Ready(tx) => &tx.hash, - AddedTransaction::Pending { hash } => hash, + Self::Ready(tx) => &tx.hash, + Self::Pending { hash } => hash, } } } diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 39b0d1cc1fe13..36e421d7a50e1 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,16 +1,12 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; +use alloy_network::AnyRpcTransaction; +use alloy_primitives::{ + map::{HashMap, HashSet}, + Address, TxHash, +}; use anvil_core::eth::transaction::{PendingTransaction, TypedTransaction}; -use ethers::types::{Address, TxHash, U256}; use parking_lot::RwLock; -use std::{ - cmp::Ordering, - collections::{BTreeSet, HashMap, HashSet}, - fmt, - str::FromStr, - sync::Arc, - time::Instant, -}; -use tracing::{trace, warn}; +use std::{cmp::Ordering, collections::BTreeSet, fmt, str::FromStr, sync::Arc, time::Instant}; /// A unique identifying marker for a transaction pub type TxMarker = Vec; @@ -26,7 +22,7 @@ pub fn to_marker(nonce: u64, from: Address) -> TxMarker { /// Modes that determine the transaction ordering of the mempool /// /// This type controls the transaction order via the priority metric of a transaction -#[derive(Debug, Clone, Eq, PartialEq, Copy, serde::Serialize, serde::Deserialize, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum TransactionOrder { /// Keep the pool transaction transactions sorted in the order they arrive. /// @@ -38,14 +34,12 @@ pub enum TransactionOrder { Fees, } -// === impl TransactionOrder === - impl TransactionOrder { /// Returns the priority of the transactions pub fn priority(&self, tx: &TypedTransaction) -> TransactionPriority { match self { - TransactionOrder::Fifo => TransactionPriority::default(), - TransactionOrder::Fees => TransactionPriority(tx.gas_price()), + Self::Fifo => TransactionPriority::default(), + Self::Fees => TransactionPriority(tx.gas_price()), } } } @@ -56,8 +50,8 @@ impl FromStr for TransactionOrder { fn from_str(s: &str) -> Result { let s = s.to_lowercase(); let order = match s.as_str() { - "fees" => TransactionOrder::Fees, - "fifo" => TransactionOrder::Fifo, + "fees" => Self::Fees, + "fifo" => Self::Fifo, _ => return Err(format!("Unknown TransactionOrder: `{s}`")), }; Ok(order) @@ -66,10 +60,10 @@ impl FromStr for TransactionOrder { /// Metric value for the priority of a transaction. /// -/// The `TransactionPriority` determines the ordering of two transactions that have all their +/// The `TransactionPriority` determines the ordering of two transactions that have all their /// markers satisfied. -#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Default)] -pub struct TransactionPriority(pub U256); +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] @@ -87,19 +81,27 @@ pub struct PoolTransaction { // == impl PoolTransaction == impl PoolTransaction { + pub fn new(transaction: PendingTransaction) -> Self { + Self { + pending_transaction: transaction, + requires: vec![], + provides: vec![], + priority: TransactionPriority(0), + } + } /// Returns the hash of this transaction - pub fn hash(&self) -> &TxHash { - self.pending_transaction.hash() + pub fn hash(&self) -> TxHash { + *self.pending_transaction.hash() } /// Returns the gas pric of this transaction - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.pending_transaction.transaction.gas_price() } } impl fmt::Debug for PoolTransaction { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "Transaction {{ ")?; write!(fmt, "hash: {:?}, ", &self.pending_transaction.hash())?; write!(fmt, "requires: [{}], ", hex_fmt_many(self.requires.iter()))?; @@ -110,10 +112,23 @@ impl fmt::Debug for PoolTransaction { } } +impl TryFrom for PoolTransaction { + type Error = eyre::Error; + fn try_from(value: AnyRpcTransaction) -> Result { + let typed_transaction = TypedTransaction::try_from(value)?; + 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 -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct PendingTransactions { /// markers that aren't yet provided by any transaction required_markers: HashMap>, @@ -135,6 +150,13 @@ impl PendingTransactions { self.waiting_queue.is_empty() } + /// Clears internal state + pub fn clear(&mut self) { + self.required_markers.clear(); + self.waiting_markers.clear(); + self.waiting_queue.clear(); + } + /// Returns an iterator over all transactions in the waiting pool pub fn transactions(&self) -> impl Iterator> + '_ { self.waiting_queue.values().map(|tx| tx.transaction.clone()) @@ -144,7 +166,7 @@ impl PendingTransactions { pub fn add_transaction(&mut self, tx: PendingPoolTransaction) -> Result<(), PoolError> { assert!(!tx.is_ready(), "transaction must not be ready"); assert!( - !self.waiting_queue.contains_key(tx.transaction.hash()), + !self.waiting_queue.contains_key(&tx.transaction.hash()), "transaction is already added" ); @@ -164,13 +186,13 @@ impl PendingTransactions { // add all missing markers for marker in &tx.missing_markers { - self.required_markers.entry(marker.clone()).or_default().insert(*tx.transaction.hash()); + self.required_markers.entry(marker.clone()).or_default().insert(tx.transaction.hash()); } // also track identifying markers - self.waiting_markers.insert(tx.transaction.provides.clone(), *tx.transaction.hash()); + self.waiting_markers.insert(tx.transaction.provides.clone(), tx.transaction.hash()); // add tx to the queue - self.waiting_queue.insert(*tx.transaction.hash(), tx); + self.waiting_queue.insert(tx.transaction.hash(), tx); Ok(()) } @@ -283,7 +305,7 @@ impl PendingPoolTransaction { } impl fmt::Debug for PendingPoolTransaction { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { write!(fmt, "PendingTransaction {{ ")?; write!(fmt, "added_at: {:?}, ", self.added_at)?; write!(fmt, "tx: {:?}, ", self.transaction)?; @@ -310,7 +332,7 @@ impl TransactionsIterator { self.independent.insert(tx_ref); } else { // otherwise we're still awaiting for some deps - self.awaiting.insert(*tx_ref.transaction.hash(), (satisfied, tx_ref)); + self.awaiting.insert(tx_ref.transaction.hash(), (satisfied, tx_ref)); } } } @@ -325,7 +347,7 @@ impl Iterator for TransactionsIterator { let hash = best.transaction.hash(); let ready = - if let Some(ready) = self.all.get(hash).cloned() { ready } else { continue }; + if let Some(ready) = self.all.get(&hash).cloned() { ready } else { continue }; // Insert transactions that just got unlocked. for hash in &ready.unlocks { @@ -350,7 +372,7 @@ impl Iterator for TransactionsIterator { } /// transactions that are ready to be included in a block. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct ReadyTransactions { /// keeps track of transactions inserted in the pool /// @@ -378,6 +400,13 @@ impl ReadyTransactions { } } + /// Clears the internal state + pub fn clear(&mut self) { + self.provided_markers.clear(); + self.ready_tx.write().clear(); + self.independent_transactions.clear(); + } + /// Returns true if the transaction is part of the queue. pub fn contains(&self, hash: &TxHash) -> bool { self.ready_tx.read().contains_key(hash) @@ -398,26 +427,26 @@ impl ReadyTransactions { id } - /// Adds a new transactions to the ready queue + /// Adds a new transactions to the ready queue. /// /// # Panics /// - /// if the pending transaction is not ready: [PendingTransaction::is_ready()] - /// or the transaction is already included + /// If the pending transaction is not ready ([`PendingPoolTransaction::is_ready`]) + /// or the transaction is already included. pub fn add_transaction( &mut self, tx: PendingPoolTransaction, ) -> Result>, PoolError> { assert!(tx.is_ready(), "transaction must be ready",); assert!( - !self.ready_tx.read().contains_key(tx.transaction.hash()), + !self.ready_tx.read().contains_key(&tx.transaction.hash()), "transaction already included" ); let (replaced_tx, unlocks) = self.replaced_transactions(&tx.transaction)?; let id = self.next_id(); - let hash = *tx.transaction.hash(); + let hash = tx.transaction.hash(); let mut independent = true; let mut requires_offset = 0; @@ -474,7 +503,7 @@ impl ReadyTransactions { // construct a list of unlocked transactions // also check for transactions that shouldn't be replaced because underpriced let ready = self.ready_tx.read(); - for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(hash)) { + for to_remove in remove_hashes.iter().filter_map(|hash| ready.get(*hash)) { // if we're attempting to replace a transaction that provides the exact same markers // (addr + nonce) then we check for gas price if to_remove.provides() == tx.provides { @@ -535,7 +564,7 @@ impl ReadyTransactions { let prev_hash = self.provided_markers.get(marker)?; let tx2 = ready.get_mut(prev_hash)?; // remove hash - if let Some(idx) = tx2.unlocks.iter().position(|i| i == hash) { + if let Some(idx) = tx2.unlocks.iter().position(|i| i == &hash) { tx2.unlocks.swap_remove(idx); } if tx2.unlocks.is_empty() { @@ -567,7 +596,7 @@ impl ReadyTransactions { for marker in &tx.provides { let removed = self.provided_markers.remove(marker); assert_eq!( - removed.as_ref(), + removed, if current_marker == marker { None } else { Some(tx.hash()) }, "The pool contains exactly one transaction providing given tag; the removed transaction claims to provide that tag, so it has to be mapped to it's hash; qed" @@ -632,7 +661,7 @@ impl ReadyTransactions { } /// A reference to a transaction in the pool -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct PoolTransactionRef { /// actual transaction pub transaction: Arc, @@ -663,7 +692,7 @@ impl Ord for PoolTransactionRef { } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ReadyTransaction { /// ref to the actual transaction pub transaction: PoolTransactionRef, @@ -673,14 +702,12 @@ pub struct ReadyTransaction { pub requires_offset: usize, } -// === impl ReadyTransaction == - impl ReadyTransaction { pub fn provides(&self) -> &[TxMarker] { &self.transaction.transaction.provides } - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.transaction.transaction.gas_price() } } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 46fbecb93725a..e2ea036a0cafb 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,20 +1,14 @@ use crate::eth::error::BlockchainError; +use alloy_consensus::SignableTransaction; +use alloy_dyn_abi::TypedData; +use alloy_network::TxSignerSync; +use alloy_primitives::{map::AddressHashMap, Address, PrimitiveSignature as Signature, B256, U256}; +use alloy_signer::Signer as AlloySigner; +use alloy_signer_local::PrivateKeySigner; use anvil_core::eth::transaction::{ - EIP1559Transaction, EIP1559TransactionRequest, EIP2930Transaction, EIP2930TransactionRequest, - LegacyTransaction, LegacyTransactionRequest, TypedTransaction, TypedTransactionRequest, + optimism::DepositTransaction, TypedTransaction, TypedTransactionRequest, }; -use ethers::{ - core::k256::ecdsa::SigningKey, - prelude::{Address, Wallet}, - signers::Signer as EthersSigner, - types::{ - transaction::{ - eip2718::TypedTransaction as EthersTypedTransactionRequest, eip712::TypedData, - }, - Signature, H256, - }, -}; -use std::collections::HashMap; +use op_alloy_consensus::TxDeposit; /// A transaction signer #[async_trait::async_trait] @@ -30,13 +24,17 @@ pub trait Signer: Send + Sync { /// Returns the signature async fn sign(&self, address: Address, message: &[u8]) -> Result; - /// Encodes and signs the typed data according EIP-712. Payload must implement Eip712 trait. + /// Encodes and signs the typed data according EIP-712. Payload must conform to the EIP-712 + /// standard. async fn sign_typed_data( &self, address: Address, payload: &TypedData, ) -> Result; + /// Signs the given hash. + async fn sign_hash(&self, address: Address, hash: B256) -> Result; + /// signs a transaction request using the given account in request fn sign_transaction( &self, @@ -48,11 +46,11 @@ pub trait Signer: Send + Sync { /// Maintains developer keys pub struct DevSigner { addresses: Vec
, - accounts: HashMap>, + accounts: AddressHashMap, } impl DevSigner { - pub fn new(accounts: Vec>) -> Self { + pub fn new(accounts: Vec) -> Self { let addresses = accounts.iter().map(|wallet| wallet.address()).collect::>(); let accounts = addresses.iter().cloned().zip(accounts).collect(); Self { addresses, accounts } @@ -80,8 +78,20 @@ impl Signer for DevSigner { address: Address, payload: &TypedData, ) -> Result { + let mut signer = + self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?.to_owned(); + + // Explicitly set chainID as none, to avoid any EIP-155 application to `v` when signing + // typed data. + signer.set_chain_id(None); + + Ok(signer.sign_dynamic_typed_data(payload).await?) + } + + async fn sign_hash(&self, address: Address, hash: B256) -> Result { let signer = self.accounts.get(&address).ok_or(BlockchainError::NoSignerAvailable)?; - Ok(signer.sign_typed_data(payload).await?) + + Ok(signer.sign_hash(&hash).await?) } fn sign_transaction( @@ -90,9 +100,15 @@ impl Signer for DevSigner { address: &Address, ) -> Result { let signer = self.accounts.get(address).ok_or(BlockchainError::NoSignerAvailable)?; - let ethers_tx: EthersTypedTransactionRequest = request.into(); - - Ok(signer.sign_transaction_sync(ðers_tx)?) + match request { + TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::Deposit(_) => { + unreachable!("op deposit txs should not be signed") + } + } } } @@ -106,92 +122,38 @@ pub fn build_typed_transaction( signature: Signature, ) -> Result { let tx = match request { - TypedTransactionRequest::Legacy(tx) => { - let LegacyTransactionRequest { - nonce, gas_price, gas_limit, kind, value, input, .. - } = tx; - TypedTransaction::Legacy(LegacyTransaction { - nonce, - gas_price, - gas_limit, - kind, - value, - input, - signature, - }) - } + TypedTransactionRequest::Legacy(tx) => TypedTransaction::Legacy(tx.into_signed(signature)), TypedTransactionRequest::EIP2930(tx) => { - let EIP2930TransactionRequest { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list, - } = tx; - - let recid: u8 = signature.recovery_id()?.into(); - - TypedTransaction::EIP2930(EIP2930Transaction { - chain_id, - nonce, - gas_price, - gas_limit, - kind, - value, - input, - access_list: access_list.into(), - odd_y_parity: recid != 0, - r: { - let mut rarr = [0_u8; 32]; - signature.r.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0_u8; 32]; - signature.s.to_big_endian(&mut sarr); - H256::from(sarr) - }, - }) + TypedTransaction::EIP2930(tx.into_signed(signature)) } TypedTransactionRequest::EIP1559(tx) => { - let EIP1559TransactionRequest { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, + TypedTransaction::EIP1559(tx.into_signed(signature)) + } + TypedTransactionRequest::EIP4844(tx) => { + TypedTransaction::EIP4844(tx.into_signed(signature)) + } + TypedTransactionRequest::Deposit(tx) => { + let TxDeposit { + from, gas_limit, - kind, + to, value, input, - access_list, + source_hash, + mint, + is_system_transaction, + .. } = tx; - - let recid: u8 = signature.recovery_id()?.into(); - - TypedTransaction::EIP1559(EIP1559Transaction { - chain_id, - nonce, - max_priority_fee_per_gas, - max_fee_per_gas, + TypedTransaction::Deposit(DepositTransaction { + from, gas_limit, - kind, + kind: to, value, input, - access_list: access_list.into(), - odd_y_parity: recid != 0, - r: { - let mut rarr = [0u8; 32]; - signature.r.to_big_endian(&mut rarr); - H256::from(rarr) - }, - s: { - let mut sarr = [0u8; 32]; - signature.s.to_big_endian(&mut sarr); - H256::from(sarr) - }, + source_hash, + mint: mint.map_or(U256::ZERO, U256::from), + is_system_tx: is_system_transaction, + nonce: 0, }) } }; diff --git a/crates/anvil/src/eth/util.rs b/crates/anvil/src/eth/util.rs index 078971c577884..ca66f2ed3d3c5 100644 --- a/crates/anvil/src/eth/util.rs +++ b/crates/anvil/src/eth/util.rs @@ -1,13 +1,12 @@ -use ethers::{abi::Address, types::H160}; -use foundry_evm::revm::{self, precompile::Precompiles, primitives::SpecId}; +use alloy_primitives::Address; +use foundry_evm::revm::{ + precompile::{PrecompileSpecId, Precompiles}, + primitives::SpecId, +}; use std::fmt; pub fn get_precompiles_for(spec_id: SpecId) -> Vec
{ - Precompiles::new(to_precompile_id(spec_id)) - .addresses() - .into_iter() - .map(|item| H160::from_slice(item)) - .collect() + Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).addresses().copied().collect() } /// wrapper type that displays byte as hex @@ -30,8 +29,8 @@ impl<'a> HexDisplay<'a> { } } -impl<'a> fmt::Display for HexDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl fmt::Display for HexDisplay<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.0.len() < 1027 { for byte in self.0 { f.write_fmt(format_args!("{byte:02x}"))?; @@ -49,34 +48,11 @@ impl<'a> fmt::Display for HexDisplay<'a> { } } -impl<'a> fmt::Debug for HexDisplay<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl fmt::Debug for HexDisplay<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for byte in self.0 { f.write_fmt(format_args!("{byte:02x}"))?; } Ok(()) } } - -pub fn to_precompile_id(spec_id: SpecId) -> revm::precompile::SpecId { - match spec_id { - SpecId::FRONTIER | - SpecId::FRONTIER_THAWING | - SpecId::HOMESTEAD | - SpecId::DAO_FORK | - SpecId::TANGERINE | - SpecId::SPURIOUS_DRAGON => revm::precompile::SpecId::HOMESTEAD, - SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { - revm::precompile::SpecId::BYZANTIUM - } - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => revm::precompile::SpecId::ISTANBUL, - SpecId::BERLIN | - SpecId::LONDON | - SpecId::ARROW_GLACIER | - SpecId::GRAY_GLACIER | - SpecId::MERGE | - SpecId::SHANGHAI | - SpecId::CANCUN | - SpecId::LATEST => revm::precompile::SpecId::BERLIN, - } -} diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm.rs new file mode 100644 index 0000000000000..794d2ce853ca9 --- /dev/null +++ b/crates/anvil/src/evm.rs @@ -0,0 +1,81 @@ +use alloy_primitives::Address; +use foundry_evm::revm::precompile::Precompile; +use std::{fmt::Debug, sync::Arc}; + +/// Object-safe trait that enables injecting extra precompiles when using +/// `anvil` as a library. +pub trait PrecompileFactory: Send + Sync + Unpin + Debug { + /// Returns a set of precompiles to extend the EVM with. + fn precompiles(&self) -> Vec<(Address, Precompile)>; +} + +/// Appends a handler register to `evm` that injects the given `precompiles`. +/// +/// This will add an additional handler that extends the default precompiles with the given set of +/// precompiles. +pub fn inject_precompiles( + evm: &mut revm::Evm<'_, I, DB>, + precompiles: Vec<(Address, Precompile)>, +) { + evm.handler.append_handler_register_box(Box::new(move |handler| { + let precompiles = precompiles.clone(); + let prev = handler.pre_execution.load_precompiles.clone(); + handler.pre_execution.load_precompiles = Arc::new(move || { + let mut cx = prev(); + cx.extend(precompiles.iter().cloned().map(|(a, b)| (a, b.into()))); + cx + }); + })); +} + +#[cfg(test)] +mod tests { + use crate::{evm::inject_precompiles, PrecompileFactory}; + use alloy_primitives::Address; + use foundry_evm::revm::primitives::{address, Bytes, Precompile, PrecompileResult, SpecId}; + use revm::primitives::PrecompileOutput; + + #[test] + fn build_evm_with_extra_precompiles() { + const PRECOMPILE_ADDR: Address = address!("0000000000000000000000000000000000000071"); + + fn my_precompile(_bytes: &Bytes, _gas_limit: u64) -> PrecompileResult { + Ok(PrecompileOutput { bytes: Bytes::new(), gas_used: 0 }) + } + + #[derive(Debug)] + struct CustomPrecompileFactory; + + impl PrecompileFactory for CustomPrecompileFactory { + fn precompiles(&self) -> Vec<(Address, Precompile)> { + vec![(PRECOMPILE_ADDR, Precompile::Standard(my_precompile))] + } + } + + let db = revm::db::EmptyDB::default(); + let env = Box::::default(); + let spec = SpecId::LATEST; + let handler_cfg = revm::primitives::HandlerCfg::new(spec); + let inspector = revm::inspectors::NoOpInspector; + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + let handler = revm::Handler::new(handler_cfg); + let mut evm = revm::Evm::new(context, handler); + assert!(!evm + .handler + .pre_execution() + .load_precompiles() + .addresses() + .any(|&addr| addr == PRECOMPILE_ADDR)); + + inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); + assert!(evm + .handler + .pre_execution() + .load_precompiles() + .addresses() + .any(|&addr| addr == PRECOMPILE_ADDR)); + + let result = evm.transact().unwrap(); + assert!(result.result.is_success()); + } +} diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index 0409195caeba4..c0c8e7aef15e1 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -4,22 +4,18 @@ use crate::{ pubsub::filter_logs, StorageInfo, }; +use alloy_primitives::{map::HashMap, TxHash}; +use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; use anvil_rpc::response::ResponseResult; -use ethers::{ - prelude::{Log as EthersLog, H256 as TxHash}, - types::{Filter, FilteredParams}, -}; use futures::{channel::mpsc::Receiver, Stream, StreamExt}; use std::{ - collections::HashMap, pin::Pin, sync::Arc, task::{Context, Poll}, time::{Duration, Instant}, }; use tokio::sync::Mutex; -use tracing::{trace, warn}; /// Type alias for filters identified by their id and their expiration timestamp type FilterMap = Arc>>; @@ -28,7 +24,7 @@ type FilterMap = Arc>>; pub const ACTIVE_FILTER_TIMEOUT_SECS: u64 = 60 * 5; /// Contains all registered filters -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Filters { /// all currently active filters active_filters: FilterMap, @@ -36,8 +32,6 @@ pub struct Filters { keepalive: Duration, } -// === impl Filters === - impl Filters { /// Adds a new `EthFilter` to the set pub async fn add_filter(&self, filter: EthFilter) -> String { @@ -126,23 +120,21 @@ pub enum EthFilter { PendingTransactions(Receiver), } -// === impl EthFilter === - impl Stream for EthFilter { type Item = ResponseResult; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); match pin { - EthFilter::Logs(logs) => Poll::Ready(Some(Ok(logs.poll(cx)).to_rpc_result())), - EthFilter::Blocks(blocks) => { + Self::Logs(logs) => Poll::Ready(Some(Ok(logs.poll(cx)).to_rpc_result())), + Self::Blocks(blocks) => { let mut new_blocks = Vec::new(); while let Poll::Ready(Some(block)) = blocks.poll_next_unpin(cx) { new_blocks.push(block.hash); } Poll::Ready(Some(Ok(new_blocks).to_rpc_result())) } - EthFilter::PendingTransactions(tx) => { + Self::PendingTransactions(tx) => { let mut new_txs = Vec::new(); while let Poll::Ready(Some(tx_hash)) = tx.poll_next_unpin(cx) { new_txs.push(tx_hash); @@ -165,14 +157,12 @@ pub struct LogsFilter { /// existing logs that matched the filter when the listener was installed /// /// They'll be returned on the first pill - pub historic: Option>, + pub historic: Option>, } -// === impl LogsFilter === - impl LogsFilter { /// Returns all the logs since the last time this filter was polled - pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { + pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { let mut logs = self.historic.take().unwrap_or_default(); while let Poll::Ready(Some(block)) = self.blocks.poll_next_unpin(cx) { let b = self.storage.block(block.hash); diff --git a/crates/anvil/src/genesis.rs b/crates/anvil/src/genesis.rs deleted file mode 100644 index 3ab285a1018ad..0000000000000 --- a/crates/anvil/src/genesis.rs +++ /dev/null @@ -1,301 +0,0 @@ -//! Bindings for geth's `genesis.json` format -use crate::revm::primitives::AccountInfo; -use ethers::{ - signers::LocalWallet, - types::{serde_helpers::*, Address, Bytes, H256, U256}, -}; -use foundry_common::errors::FsPathError; -use foundry_evm::{ - revm::primitives::{Bytecode, Env, KECCAK_EMPTY, U256 as rU256}, - utils::h160_to_b160, -}; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashMap}, - path::Path, -}; - -/// Genesis specifies the header fields, state of a genesis block. It also defines hard fork -/// switch-over blocks through the chain configuration See also: -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Genesis { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub config: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub nonce: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub timestamp: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub extra_data: Option, - #[serde(deserialize_with = "deserialize_stringified_u64")] - pub gas_limit: u64, - #[serde(deserialize_with = "deserialize_stringified_u64")] - pub difficulty: u64, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub mix_hash: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub coinbase: Option
, - #[serde(default)] - pub alloc: Alloc, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub number: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub gas_used: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub parent_hash: Option, - #[serde( - default, - deserialize_with = "deserialize_stringified_numeric_opt", - skip_serializing_if = "Option::is_none" - )] - pub base_fee_per_gas: Option, -} - -impl Genesis { - /// Loads the `Genesis` object from the given json file path - pub fn load(path: impl AsRef) -> Result { - foundry_common::fs::read_json_file(path.as_ref()) - } - - /// The clap `value_parser` function - pub(crate) fn parse(path: &str) -> Result { - Self::load(path).map_err(|err| err.to_string()) - } - - pub fn chain_id(&self) -> Option { - self.config.as_ref().and_then(|c| c.chain_id) - } - - /// Applies all settings to the given `env` - pub fn apply(&self, env: &mut Env) { - if let Some(chain_id) = self.chain_id() { - env.cfg.chain_id = rU256::from(chain_id); - } - if let Some(timestamp) = self.timestamp { - env.block.timestamp = rU256::from(timestamp); - } - if let Some(base_fee) = self.base_fee_per_gas { - env.block.basefee = base_fee.into(); - } - if let Some(number) = self.number { - env.block.number = rU256::from(number); - } - if let Some(coinbase) = self.coinbase { - env.block.coinbase = h160_to_b160(coinbase); - } - env.block.difficulty = rU256::from(self.difficulty); - env.block.gas_limit = rU256::from(self.gas_limit); - } - - /// Returns all private keys from the genesis accounts, if they exist - pub fn private_keys(&self) -> Vec { - self.alloc.accounts.values().filter_map(|acc| acc.private_key.clone()).collect() - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, Default)] -#[serde(transparent)] -pub struct Alloc { - pub accounts: BTreeMap, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GenesisAccount { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub code: Option, - #[serde(default, skip_serializing_if = "HashMap::is_empty")] - pub storage: HashMap, - #[serde(deserialize_with = "deserialize_stringified_numeric")] - pub balance: U256, - #[serde( - default, - deserialize_with = "deserialize_stringified_u64_opt", - skip_serializing_if = "Option::is_none" - )] - pub nonce: Option, - #[serde( - rename = "secretKey", - default, - skip_serializing_if = "Option::is_none", - with = "secret_key" - )] - pub private_key: Option, -} - -impl From for AccountInfo { - fn from(acc: GenesisAccount) -> Self { - let GenesisAccount { code, balance, nonce, .. } = acc; - let code = code.map(|code| Bytecode::new_raw(code.to_vec().into())); - AccountInfo { - balance: balance.into(), - nonce: nonce.unwrap_or_default(), - code_hash: code.as_ref().map(|code| code.hash).unwrap_or(KECCAK_EMPTY), - code, - } - } -} - -/// ChainConfig is the core config which determines the blockchain settings. -/// -/// ChainConfig is stored in the database on a per block basis. This means -/// that any network, identified by its genesis block, can have its own -/// set of configuration options. -/// <(https://github.com/ethereum/go-ethereum/blob/0ce494b60cd00d70f1f9f2dd0b9bfbd76204168a/params/config.go#L342-L387> -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Config { - #[serde(default, skip_serializing_if = "Option::is_none")] - pub chain_id: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub homestead_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub dao_fork_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub dao_fork_support: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip150_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip150_hash: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip155_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub eip158_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub byzantium_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub constantinople_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub petersburg_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub istanbul_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub muir_glacier_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub berlin_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub london_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub arrow_glacier_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub gray_glacier_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub merge_netsplit_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub shanghai_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub cancun_block: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub terminal_total_difficulty: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub terminal_total_difficulty_passed: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub ethash: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub clique: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct EthashConfig {} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct CliqueConfig { - pub period: u64, - pub epoch: u64, -} - -/// serde support for `secretKey` in genesis - -pub mod secret_key { - use ethers::{core::k256::SecretKey, signers::LocalWallet, types::Bytes}; - use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; - - pub fn serialize(value: &Option, serializer: S) -> Result - where - S: Serializer, - { - if let Some(wallet) = value { - Bytes::from(wallet.signer().to_bytes().as_ref()).serialize(serializer) - } else { - serializer.serialize_none() - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - if let Some(s) = Option::::deserialize(deserializer)? { - if s.is_empty() { - return Ok(None) - } - SecretKey::from_bytes(s.as_ref().into()) - .map_err(de::Error::custom) - .map(Into::into) - .map(Some) - } else { - Ok(None) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_parse_genesis_json() { - let s = r#"{ - "config": { - "chainId": 19763, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "ethash": {} - }, - "nonce": "0xdeadbeefdeadbeef", - "timestamp": "0x0", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x80000000", - "difficulty": "0x20000", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "alloc": { - "71562b71999873db5b286df957af199ec94617f7": { - "balance": "0xffffffffffffffffffffffffff", - "secretkey": "0x305b526d493844b63466be6d48a424ab83f5216011eef860acc6db4c1821adc9" - } - }, - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} -"#; - - let gen: Genesis = serde_json::from_str(s).unwrap(); - assert_eq!(gen.nonce, Some(16045690984833335023)); - assert_eq!(gen.gas_limit, 2147483648); - assert_eq!(gen.difficulty, 131072); - assert_eq!(gen.alloc.accounts.len(), 1); - let config = gen.config.unwrap(); - assert_eq!(config.chain_id, Some(19763)); - } -} diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index 82143c4fe4f36..2aedb7986e49b 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -1,10 +1,37 @@ -use ethereum_forkid::{ForkHash, ForkId}; -use ethers::types::BlockNumber; +use alloy_rpc_types::BlockNumberOrTag; +use eyre::bail; use foundry_evm::revm::primitives::SpecId; use std::str::FromStr; -#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] -pub enum Hardfork { +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ChainHardfork { + Ethereum(EthereumHardfork), + Optimism(OptimismHardfork), +} + +impl From for ChainHardfork { + fn from(value: EthereumHardfork) -> Self { + Self::Ethereum(value) + } +} + +impl From for ChainHardfork { + fn from(value: OptimismHardfork) -> Self { + Self::Optimism(value) + } +} + +impl From for SpecId { + fn from(fork: ChainHardfork) -> Self { + match fork { + ChainHardfork::Ethereum(hardfork) => hardfork.into(), + ChainHardfork::Optimism(hardfork) => hardfork.into(), + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum EthereumHardfork { Frontier, Homestead, Dao, @@ -21,224 +48,185 @@ pub enum Hardfork { GrayGlacier, Paris, Shanghai, + Cancun, + Prague, + PragueEOF, #[default] Latest, } -impl Hardfork { +impl EthereumHardfork { /// Get the first block number of the hardfork. pub fn fork_block(&self) -> u64 { match *self { - Hardfork::Frontier => 0, - Hardfork::Homestead => 1150000, - Hardfork::Dao => 1920000, - Hardfork::Tangerine => 2463000, - Hardfork::SpuriousDragon => 2675000, - Hardfork::Byzantium => 4370000, - Hardfork::Constantinople | Hardfork::Petersburg => 7280000, - Hardfork::Istanbul => 9069000, - Hardfork::Muirglacier => 9200000, - Hardfork::Berlin => 12244000, - Hardfork::London => 12965000, - Hardfork::ArrowGlacier => 13773000, - Hardfork::GrayGlacier => 15050000, - Hardfork::Paris => 15537394, - Hardfork::Shanghai | Hardfork::Latest => 17034870, - } - } - - /// Get the EIP-2124 fork id for a given hardfork - /// - /// The [`ForkId`](ethereum_forkid::ForkId) includes a CRC32 checksum of the all fork block - /// numbers from genesis, and the next upcoming fork block number. - /// If the next fork block number is not yet known, it is set to 0. - pub fn fork_id(&self) -> ForkId { - match *self { - Hardfork::Frontier => { - ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 } - } - Hardfork::Homestead => { - ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 } - } - Hardfork::Dao => ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 }, - Hardfork::Tangerine => { - ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 } - } - Hardfork::SpuriousDragon => { - ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 } - } - Hardfork::Byzantium => { - ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 } - } - Hardfork::Constantinople | Hardfork::Petersburg => { - ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 } - } - Hardfork::Istanbul => { - ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 } - } - Hardfork::Muirglacier => { - ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 } - } - Hardfork::Berlin => ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 }, - Hardfork::London => ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 }, - Hardfork::ArrowGlacier => { - ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 } - } - Hardfork::GrayGlacier => { - ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 15537394 } - } - Hardfork::Paris => ForkId { hash: ForkHash([0x4f, 0xb8, 0xa8, 0x72]), next: 17034870 }, - Hardfork::Shanghai | Hardfork::Latest => { - // update `next` when another fork block num is known - ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 0 } - } + Self::Frontier => 0, + Self::Homestead => 1150000, + Self::Dao => 1920000, + Self::Tangerine => 2463000, + Self::SpuriousDragon => 2675000, + Self::Byzantium => 4370000, + Self::Constantinople | Self::Petersburg => 7280000, + Self::Istanbul => 9069000, + Self::Muirglacier => 9200000, + Self::Berlin => 12244000, + Self::London => 12965000, + Self::ArrowGlacier => 13773000, + Self::GrayGlacier => 15050000, + Self::Paris => 15537394, + Self::Shanghai => 17034870, + Self::Cancun | Self::Latest => 19426587, + // TODO: add block after activation + Self::Prague | Self::PragueEOF => unreachable!(), } } } -impl FromStr for Hardfork { - type Err = String; +impl FromStr for EthereumHardfork { + type Err = eyre::Report; fn from_str(s: &str) -> Result { let s = s.to_lowercase(); let hardfork = match s.as_str() { - "frontier" | "1" => Hardfork::Frontier, - "homestead" | "2" => Hardfork::Homestead, - "dao" | "3" => Hardfork::Dao, - "tangerine" | "4" => Hardfork::Tangerine, - "spuriousdragon" | "5" => Hardfork::SpuriousDragon, - "byzantium" | "6" => Hardfork::Byzantium, - "constantinople" | "7" => Hardfork::Constantinople, - "petersburg" | "8" => Hardfork::Petersburg, - "istanbul" | "9" => Hardfork::Istanbul, - "muirglacier" | "10" => Hardfork::Muirglacier, - "berlin" | "11" => Hardfork::Berlin, - "london" | "12" => Hardfork::London, - "arrowglacier" | "13" => Hardfork::ArrowGlacier, - "grayglacier" | "14" => Hardfork::GrayGlacier, - "paris" | "merge" | "15" => Hardfork::Paris, - "shanghai" | "16" => Hardfork::Shanghai, - // "cancun" | "17"=> Hardfork::Cancun, - "latest" => Hardfork::Latest, - _ => return Err(format!("Unknown hardfork {s}")), + "frontier" | "1" => Self::Frontier, + "homestead" | "2" => Self::Homestead, + "dao" | "3" => Self::Dao, + "tangerine" | "4" => Self::Tangerine, + "spuriousdragon" | "5" => Self::SpuriousDragon, + "byzantium" | "6" => Self::Byzantium, + "constantinople" | "7" => Self::Constantinople, + "petersburg" | "8" => Self::Petersburg, + "istanbul" | "9" => Self::Istanbul, + "muirglacier" | "10" => Self::Muirglacier, + "berlin" | "11" => Self::Berlin, + "london" | "12" => Self::London, + "arrowglacier" | "13" => Self::ArrowGlacier, + "grayglacier" | "14" => Self::GrayGlacier, + "paris" | "merge" | "15" => Self::Paris, + "shanghai" | "16" => Self::Shanghai, + "cancun" | "17" => Self::Cancun, + "prague" | "18" => Self::Prague, + "pragueeof" | "19" | "prague-eof" => Self::PragueEOF, + "latest" => Self::Latest, + _ => bail!("Unknown hardfork {s}"), }; Ok(hardfork) } } -impl From for SpecId { - fn from(fork: Hardfork) -> Self { +impl From for SpecId { + fn from(fork: EthereumHardfork) -> Self { match fork { - Hardfork::Frontier => SpecId::FRONTIER, - Hardfork::Homestead => SpecId::HOMESTEAD, - Hardfork::Dao => SpecId::HOMESTEAD, - Hardfork::Tangerine => SpecId::TANGERINE, - Hardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, - Hardfork::Byzantium => SpecId::BYZANTIUM, - Hardfork::Constantinople => SpecId::CONSTANTINOPLE, - Hardfork::Petersburg => SpecId::PETERSBURG, - Hardfork::Istanbul => SpecId::ISTANBUL, - Hardfork::Muirglacier => SpecId::MUIR_GLACIER, - Hardfork::Berlin => SpecId::BERLIN, - Hardfork::London => SpecId::LONDON, - Hardfork::ArrowGlacier => SpecId::LONDON, - Hardfork::GrayGlacier => SpecId::GRAY_GLACIER, - Hardfork::Paris => SpecId::MERGE, - Hardfork::Shanghai | Hardfork::Latest => SpecId::SHANGHAI, + EthereumHardfork::Frontier => Self::FRONTIER, + EthereumHardfork::Homestead => Self::HOMESTEAD, + EthereumHardfork::Dao => Self::HOMESTEAD, + EthereumHardfork::Tangerine => Self::TANGERINE, + EthereumHardfork::SpuriousDragon => Self::SPURIOUS_DRAGON, + EthereumHardfork::Byzantium => Self::BYZANTIUM, + EthereumHardfork::Constantinople => Self::CONSTANTINOPLE, + EthereumHardfork::Petersburg => Self::PETERSBURG, + EthereumHardfork::Istanbul => Self::ISTANBUL, + EthereumHardfork::Muirglacier => Self::MUIR_GLACIER, + EthereumHardfork::Berlin => Self::BERLIN, + EthereumHardfork::London => Self::LONDON, + EthereumHardfork::ArrowGlacier => Self::LONDON, + EthereumHardfork::GrayGlacier => Self::GRAY_GLACIER, + EthereumHardfork::Paris => Self::MERGE, + EthereumHardfork::Shanghai => Self::SHANGHAI, + EthereumHardfork::Cancun | EthereumHardfork::Latest => Self::CANCUN, + EthereumHardfork::Prague => Self::PRAGUE, + // TODO: switch to latest after activation + // EOF is included in OSAKA from Revm 16.0.0 + EthereumHardfork::PragueEOF => Self::OSAKA, } } } -impl> From for Hardfork { - fn from(block: T) -> Hardfork { +impl> From for EthereumHardfork { + fn from(block: T) -> Self { let num = match block.into() { - BlockNumber::Earliest => 0, - BlockNumber::Number(num) => num.as_u64(), + BlockNumberOrTag::Earliest => 0, + BlockNumberOrTag::Number(num) => num, _ => u64::MAX, }; match num { - _i if num < 1_150_000 => Hardfork::Frontier, - _i if num < 1_920_000 => Hardfork::Dao, - _i if num < 2_463_000 => Hardfork::Homestead, - _i if num < 2_675_000 => Hardfork::Tangerine, - _i if num < 4_370_000 => Hardfork::SpuriousDragon, - _i if num < 7_280_000 => Hardfork::Byzantium, - _i if num < 9_069_000 => Hardfork::Constantinople, - _i if num < 9_200_000 => Hardfork::Istanbul, - _i if num < 12_244_000 => Hardfork::Muirglacier, - _i if num < 12_965_000 => Hardfork::Berlin, - _i if num < 13_773_000 => Hardfork::London, - _i if num < 15_050_000 => Hardfork::ArrowGlacier, - _i if num < 17_034_870 => Hardfork::Paris, - _ => Hardfork::Latest, + _i if num < 1_150_000 => Self::Frontier, + _i if num < 1_920_000 => Self::Dao, + _i if num < 2_463_000 => Self::Homestead, + _i if num < 2_675_000 => Self::Tangerine, + _i if num < 4_370_000 => Self::SpuriousDragon, + _i if num < 7_280_000 => Self::Byzantium, + _i if num < 9_069_000 => Self::Constantinople, + _i if num < 9_200_000 => Self::Istanbul, + _i if num < 12_244_000 => Self::Muirglacier, + _i if num < 12_965_000 => Self::Berlin, + _i if num < 13_773_000 => Self::London, + _i if num < 15_050_000 => Self::ArrowGlacier, + _i if num < 17_034_870 => Self::Paris, + _i if num < 19_426_587 => Self::Shanghai, + _ => Self::Latest, + } + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum OptimismHardfork { + Bedrock, + Regolith, + Canyon, + Ecotone, + Fjord, + Granite, + #[default] + Latest, +} + +impl FromStr for OptimismHardfork { + type Err = eyre::Report; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + let hardfork = match s.as_str() { + "bedrock" => Self::Bedrock, + "regolith" => Self::Regolith, + "canyon" => Self::Canyon, + "ecotone" => Self::Ecotone, + "fjord" => Self::Fjord, + "granite" => Self::Granite, + "latest" => Self::Latest, + _ => bail!("Unknown hardfork {s}"), + }; + Ok(hardfork) + } +} + +impl From for SpecId { + fn from(fork: OptimismHardfork) -> Self { + match fork { + OptimismHardfork::Bedrock => Self::BEDROCK, + OptimismHardfork::Regolith => Self::REGOLITH, + OptimismHardfork::Canyon => Self::CANYON, + OptimismHardfork::Ecotone => Self::ECOTONE, + OptimismHardfork::Fjord => Self::FJORD, + OptimismHardfork::Granite => Self::GRANITE, + OptimismHardfork::Latest => Self::LATEST, } } } #[cfg(test)] mod tests { - use crate::Hardfork; - use crc::{Crc, CRC_32_ISO_HDLC}; - use ethers::utils::hex; + use crate::EthereumHardfork; #[test] fn test_hardfork_blocks() { - let hf: Hardfork = 12_965_000u64.into(); - assert_eq!(hf, Hardfork::London); + let hf: EthereumHardfork = 12_965_000u64.into(); + assert_eq!(hf, EthereumHardfork::London); - let hf: Hardfork = 4370000u64.into(); - assert_eq!(hf, Hardfork::Byzantium); + let hf: EthereumHardfork = 4370000u64.into(); + assert_eq!(hf, EthereumHardfork::Byzantium); - let hf: Hardfork = 12244000u64.into(); - assert_eq!(hf, Hardfork::Berlin); - } - - #[test] - // this test checks that the fork hash assigned to forks accurately map to the fork_id method - fn test_forkhash_from_fork_blocks() { - // set the genesis hash - let genesis = - hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - .unwrap(); - - // instantiate the crc "hasher" - let crc_hasher = Crc::::new(&CRC_32_ISO_HDLC); - let mut crc_digest = crc_hasher.digest(); - - // check frontier forkhash - crc_digest.update(&genesis); - - // now we go through enum members - let frontier_forkid = Hardfork::Frontier.fork_id(); - let frontier_forkhash = u32::from_be_bytes(frontier_forkid.hash.0); - // clone the digest for finalization so we can update it again - assert_eq!(crc_digest.clone().finalize(), frontier_forkhash); - - // list of the above hardforks - let hardforks = vec![ - Hardfork::Homestead, - Hardfork::Dao, - Hardfork::Tangerine, - Hardfork::SpuriousDragon, - Hardfork::Byzantium, - Hardfork::Constantinople, - Hardfork::Istanbul, - Hardfork::Muirglacier, - Hardfork::Berlin, - Hardfork::London, - Hardfork::ArrowGlacier, - Hardfork::GrayGlacier, - ]; - - // now loop through each hardfork, conducting each forkhash test - for hardfork in hardforks { - // this could also be done with frontier_forkhash.next, but fork_block is used for more - // coverage - let fork_block = hardfork.fork_block().to_be_bytes(); - crc_digest.update(&fork_block); - let fork_hash = u32::from_be_bytes(hardfork.fork_id().hash.0); - assert_eq!(crc_digest.clone().finalize(), fork_hash); - } + let hf: EthereumHardfork = 12244000u64.into(); + assert_eq!(hf, EthereumHardfork::Berlin); } } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 17005bb598e06..c9d2598e1dfee 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -1,3 +1,7 @@ +//! Anvil is a fast local Ethereum development node. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + use crate::{ eth::{ backend::{info::StorageInfo, mem}, @@ -9,29 +13,26 @@ use crate::{ }, filter::Filters, logging::{LoggingManager, NodeLogLayer}, + server::error::{NodeError, NodeResult}, service::NodeService, shutdown::Signal, tasks::TaskManager, }; +use alloy_primitives::{Address, U256}; +use alloy_signer_local::PrivateKeySigner; use eth::backend::fork::ClientFork; -use ethers::{ - core::k256::ecdsa::SigningKey, - prelude::Wallet, - providers::{Http, Provider, Ws}, - signers::Signer, - types::{Address, U256}, -}; +use eyre::Result; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; +use server::try_spawn_ipc; use std::{ future::Future, - io, net::SocketAddr, pin::Pin, sync::Arc, task::{Context, Poll}, - time::Duration, }; use tokio::{ runtime::Handle, @@ -42,20 +43,20 @@ use tokio::{ mod service; mod config; -pub use config::{AccountGenerator, NodeConfig, CHAIN_ID, VERSION_MESSAGE}; -mod hardfork; -use crate::server::{ - error::{NodeError, NodeResult}, - spawn_ipc, +pub use config::{ + AccountGenerator, ForkChoice, NodeConfig, CHAIN_ID, DEFAULT_GAS_LIMIT, VERSION_MESSAGE, }; -pub use hardfork::Hardfork; + +mod hardfork; +pub use hardfork::EthereumHardfork; /// ethereum related implementations pub mod eth; +/// Evm related abstractions +mod evm; +pub use evm::{inject_precompiles, PrecompileFactory}; /// support for polling filters pub mod filter; -/// support for handling `genesis.json` files -pub mod genesis; /// commandline output pub mod logging; /// types for subscriptions @@ -71,36 +72,72 @@ mod tasks; #[cfg(feature = "cmd")] pub mod cmd; -/// Creates the node and runs the server +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; + +/// Creates the node and runs the server. /// /// Returns the [EthApi] that can be used to interact with the node and the [JoinHandle] of the /// task. /// -/// # Example +/// # Panics +/// +/// Panics if any error occurs. For a non-panicking version, use [`try_spawn`]. +/// /// -/// ```rust +/// # Examples +/// +/// ```no_run /// # use anvil::NodeConfig; -/// # async fn spawn() { +/// # async fn spawn() -> eyre::Result<()> { /// let config = NodeConfig::default(); /// let (api, handle) = anvil::spawn(config).await; /// /// // use api /// /// // wait forever -/// handle.await.unwrap(); +/// handle.await.unwrap().unwrap(); +/// # Ok(()) /// # } /// ``` -pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { +pub async fn spawn(config: NodeConfig) -> (EthApi, NodeHandle) { + try_spawn(config).await.expect("failed to spawn node") +} + +/// Creates the node and runs the server +/// +/// Returns the [EthApi] that can be used to interact with the node and the [JoinHandle] of the +/// task. +/// +/// # Examples +/// +/// ```no_run +/// # use anvil::NodeConfig; +/// # async fn spawn() -> eyre::Result<()> { +/// let config = NodeConfig::default(); +/// let (api, handle) = anvil::try_spawn(config).await?; +/// +/// // use api +/// +/// // wait forever +/// handle.await??; +/// # Ok(()) +/// # } +/// ``` +pub async fn try_spawn(mut config: NodeConfig) -> Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); - let backend = Arc::new(config.setup().await); + let backend = 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().cloned(); + let fork = backend.get_fork(); let NodeConfig { signer_accounts, @@ -111,13 +148,19 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { no_mining, transaction_order, genesis, + mixed_mining, .. } = config.clone(); let pool = Arc::new(Pool::default()); let mode = if let Some(block_time) = block_time { - MiningMode::interval(block_time) + if mixed_mining { + let listener = pool.add_ready_listener(); + MiningMode::mixed(max_transactions, listener, block_time) + } else { + MiningMode::interval(block_time) + } } else if no_mining { MiningMode::None } else { @@ -125,27 +168,38 @@ pub async fn spawn(mut config: NodeConfig) -> (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]; if let Some(genesis) = genesis { - // include all signers from genesis.json if any - let genesis_signers = genesis.private_keys(); + let genesis_signers = genesis + .alloc + .values() + .filter_map(|acc| acc.private_key) + .flat_map(|k| PrivateKeySigner::from_bytes(&k)) + .collect::>(); if !genesis_signers.is_empty() { - let genesis_signers: Box = Box::new(DevSigner::new(genesis_signers)); - signers.push(genesis_signers); + signers.push(Box::new(DevSigner::new(genesis_signers))); } } - 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(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(); @@ -166,25 +220,27 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { let node_service = tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters)); - let mut servers = Vec::new(); - let mut addresses = Vec::new(); + let mut servers = Vec::with_capacity(config.host.len()); + let mut addresses = Vec::with_capacity(config.host.len()); - for addr in config.host.iter() { - let sock_addr = SocketAddr::new(addr.to_owned(), port); - let srv = server::serve(sock_addr, api.clone(), server_config.clone()); + for addr in &config.host { + let sock_addr = SocketAddr::new(*addr, port); - addresses.push(srv.local_addr()); + // Create a TCP listener. + let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?; + addresses.push(tcp_listener.local_addr()?); - // spawn the server on a new task - let srv = tokio::task::spawn(srv.map_err(NodeError::from)); - servers.push(srv); + // Spawn the server future on a new task. + let srv = server::serve_on(tcp_listener, api.clone(), server_config.clone()); + servers.push(tokio::task::spawn(srv.map_err(Into::into))); } let tokio_handle = Handle::current(); let (signal, on_shutdown) = shutdown::signal(); let task_manager = TaskManager::new(tokio_handle, on_shutdown); - let ipc_task = config.get_ipc_path().map(|path| spawn_ipc(api.clone(), path)); + let ipc_task = + config.get_ipc_path().map(|path| try_spawn_ipc(api.clone(), path)).transpose()?; let handle = NodeHandle { config, @@ -196,138 +252,142 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { task_manager, }; - handle.print(fork.as_ref()); + handle.print(fork.as_ref())?; - (api, handle) + Ok((api, handle)) } -type IpcTask = JoinHandle>; +type IpcTask = JoinHandle<()>; -/// A handle to the spawned node and server tasks +/// A handle to the spawned node and server tasks. /// /// This future will resolve if either the node or server task resolve/fail. pub struct NodeHandle { config: NodeConfig, - /// The address of the running rpc server + /// The address of the running rpc server. addresses: Vec, - /// Join handle for the Node Service + /// Join handle for the Node Service. pub node_service: JoinHandle>, /// Join handles (one per socket) for the Anvil server. pub servers: Vec>>, - // The future that joins the ipc server, if any + /// The future that joins the ipc server, if any. ipc_task: Option, /// A signal that fires the shutdown, fired on drop. _signal: Option, - /// A task manager that can be used to spawn additional tasks + /// A task manager that can be used to spawn additional tasks. task_manager: TaskManager, } +impl Drop for NodeHandle { + fn drop(&mut self) { + // Fire shutdown signal to make sure anvil instance is terminated. + if let Some(signal) = self._signal.take() { + let _ = signal.fire(); + } + } +} + impl NodeHandle { - /// The [NodeConfig] the node was launched with + /// The [NodeConfig] the node was launched with. pub fn config(&self) -> &NodeConfig { &self.config } - /// Prints the launch info - pub(crate) fn print(&self, fork: Option<&ClientFork>) { - self.config.print(fork); + /// Prints the launch info. + pub(crate) fn print(&self, fork: Option<&ClientFork>) -> Result<()> { + self.config.print(fork)?; if !self.config.silent { - println!( + if let Some(ipc_path) = self.ipc_path() { + sh_println!("IPC path: {ipc_path}")?; + } + sh_println!( "Listening on {}", self.addresses .iter() .map(|addr| { addr.to_string() }) .collect::>() .join(", ") - ) + )?; } + Ok(()) } - /// The address of the launched server + /// The address of the launched server. /// /// **N.B.** this may not necessarily be the same `host + port` as configured in the - /// `NodeConfig`, if port was set to 0, then the OS auto picks an available port + /// `NodeConfig`, if port was set to 0, then the OS auto picks an available port. pub fn socket_address(&self) -> &SocketAddr { &self.addresses[0] } - /// Returns the http endpoint + /// Returns the http endpoint. pub fn http_endpoint(&self) -> String { format!("http://{}", self.socket_address()) } - /// Returns the websocket endpoint + /// Returns the websocket endpoint. pub fn ws_endpoint(&self) -> String { format!("ws://{}", self.socket_address()) } - /// Returns the path of the launched ipc server, if any + /// Returns the path of the launched ipc server, if any. pub fn ipc_path(&self) -> Option { self.config.get_ipc_path() } - /// Returns a Provider for the http endpoint - pub fn http_provider(&self) -> Provider { - Provider::::try_from(self.http_endpoint()) - .unwrap() - .interval(Duration::from_millis(500)) + /// Constructs a [`RetryProvider`] for this handle's HTTP endpoint. + pub fn http_provider(&self) -> RetryProvider { + ProviderBuilder::new(&self.http_endpoint()).build().expect("failed to build HTTP provider") } - /// Connects to the websocket Provider of the node - pub async fn ws_provider(&self) -> Provider { - Provider::new( - Ws::connect(self.ws_endpoint()).await.expect("Failed to connect to node's websocket"), - ) + /// Constructs a [`RetryProvider`] for this handle's WS endpoint. + pub fn ws_provider(&self) -> RetryProvider { + ProviderBuilder::new(&self.ws_endpoint()).build().expect("failed to build WS provider") } - /// Connects to the ipc endpoint of the node, if spawned - pub async fn ipc_provider(&self) -> Option> { - let ipc_path = self.config.get_ipc_path()?; - tracing::trace!(target: "ipc", ?ipc_path, "connecting ipc provider"); - let provider = Provider::connect_ipc(&ipc_path).await.unwrap_or_else(|err| { - panic!("Failed to connect to node's ipc endpoint {ipc_path}: {err:?}") - }); - Some(provider) + /// Constructs a [`RetryProvider`] for this handle's IPC endpoint, if any. + pub fn ipc_provider(&self) -> Option { + ProviderBuilder::new(&self.config.get_ipc_path()?).build().ok() } - /// Signer accounts that can sign messages/transactions from the EVM node + /// Signer accounts that can sign messages/transactions from the EVM node. pub fn dev_accounts(&self) -> impl Iterator + '_ { self.config.signer_accounts.iter().map(|wallet| wallet.address()) } - /// Signer accounts that can sign messages/transactions from the EVM node - pub fn dev_wallets(&self) -> impl Iterator> + '_ { + /// Signer accounts that can sign messages/transactions from the EVM node. + pub fn dev_wallets(&self) -> impl Iterator + '_ { self.config.signer_accounts.iter().cloned() } - /// Accounts that will be initialised with `genesis_balance` in the genesis block + /// Accounts that will be initialised with `genesis_balance` in the genesis block. pub fn genesis_accounts(&self) -> impl Iterator + '_ { self.config.genesis_accounts.iter().map(|w| w.address()) } - /// Native token balance of every genesis account in the genesis block + /// Native token balance of every genesis account in the genesis block. pub fn genesis_balance(&self) -> U256 { self.config.genesis_balance } - /// Default gas price for all txs - pub fn gas_price(&self) -> U256 { + /// Default gas price for all txs. + pub fn gas_price(&self) -> u128 { self.config.get_gas_price() } - /// Returns the shutdown signal + /// Returns the shutdown signal. pub fn shutdown_signal(&self) -> &Option { &self._signal } - /// Returns mutable access to the shutdown signal + /// Returns mutable access to the shutdown signal. /// - /// This can be used to extract the Signal + /// This can be used to extract the Signal. pub fn shutdown_signal_mut(&mut self) -> &mut Option { &mut self._signal } - /// Returns the task manager that can be used to spawn new tasks + /// Returns the task manager that can be used to spawn new tasks. /// /// ``` /// use anvil::NodeHandle; @@ -356,7 +416,7 @@ impl Future for NodeHandle { // poll the ipc task if let Some(mut ipc) = pin.ipc_task.take() { if let Poll::Ready(res) = ipc.poll_unpin(cx) { - return Poll::Ready(res.map(|res| res.map_err(NodeError::from))) + return Poll::Ready(res.map(|()| Ok(()))); } else { pin.ipc_task = Some(ipc); } @@ -364,13 +424,13 @@ impl Future for NodeHandle { // poll the node service task if let Poll::Ready(res) = pin.node_service.poll_unpin(cx) { - return Poll::Ready(res) + return Poll::Ready(res); } // poll the axum server handles for server in pin.servers.iter_mut() { if let Poll::Ready(res) = server.poll_unpin(cx) { - return Poll::Ready(res) + return Poll::Ready(res); } } @@ -378,18 +438,17 @@ impl Future for NodeHandle { } } -#[allow(unused)] #[doc(hidden)] pub fn init_tracing() -> LoggingManager { use tracing_subscriber::prelude::*; let manager = LoggingManager::default(); // check whether `RUST_LOG` is explicitly set - if std::env::var("RUST_LOG").is_ok() { + let _ = if std::env::var("RUST_LOG").is_ok() { tracing_subscriber::Registry::default() .with(tracing_subscriber::EnvFilter::from_default_env()) .with(tracing_subscriber::fmt::layer()) - .init(); + .try_init() } else { tracing_subscriber::Registry::default() .with(NodeLogLayer::new(manager.clone())) @@ -399,8 +458,8 @@ pub fn init_tracing() -> LoggingManager { .with_target(false) .with_level(false), ) - .init(); - } + .try_init() + }; manager } diff --git a/crates/anvil/src/logging.rs b/crates/anvil/src/logging.rs index ecab2000ec5ae..e738254cbbec5 100644 --- a/crates/anvil/src/logging.rs +++ b/crates/anvil/src/logging.rs @@ -8,17 +8,18 @@ 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 /// eventually be logged to stdout -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct NodeLogLayer { state: LoggingManager, } -// === impl NodeLogLayer === - impl NodeLogLayer { /// Returns a new instance of this layer pub fn new(state: LoggingManager) -> Self { @@ -32,27 +33,28 @@ 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) } } /// Contains the configuration of the logger -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct LoggingManager { /// Whether the logger is currently enabled pub enabled: Arc>, } -// === impl LoggingManager === - impl LoggingManager { /// Returns true if logging is currently enabled pub fn is_enabled(&self) -> bool { diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index 0c58327959315..b28cdba8ae84c 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -1,17 +1,11 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, - StorageInfo, U256, -}; -use anvil_core::eth::{ - block::Block, - receipt::{EIP658Receipt, Log, TypedReceipt}, - subscription::{SubscriptionId, SubscriptionResult}, + StorageInfo, }; +use alloy_primitives::{TxHash, B256}; +use alloy_rpc_types::{pubsub::SubscriptionResult, FilteredParams, Log, Transaction}; +use anvil_core::eth::{block::Block, subscription::SubscriptionId, transaction::TypedReceipt}; use anvil_rpc::{request::Version, response::ResponseResult}; -use ethers::{ - prelude::{Log as EthersLog, H256, H256 as TxHash, U64}, - types::FilteredParams, -}; use futures::{channel::mpsc::Receiver, ready, Stream, StreamExt}; use serde::Serialize; use std::{ @@ -26,12 +20,10 @@ pub struct LogsSubscription { pub blocks: NewBlockNotifications, pub storage: StorageInfo, pub filter: FilteredParams, - pub queued: VecDeque, + pub queued: VecDeque, pub id: SubscriptionId, } -// === impl LogsSubscription === - impl LogsSubscription { fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { @@ -40,7 +32,7 @@ impl LogsSubscription { subscription: self.id.clone(), result: to_rpc_result(log), }; - return Poll::Ready(Some(EthSubscriptionResponse::new(params))) + return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } if let Some(block) = ready!(self.blocks.poll_next_unpin(cx)) { @@ -52,30 +44,28 @@ impl LogsSubscription { // this ensures we poll the receiver until it is pending, in which case the // underlying `UnboundedReceiver` will register the new waker, see // [`futures::channel::mpsc::UnboundedReceiver::poll_next()`] - continue + continue; } self.queued.extend(logs) } } else { - return Poll::Ready(None) + return Poll::Ready(None); } if self.queued.is_empty() { - return Poll::Pending + return Poll::Pending; } } } } -#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct EthSubscriptionResponse { jsonrpc: Version, method: &'static str, params: EthSubscriptionParams, } -// === impl EthSubscriptionResponse === - impl EthSubscriptionResponse { pub fn new(params: EthSubscriptionParams) -> Self { Self { jsonrpc: Version::V2, method: "eth_subscription", params } @@ -83,7 +73,7 @@ impl EthSubscriptionResponse { } /// Represents the `params` field of an `eth_subscription` event -#[derive(Debug, PartialEq, Eq, Clone, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct EthSubscriptionParams { subscription: SubscriptionId, #[serde(flatten)] @@ -98,13 +88,11 @@ pub enum EthSubscription { PendingTransactions(Receiver, SubscriptionId), } -// === impl EthSubscription === - impl EthSubscription { fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll> { match self { - EthSubscription::Logs(listener) => listener.poll(cx), - EthSubscription::Header(blocks, storage, id) => { + Self::Logs(listener) => listener.poll(cx), + Self::Header(blocks, storage, id) => { // this loop ensures we poll the receiver until it is pending, in which case the // underlying `UnboundedReceiver` will register the new waker, see // [`futures::channel::mpsc::UnboundedReceiver::poll_next()`] @@ -115,16 +103,16 @@ impl EthSubscription { subscription: id.clone(), result: to_rpc_result(block), }; - return Poll::Ready(Some(EthSubscriptionResponse::new(params))) + return Poll::Ready(Some(EthSubscriptionResponse::new(params))); } } else { - return Poll::Ready(None) + return Poll::Ready(None); } } } - EthSubscription::PendingTransactions(tx, id) => { + Self::PendingTransactions(tx, id) => { let res = ready!(tx.poll_next_unpin(cx)) - .map(SubscriptionResult::TransactionHash) + .map(SubscriptionResult::::TransactionHash) .map(to_rpc_result) .map(|result| { let params = EthSubscriptionParams { subscription: id.clone(), result }; @@ -149,64 +137,43 @@ impl Stream for EthSubscription { } /// Returns all the logs that match the given filter -pub fn filter_logs( - block: Block, - receipts: Vec, - filter: &FilteredParams, -) -> Vec { +pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredParams) -> Vec { /// Determines whether to add this log - fn add_log(block_hash: H256, l: &Log, block: &Block, params: &FilteredParams) -> bool { - let log = EthersLog { - address: l.address, - topics: l.topics.clone(), - data: l.data.clone(), - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - transaction_log_index: None, - log_type: None, - removed: Some(false), - }; + fn add_log( + block_hash: B256, + l: &alloy_primitives::Log, + block: &Block, + params: &FilteredParams, + ) -> bool { if params.filter.is_some() { - let block_number = block.header.number.as_u64(); + let block_number = block.header.number; if !params.filter_block_range(block_number) || !params.filter_block_hash(block_hash) || - !params.filter_address(&log) || - !params.filter_topics(&log) + !params.filter_address(&l.address) || + !params.filter_topics(l.topics()) { - return false + return false; } } true } - let block_hash = block.header.hash(); + let block_hash = block.header.hash_slow(); let mut logs = vec![]; let mut log_index: u32 = 0; for (receipt_index, receipt) in receipts.into_iter().enumerate() { - let receipt: EIP658Receipt = receipt.into(); - let receipt_logs = receipt.logs; - let transaction_hash: Option = if !receipt_logs.is_empty() { - Some(block.transactions[receipt_index].hash()) - } else { - None - }; - for (transaction_log_index, log) in receipt_logs.into_iter().enumerate() { - if add_log(block_hash, &log, &block, filter) { - logs.push(EthersLog { - address: log.address, - topics: log.topics, - data: log.data, + let transaction_hash = block.transactions[receipt_index].hash(); + for log in receipt.logs() { + if add_log(block_hash, log, &block, filter) { + logs.push(Log { + inner: log.clone(), block_hash: Some(block_hash), - block_number: Some(block.header.number.as_u64().into()), - transaction_hash, - transaction_index: Some(U64::from(receipt_index)), - log_index: Some(U256::from(log_index)), - transaction_log_index: Some(U256::from(transaction_log_index)), - log_type: None, - removed: Some(false), + block_number: Some(block.header.number), + transaction_hash: Some(transaction_hash), + transaction_index: Some(receipt_index as u64), + log_index: Some(log_index as u64), + removed: false, + block_timestamp: Some(block.header.timestamp), }); } log_index += 1; diff --git a/crates/anvil/src/server/handler.rs b/crates/anvil/src/server/handler.rs index eed6e1931aa4b..79adb87df7bcb 100644 --- a/crates/anvil/src/server/handler.rs +++ b/crates/anvil/src/server/handler.rs @@ -4,14 +4,13 @@ use crate::{ pubsub::{EthSubscription, LogsSubscription}, EthApi, }; -use anvil_core::eth::{ - subscription::{SubscriptionId, SubscriptionKind}, - EthPubSub, EthRequest, EthRpcCall, +use alloy_rpc_types::{ + pubsub::{Params, SubscriptionKind}, + FilteredParams, }; +use anvil_core::eth::{subscription::SubscriptionId, EthPubSub, EthRequest, EthRpcCall}; use anvil_rpc::{error::RpcError, response::ResponseResult}; use anvil_server::{PubSubContext, PubSubRpcHandler, RpcHandler}; -use ethers::types::FilteredParams; -use tracing::trace; /// A `RpcHandler` that expects `EthRequest` rpc calls via http #[derive(Clone)] @@ -20,8 +19,6 @@ pub struct HttpEthRpcHandler { api: EthApi, } -// === impl WsEthRpcHandler === - impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` pub fn new(api: EthApi) -> Self { @@ -62,7 +59,16 @@ impl PubSubEthRpcHandler { ResponseResult::Success(canceled.into()) } EthPubSub::EthSubscribe(kind, params) => { - let params = FilteredParams::new(params.filter); + let filter = match *params { + Params::None => None, + Params::Logs(filter) => Some(*filter), + Params::Bool(_) => { + return ResponseResult::Error(RpcError::invalid_params( + "Expected params for logs subscription", + )) + } + }; + let params = FilteredParams::new(filter); let subscription = match kind { SubscriptionKind::Logs => { diff --git a/crates/anvil/src/server/mod.rs b/crates/anvil/src/server/mod.rs index ef98e8b3d20e2..c488bcdc15ca3 100644 --- a/crates/anvil/src/server/mod.rs +++ b/crates/anvil/src/server/mod.rs @@ -1,49 +1,67 @@ -//! Contains the code to launch an ethereum RPC-Server -use crate::EthApi; -use anvil_server::{ipc::IpcEndpoint, AnvilServer, ServerConfig}; +//! Contains the code to launch an Ethereum RPC server. + +use crate::{EthApi, IpcTask}; +use anvil_server::{ipc::IpcEndpoint, ServerConfig}; +use axum::Router; use futures::StreamExt; use handler::{HttpEthRpcHandler, PubSubEthRpcHandler}; -use std::net::SocketAddr; -use tokio::{io, task::JoinHandle}; -use tracing::trace; +use std::{future::Future, io, net::SocketAddr, pin::pin}; +use tokio::net::TcpListener; +pub mod error; mod handler; -pub mod error; +/// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. +/// +/// The returned future creates a new server, binding it to the given address, which returns another +/// future that runs it. +pub async fn serve( + addr: SocketAddr, + api: EthApi, + config: ServerConfig, +) -> io::Result>> { + let tcp_listener = TcpListener::bind(addr).await?; + Ok(serve_on(tcp_listener, api, config)) +} -/// Configures an [axum::Server] that handles [EthApi] related JSON-RPC calls via HTTP and WS -pub fn serve(addr: SocketAddr, api: EthApi, config: ServerConfig) -> AnvilServer { +/// Configures a server that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. +pub async fn serve_on( + tcp_listener: TcpListener, + api: EthApi, + config: ServerConfig, +) -> io::Result<()> { + axum::serve(tcp_listener, router(api, config).into_make_service()).await +} + +/// Configures an [`axum::Router`] that handles [`EthApi`] related JSON-RPC calls via HTTP and WS. +pub fn router(api: EthApi, config: ServerConfig) -> Router { let http = HttpEthRpcHandler::new(api.clone()); let ws = PubSubEthRpcHandler::new(api); - anvil_server::serve_http_ws(addr, config, http, ws) + anvil_server::http_ws_router(config, http, ws) } /// Launches an ipc server at the given path in a new task /// /// # Panics /// -/// if setting up the ipc connection was unsuccessful -pub fn spawn_ipc(api: EthApi, path: impl Into) -> JoinHandle> { +/// Panics if setting up the IPC connection was unsuccessful. +#[track_caller] +pub fn spawn_ipc(api: EthApi, path: String) -> IpcTask { try_spawn_ipc(api, path).expect("failed to establish ipc connection") } -/// Launches an ipc server at the given path in a new task -pub fn try_spawn_ipc( - api: EthApi, - path: impl Into, -) -> io::Result>> { - let path = path.into(); +/// Launches an ipc server at the given path in a new task. +pub fn try_spawn_ipc(api: EthApi, path: String) -> io::Result { let handler = PubSubEthRpcHandler::new(api); let ipc = IpcEndpoint::new(handler, path); let incoming = ipc.incoming()?; let task = tokio::task::spawn(async move { - tokio::pin!(incoming); + let mut incoming = pin!(incoming); while let Some(stream) = incoming.next().await { trace!(target: "ipc", "new ipc connection"); tokio::task::spawn(stream); } - Ok(()) }); Ok(task) diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index b1555a79c1664..0f70ad3b0b4a0 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -18,23 +18,22 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -use tokio::time::Interval; -use tracing::trace; +use tokio::{task::JoinHandle, time::Interval}; /// The type that drives the blockchain's state /// /// This service is basically an endless future that continuously polls the miner which returns -/// transactions for the next block, then those transactions are handed off to the -/// [backend](backend::mem::Backend) to construct a new block, if all transactions were successfully -/// included in a new block they get purged from the `Pool`. +/// transactions for the next block, then those transactions are handed off to the backend to +/// construct a new block, if all transactions were successfully included in a new block they get +/// purged from the `Pool`. pub struct NodeService { - /// the pool that holds all transactions + /// The pool that holds all transactions. pool: Arc, - /// creates new blocks + /// Creates new blocks. block_producer: BlockProducer, - /// the miner responsible to select transactions from the `pool´ + /// The miner responsible to select transactions from the `pool`. miner: Miner, - /// maintenance task for fee history related tasks + /// Maintenance task for fee history related tasks. fee_history: FeeHistoryService, /// Tracks all active filters filters: Filters, @@ -93,34 +92,26 @@ impl Future for NodeService { if pin.filter_eviction_interval.poll_tick(cx).is_ready() { let filters = pin.filters.clone(); + // evict filters that timed out #[allow(clippy::redundant_async_block)] - tokio::task::spawn(async move { - // evict filters that timed out - filters.evict().await - }); + tokio::task::spawn(async move { filters.evict().await }); } Poll::Pending } } -// The type of the future that mines a new block -type BlockMiningFuture = - Pin)> + Send + Sync>>; - /// A type that exclusively mines one block at a time -#[must_use = "BlockProducer does nothing unless polled"] +#[must_use = "streams do nothing unless polled"] struct BlockProducer { /// Holds the backend if no block is being mined idle_backend: Option>, /// Single active future that mines a new block - block_mining: Option, + block_mining: Option)>>, /// backlog of sets of transactions ready to be mined queued: VecDeque>>, } -// === impl BlockProducer === - impl BlockProducer { fn new(backend: Arc) -> Self { Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() } @@ -136,19 +127,33 @@ impl Stream for BlockProducer { if !pin.queued.is_empty() { if let Some(backend) = pin.idle_backend.take() { let transactions = pin.queued.pop_front().expect("not empty; qed"); - pin.block_mining = Some(Box::pin(async move { - trace!(target: "miner", "creating new block"); - let block = backend.mine_block(transactions).await; - trace!(target: "miner", "created new block: {}", block.block_number); - (block, backend) - })); + + // we spawn this on as blocking task because in this can be blocking for a while in + // forking mode, because of all the rpc calls to fetch the required state + let handle = tokio::runtime::Handle::current(); + let mining = tokio::task::spawn_blocking(move || { + handle.block_on(async move { + trace!(target: "miner", "creating new block"); + let block = backend.mine_block(transactions).await; + trace!(target: "miner", "created new block: {}", block.block_number); + (block, backend) + }) + }); + pin.block_mining = Some(mining); } } if let Some(mut mining) = pin.block_mining.take() { - if let Poll::Ready((outcome, backend)) = mining.poll_unpin(cx) { - pin.idle_backend = Some(backend); - return Poll::Ready(Some(outcome)) + if let Poll::Ready(res) = mining.poll_unpin(cx) { + return match res { + Ok((outcome, backend)) => { + pin.idle_backend = Some(backend); + Poll::Ready(Some(outcome)) + } + Err(err) => { + panic!("miner task failed: {err}"); + } + } } else { pin.block_mining = Some(mining) } diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index 5ecbbaeaadfa5..2778689893817 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -1,12 +1,13 @@ //! Task management support +#![allow(rustdoc::private_doc_tests)] + use crate::{shutdown::Shutdown, tasks::block_listener::BlockListener, EthApi}; -use anvil_core::types::Forking; -use ethers::{ - prelude::Middleware, - providers::{JsonRpcClient, PubsubClient}, - types::{Block, H256}, -}; +use alloy_network::{AnyHeader, AnyNetwork}; +use alloy_primitives::B256; +use alloy_provider::Provider; +use alloy_rpc_types::anvil::Forking; +use futures::StreamExt; use std::{fmt, future::Future}; use tokio::{runtime::Handle, task::JoinHandle}; @@ -21,8 +22,6 @@ pub struct TaskManager { on_shutdown: Shutdown, } -// === impl TaskManager === - impl TaskManager { /// Creates a new instance of the task manager pub fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { @@ -51,33 +50,32 @@ impl TaskManager { /// block /// /// ``` - /// use std::sync::Arc; - /// use ethers::providers::Provider; - /// use anvil::{NodeConfig, spawn}; + /// use alloy_network::Ethereum; + /// use alloy_provider::RootProvider; + /// use anvil::{spawn, NodeConfig}; + /// /// # async fn t() { /// let endpoint = "http://...."; /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some(endpoint))).await; /// - /// let provider = Arc::new(Provider::try_from(endpoint).unwrap()); + /// let provider = RootProvider::connect_builtin(endpoint).await.unwrap(); /// /// handle.task_manager().spawn_reset_on_new_polled_blocks(provider, api); - /// /// # } /// ``` pub fn spawn_reset_on_new_polled_blocks

(&self, provider: P, api: EthApi) where - P: Middleware + Clone + Unpin + 'static + Send + Sync, -

::Provider: JsonRpcClient, + P: Provider + Clone + Unpin + 'static, { self.spawn_block_poll_listener(provider.clone(), move |hash| { let provider = provider.clone(); let api = api.clone(); async move { - if let Ok(Some(block)) = provider.get_block(hash).await { + if let Ok(Some(block)) = provider.get_block(hash.into(), false.into()).await { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.number.map(|b| b.as_u64()), + block_number: Some(block.header.number), })) .await; } @@ -90,14 +88,18 @@ impl TaskManager { /// block hash pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) where - P: Middleware + Unpin + 'static, -

::Provider: JsonRpcClient, - F: Fn(H256) -> Fut + Unpin + Send + Sync + 'static, + P: Provider + 'static, + F: Fn(B256) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); self.spawn(async move { - let blocks = provider.watch_blocks().await.unwrap(); + let blocks = provider + .watch_blocks() + .await + .unwrap() + .into_stream() + .flat_map(futures::stream::iter); BlockListener::new(shutdown, blocks, task_factory).await; }); } @@ -106,12 +108,14 @@ impl TaskManager { /// block /// /// ``` - /// use ethers::providers::Provider; - /// use anvil::{NodeConfig, spawn}; + /// use alloy_network::Ethereum; + /// use alloy_provider::RootProvider; + /// use anvil::{spawn, NodeConfig}; + /// /// # async fn t() { /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some("http://...."))).await; /// - /// let provider = Provider::connect("ws://...").await.unwrap(); + /// let provider = RootProvider::connect_builtin("ws://...").await.unwrap(); /// /// handle.task_manager().spawn_reset_on_subscribed_blocks(provider, api); /// @@ -119,16 +123,15 @@ impl TaskManager { /// ``` pub fn spawn_reset_on_subscribed_blocks

(&self, provider: P, api: EthApi) where - P: Middleware + Unpin + 'static + Send + Sync, -

::Provider: PubsubClient, + P: Provider + 'static, { - self.spawn_block_subscription(provider, move |block| { + self.spawn_block_subscription(provider, move |header| { let api = api.clone(); async move { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.number.map(|b| b.as_u64()), + block_number: Some(header.number), })) .await; } @@ -140,14 +143,13 @@ impl TaskManager { /// new block hash pub fn spawn_block_subscription(&self, provider: P, task_factory: F) where - P: Middleware + Unpin + 'static, -

::Provider: PubsubClient, - F: Fn(Block) -> Fut + Unpin + Send + Sync + 'static, + P: Provider + 'static, + F: Fn(alloy_rpc_types::Header) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); self.spawn(async move { - let blocks = provider.subscribe_blocks().await.unwrap(); + let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); BlockListener::new(shutdown, blocks, task_factory).await; }); } diff --git a/crates/anvil/test-data/SimpleStorage.json b/crates/anvil/test-data/SimpleStorage.json index 3d4a8b81aa5b9..8ff4ab3812fe3 100644 --- a/crates/anvil/test-data/SimpleStorage.json +++ b/crates/anvil/test-data/SimpleStorage.json @@ -1,117 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "author", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "oldAuthor", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "oldValue", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "newValue", - "type": "string" - } - ], - "name": "ValueChanged", - "type": "event" - }, - { - "inputs": [], - "name": "_hashPuzzle", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getValue", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lastSender", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - }, - { - "internalType": "string", - "name": "value2", - "type": "string" - } - ], - "name": "setValues", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bin": "60806040523480156200001157600080fd5b5060405162000d6a38038062000d6a83398181016040528101906200003791906200030f565b600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c36001846040516200009a929190620004c3565b60405180910390a38060019080519060200190620000ba929190620000c2565b5050620004fe565b828054620000d0906200038f565b90600052602060002090601f016020900481019282620000f4576000855562000140565b82601f106200010f57805160ff191683800117855562000140565b8280016001018555821562000140579182015b828111156200013f57825182559160200191906001019062000122565b5b5090506200014f919062000153565b5090565b5b808211156200016e57600081600090555060010162000154565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001db8262000190565b810181811067ffffffffffffffff82111715620001fd57620001fc620001a1565b5b80604052505050565b60006200021262000172565b9050620002208282620001d0565b919050565b600067ffffffffffffffff821115620002435762000242620001a1565b5b6200024e8262000190565b9050602081019050919050565b60005b838110156200027b5780820151818401526020810190506200025e565b838111156200028b576000848401525b50505050565b6000620002a8620002a28462000225565b62000206565b905082815260208101848484011115620002c757620002c66200018b565b5b620002d48482856200025b565b509392505050565b600082601f830112620002f457620002f362000186565b5b81516200030684826020860162000291565b91505092915050565b6000602082840312156200032857620003276200017c565b5b600082015167ffffffffffffffff81111562000349576200034862000181565b5b6200035784828501620002dc565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003a857607f821691505b60208210811415620003bf57620003be62000360565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b60008154620003fa816200038f565b620004068186620003c5565b9450600182166000811462000424576001811462000437576200046e565b60ff19831686526020860193506200046e565b6200044285620003d6565b60005b83811015620004665781548189015260018201915060208101905062000445565b808801955050505b50505092915050565b600081519050919050565b60006200048f8262000477565b6200049b8185620003c5565b9350620004ad8185602086016200025b565b620004b88162000190565b840191505092915050565b60006040820190508181036000830152620004df8185620003eb565b90508181036020830152620004f5818462000482565b90509392505050565b61085c806200050e6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063018ba9911461005c578063209652551461007a578063256fec88146100985780637ffaa4b6146100b657806393a09352146100d2575b600080fd5b6100646100ee565b60405161007191906103bd565b60405180910390f35b6100826100f7565b60405161008f9190610471565b60405180910390f35b6100a0610189565b6040516100ad91906104d4565b60405180910390f35b6100d060048036038101906100cb9190610638565b6101ad565b005b6100ec60048036038101906100e791906106b0565b61021f565b005b60006064905090565b60606001805461010690610728565b80601f016020809104026020016040519081016040528092919081815260200182805461013290610728565b801561017f5780601f106101545761010080835404028352916020019161017f565b820191906000526020600020905b81548152906001019060200180831161016257829003601f168201915b5050505050905090565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b81600190805190602001906101c3929190610301565b5080600290805190602001906101da929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c360018460405161029f9291906107ef565b60405180910390a380600190805190602001906102bd929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b82805461030d90610728565b90600052602060002090601f01602090048101928261032f5760008555610376565b82601f1061034857805160ff1916838001178555610376565b82800160010185558215610376579182015b8281111561037557825182559160200191906001019061035a565b5b5090506103839190610387565b5090565b5b808211156103a0576000816000905550600101610388565b5090565b6000819050919050565b6103b7816103a4565b82525050565b60006020820190506103d260008301846103ae565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104125780820151818401526020810190506103f7565b83811115610421576000848401525b50505050565b6000601f19601f8301169050919050565b6000610443826103d8565b61044d81856103e3565b935061045d8185602086016103f4565b61046681610427565b840191505092915050565b6000602082019050818103600083015261048b8184610438565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104be82610493565b9050919050565b6104ce816104b3565b82525050565b60006020820190506104e960008301846104c5565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61054582610427565b810181811067ffffffffffffffff821117156105645761056361050d565b5b80604052505050565b60006105776104ef565b9050610583828261053c565b919050565b600067ffffffffffffffff8211156105a3576105a261050d565b5b6105ac82610427565b9050602081019050919050565b82818337600083830152505050565b60006105db6105d684610588565b61056d565b9050828152602081018484840111156105f7576105f6610508565b5b6106028482856105b9565b509392505050565b600082601f83011261061f5761061e610503565b5b813561062f8482602086016105c8565b91505092915050565b6000806040838503121561064f5761064e6104f9565b5b600083013567ffffffffffffffff81111561066d5761066c6104fe565b5b6106798582860161060a565b925050602083013567ffffffffffffffff81111561069a576106996104fe565b5b6106a68582860161060a565b9150509250929050565b6000602082840312156106c6576106c56104f9565b5b600082013567ffffffffffffffff8111156106e4576106e36104fe565b5b6106f08482850161060a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061074057607f821691505b60208210811415610754576107536106f9565b5b50919050565b60008190508160005260206000209050919050565b6000815461077c81610728565b61078681866103e3565b945060018216600081146107a157600181146107b3576107e6565b60ff19831686526020860193506107e6565b6107bc8561075a565b60005b838110156107de578154818901526001820191506020810190506107bf565b808801955050505b50505092915050565b60006040820190508181036000830152610809818561076f565b9050818103602083015261081d8184610438565b9050939250505056fea2646970667358221220e37ed4b56859ad0b3a3773f57ff7b4e7a99406933fc4ff9f8ae053c52cdf3e3264736f6c63430008090033" -} \ No newline at end of file +{"abi":[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":true,"internalType":"address","name":"oldAuthor","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"_hashPuzzle","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastSender","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"},{"internalType":"string","name":"value2","type":"string"}],"name":"setValues","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"60806040523480156200001157600080fd5b5060405162000d6a38038062000d6a83398181016040528101906200003791906200030f565b600073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c36001846040516200009a929190620004c3565b60405180910390a38060019080519060200190620000ba929190620000c2565b5050620004fe565b828054620000d0906200038f565b90600052602060002090601f016020900481019282620000f4576000855562000140565b82601f106200010f57805160ff191683800117855562000140565b8280016001018555821562000140579182015b828111156200013f57825182559160200191906001019062000122565b5b5090506200014f919062000153565b5090565b5b808211156200016e57600081600090555060010162000154565b5090565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620001db8262000190565b810181811067ffffffffffffffff82111715620001fd57620001fc620001a1565b5b80604052505050565b60006200021262000172565b9050620002208282620001d0565b919050565b600067ffffffffffffffff821115620002435762000242620001a1565b5b6200024e8262000190565b9050602081019050919050565b60005b838110156200027b5780820151818401526020810190506200025e565b838111156200028b576000848401525b50505050565b6000620002a8620002a28462000225565b62000206565b905082815260208101848484011115620002c757620002c66200018b565b5b620002d48482856200025b565b509392505050565b600082601f830112620002f457620002f362000186565b5b81516200030684826020860162000291565b91505092915050565b6000602082840312156200032857620003276200017c565b5b600082015167ffffffffffffffff81111562000349576200034862000181565b5b6200035784828501620002dc565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b60006002820490506001821680620003a857607f821691505b60208210811415620003bf57620003be62000360565b5b50919050565b600082825260208201905092915050565b60008190508160005260206000209050919050565b60008154620003fa816200038f565b620004068186620003c5565b9450600182166000811462000424576001811462000437576200046e565b60ff19831686526020860193506200046e565b6200044285620003d6565b60005b83811015620004665781548189015260018201915060208101905062000445565b808801955050505b50505092915050565b600081519050919050565b60006200048f8262000477565b6200049b8185620003c5565b9350620004ad8185602086016200025b565b620004b88162000190565b840191505092915050565b60006040820190508181036000830152620004df8185620003eb565b90508181036020830152620004f5818462000482565b90509392505050565b61085c806200050e6000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c8063018ba9911461005c578063209652551461007a578063256fec88146100985780637ffaa4b6146100b657806393a09352146100d2575b600080fd5b6100646100ee565b60405161007191906103bd565b60405180910390f35b6100826100f7565b60405161008f9190610471565b60405180910390f35b6100a0610189565b6040516100ad91906104d4565b60405180910390f35b6100d060048036038101906100cb9190610638565b6101ad565b005b6100ec60048036038101906100e791906106b0565b61021f565b005b60006064905090565b60606001805461010690610728565b80601f016020809104026020016040519081016040528092919081815260200182805461013290610728565b801561017f5780601f106101545761010080835404028352916020019161017f565b820191906000526020600020905b81548152906001019060200180831161016257829003601f168201915b5050505050905090565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b81600190805190602001906101c3929190610301565b5080600290805190602001906101da929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f999b6d464c4e3383c341bdd3a22b02dda8a7e1d69c069d252e35cb2ee2f4a3c360018460405161029f9291906107ef565b60405180910390a380600190805190602001906102bd929190610301565b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b82805461030d90610728565b90600052602060002090601f01602090048101928261032f5760008555610376565b82601f1061034857805160ff1916838001178555610376565b82800160010185558215610376579182015b8281111561037557825182559160200191906001019061035a565b5b5090506103839190610387565b5090565b5b808211156103a0576000816000905550600101610388565b5090565b6000819050919050565b6103b7816103a4565b82525050565b60006020820190506103d260008301846103ae565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b838110156104125780820151818401526020810190506103f7565b83811115610421576000848401525b50505050565b6000601f19601f8301169050919050565b6000610443826103d8565b61044d81856103e3565b935061045d8185602086016103f4565b61046681610427565b840191505092915050565b6000602082019050818103600083015261048b8184610438565b905092915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104be82610493565b9050919050565b6104ce816104b3565b82525050565b60006020820190506104e960008301846104c5565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61054582610427565b810181811067ffffffffffffffff821117156105645761056361050d565b5b80604052505050565b60006105776104ef565b9050610583828261053c565b919050565b600067ffffffffffffffff8211156105a3576105a261050d565b5b6105ac82610427565b9050602081019050919050565b82818337600083830152505050565b60006105db6105d684610588565b61056d565b9050828152602081018484840111156105f7576105f6610508565b5b6106028482856105b9565b509392505050565b600082601f83011261061f5761061e610503565b5b813561062f8482602086016105c8565b91505092915050565b6000806040838503121561064f5761064e6104f9565b5b600083013567ffffffffffffffff81111561066d5761066c6104fe565b5b6106798582860161060a565b925050602083013567ffffffffffffffff81111561069a576106996104fe565b5b6106a68582860161060a565b9150509250929050565b6000602082840312156106c6576106c56104f9565b5b600082013567ffffffffffffffff8111156106e4576106e36104fe565b5b6106f08482850161060a565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061074057607f821691505b60208210811415610754576107536106f9565b5b50919050565b60008190508160005260206000209050919050565b6000815461077c81610728565b61078681866103e3565b945060018216600081146107a157600181146107b3576107e6565b60ff19831686526020860193506107e6565b6107bc8561075a565b60005b838110156107de578154818901526001820191506020810190506107bf565b808801955050505b50505092915050565b60006040820190508181036000830152610809818561076f565b9050818103602083015261081d8184610438565b9050939250505056fea2646970667358221220e37ed4b56859ad0b3a3773f57ff7b4e7a99406933fc4ff9f8ae053c52cdf3e3264736f6c63430008090033"} \ No newline at end of file diff --git a/crates/anvil/test-data/emit_logs.json b/crates/anvil/test-data/emit_logs.json index 0113e1c49d926..635019ae3ab33 100644 --- a/crates/anvil/test-data/emit_logs.json +++ b/crates/anvil/test-data/emit_logs.json @@ -1,67 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "author", - "type": "address" - }, - { - "indexed": false, - "internalType": "string", - "name": "oldValue", - "type": "string" - }, - { - "indexed": false, - "internalType": "string", - "name": "newValue", - "type": "string" - } - ], - "name": "ValueChanged", - "type": "event" - }, - { - "inputs": [], - "name": "getValue", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "value", - "type": "string" - } - ], - "name": "setValue", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bin": "608060405234801561001057600080fd5b506040516105a63803806105a68339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6103f9806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b57806393a09352146100b8575b600080fd5b610043610160565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561007d578181015183820152602001610065565b50505050905090810190601f1680156100aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61015e600480360360208110156100ce57600080fd5b8101906020810181356401000000008111156100e957600080fd5b8201836020820111156100fb57600080fd5b8035906020019184600183028401116401000000008311171561011d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101f6945050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ec5780601f106101c1576101008083540402835291602001916101ec565b820191906000526020600020905b8154815290600101906020018083116101cf57829003601f168201915b5050505050905090565b60408051818152600080546002600019610100600184161502019091160492820183905233927fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb9285918190602082019060608301908690801561029b5780601f106102705761010080835404028352916020019161029b565b820191906000526020600020905b81548152906001019060200180831161027e57829003601f168201915b5050838103825284518152845160209182019186019080838360005b838110156102cf5781810151838201526020016102b7565b50505050905090810190601f1680156102fc5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a2805161031e906000906020840190610322565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610358576000855561039e565b82601f1061037157805160ff191683800117855561039e565b8280016001018555821561039e579182015b8281111561039e578251825591602001919060010190610383565b506103aa9291506103ae565b5090565b5b808211156103aa57600081556001016103af56fea2646970667358221220c1367a0db85dfe60814cdfc5141a8fe8b95c9d051a6824343085c3ba9697244a64736f6c63430007060033" -} \ No newline at end of file +{"abi":[{"inputs":[{"internalType":"string","name":"value","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"oldValue","type":"string"},{"indexed":false,"internalType":"string","name":"newValue","type":"string"}],"name":"ValueChanged","type":"event"},{"inputs":[],"name":"getValue","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"value","type":"string"}],"name":"setValue","outputs":[],"stateMutability":"nonpayable","type":"function"}],"bin":"608060405234801561001057600080fd5b506040516105a63803806105a68339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6103f9806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063209652551461003b57806393a09352146100b8575b600080fd5b610043610160565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561007d578181015183820152602001610065565b50505050905090810190601f1680156100aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61015e600480360360208110156100ce57600080fd5b8101906020810181356401000000008111156100e957600080fd5b8201836020820111156100fb57600080fd5b8035906020019184600183028401116401000000008311171561011d57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506101f6945050505050565b005b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156101ec5780601f106101c1576101008083540402835291602001916101ec565b820191906000526020600020905b8154815290600101906020018083116101cf57829003601f168201915b5050505050905090565b60408051818152600080546002600019610100600184161502019091160492820183905233927fe826f71647b8486f2bae59832124c70792fba044036720a54ec8dacdd5df4fcb9285918190602082019060608301908690801561029b5780601f106102705761010080835404028352916020019161029b565b820191906000526020600020905b81548152906001019060200180831161027e57829003601f168201915b5050838103825284518152845160209182019186019080838360005b838110156102cf5781810151838201526020016102b7565b50505050905090810190601f1680156102fc5780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a2805161031e906000906020840190610322565b5050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282610358576000855561039e565b82601f1061037157805160ff191683800117855561039e565b8280016001018555821561039e579182015b8281111561039e578251825591602001919060010190610383565b506103aa9291506103ae565b5090565b5b808211156103aa57600081556001016103af56fea2646970667358221220c1367a0db85dfe60814cdfc5141a8fe8b95c9d051a6824343085c3ba9697244a64736f6c63430007060033"} \ No newline at end of file diff --git a/crates/anvil/test-data/greeter.json b/crates/anvil/test-data/greeter.json index 0892e4d43f920..93c50f01edb8f 100644 --- a/crates/anvil/test-data/greeter.json +++ b/crates/anvil/test-data/greeter.json @@ -1,44 +1 @@ -{ - "bytecode": { - "object": "608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033" - }, - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "greet", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_greeting", - "type": "string" - } - ], - "name": "setGreeting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ] -} \ No newline at end of file +{"bytecode":{"object":"608060405234801561001057600080fd5b506040516104913803806104918339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b90830190602082018581111561006857600080fd5b825164010000000081118282018810171561008257600080fd5b82525081516020918201929091019080838360005b838110156100af578181015183820152602001610097565b50505050905090810190601f1680156100dc5780820380516001836020036101000a031916815260200191505b50604052505081516100f6915060009060208401906100fd565b505061019e565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826101335760008555610179565b82601f1061014c57805160ff1916838001178555610179565b82800160010185558215610179579182015b8281111561017957825182559160200191906001019061015e565b50610185929150610189565b5090565b5b80821115610185576000815560010161018a565b6102e4806101ad6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae3217146100e3575b600080fd5b6100e16004803603602081101561005157600080fd5b81019060208101813564010000000081111561006c57600080fd5b82018360208201111561007e57600080fd5b803590602001918460018302840111640100000000831117156100a057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610160945050505050565b005b6100eb610177565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561012557818101518382015260200161010d565b50505050905090810190601f1680156101525780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b805161017390600090602084019061020d565b5050565b60008054604080516020601f60026000196101006001881615020190951694909404938401819004810282018101909252828152606093909290918301828280156102035780601f106101d857610100808354040283529160200191610203565b820191906000526020600020905b8154815290600101906020018083116101e657829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f0160209004810192826102435760008555610289565b82601f1061025c57805160ff1916838001178555610289565b82800160010185558215610289579182015b8281111561028957825182559160200191906001019061026e565b50610295929150610299565b5090565b5b80821115610295576000815560010161029a56fea26469706673582212208b9161dfd195d53618942a72a3b481d61a7b142de919925a0b34f9c986e5707e64736f6c63430007060033"},"abi":[{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"greet","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greeting","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]} \ No newline at end of file diff --git a/crates/anvil/test-data/multicall.json b/crates/anvil/test-data/multicall.json index e0be8fc5df4e2..e7f7d9f1138f7 100644 --- a/crates/anvil/test-data/multicall.json +++ b/crates/anvil/test-data/multicall.json @@ -1,144 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "bytes", - "name": "callData", - "type": "bytes" - } - ], - "internalType": "struct Multicall.Call[]", - "name": "calls", - "type": "tuple[]" - } - ], - "name": "aggregate", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "bytes[]", - "name": "returnData", - "type": "bytes[]" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - } - ], - "name": "getBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockCoinbase", - "outputs": [ - { - "internalType": "address", - "name": "coinbase", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockDifficulty", - "outputs": [ - { - "internalType": "uint256", - "name": "difficulty", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockGasLimit", - "outputs": [ - { - "internalType": "uint256", - "name": "gaslimit", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getCurrentBlockTimestamp", - "outputs": [ - { - "internalType": "uint256", - "name": "timestamp", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "getEthBalance", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getLastBlockHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "blockHash", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bin": "608060405234801561001057600080fd5b50610abb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806372425d9d1161005b57806372425d9d1461012a57806386d516e814610148578063a8b0574e14610166578063ee82ac5e1461018457610088565b80630f28c97d1461008d578063252dba42146100ab57806327e86d6e146100dc5780634d2301cc146100fa575b600080fd5b6100956101b4565b6040516100a29190610381565b60405180910390f35b6100c560048036038101906100c091906106b0565b6101bc565b6040516100d3929190610843565b60405180910390f35b6100e461030f565b6040516100f1919061088c565b60405180910390f35b610114600480360381019061010f91906108a7565b610324565b6040516101219190610381565b60405180910390f35b610132610345565b60405161013f9190610381565b60405180910390f35b61015061034d565b60405161015d9190610381565b60405180910390f35b61016e610355565b60405161017b91906108e3565b60405180910390f35b61019e6004803603810190610199919061092a565b61035d565b6040516101ab919061088c565b60405180910390f35b600042905090565b60006060439150825167ffffffffffffffff8111156101de576101dd6103c6565b5b60405190808252806020026020018201604052801561021157816020015b60608152602001906001900390816101fc5790505b50905060005b83518110156103095760008085838151811061023657610235610957565b5b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1686848151811061026b5761026a610957565b5b60200260200101516020015160405161028491906109c2565b6000604051808303816000865af19150503d80600081146102c1576040519150601f19603f3d011682016040523d82523d6000602084013e6102c6565b606091505b5091509150816102d557600080fd5b808484815181106102e9576102e8610957565b5b60200260200101819052505050808061030190610a08565b915050610217565b50915091565b600060014361031e9190610a51565b40905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b600045905090565b600041905090565b600081409050919050565b6000819050919050565b61037b81610368565b82525050565b60006020820190506103966000830184610372565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103fe826103b5565b810181811067ffffffffffffffff8211171561041d5761041c6103c6565b5b80604052505050565b600061043061039c565b905061043c82826103f5565b919050565b600067ffffffffffffffff82111561045c5761045b6103c6565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a78261047c565b9050919050565b6104b78161049c565b81146104c257600080fd5b50565b6000813590506104d4816104ae565b92915050565b600080fd5b600067ffffffffffffffff8211156104fa576104f96103c6565b5b610503826103b5565b9050602081019050919050565b82818337600083830152505050565b600061053261052d846104df565b610426565b90508281526020810184848401111561054e5761054d6104da565b5b610559848285610510565b509392505050565b600082601f830112610576576105756103b0565b5b813561058684826020860161051f565b91505092915050565b6000604082840312156105a5576105a4610472565b5b6105af6040610426565b905060006105bf848285016104c5565b600083015250602082013567ffffffffffffffff8111156105e3576105e2610477565b5b6105ef84828501610561565b60208301525092915050565b600061060e61060984610441565b610426565b905080838252602082019050602084028301858111156106315761063061046d565b5b835b8181101561067857803567ffffffffffffffff811115610656576106556103b0565b5b808601610663898261058f565b85526020850194505050602081019050610633565b5050509392505050565b600082601f830112610697576106966103b0565b5b81356106a78482602086016105fb565b91505092915050565b6000602082840312156106c6576106c56103a6565b5b600082013567ffffffffffffffff8111156106e4576106e36103ab565b5b6106f084828501610682565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561075f578082015181840152602081019050610744565b8381111561076e576000848401525b50505050565b600061077f82610725565b6107898185610730565b9350610799818560208601610741565b6107a2816103b5565b840191505092915050565b60006107b98383610774565b905092915050565b6000602082019050919050565b60006107d9826106f9565b6107e38185610704565b9350836020820285016107f585610715565b8060005b85811015610831578484038952815161081285826107ad565b945061081d836107c1565b925060208a019950506001810190506107f9565b50829750879550505050505092915050565b60006040820190506108586000830185610372565b818103602083015261086a81846107ce565b90509392505050565b6000819050919050565b61088681610873565b82525050565b60006020820190506108a1600083018461087d565b92915050565b6000602082840312156108bd576108bc6103a6565b5b60006108cb848285016104c5565b91505092915050565b6108dd8161049c565b82525050565b60006020820190506108f860008301846108d4565b92915050565b61090781610368565b811461091257600080fd5b50565b600081359050610924816108fe565b92915050565b6000602082840312156109405761093f6103a6565b5b600061094e84828501610915565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081905092915050565b600061099c82610725565b6109a68185610986565b93506109b6818560208601610741565b80840191505092915050565b60006109ce8284610991565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a1382610368565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610a4657610a456109d9565b5b600182019050919050565b6000610a5c82610368565b9150610a6783610368565b925082821015610a7a57610a796109d9565b5b82820390509291505056fea2646970667358221220e5023d90063e0939116a41565414721ba1350cd3e98b12e7b65983a039644df964736f6c634300080a0033" -} \ No newline at end of file +{"abi":[{"inputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"bytes","name":"callData","type":"bytes"}],"internalType":"struct Multicall.Call[]","name":"calls","type":"tuple[]"}],"name":"aggregate","outputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"},{"internalType":"bytes[]","name":"returnData","type":"bytes[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"getBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockCoinbase","outputs":[{"internalType":"address","name":"coinbase","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockDifficulty","outputs":[{"internalType":"uint256","name":"difficulty","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockGasLimit","outputs":[{"internalType":"uint256","name":"gaslimit","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCurrentBlockTimestamp","outputs":[{"internalType":"uint256","name":"timestamp","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getEthBalance","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastBlockHash","outputs":[{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"}],"bin":"608060405234801561001057600080fd5b50610abb806100206000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806372425d9d1161005b57806372425d9d1461012a57806386d516e814610148578063a8b0574e14610166578063ee82ac5e1461018457610088565b80630f28c97d1461008d578063252dba42146100ab57806327e86d6e146100dc5780634d2301cc146100fa575b600080fd5b6100956101b4565b6040516100a29190610381565b60405180910390f35b6100c560048036038101906100c091906106b0565b6101bc565b6040516100d3929190610843565b60405180910390f35b6100e461030f565b6040516100f1919061088c565b60405180910390f35b610114600480360381019061010f91906108a7565b610324565b6040516101219190610381565b60405180910390f35b610132610345565b60405161013f9190610381565b60405180910390f35b61015061034d565b60405161015d9190610381565b60405180910390f35b61016e610355565b60405161017b91906108e3565b60405180910390f35b61019e6004803603810190610199919061092a565b61035d565b6040516101ab919061088c565b60405180910390f35b600042905090565b60006060439150825167ffffffffffffffff8111156101de576101dd6103c6565b5b60405190808252806020026020018201604052801561021157816020015b60608152602001906001900390816101fc5790505b50905060005b83518110156103095760008085838151811061023657610235610957565b5b60200260200101516000015173ffffffffffffffffffffffffffffffffffffffff1686848151811061026b5761026a610957565b5b60200260200101516020015160405161028491906109c2565b6000604051808303816000865af19150503d80600081146102c1576040519150601f19603f3d011682016040523d82523d6000602084013e6102c6565b606091505b5091509150816102d557600080fd5b808484815181106102e9576102e8610957565b5b60200260200101819052505050808061030190610a08565b915050610217565b50915091565b600060014361031e9190610a51565b40905090565b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600044905090565b600045905090565b600041905090565b600081409050919050565b6000819050919050565b61037b81610368565b82525050565b60006020820190506103966000830184610372565b92915050565b6000604051905090565b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6103fe826103b5565b810181811067ffffffffffffffff8211171561041d5761041c6103c6565b5b80604052505050565b600061043061039c565b905061043c82826103f5565b919050565b600067ffffffffffffffff82111561045c5761045b6103c6565b5b602082029050602081019050919050565b600080fd5b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104a78261047c565b9050919050565b6104b78161049c565b81146104c257600080fd5b50565b6000813590506104d4816104ae565b92915050565b600080fd5b600067ffffffffffffffff8211156104fa576104f96103c6565b5b610503826103b5565b9050602081019050919050565b82818337600083830152505050565b600061053261052d846104df565b610426565b90508281526020810184848401111561054e5761054d6104da565b5b610559848285610510565b509392505050565b600082601f830112610576576105756103b0565b5b813561058684826020860161051f565b91505092915050565b6000604082840312156105a5576105a4610472565b5b6105af6040610426565b905060006105bf848285016104c5565b600083015250602082013567ffffffffffffffff8111156105e3576105e2610477565b5b6105ef84828501610561565b60208301525092915050565b600061060e61060984610441565b610426565b905080838252602082019050602084028301858111156106315761063061046d565b5b835b8181101561067857803567ffffffffffffffff811115610656576106556103b0565b5b808601610663898261058f565b85526020850194505050602081019050610633565b5050509392505050565b600082601f830112610697576106966103b0565b5b81356106a78482602086016105fb565b91505092915050565b6000602082840312156106c6576106c56103a6565b5b600082013567ffffffffffffffff8111156106e4576106e36103ab565b5b6106f084828501610682565b91505092915050565b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b600081519050919050565b600082825260208201905092915050565b60005b8381101561075f578082015181840152602081019050610744565b8381111561076e576000848401525b50505050565b600061077f82610725565b6107898185610730565b9350610799818560208601610741565b6107a2816103b5565b840191505092915050565b60006107b98383610774565b905092915050565b6000602082019050919050565b60006107d9826106f9565b6107e38185610704565b9350836020820285016107f585610715565b8060005b85811015610831578484038952815161081285826107ad565b945061081d836107c1565b925060208a019950506001810190506107f9565b50829750879550505050505092915050565b60006040820190506108586000830185610372565b818103602083015261086a81846107ce565b90509392505050565b6000819050919050565b61088681610873565b82525050565b60006020820190506108a1600083018461087d565b92915050565b6000602082840312156108bd576108bc6103a6565b5b60006108cb848285016104c5565b91505092915050565b6108dd8161049c565b82525050565b60006020820190506108f860008301846108d4565b92915050565b61090781610368565b811461091257600080fd5b50565b600081359050610924816108fe565b92915050565b6000602082840312156109405761093f6103a6565b5b600061094e84828501610915565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081905092915050565b600061099c82610725565b6109a68185610986565b93506109b6818560208601610741565b80840191505092915050565b60006109ce8284610991565b915081905092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610a1382610368565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610a4657610a456109d9565b5b600182019050919050565b6000610a5c82610368565b9150610a6783610368565b925082821015610a7a57610a796109d9565b5b82820390509291505056fea2646970667358221220e5023d90063e0939116a41565414721ba1350cd3e98b12e7b65983a039644df964736f6c634300080a0033"} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump-legacy-stress.json b/crates/anvil/test-data/state-dump-legacy-stress.json new file mode 100644 index 0000000000000..f6605d5add4e3 --- /dev/null +++ b/crates/anvil/test-data/state-dump-legacy-stress.json @@ -0,0 +1 @@ +{"block":{"number":"0x5","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x66b200cb","gas_limit":"0x1c9c380","basefee":"0x12e09c7a","difficulty":"0x0","prevrandao":"0xe7ef87fc7c2090741a6749a087e4ca8092cb4d07136008799e4ebeac3b69e34a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0x1088aa62285a00","code":"0x","storage":{}},"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd":{"nonce":1,"balance":"0x0","code":"0x6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x19ba1fac55eea44d12a01372a8eb0c2ebbf9ca21":{"nonce":1,"balance":"0x21e19df7c2963f0ac6b","code":"0x","storage":{}},"0x19c6ab860dbe2bc433574193a4409770a8748bf6":{"nonce":1,"balance":"0x21e19df8da6b7bdc410","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x40567ec443c1d1872af5155755ac3803cc3fe61e":{"nonce":1,"balance":"0x21e19da82562f921b40","code":"0x","storage":{}},"0x47d08dad17ccb558b3ea74b1a0e73a9cc804a9dc":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","storage":{"0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0x0"}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":2,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x8138ef7cf908021d117e542120b7a39065016107":{"nonce":1,"balance":"0x0","code":"0x608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","storage":{}},"0x83a0444b93927c3afcbe46e522280390f748e171":{"nonce":1,"balance":"0x0","code":"0x6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c63430008110033","storage":{"0x5a648c35a2f5512218b4683cf10e03f5b7c9dc7346e1bf77d304ae97f60f592b":"0x108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","0x5c7865864a2a990d80b5bb5c40e7b73a029960dc711fbb56120dfab976e92ea3":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xc67e2bd3108604cf0168c0e5ef9cd6d78b9bb14b":{"nonce":1,"balance":"0x21e19c6edb7e2445f20","code":"0x","storage":{}},"0xeb045d78d273107348b0300c01d29b7552d622ab":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e08b86820a43ea","code":"0x","storage":{}}},"best_block_number":"0x5","blocks":[{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xcd346446ed010523161f40a5f2b512def549bfb79e165b4354488738416481f2","transactionsRoot":"0xb3a4689832e0b599260ae70362ffcf224b60571b35ff8836904a3d81e2675d66","receiptsRoot":"0x2d13fdc120ab90536fed583939de7fb68b64926a306c1f629593ca9c2c93b198","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x3ea90d","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x2e0b6260","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3ea90d","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b5061494f806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632a952b2d1461005c57806350c946fe14610085578063625ca21c146100a5578063daa250be146100c6578063deba1b98146100d9575b600080fd5b61006f61006a366004613a63565b6100ec565b60405161007c9190613a7c565b60405180910390f35b610098610093366004613a63565b61011c565b60405161007c9190613b21565b6100b86100b3366004613c92565b610276565b60405190815260200161007c565b61006f6100d4366004613d5f565b6102bb565b6100b86100e7366004613c92565b6102d6565b6100f46139e4565b6040805160008082526020820190815281830190925261011691849190610310565b92915050565b6101416040805160608101909152806000815260200160608152602001606081525090565b61014a82610ab6565b60408051606081019091528154909190829060ff16600981111561017057610170613aa7565b600981111561018157610181613aa7565b815260200160018201805461019590613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546101c190613dc2565b801561020e5780601f106101e35761010080835404028352916020019161020e565b820191906000526020600020905b8154815290600101906020018083116101f157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561026657602002820191906000526020600020905b815481526020019060010190808311610252575b5050505050815250509050919050565b600080604051806060016040528086600981111561029657610296613aa7565b81526020018581526020018481525090506102b081610ac1565b9150505b9392505050565b6102c36139e4565b6102ce848484610310565b949350505050565b60008060405180606001604052808660098111156102f6576102f6613aa7565b81526020018581526020018481525090506102b081610acc565b6103186139e4565b81518351146103a05760408051634bab873760e11b81526004810191909152600d60448201526c72756e74696d6556616c75657360981b606482015260806024820152602260848201527f6d7573742062652073616d65206c656e6774682061732072756e74696d654b6560a482015261797360f01b60c482015260e4015b60405180910390fd5b60006103ab85610c26565b805490915060ff1660018160098111156103c7576103c7613aa7565b036104755761046c6103da838787610c84565b8360010180546103e990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461041590613dc2565b80156104625780601f1061043757610100808354040283529160200191610462565b820191906000526020600020905b81548152906001019060200180831161044557829003601f168201915b5050505050610d46565b925050506102b4565b600281600981111561048957610489613aa7565b036105305761046c61049c838787610c84565b8360010180546104ab90613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546104d790613dc2565b80156105245780601f106104f957610100808354040283529160200191610524565b820191906000526020600020905b81548152906001019060200180831161050757829003601f168201915b50505050508787610ebb565b600381600981111561054457610544613aa7565b036105de5761046c82600101805461055b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461058790613dc2565b80156105d45780601f106105a9576101008083540402835291602001916105d4565b820191906000526020600020905b8154815290600101906020018083116105b757829003601f168201915b5050505050610f59565b60048160098111156105f2576105f2613aa7565b0361068c5761046c82600101805461060990613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461063590613dc2565b80156106825780601f1061065757610100808354040283529160200191610682565b820191906000526020600020905b81548152906001019060200180831161066557829003601f168201915b5050505050611087565b60058160098111156106a0576106a0613aa7565b0361073a5761046c8260010180546106b790613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546106e390613dc2565b80156107305780601f1061070557610100808354040283529160200191610730565b820191906000526020600020905b81548152906001019060200180831161071357829003601f168201915b505050505061131e565b600981600981111561074e5761074e613aa7565b036107ea5761046c82600101805461076590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461079190613dc2565b80156107de5780601f106107b3576101008083540402835291602001916107de565b820191906000526020600020905b8154815290600101906020018083116107c157829003601f168201915b505050505086866114b5565b60068160098111156107fe576107fe613aa7565b036108a35761046c610811838787610c84565b83600101805461082090613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461084c90613dc2565b80156108995780601f1061086e57610100808354040283529160200191610899565b820191906000526020600020905b81548152906001019060200180831161087c57829003601f168201915b50505050506115c7565b60078160098111156108b7576108b7613aa7565b036109ec576040805160608101909152825461046c91908490829060ff1660098111156108e6576108e6613aa7565b60098111156108f7576108f7613aa7565b815260200160018201805461090b90613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461093790613dc2565b80156109845780601f1061095957610100808354040283529160200191610984565b820191906000526020600020905b81548152906001019060200180831161096757829003601f168201915b50505050508152602001600282018054806020026020016040519081016040528092919081815260200182805480156109dc57602002820191906000526020600020905b8154815260200190600101908083116109c8575b5050505050815250508686611728565b6008816009811115610a0057610a00613aa7565b03610a9a5761046c826001018054610a1790613dc2565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4390613dc2565b8015610a905780601f10610a6557610100808354040283529160200191610a90565b820191906000526020600020905b815481529060010190602001808311610a7357829003601f168201915b50505050506118a5565b6040516323a9bbc960e01b815260048101879052602401610397565b600061011682610c26565b6000610116826118ea565b6000610ad782610ac1565b9050610ae28161192a565b15610b35577fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e81836000015184602001518560400151604051610b289493929190613e32565b60405180910390a1919050565b610b3e82611a8c565b610b5d578160405163382bbbc960e11b81526004016103979190613b21565b60005b826040015151811015610bd957610b9383604001518281518110610b8657610b86613e6a565b602002602001015161192a565b610bd15782604001518181518110610bad57610bad613e6a565b6020026020010151604051632f19f96160e11b815260040161039791815260200190565b600101610b60565b50610be382611c31565b8351602085015160408087015190519395507fcb64985827770858ec421ad26da7e558c757541643036ce44d6b4eb9e8e5dc5e9450610b28938693929190613e32565b604080516020808201839052606082018190527f696f2e73796e7468657469782e6f7261636c652d6d616e616765722e4e6f6465608080840191909152828401949094528251808303909401845260a0909101909152815191012090565b600283015460609067ffffffffffffffff811115610ca457610ca4613b9a565b604051908082528060200260200182016040528015610cdd57816020015b610cca6139e4565b815260200190600190039081610cc25790505b50905060005b6002850154811015610d3e57610d19856002018281548110610d0757610d07613e6a565b90600052602060002001548585610310565b828281518110610d2b57610d2b613e6a565b6020908102919091010152600101610ce3565b509392505050565b610d4e6139e4565b600082806020019051810190610d649190613e80565b90506000816008811115610d7a57610d7a613aa7565b03610d9057610d8884611ca5565b915050610116565b6001816008811115610da457610da4613aa7565b03610db257610d8884611d0d565b6002816008811115610dc657610dc6613aa7565b03610dd457610d8884611d90565b6003816008811115610de857610de8613aa7565b03610df657610d8884611e13565b6004816008811115610e0a57610e0a613aa7565b03610e1857610d8884611ec9565b6005816008811115610e2c57610e2c613aa7565b03610e3a57610d8884612009565b6006816008811115610e4e57610e4e613aa7565b03610e5c57610d88846120e4565b6007816008811115610e7057610e70613aa7565b03610e7e57610d888461220c565b6008816008811115610e9257610e92613aa7565b03610ea057610d88846122ce565b80604051631be413d360e11b81526004016103979190613ea1565b610ec36139e4565b600084806020019051810190610ed99190613ed3565b604051631ecba7c360e31b81529091506001600160a01b0382169063f65d3e1890610f0e908990899089908990600401613ef0565b608060405180830381865afa158015610f2b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f4f9190613f91565b9695505050505050565b610f616139e4565b600080600084806020019051810190610f7a9190613fe8565b92509250925060008390506000806000836001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015610fc8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fec9190614041565b509350509250925060008660001461100f5761100a8585858a6123c7565b611011565b825b905060128660ff161161103b5761103661102f60ff881660126140a7565b82906124c2565b611053565b61105361104c601260ff89166140a7565b82906124dc565b9050604051806080016040528082815260200183815260200160008152602001600081525098505050505050505050919050565b61108f6139e4565b600080600080600080878060200190518101906110ac91906140ba565b604080516002808252606082018352979d50959b50939950919750955093506000929060208301908036833701905050905081816000815181106110f2576110f2613e6a565b602002602001019063ffffffff16908163ffffffff168152505060008160018151811061112157611121613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526000906001600160a01b0385169063883bdbfd90611165908590600401614143565b600060405180830381865afa158015611182573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526111aa91908101906141f5565b5090506000816000815181106111c2576111c2613e6a565b6020026020010151826001815181106111dd576111dd613e6a565b60200260200101516111ef91906142c1565b9050600061121761120563ffffffff87166124f6565b61120f9084614304565b60060b61252d565b905060008260060b12801561124c575061123b63ffffffff8616612569565b612569565b8260060b6112499190614342565b15155b1561125f578061125b81614356565b9150505b600061126d6012600a61445d565b9050600061128061123684848f8f612593565b905060006112908a60ff16612569565b61129c8c60ff16612569565b6112a6919061446c565b905060008082136112d1576112cc6112c56112c084614493565b612686565b84906124dc565b6112e4565b6112e46112dd83612686565b84906124c2565b905060405180608001604052808281526020014281526020016000815260200160008152509e505050505050505050505050505050919050565b6113266139e4565b60008060008480602001905181019061133f91906144bf565b91945092509050826000826113bc576040516396834ad360e01b8152600481018590526001600160a01b038316906396834ad390602401608060405180830381865afa158015611393573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906113b791906144f5565b611425565b604051639474f45b60e01b8152600481018590526001600160a01b03831690639474f45b90602401608060405180830381865afa158015611401573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061142591906144f5565b90506000816040015160030b601261143d919061456f565b90506000808213611467576114626114576112c084614493565b845160070b906124dc565b61147e565b61147e61147383612686565b845160070b906124c2565b9050604051806080016040528082815260200184606001518152602001600081526020016000815250975050505050505050919050565b6114bd6139e4565b6000806000868060200190518101906114d69190614597565b92509250925060005b8651811015611545578681815181106114fa576114fa613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b0361153d5785818151811061152f5761152f613e6a565b602002602001015160001c91505b6001016114df565b5060408051600180825281830190925260009160208083019080368337019050509050828160008151811061157c5761157c613e6a565b602002602001018181525050836001838360405160200161159f939291906145ce565b60408051601f198184030181529082905263cf2cabdf60e01b82526103979291600401614603565b6115cf6139e4565b6000828060200190518101906115e59190614627565b90506000846000815181106115fc576115fc613e6a565b602002602001015160000151905060008560018151811061161f5761161f613e6a565b6020026020010151600001519050808214611702576000611653601261164d611648858761446c565b6126a9565b906124c2565b905082158061167b5750611666836126a9565b6116709082614640565b61167985612569565b125b15611700576002875111156116b0578660028151811061169d5761169d613e6a565b6020026020010151945050505050610116565b826000036116d15760405163014cc07160e01b815260040160405180910390fd5b6116da836126a9565b6116e49082614640565b60405163dcac091960e01b815260040161039791815260200190565b505b8560008151811061171557611715613e6a565b6020026020010151935050505092915050565b6117306139e4565b6000846020015180602001905181019061174a9190614627565b905060005b84518110156117bc5784818151811061176a5761176a613e6a565b6020026020010151717374616c656e657373546f6c6572616e636560701b036117b4576117ad8482815181106117a2576117a2613e6a565b602002602001015190565b91506117bc565b60010161174f565b50600085604001516000815181106117d6576117d6613e6a565b6020026020010151905060006117ed828787610310565b60208101519091506117ff84426140a7565b1161180e5792506102b4915050565b86604001515160010361187157866040015160008151811061183257611832613e6a565b602002602001015181600001518260200151604051631808066560e21b8152600401610397939291909283526020830191909152604082015260600190565b61189a876040015160018151811061188b5761188b613e6a565b60200260200101518787610310565b979650505050505050565b6118ad6139e4565b6040518060800160405280838060200190518101906118cc9190614627565b81526020014281526020016000815260200160008152509050919050565b600081600001518260200151836040015160405160200161190d9392919061466e565b604051602081830303815290604052805190602001209050919050565b60008061193683610c26565b60408051606081019091528154909190829060ff16600981111561195c5761195c613aa7565b600981111561196d5761196d613aa7565b815260200160018201805461198190613dc2565b80601f01602080910402602001604051908101604052809291908181526020018280546119ad90613dc2565b80156119fa5780601f106119cf576101008083540402835291602001916119fa565b820191906000526020600020905b8154815290600101906020018083116119dd57829003601f168201915b5050505050815260200160028201805480602002602001604051908101604052809291908181526020018280548015611a5257602002820191906000526020600020905b815481526020019060010190808311611a3e575b505050505081525050905060006009811115611a7057611a70613aa7565b81516009811115611a8357611a83613aa7565b14159392505050565b6000600182516009811115611aa357611aa3613aa7565b1480611ac15750600682516009811115611abf57611abf613aa7565b145b80611ade5750600782516009811115611adc57611adc613aa7565b145b15611aee57611aec826126c1565b505b600182516009811115611b0357611b03613aa7565b03611b11576101168261284a565b600282516009811115611b2657611b26613aa7565b03611b3457610116826128a5565b600382516009811115611b4957611b49613aa7565b03611b575761011682612973565b600482516009811115611b6c57611b6c613aa7565b03611b7a5761011682612aae565b600582516009811115611b8f57611b8f613aa7565b03611b9d5761011682612e92565b600982516009811115611bb257611bb2613aa7565b03611bc05761011682612fcb565b600682516009811115611bd557611bd5613aa7565b03611be3576101168261300e565b600782516009811115611bf857611bf8613aa7565b03611c065761011682613052565b600882516009811115611c1b57611c1b613aa7565b03611c295761011682613078565b506000919050565b600080611c3d836118ea565b9050611c4881610c26565b8351815491935090839060ff19166001836009811115611c6a57611c6a613aa7565b021790555060208301516001830190611c8390826146ed565b5060408301518051611c9f916002850191602090910190613a0c565b50915091565b611cad6139e4565b60005b8251811015611d07578160200151838281518110611cd057611cd0613e6a565b6020026020010151602001511115611cff57828181518110611cf457611cf4613e6a565b602002602001015191505b600101611cb0565b50919050565b611d156139e4565b81600081518110611d2857611d28613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611d5957611d59613e6a565b6020026020010151600001511215611d8857828181518110611d7d57611d7d613e6a565b602002602001015191505b600101611d39565b611d986139e4565b81600081518110611dab57611dab613e6a565b602002602001015190506000600190505b8251811015611d07578160000151838281518110611ddc57611ddc613e6a565b6020026020010151600001511315611e0b57828181518110611e0057611e00613e6a565b602002602001015191505b600101611dbc565b611e1b6139e4565b60005b8251811015611e9557828181518110611e3957611e39613e6a565b60200260200101516000015182600001818151611e56919061456f565b9052508251839082908110611e6d57611e6d613e6a565b60200260200101516020015182602001818151611e8a91906147ad565b905250600101611e1e565b50611ea08251612569565b8151611eac9190614640565b815281516020820151611ebf91906147c0565b6020820152919050565b611ed16139e4565b611eed826000611ee86001865161123691906140a7565b6130a4565b60028251611efb91906147d4565b600003611fd65760408051600280825260608201909252600091816020015b611f226139e4565b815260200190600190039081611f1a57905050905082600160028551611f4891906147c0565b611f5291906140a7565b81518110611f6257611f62613e6a565b602002602001015181600081518110611f7d57611f7d613e6a565b60200260200101819052508260028451611f9791906147c0565b81518110611fa757611fa7613e6a565b602002602001015181600181518110611fc257611fc2613e6a565b60200260200101819052506102b481611e13565b8160028351611fe591906147c0565b81518110611ff557611ff5613e6a565b60200260200101519050919050565b919050565b6120116139e4565b8160008151811061202457612024613e6a565b60209081029190910101515181528151829060009061204557612045613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061207657612076613e6a565b6020026020010151600001518260000181815161209391906147e8565b90525082518390829081106120aa576120aa613e6a565b602002602001015160200151826020018181516120c791906147ad565b90525060010161205b565b5081518160200151611ebf91906147c0565b6120ec6139e4565b816000815181106120ff576120ff613e6a565b60209081029190910101515181528151829060009061212057612120613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061215157612151613e6a565b60200260200101516000015160000361219e5782818151811061217657612176613e6a565b6020026020010151600001516040516338ee04a760e01b815260040161039791815260200190565b8281815181106121b0576121b0613e6a565b602002602001015160000151826000018181516121cd9190614640565b90525082518390829081106121e4576121e4613e6a565b6020026020010151602001518260200181815161220191906147ad565b905250600101612136565b6122146139e4565b8160008151811061222757612227613e6a565b60209081029190910101515181528151829060009061224857612248613e6a565b6020908102919091018101518101519082015260015b82518110156120d25761229083828151811061227c5761227c613e6a565b602090810291909101015151835190613264565b825282518390829081106122a6576122a6613e6a565b602002602001015160200151826020018181516122c391906147ad565b90525060010161225e565b6122d66139e4565b816000815181106122e9576122e9613e6a565b60209081029190910101515181528151829060009061230a5761230a613e6a565b6020908102919091018101518101519082015260015b82518110156120d25782818151811061233b5761233b613e6a565b6020026020010151600001516000036123605782818151811061217657612176613e6a565b61238983828151811061237557612375613e6a565b602090810291909101015151835190613283565b8252825183908290811061239f5761239f613e6a565b602002602001015160200151826020018181516123bc91906147ad565b905250600101612320565b6000826001826123d785426140a7565b90505b69ffffffffffffffffffff8716156124a3576001600160a01b038816639a6fc8f561240489614818565b6040516001600160e01b031960e084901b16815269ffffffffffffffffffff8216600482015290995060240160a060405180830381865afa925050508015612469575060408051601f3d908101601f1916820190925261246691810190614041565b60015b156124a357858210156124805750505050506124a3565b61248a848961456f565b97508661249681614834565b97505050505050506123da565b6124ac82612569565b6124b69084614640565b98975050505050505050565b60006124d261123683600a61484d565b6102b490846147e8565b60006124ec61123683600a61484d565b6102b49084614640565b6000667fffffffffffff66ffffffffffffff83161115612529576040516329d2678160e21b815260040160405180910390fd5b5090565b6000627fffff19600683900b128061254b5750627fffff600683900b135b1561252957604051630d962f7960e21b815260040160405180910390fd5b60006001600160ff1b038211156125295760405163677c430560e11b815260040160405180910390fd5b60008061259f86613298565b90506fffffffffffffffffffffffffffffffff6001600160a01b0382161161261c5760006125d66001600160a01b03831680614859565b9050836001600160a01b0316856001600160a01b03161061260557612600600160c01b87836136cd565b612614565b6126148187600160c01b6136cd565b92505061267d565b600061263b6001600160a01b03831680680100000000000000006136cd565b9050836001600160a01b0316856001600160a01b03161061266a57612665600160801b87836136cd565b612679565b6126798187600160801b6136cd565b9250505b50949350505050565b6000808212156125295760405163029f024d60e31b815260040160405180910390fd5b600080821215612529576126bc82614493565b610116565b6000805b8260400151518110156128415760006126fa846040015183815181106126ed576126ed613e6a565b6020026020010151610ab6565b60408051606081019091528154909190829060ff16600981111561272057612720613aa7565b600981111561273157612731613aa7565b815260200160018201805461274590613dc2565b80601f016020809104026020016040519081016040528092919081815260200182805461277190613dc2565b80156127be5780601f10612793576101008083540402835291602001916127be565b820191906000526020600020905b8154815290600101906020018083116127a157829003601f168201915b505050505081526020016002820180548060200260200160405190810160405280929190818152602001828054801561281657602002820191906000526020600020905b815481526020019060010190808311612802575b505050505081525050905061282a81611a8c565b612838575060009392505050565b506001016126c5565b50600192915050565b60006002826040015151101561286257506000919050565b81602001515160201461287757506000919050565b600082602001518060200190518101906128919190614627565b905060088111156128415750600092915050565b6000602082602001515110156128bd57506000919050565b600082602001518060200190518101906128d79190613ed3565b90506128ea816306e7ea3960e21b6138e2565b6128f75750600092915050565b604051633b70a5bf60e21b81526001600160a01b0382169063edc296fc90612923908690600401613b21565b6020604051808303816000875af1158015612942573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129669190614870565b6128415750600092915050565b6040810151516000901561298957506000919050565b81602001515160601461299e57506000919050565b60008083602001518060200190518101906129b99190613fe8565b92505091506000829050806001600160a01b031663feaf968c6040518163ffffffff1660e01b815260040160a060405180830381865afa158015612a01573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a259190614041565b5050505050806001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612a68573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612a8c919061488b565b60ff168260ff1614612aa357506000949350505050565b506001949350505050565b60408101515160009015612ac457506000919050565b81602001515160c014612ad957506000919050565b6000806000806000808760200151806020019051810190612afa91906140ba565b9550955095509550955095508360ff16866001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b48573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6c919061488b565b60ff1614612b8257506000979650505050505050565b8260ff16856001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015612bc4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612be8919061488b565b60ff1614612bfe57506000979650505050505050565b6000826001600160a01b0316630dfe16816040518163ffffffff1660e01b8152600401602060405180830381865afa158015612c3e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c629190613ed3565b90506000836001600160a01b031663d21220a76040518163ffffffff1660e01b8152600401602060405180830381865afa158015612ca4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612cc89190613ed3565b9050876001600160a01b0316826001600160a01b0316148015612cfc5750866001600160a01b0316816001600160a01b0316145b158015612d385750866001600160a01b0316826001600160a01b0316148015612d365750876001600160a01b0316816001600160a01b0316145b155b15612d4d575060009998505050505050505050565b60128660ff161180612d62575060128560ff16115b15612d77575060009998505050505050505050565b8263ffffffff16600003612d95575060009998505050505050505050565b6040805160028082526060820183526000926020830190803683370190505090508381600081518110612dca57612dca613e6a565b602002602001019063ffffffff16908163ffffffff1681525050600081600181518110612df957612df9613e6a565b63ffffffff9092166020928302919091019091015260405163883bdbfd60e01b81526001600160a01b0386169063883bdbfd90612e3a908490600401614143565b600060405180830381865afa158015612e57573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612e7f91908101906141f5565b5060019c9b505050505050505050505050565b60408101515160009015612ea857506000919050565b816020015151606014612ebd57506000919050565b60008060008460200151806020019051810190612eda91906144bf565b919450925090508281612f55576040516396834ad360e01b8152600481018490526001600160a01b038216906396834ad390602401608060405180830381865afa158015612f2c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612f5091906144f5565b612fbe565b604051639474f45b60e01b8152600481018490526001600160a01b03821690639474f45b90602401608060405180830381865afa158015612f9a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fbe91906144f5565b5060019695505050505050565b60408101515160009015612fe157506000919050565b816020015151606014612ff657506000919050565b8160200151806020019051810190612aa39190614597565b60008160400151516002148061302957508160400151516003145b61303557506000919050565b81602001515160201461304a57506000919050565b506001919050565b600081604001515160011480613029575081604001515160021461303557506000919050565b6040810151516000901561308e57506000919050565b6020826020015151101561304a57506000919050565b81818082036130b4575050505050565b6000856130da60026130c6888861446c565b6130d09190614640565b6112c0908861456f565b815181106130ea576130ea613e6a565b60200260200101516000015190505b818313613236575b808661310c85612686565b8151811061311c5761311c613e6a565b60200260200101516000015112156131405782613138816148a6565b935050613101565b8561314a83612686565b8151811061315a5761315a613e6a565b60200260200101516000015181121561317f5781613177816148be565b925050613140565b818313613231578561319083612686565b815181106131a0576131a0613e6a565b6020026020010151866131b285612686565b815181106131c2576131c2613e6a565b6020026020010151876131d486612686565b815181106131e4576131e4613e6a565b60200260200101886131f586612686565b8151811061320557613205613e6a565b602002602001018290528290525050828061321f906148a6565b935050818061322d906148be565b9250505b6130f9565b81851215613249576132498686846130a4565b8383121561325c5761325c8684866130a4565b505050505050565b6000670de0b6b3a764000061327983856147e8565b6102b49190614640565b600081613279670de0b6b3a7640000856147e8565b60008060008360020b126132b8576132b3600284900b612686565b6132c8565b6132c86112c0600285900b614493565b90506132e36112c06132dd620d89e7196148db565b60020b90565b8111156133165760405162461bcd60e51b81526020600482015260016024820152601560fa1b6044820152606401610397565b60008160011660000361332d57600160801b61333f565b6ffffcb933bd6fad37aa2d162d1a5940015b70ffffffffffffffffffffffffffffffffff169050600282161561337e576080613379826ffff97272373d413259a46990580e213a614859565b901c90505b60048216156133a85760806133a3826ffff2e50f5f656932ef12357cf3c7fdcc614859565b901c90505b60088216156133d25760806133cd826fffe5caca7e10e4e61c3624eaa0941cd0614859565b901c90505b60108216156133fc5760806133f7826fffcb9843d60f6159c9db58835c926644614859565b901c90505b6020821615613426576080613421826fff973b41fa98c081472e6896dfb254c0614859565b901c90505b604082161561345057608061344b826fff2ea16466c96a3843ec78b326b52861614859565b901c90505b608082161561347a576080613475826ffe5dee046a99a2a811c461f1969c3053614859565b901c90505b6101008216156134a55760806134a0826ffcbe86c7900a88aedcffc83b479aa3a4614859565b901c90505b6102008216156134d05760806134cb826ff987a7253ac413176f2b074cf7815e54614859565b901c90505b6104008216156134fb5760806134f6826ff3392b0822b70005940c7a398e4b70f3614859565b901c90505b610800821615613526576080613521826fe7159475a2c29b7443b29c7fa6e889d9614859565b901c90505b61100082161561355157608061354c826fd097f3bdfd2022b8845ad8f792aa5825614859565b901c90505b61200082161561357c576080613577826fa9f746462d870fdf8a65dc1f90e061e5614859565b901c90505b6140008216156135a75760806135a2826f70d869a156d2a1b890bb3df62baf32f7614859565b901c90505b6180008216156135d25760806135cd826f31be135f97d08fd981231505542fcfa6614859565b901c90505b620100008216156135fe5760806135f9826f09aa508b5b7a84e1c677de54f3e99bc9614859565b901c90505b62020000821615613629576080613624826e5d6af8dedb81196699c329225ee604614859565b901c90505b6204000082161561365357608061364e826d2216e584f5fa1ea926041bedfe98614859565b901c90505b6208000082161561367b576080613676826b048a170391f7dc42444e8fa2614859565b901c90505b60008460020b131561369657613693816000196147c0565b90505b6102ce6136a8640100000000836147d4565b156136b45760016136b7565b60005b6136c89060ff16602084901c6147ad565b6139ba565b6000808060001985870985870292508281108382030391505080600003613749576000841161373e5760405162461bcd60e51b815260206004820152601960248201527f48616e646c65206e6f6e2d6f766572666c6f77206361736573000000000000006044820152606401610397565b5082900490506102b4565b8084116137985760405162461bcd60e51b815260206004820152601960248201527f70726576656e74732064656e6f6d696e61746f72203d3d2030000000000000006044820152606401610397565b60008486880980840393811190920391905060006137d06137b887612569565b6137c188612569565b6137ca90614493565b16612686565b9586900495938490049360008190030460010190506137ef8184614859565b909317926000613800876003614859565b600218905061380f8188614859565b61381a9060026140a7565b6138249082614859565b90506138308188614859565b61383b9060026140a7565b6138459082614859565b90506138518188614859565b61385c9060026140a7565b6138669082614859565b90506138728188614859565b61387d9060026140a7565b6138879082614859565b90506138938188614859565b61389e9060026140a7565b6138a89082614859565b90506138b48188614859565b6138bf9060026140a7565b6138c99082614859565b90506138d58186614859565b9998505050505050505050565b604080516001600160e01b0319831660248083019190915282518083039091018152604490910182526020810180516001600160e01b03166301ffc9a760e01b1790529051600091829182916001600160a01b0387169161394391906148fd565b6000604051808303816000865af19150503d8060008114613980576040519150601f19603f3d011682016040523d82523d6000602084013e613985565b606091505b50915091508161399a57600092505050610116565b80516000036139ae57600092505050610116565b60200151949350505050565b60006001600160a01b038211156125295760405163dccde8ed60e01b815260040160405180910390fd5b6040518060800160405280600081526020016000815260200160008152602001600081525090565b828054828255906000526020600020908101928215613a47579160200282015b82811115613a47578251825591602001919060010190613a2c565b506125299291505b808211156125295760008155600101613a4f565b600060208284031215613a7557600080fd5b5035919050565b8151815260208083015190820152604080830151908201526060808301519082015260808101610116565b634e487b7160e01b600052602160045260246000fd5b600a8110613acd57613acd613aa7565b9052565b60005b83811015613aec578181015183820152602001613ad4565b50506000910152565b60008151808452613b0d816020860160208601613ad1565b601f01601f19169290920160200192915050565b60006020808352613b358184018551613abd565b8084015160606040850152613b4d6080850182613af5565b6040860151858203601f19016060870152805180835290840192506000918401905b80831015613b8f5783518252928401926001929092019190840190613b6f565b509695505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff81118282101715613bd357613bd3613b9a565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715613c0257613c02613b9a565b604052919050565b600067ffffffffffffffff821115613c2457613c24613b9a565b5060051b60200190565b600082601f830112613c3f57600080fd5b81356020613c54613c4f83613c0a565b613bd9565b8083825260208201915060208460051b870101935086841115613c7657600080fd5b602086015b84811015613b8f5780358352918301918301613c7b565b600080600060608486031215613ca757600080fd5b8335600a8110613cb657600080fd5b925060208481013567ffffffffffffffff80821115613cd457600080fd5b818701915087601f830112613ce857600080fd5b813581811115613cfa57613cfa613b9a565b613d0c601f8201601f19168501613bd9565b8181528985838601011115613d2057600080fd5b818585018683013760009181019094015291935060408601359180831115613d4757600080fd5b5050613d5586828701613c2e565b9150509250925092565b600080600060608486031215613d7457600080fd5b83359250602084013567ffffffffffffffff80821115613d9357600080fd5b613d9f87838801613c2e565b93506040860135915080821115613db557600080fd5b50613d5586828701613c2e565b600181811c90821680613dd657607f821691505b602082108103611d0757634e487b7160e01b600052602260045260246000fd5b60008151808452602080850194506020840160005b83811015613e2757815187529582019590820190600101613e0b565b509495945050505050565b848152613e426020820185613abd565b608060408201526000613e586080830185613af5565b828103606084015261189a8185613df6565b634e487b7160e01b600052603260045260246000fd5b600060208284031215613e9257600080fd5b8151600981106102b457600080fd5b6020810160098310613eb557613eb5613aa7565b91905290565b6001600160a01b0381168114613ed057600080fd5b50565b600060208284031215613ee557600080fd5b81516102b481613ebb565b608080825285518282018190526000919060209060a0850190828a01855b82811015613f5257613f42848351805182526020810151602083015260408101516040830152606081015160608301525050565b9285019290840190600101613f0e565b5050508481036020860152613f678189613af5565b925050508281036040840152613f7d8186613df6565b9050828103606084015261189a8185613df6565b600060808284031215613fa357600080fd5b613fab613bb0565b825181526020830151602082015260408301516040820152606083015160608201528091505092915050565b805160ff8116811461200457600080fd5b600080600060608486031215613ffd57600080fd5b835161400881613ebb565b6020850151909350915061401e60408501613fd7565b90509250925092565b805169ffffffffffffffffffff8116811461200457600080fd5b600080600080600060a0868803121561405957600080fd5b61406286614027565b945060208601519350604086015192506060860151915061408560808701614027565b90509295509295909350565b634e487b7160e01b600052601160045260246000fd5b8181038181111561011657610116614091565b60008060008060008060c087890312156140d357600080fd5b86516140de81613ebb565b60208801519096506140ef81613ebb565b94506140fd60408801613fd7565b935061410b60608801613fd7565b9250608087015161411b81613ebb565b60a088015190925063ffffffff8116811461413557600080fd5b809150509295509295509295565b6020808252825182820181905260009190848201906040850190845b8181101561418157835163ffffffff168352928401929184019160010161415f565b50909695505050505050565b600082601f83011261419e57600080fd5b815160206141ae613c4f83613c0a565b8083825260208201915060208460051b8701019350868411156141d057600080fd5b602086015b84811015613b8f5780516141e881613ebb565b83529183019183016141d5565b6000806040838503121561420857600080fd5b825167ffffffffffffffff8082111561422057600080fd5b818501915085601f83011261423457600080fd5b81516020614244613c4f83613c0a565b82815260059290921b8401810191818101908984111561426357600080fd5b948201945b838610156142915785518060060b81146142825760008081fd5b82529482019490820190614268565b918801519196509093505050808211156142aa57600080fd5b506142b78582860161418d565b9150509250929050565b600682810b9082900b03667fffffffffffff198112667fffffffffffff8213171561011657610116614091565b634e487b7160e01b600052601260045260246000fd5b60008160060b8360060b8061431b5761431b6142ee565b667fffffffffffff1982146000198214161561433957614339614091565b90059392505050565b600082614351576143516142ee565b500790565b60008160020b627fffff19810361436f5761436f614091565b6000190192915050565b600181815b808511156143b457816000190482111561439a5761439a614091565b808516156143a757918102915b93841c939080029061437e565b509250929050565b6000826143cb57506001610116565b816143d857506000610116565b81600181146143ee57600281146143f857614414565b6001915050610116565b60ff84111561440957614409614091565b50506001821b610116565b5060208310610133831016604e8410600b8410161715614437575081810a610116565b6144418383614379565b806000190482111561445557614455614091565b029392505050565b60006102b460ff8416836143bc565b818103600083128015838313168383128216171561448c5761448c614091565b5092915050565b6000600160ff1b82016144a8576144a8614091565b5060000390565b8051801515811461200457600080fd5b6000806000606084860312156144d457600080fd5b83516144df81613ebb565b6020850151909350915061401e604085016144af565b60006080828403121561450757600080fd5b61450f613bb0565b82518060070b811461452057600080fd5b8152602083015167ffffffffffffffff8116811461453d57600080fd5b60208201526040830151600381900b811461455757600080fd5b60408201526060928301519281019290925250919050565b808201828112600083128015821682158216171561458f5761458f614091565b505092915050565b6000806000606084860312156145ac57600080fd5b83516145b781613ebb565b602085015160409095015190969495509392505050565b60ff8416815267ffffffffffffffff831660208201526060604082015260006145fa6060830184613df6565b95945050505050565b6001600160a01b03831681526040602082018190526000906102ce90830184613af5565b60006020828403121561463957600080fd5b5051919050565b60008261464f5761464f6142ee565b600160ff1b82146000198414161561466957614669614091565b500590565b6146788185613abd565b60606020820152600061468e6060830185613af5565b8281036040840152610f4f8185613df6565b601f8211156146e8576000816000526020600020601f850160051c810160208610156146c95750805b601f850160051c820191505b8181101561325c578281556001016146d5565b505050565b815167ffffffffffffffff81111561470757614707613b9a565b61471b816147158454613dc2565b846146a0565b602080601f83116001811461475057600084156147385750858301515b600019600386901b1c1916600185901b17855561325c565b600085815260208120601f198616915b8281101561477f57888601518255948401946001909101908401614760565b508582101561479d5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b8082018082111561011657610116614091565b6000826147cf576147cf6142ee565b500490565b6000826147e3576147e36142ee565b500690565b80820260008212600160ff1b8414161561480457614804614091565b818105831482151761011657610116614091565b600069ffffffffffffffffffff82168061436f5761436f614091565b60006001820161484657614846614091565b5060010190565b60006102b483836143bc565b808202811582820484141761011657610116614091565b60006020828403121561488257600080fd5b6102b4826144af565b60006020828403121561489d57600080fd5b6102b482613fd7565b60006001600160ff1b01820161484657614846614091565b6000600160ff1b82016148d3576148d3614091565b506000190190565b60008160020b627fffff1981036148f4576148f4614091565b60000392915050565b6000825161490f818460208701613ad1565b919091019291505056fea264697066735822122074f32fef384fdc296b0859f1c1f941c8e736c6cb972aa9e2b894956ebd6a80b364736f6c63430008160033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xbc73db80bf4b8784ba10a8910a0b7ef85f6846d102b41dd990969ea205335354"}}],"ommers":[]},{"header":{"parentHash":"0x026ae0c6ae91f186a9befa1ac8be30eea35e30e77de51a731085221e5cd39209","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x6e4969a136061ca7a390d12830d47a151585325a8d396819fb2b958ff85e9f8f","receiptsRoot":"0xc3e81df67d3e2a6c8345a954ef250cfcc41abcc2292a5aa263071124533fc9ad","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x3c0f6","timestamp":"0x66b200ce","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x18993a68","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3c0f6","maxFeePerGas":"0x5d4285cd","maxPriorityFeePerGas":"0x3b9aca00","value":"0x0","accessList":[],"input":"0x608060405234801561001057600080fd5b50610380806100206000396000f3fe6080604052600080357fffffffff0000000000000000000000000000000000000000000000000000000016905060008160e01c610251565b60006379ba509782101561015e5781631627540c811461009857632a952b2d81146100b457633659cfe681146100d0576350c946fe81146100ec576353a47bb781146101085763625ca21c81146101245763718fe928811461014057610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc9150610158565b738138ef7cf908021d117e542120b7a390650161079150610158565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc91505b5061024c565b816379ba509781146101a657638da5cb5b81146101c25763aaf10f4281146101de5763c7f62cda81146101fa5763daa250be81146102165763deba1b9881146102325761024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b7347d08dad17ccb558b3ea74b1a0e73a9cc804a9dc915061024a565b738138ef7cf908021d117e542120b7a39065016107915061024a565b738138ef7cf908021d117e542120b7a3906501610791505b505b919050565b61025a81610037565b915050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036102ce57816040517fc2a825f50000000000000000000000000000000000000000000000000000000081526004016102c5919061032f565b60405180910390fd5b3660008037600080366000845af43d6000803e80600081146102ef573d6000f35b3d6000fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610329816102f4565b82525050565b60006020820190506103446000830184610320565b9291505056fea264697066735822122017a4b7fdaaab3897a7b47abaed8d2ee92d558883d3bb2a8454f9601b2ab2c3db64736f6c63430008150033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x2476e039803622aeb040f924f04c493f559aed3d6c9372ab405cb33c8c695328"}}],"ommers":[]},{"header":{"parentHash":"0x3d22100ac0ee8d5cde334f7f926191a861b0648971ebc179547df28a0224c6d0","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9511d4711e5c30a72b0bff38a261daa75dcc5ba8b772d970a5c742244b4c861b","transactionsRoot":"0xba5fff578d3d6c2cd63acbe9bca353eaa6fe22a5c408956eff49106e0a96c507","receiptsRoot":"0xbae111f01cb07677e3a8c5031546138407c01bc964d3493d732dc4edf47d36d3","logsBloom":"0x00000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000020000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000001000000000000000000000400000001000010000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x5","gasLimit":"0x1c9c380","gasUsed":"0xcae7","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x12e09c7a","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xcc4d","maxFeePerGas":"0x557e5ec4","maxPriorityFeePerGas":"0x3b9aca00","to":"0x83a0444b93927c3afcbe46e522280390f748e171","value":"0x0","accessList":[],"input":"0x3659cfe6000000000000000000000000108f53faf774d7c4c56f5bce9ca6e605ce8aeadd","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xf88e7b19ee347145c257e0cf7ac4ecc2bae83ca79d7edaa231e71d3213aeb151"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x9c8eaf493f8b4edce2ba1647343eadcc0989cf461e712c0a6253ff2ca1842bb7","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xdd07c07470e1deff3749831f0f1ad8d4b6e35505e83b3c6ea14181716197cd8a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x24a1ab52","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200c9","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xf6930be4847cac5017bbcbec2756eed19f36b4196526a98a88e311c296e3a9be","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x29aa352e71b139e83b397bdd3dcf9b65d74770edaf3a9624d0dbc4f96f868680","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200cc","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x200d75e8","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xb6003e7ba07a15a9e35f63daa484728ec4ceeded0c4d10ac1b04e9552d412b3c","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x4","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1592fbf9","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x149d41e3b89d8324cef3feff98ef308e97bafe8745cc8461c60172bc7d4c44ba","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x0b44110186e52ff0ceb6b0776ca2992c94144a4ed712eef65ea038260ef0fcc7","receiptsRoot":"0xc2823b8eb4730d9f2657137cc2ddc2c4f22ab68e0ab826236cf6a1551ca2b3a5","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0xe61f9","timestamp":"0x66b200cb","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342770c0","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0xe94d1","maxFeePerGas":"0x83215600","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060008061002661006d60201b61081b1760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610141565b60008060405160200161007f90610121565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b600061010b60238361009e565b9150610116826100af565b604082019050919050565b6000602082019050818103600083015261013a816100fe565b9050919050565b611000806101506000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806379ba50971161005b57806379ba5097146100ed5780638da5cb5b146100f7578063aaf10f4214610115578063c7f62cda1461013357610088565b80631627540c1461008d5780633659cfe6146100a957806353a47bb7146100c5578063718fe928146100e3575b600080fd5b6100a760048036038101906100a29190610d25565b61014f565b005b6100c360048036038101906100be9190610d25565b6102d0565b005b6100cd6102e4565b6040516100da9190610d61565b60405180910390f35b6100eb610317565b005b6100f56103fe565b005b6100ff61058b565b60405161010c9190610d61565b60405180910390f35b61011d6105be565b60405161012a9190610d61565b60405180910390f35b61014d60048036038101906101489190610d25565b6105f1565b005b61015761084c565b600061016161081b565b9050600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036101c9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610252576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce22826040516102c49190610d61565b60405180910390a15050565b6102d861084c565b6102e1816108c5565b50565b60006102ee61081b565b60010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600061032161081b565b90503373ffffffffffffffffffffffffffffffffffffffff168160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146103b757336040517fa0e5a0d70000000000000000000000000000000000000000000000000000000081526004016103ae9190610d61565b60405180910390fd5b60008160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600061040861081b565b905060008160010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146104a357336040517fa0e5a0d700000000000000000000000000000000000000000000000000000000815260040161049a9190610d61565b60405180910390fd5b7fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c8260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16826040516104f8929190610d7c565b60405180910390a1808260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008260010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505050565b600061059561081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105c8610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60006105fb610b05565b905060018160000160146101000a81548160ff02191690831515021790555060008160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050828260000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008373ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff16633659cfe6846040516024016106cc9190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161071b9190610e16565b600060405180830381855af49150503d8060008114610756576040519150601f19603f3d011682016040523d82523d6000602084013e61075b565b606091505b505090508015806107c357508173ffffffffffffffffffffffffffffffffffffffff16610786610b05565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b156107fa576040517fa1cfa5a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008360000160146101000a81548160ff0219169083151502179055600080fd5b60008060405160200161082d90610eb0565b6040516020818303038152906040528051906020012090508091505090565b610854610b36565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146108c357336040517f8e4a23d60000000000000000000000000000000000000000000000000000000081526004016108ba9190610d61565b60405180910390fd5b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361092b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093481610b69565b61097557806040517f8a8b41ec00000000000000000000000000000000000000000000000000000000815260040161096c9190610d61565b60405180910390fd5b600061097f610b05565b90508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610a0a576040517fa88ee57700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060000160149054906101000a900460ff16158015610a2e5750610a2d82610b7c565b5b15610a7057816040517f15504301000000000000000000000000000000000000000000000000000000008152600401610a679190610d61565b60405180910390fd5b818160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055503073ffffffffffffffffffffffffffffffffffffffff167f5d611f318680d00598bb735d61bacf0c514c6b50e1e5ad30040a4df2b12791c783604051610af99190610d61565b60405180910390a25050565b600080604051602001610b1790610f42565b6040516020818303038152906040528051906020012090508091505090565b6000610b4061081b565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080823b905060008111915050919050565b60008060003073ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff1663c7f62cda86604051602401610bc59190610d61565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c149190610e16565b600060405180830381855af49150503d8060008114610c4f576040519150601f19603f3d011682016040523d82523d6000602084013e610c54565b606091505b509150915081158015610cb9575063a1cfa5a860e01b604051602001610c7a9190610faf565b6040516020818303038152906040528051906020012081604051602001610ca19190610e16565b60405160208183030381529060405280519060200120145b92505050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610cf282610cc7565b9050919050565b610d0281610ce7565b8114610d0d57600080fd5b50565b600081359050610d1f81610cf9565b92915050565b600060208284031215610d3b57610d3a610cc2565b5b6000610d4984828501610d10565b91505092915050565b610d5b81610ce7565b82525050565b6000602082019050610d766000830184610d52565b92915050565b6000604082019050610d916000830185610d52565b610d9e6020830184610d52565b9392505050565b600081519050919050565b600081905092915050565b60005b83811015610dd9578082015181840152602081019050610dbe565b60008484015250505050565b6000610df082610da5565b610dfa8185610db0565b9350610e0a818560208601610dbb565b80840191505092915050565b6000610e228284610de5565b915081905092915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b6000610e9a602383610e2d565b9150610ea582610e3e565b604082019050919050565b60006020820190508181036000830152610ec981610e8d565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b6000610f2c602183610e2d565b9150610f3782610ed0565b604082019050919050565b60006020820190508181036000830152610f5b81610f1f565b9050919050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b610fa9610fa482610f62565b610f8e565b82525050565b6000610fbb8284610f98565b6004820191508190509291505056fea264697066735822122023a7c33d7b91dce35ffbcf8837693364ab22a3905d0fc00016833e5fac45ca2f64736f6c63430008110033","r":"0x1","s":"0x1","yParity":"0x0","hash":"0x4feae6769d748b4f0f7c9bf21d782236c88f13906789a3ec602961296e4c3e43"}}],"ommers":[]},{"header":{"parentHash":"0xb3535af5103fd1c2bbd6dc7ff23f0799037a6542c231ebcb85abd776560fa512","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x23d74fb99ff6e42cbb5c33f92b078e37be6af2b6092459b103ff7059a6517ebc","transactionsRoot":"0x9eab45eca206fe11c107ea985c7d02fcfa442836aea3e04ba11dc4df587d5aa6","receiptsRoot":"0xe25abcfa973db8c55f73292137c626430de130a382ad4466337fefb0f7c8fde0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x3ce3f","timestamp":"0x66b200cd","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x1c0bc72b","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x343a","nonce":"0x0","gas":"0x3d8a8","maxFeePerGas":"0x6211577c","maxPriorityFeePerGas":"0x3b9aca00","to":"0x4e59b44847b379578588920ca78fbf26c0b4956c","value":"0x0","accessList":[],"input":"0x4786e4342646b3ba97c1790b6cf5a55087a36240b22570f5d3a5d6bcc929d93b608060405234801561001057600080fd5b5060405161068538038061068583398181016040528101906100329190610275565b818181600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361009b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6100ae8161019d60201b61004f1760201c565b6100ef57806040517f8a8b41ec0000000000000000000000000000000000000000000000000000000081526004016100e691906102c4565b60405180910390fd5b806100fe6101b060201b60201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050806101536101e160201b6100621760201c565b60000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050505050610414565b600080823b905060008111915050919050565b6000806040516020016101c290610362565b6040516020818303038152906040528051906020012090508091505090565b6000806040516020016101f3906103f4565b6040516020818303038152906040528051906020012090508091505090565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061024282610217565b9050919050565b61025281610237565b811461025d57600080fd5b50565b60008151905061026f81610249565b92915050565b6000806040838503121561028c5761028b610212565b5b600061029a85828601610260565b92505060206102ab85828601610260565b9150509250929050565b6102be81610237565b82525050565b60006020820190506102d960008301846102b5565b92915050565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b600061034c6021836102df565b9150610357826102f0565b604082019050919050565b6000602082019050818103600083015261037b8161033f565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006103de6023836102df565b91506103e982610382565b604082019050919050565b6000602082019050818103600083015261040d816103d1565b9050919050565b610262806104236000396000f3fe6080604052366100135761001161001d565b005b61001b61001d565b005b6000610027610093565b90503660008037600080366000845af43d6000803e806000811461004a573d6000f35b3d6000fd5b600080823b905060008111915050919050565b6000806040516020016100749061017a565b6040516020818303038152906040528051906020012090508091505090565b600061009d6100c6565b60000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000806040516020016100d89061020c565b6040516020818303038152906040528051906020012090508091505090565b600082825260208201905092915050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e4f776e6160008201527f626c650000000000000000000000000000000000000000000000000000000000602082015250565b60006101646023836100f7565b915061016f82610108565b604082019050919050565b6000602082019050818103600083015261019381610157565b9050919050565b7f696f2e73796e7468657469782e636f72652d636f6e7472616374732e50726f7860008201527f7900000000000000000000000000000000000000000000000000000000000000602082015250565b60006101f66021836100f7565b91506102018261019a565b604082019050919050565b60006020820190508181036000830152610225816101e9565b905091905056fea2646970667358221220800da1f73cebd5e4afa07496d9bca6b6c4f526bdd3f4014ec15c70fe3a1c441364736f6c6343000811003300000000000000000000000047d08dad17ccb558b3ea74b1a0e73a9cc804a9dc000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266","r":"0x1","s":"0x1","yParity":"0x0","hash":"0xb6794d5c7abed6f91d447e8efb72ef2580595a6d7c8dee57ba1dbb330970146a"}}],"ommers":[]},{"header":{"parentHash":"0x08abe6e453727534d8dd708843a7522b7d500338bdfe2402ca105dcdb05eebe9","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x510f2275449c013534a25ad0b13c867caf720947b68bcbcd4863f7b172a5d023","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x3","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66b200ca","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x29dd5614","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]}]} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump-legacy.json b/crates/anvil/test-data/state-dump-legacy.json new file mode 100644 index 0000000000000..273442701292e --- /dev/null +++ b/crates/anvil/test-data/state-dump-legacy.json @@ -0,0 +1 @@ +{"block":{"number":"0x2","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x66cdc823","gas_limit":"0x1c9c380","basefee":"0x342a1c58","difficulty":"0x0","prevrandao":"0xb92480171c0235f8c6710a4047d7ee14a3be58c630839fb4422826ff3a013e44","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":"0x2","blocks":[{"header":{"parentHash":"0xceb0fe420d6f14a8eeec4319515b89acbb0bb4861cad9983d529ab4b1e4af929","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc823","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}}],"ommers":[]},{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdc80e","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0xa00dc0c9ee9a888e67ea32d8772f8cc28eff62448c9ec985ee941fcbc921ba59","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdc814","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}}],"ommers":[]}]} \ No newline at end of file diff --git a/crates/anvil/test-data/state-dump.json b/crates/anvil/test-data/state-dump.json new file mode 100644 index 0000000000000..e868bf2efea51 --- /dev/null +++ b/crates/anvil/test-data/state-dump.json @@ -0,0 +1 @@ +{"block":{"number":"0x2","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x66cdcc2b","gas_limit":"0x1c9c380","basefee":"0x342a1c58","difficulty":"0x0","prevrandao":"0xdb639d7f8af4f0ff2aa9cc49861820e72f5f8bfeeed677d1e3569f6b1625df4a","blob_excess_gas_and_price":{"excess_blob_gas":0,"blob_gasprice":1}},"accounts":{"0x0000000000000000000000000000000000000000":{"nonce":0,"balance":"0xa410","code":"0x","storage":{}},"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x4e59b44847b379578588920ca78fbf26c0b4956c":{"nonce":0,"balance":"0x0","code":"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3","storage":{}},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"nonce":1,"balance":"0x21e19e0b90393da9b38","code":"0x","storage":{}},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"nonce":0,"balance":"0x21e19e0c9bab2400000","code":"0x","storage":{}},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"nonce":1,"balance":"0x21e19e0b6a140b55df8","code":"0x","storage":{}}},"best_block_number":"0x2","blocks":[{"header":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x0","gasLimit":"0x1c9c380","gasUsed":"0x0","timestamp":"0x66cdcc25","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[],"ommers":[]},{"header":{"parentHash":"0x3a52101c98a4319c419681131d3585d70a6cf13a9af25136be20d451eed5480a","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x6e5f60b37eeaece7dedfc42cc394731a0ae3ed3d3be93c402780b2e23e141175","transactionsRoot":"0x9ceaeb1b16b924afbf4bf4df4c2c49dc9cfbe23ac7a40bf26a704158ea2d352f","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x1","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc29","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x3b9aca00","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","value":"0x0","accessList":[],"input":"0x","r":"0x703a4b4d6dbff2fa2345df73263df2098faa7214863b5ec82c4c07162d87b853","s":"0x17dea762c4ce600ad1d9d2c9ae6dd35b9e526d03c875f868ad0792fd4fad72e0","yParity":"0x0","hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3"}},"impersonated_sender":null}],"ommers":[]},{"header":{"parentHash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xe1423fd180478ab4fd05a7103277d64496b15eb914ecafe71eeec871b552efd1","transactionsRoot":"0x2b5598ef261e5f88e4303bb2b3986b3d5c0ebf4cd9977daebccae82a6469b988","receiptsRoot":"0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x0","number":"0x2","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x66cdcc2b","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":"0x342a1c58","blobGasUsed":"0x0","excessBlobGas":"0x0","extraData":"0x"},"transactions":[{"transaction":{"EIP1559":{"chainId":"0x7a69","nonce":"0x0","gas":"0x5209","maxFeePerGas":"0x77359401","maxPriorityFeePerGas":"0x1","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","value":"0x0","accessList":[],"input":"0x","r":"0x85c2794a580da137e24ccc823b45ae5cea99371ae23ee13860fcc6935f8305b0","s":"0x41de7fa4121dab284af4453d30928241208bafa90cdb701fe9bc7054759fe3cd","yParity":"0x0","hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515"}},"impersonated_sender":null}],"ommers":[]}],"transactions":[{"info":{"transaction_hash":"0xf8d5fb22350f52ae8c30cd7f6969eb73de849c8dc010f4215d4c5c24824fe2b3","transaction_index":0,"from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x0d575f9ca968cd483549172245483a12343afc3cabef80f0fa39855b10b98c70","block_number":1},{"info":{"transaction_hash":"0x8c9b68e8947ace33028dba167354fde369ed7bbe34911b772d09b3c64b861515","transaction_index":0,"from":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","to":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","contract_address":null,"traces":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x70997970c51812dc3a010c7d01b50e0d17dc79c8","address":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","maybe_precompile":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":0,"gas_limit":1,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}],"exit":"Stop","out":"0x","nonce":0,"gas_used":21000},"receipt":{"type":"0x2","status":"0x1","cumulativeGasUsed":"0x5208","logs":[],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},"block_hash":"0x1f435a603c1bf6d544a90156b572b96d7a1730028422d800839bae78bb3506d0","block_number":2}]} \ No newline at end of file diff --git a/crates/anvil/test-data/storage_sample.json b/crates/anvil/test-data/storage_sample.json new file mode 100644 index 0000000000000..5241cb08e8f77 --- /dev/null +++ b/crates/anvil/test-data/storage_sample.json @@ -0,0 +1 @@ +{"0x0000000000000000000000000000000000000000000000000000000000000022":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","0x0000000000000000000000000000000000000000000000000000000000000023":"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71","0x0000000000000000000000000000000000000000000000000000000000000024":"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c","0x0000000000000000000000000000000000000000000000000000000000000025":"0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c","0x0000000000000000000000000000000000000000000000000000000000000026":"0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30","0x0000000000000000000000000000000000000000000000000000000000000027":"0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1","0x0000000000000000000000000000000000000000000000000000000000000028":"0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c","0x0000000000000000000000000000000000000000000000000000000000000029":"0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193","0x000000000000000000000000000000000000000000000000000000000000002a":"0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1","0x000000000000000000000000000000000000000000000000000000000000002b":"0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b","0x000000000000000000000000000000000000000000000000000000000000002c":"0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220","0x000000000000000000000000000000000000000000000000000000000000002d":"0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f","0x000000000000000000000000000000000000000000000000000000000000002e":"0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e","0x000000000000000000000000000000000000000000000000000000000000002f":"0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784","0x0000000000000000000000000000000000000000000000000000000000000030":"0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb","0x0000000000000000000000000000000000000000000000000000000000000031":"0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb","0x0000000000000000000000000000000000000000000000000000000000000032":"0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab","0x0000000000000000000000000000000000000000000000000000000000000033":"0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4","0x0000000000000000000000000000000000000000000000000000000000000034":"0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f","0x0000000000000000000000000000000000000000000000000000000000000035":"0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa","0x0000000000000000000000000000000000000000000000000000000000000036":"0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c","0x0000000000000000000000000000000000000000000000000000000000000037":"0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167","0x0000000000000000000000000000000000000000000000000000000000000038":"0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7","0x0000000000000000000000000000000000000000000000000000000000000039":"0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0","0x000000000000000000000000000000000000000000000000000000000000003a":"0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544","0x000000000000000000000000000000000000000000000000000000000000003b":"0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765","0x000000000000000000000000000000000000000000000000000000000000003c":"0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4","0x000000000000000000000000000000000000000000000000000000000000003d":"0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1","0x000000000000000000000000000000000000000000000000000000000000003e":"0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636","0x000000000000000000000000000000000000000000000000000000000000003f":"0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c","0x0000000000000000000000000000000000000000000000000000000000000040":"0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7"} \ No newline at end of file diff --git a/crates/anvil/tests/it/abi.rs b/crates/anvil/tests/it/abi.rs index c9cb0d7b1d818..9bd3b84e9d934 100644 --- a/crates/anvil/tests/it/abi.rs +++ b/crates/anvil/tests/it/abi.rs @@ -1,67 +1,73 @@ -//! commonly used abigen generated types +//! commonly used sol generated types +use alloy_sol_types::sol; -use ethers::{ - contract::{abigen, EthEvent}, - types::Address, -}; +sol!( + #[sol(rpc)] + Greeter, + "test-data/greeter.json" +); -#[derive(Clone, Debug, EthEvent)] -pub struct ValueChanged { - #[ethevent(indexed)] - pub old_author: Address, - #[ethevent(indexed)] - pub new_author: Address, - pub old_value: String, - pub new_value: String, -} +sol!( + #[derive(Debug)] + #[sol(rpc)] + SimpleStorage, + "test-data/SimpleStorage.json" +); -abigen!(Greeter, "test-data/greeter.json"); -abigen!(SimpleStorage, "test-data/SimpleStorage.json"); -abigen!(MulticallContract, "test-data/multicall.json"); -abigen!( - Erc721, - r#"[ - balanceOf(address)(uint256) - ownerOf(uint256)(address) - name()(string) - symbol()(string) - tokenURI(uint256)(string) - getApproved(uint256)(address) - setApprovalForAll(address,bool) - isApprovedForAll(address,address) - transferFrom(address,address,uint256) - safeTransferFrom(address,address,uint256,bytes) - _transfer(address,address,uint256) - _approve(address, uint256) - _burn(uint256) - _safeMint(address,uint256,bytes) - _mint(address,uint256) - _exists(uint256)(bool) -]"# +sol!( + #[sol(rpc)] + Multicall, + "test-data/multicall.json" ); -abigen!( - BUSD, - r#"[ - balanceOf(address)(uint256) -]"# + +sol!( + #[sol(rpc)] + contract BUSD { + function balanceOf(address) external view returns (uint256); + } ); -// -pub(crate) const VENDING_MACHINE_CONTRACT: &str = r#"// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.13; +sol!( + #[sol(rpc)] + interface ERC721 { + function balanceOf(address owner) public view virtual returns (uint256); + function ownerOf(uint256 tokenId) public view virtual returns (address); + function name() public view virtual returns (string memory); + function symbol() public view virtual returns (string memory); + function tokenURI(uint256 tokenId) public view virtual returns (string memory); + function getApproved(uint256 tokenId) public view virtual returns (address); + function setApprovalForAll(address operator, bool approved) public virtual; + function isApprovedForAll(address owner, address operator) public view virtual returns (bool); + function transferFrom(address from, address to, uint256 tokenId) public virtual; + function safeTransferFrom(address from, address to, uint256 tokenId) public; + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual; + function _mint(address to, uint256 tokenId) internal; + function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual; + function _burn(uint256 tokenId) internal; + function _transfer(address from, address to, uint256 tokenId) internal; + function _approve(address to, uint256 tokenId, address auth) internal; + } +); + +// https://docs.soliditylang.org/en/latest/control-structures.html#revert +sol!( +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.4; +#[sol(rpc, bytecode = "6080806040523460155761011e908161001a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c9081633ccfd60b146094575063d96a094a14602f575f80fd5b6020366003190112609057671bc16d674ec80000340460043511604e57005b60405162461bcd60e51b815260206004820152601a6024820152792737ba1032b737bab3b41022ba3432b910383937bb34b232b21760311b6044820152606490fd5b5f80fd5b346090575f3660031901126090575f546001600160a01b0316330360da575f8080804781811560d2575b3390f11560c757005b6040513d5f823e3d90fd5b506108fc60be565b6282b42960e81b8152600490fdfea2646970667358221220c143fcbf0da5cee61ae3fcc385d9f7c4d6a7fb2ea42530d70d6049478db0b8a964736f6c63430008190033")] contract VendingMachine { address owner; error Unauthorized(); - function buyRevert(uint amount) public payable { + #[derive(Debug)] + function buy(uint amount) public payable { if (amount > msg.value / 2 ether) revert("Not enough Ether provided."); - } - function buyRequire(uint amount) public payable { + // Alternative way to do it: require( amount <= msg.value / 2 ether, "Not enough Ether provided." ); + // Perform the purchase. } function withdraw() public { if (msg.sender != owner) @@ -69,4 +75,5 @@ contract VendingMachine { payable(msg.sender).transfer(address(this).balance); } -}"#; +} +); diff --git a/crates/anvil/tests/it/anvil.rs b/crates/anvil/tests/it/anvil.rs index 07d8e3f967121..65eeac70baf77 100644 --- a/crates/anvil/tests/it/anvil.rs +++ b/crates/anvil/tests/it/anvil.rs @@ -1,7 +1,10 @@ //! tests for anvil specific logic -use anvil::{spawn, NodeConfig}; -use ethers::{prelude::Middleware, types::Address}; +use alloy_consensus::EMPTY_ROOT_HASH; +use alloy_eips::BlockNumberOrTag; +use alloy_primitives::Address; +use alloy_provider::Provider; +use anvil::{spawn, EthereumHardfork, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { @@ -9,27 +12,30 @@ async fn test_can_change_mining_mode() { let provider = handle.http_provider(); assert!(api.anvil_get_auto_mine().unwrap()); + assert!(api.anvil_get_interval_mining().unwrap().is_none()); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0); + assert_eq!(num, 0); api.anvil_set_interval_mining(1).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); + assert!(matches!(api.anvil_get_interval_mining().unwrap(), Some(1))); // changing the mining mode will instantly mine a new block tokio::time::sleep(std::time::Duration::from_millis(500)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0); + assert_eq!(num, 0); tokio::time::sleep(std::time::Duration::from_millis(700)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 1); + assert_eq!(num, 1); // assert that no block is mined when the interval is set to 0 api.anvil_set_interval_mining(0).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); + assert!(api.anvil_get_interval_mining().unwrap().is_none()); tokio::time::sleep(std::time::Duration::from_millis(1000)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 1); + assert_eq!(num, 1); } #[tokio::test(flavor = "multi_thread")] @@ -39,6 +45,7 @@ async fn can_get_default_dev_keys() { let dev_accounts = handle.dev_accounts().collect::>(); let accounts = provider.get_accounts().await.unwrap(); + assert_eq!(dev_accounts, accounts); } @@ -58,7 +65,10 @@ async fn test_can_set_genesis_timestamp() { spawn(NodeConfig::test().with_genesis_timestamp(genesis_timestamp.into())).await; let provider = handle.http_provider(); - assert_eq!(genesis_timestamp, provider.get_block(0).await.unwrap().unwrap().timestamp.as_u64()); + assert_eq!( + genesis_timestamp, + provider.get_block(0.into(), false.into()).await.unwrap().unwrap().header.timestamp + ); } #[tokio::test(flavor = "multi_thread")] @@ -66,5 +76,45 @@ async fn test_can_use_default_genesis_timestamp() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - assert_ne!(0u64, provider.get_block(0).await.unwrap().unwrap().timestamp.as_u64()); + assert_ne!( + 0u64, + provider.get_block(0.into(), false.into()).await.unwrap().unwrap().header.timestamp + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_can_handle_large_timestamp() { + let (api, _handle) = spawn(NodeConfig::test()).await; + let num = 317071597274; + api.evm_set_next_block_timestamp(num).unwrap(); + api.mine_one().await; + + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, num); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_shanghai_fields() { + let (api, _handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Shanghai.into()))).await; + api.mine_one().await; + + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block.header.withdrawals_root, Some(EMPTY_ROOT_HASH)); + assert_eq!(block.withdrawals, Some(Default::default())); + assert!(block.header.blob_gas_used.is_none()); + assert!(block.header.excess_blob_gas.is_none()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cancun_fields() { + let (api, _handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into()))).await; + api.mine_one().await; + + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block.header.withdrawals_root, Some(EMPTY_ROOT_HASH)); + assert_eq!(block.withdrawals, Some(Default::default())); + assert!(block.header.blob_gas_used.is_some()); + assert!(block.header.excess_blob_gas.is_some()); } diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index a4213dc495d5b..af37bd64e6a37 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -1,46 +1,63 @@ //! tests for custom anvil endpoints -use crate::{abi::*, fork::fork_config}; -use anvil::{spawn, Hardfork, NodeConfig}; -use anvil_core::{ - eth::EthRequest, - types::{NodeEnvironment, NodeForkConfig, NodeInfo}, + +use crate::{ + abi::{self, Greeter, Multicall, BUSD}, + fork::fork_config, + utils::http_provider_with_signer, +}; +use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; +use alloy_primitives::{address, fixed_bytes, utils::Unit, Address, Bytes, TxKind, U256}; +use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_rpc_types::{ + anvil::{ + ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, + }, + BlockId, BlockNumberOrTag, BlockTransactionsKind, TransactionRequest, }; -use ethers::{ - abi::{ethereum_types::BigEndianHash, AbiDecode}, - prelude::{Middleware, SignerMiddleware}, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest, - TransactionRequest, H256, U256, U64, +use alloy_serde::WithOtherFields; +use anvil::{ + eth::{ + api::CLIENT_VERSION, + backend::mem::{EXECUTOR, P256_DELEGATION_CONTRACT, P256_DELEGATION_RUNTIME_CODE}, }, - utils::hex, + spawn, EthereumHardfork, NodeConfig, +}; +use anvil_core::{ + eth::{ + wallet::{Capabilities, DelegationCapability, WalletCapabilities}, + EthRequest, + }, + types::{ReorgOptions, TransactionData}, }; use foundry_evm::revm::primitives::SpecId; use std::{ str::FromStr, - sync::Arc, time::{Duration, SystemTime}, }; #[tokio::test(flavor = "multi_thread")] async fn can_set_gas_price() { - let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; + let (api, handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; let provider = handle.http_provider(); - let gas_price = 1337u64.into(); + let gas_price = U256::from(1337); api.anvil_set_min_gas_price(gas_price).await.unwrap(); - assert_eq!(gas_price, provider.get_gas_price().await.unwrap()); + assert_eq!(gas_price.to::(), provider.get_gas_price().await.unwrap()); } #[tokio::test(flavor = "multi_thread")] async fn can_set_block_gas_limit() { - let (api, _) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; + let (api, _) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; - let block_gas_limit = 1337u64.into(); + let block_gas_limit = U256::from(1337); assert!(api.evm_set_block_gas_limit(block_gas_limit).unwrap()); // Mine a new block, and check the new block gas limit api.mine_one().await; - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block_gas_limit, latest_block.gas_limit); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block_gas_limit.to::(), latest_block.header.gas_limit); } // Ref @@ -58,17 +75,17 @@ async fn can_set_storage() { let storage_value = api.storage_at(addr, slot, None).await.unwrap(); assert_eq!(val, storage_value); - assert_eq!(val, H256::from_uint(&U256::from(12345))); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(impersonate, funding).await.unwrap(); @@ -76,35 +93,38 @@ async fn can_impersonate_account() { let balance = api.balance(impersonate, None).await.unwrap(); assert_eq!(balance, funding); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_impersonate_account(impersonate).await.unwrap(); + assert!(api.accounts().unwrap().contains(&impersonate)); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); - let nonce = provider.get_transaction_count(impersonate, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate).await.unwrap(); + assert_eq!(nonce, 1); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); api.anvil_stop_impersonating_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); } #[tokio::test(flavor = "multi_thread")] async fn can_auto_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(impersonate, funding).await.unwrap(); @@ -112,68 +132,69 @@ async fn can_auto_impersonate_account() { let balance = api.balance(impersonate, None).await.unwrap(); assert_eq!(balance, funding); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_auto_impersonate_account(true).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); - let nonce = provider.get_transaction_count(impersonate, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate).await.unwrap(); + assert_eq!(nonce, 1); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); api.anvil_auto_impersonate_account(false).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); + + // explicitly impersonated accounts get returned by `eth_accounts` + api.anvil_impersonate_account(impersonate).await.unwrap(); + assert!(api.accounts().unwrap().contains(&impersonate)); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_contract() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let provider = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); - let greeter_contract = - Greeter::deploy(provider, "Hello World!".to_string()).unwrap().send().await.unwrap(); - let impersonate = greeter_contract.address(); + let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); + let impersonate = greeter_contract.address().to_owned(); let to = Address::random(); - let val = 1337u64; - - let provider = handle.http_provider(); + let val = U256::from(1337); - // fund the impersonated account + // // fund the impersonated account api.anvil_set_balance(impersonate, U256::from(1e18 as u64)).await.unwrap(); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap()._0; assert_eq!("Hello World!", greeting); api.anvil_impersonate_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, impersonate); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); api.anvil_stop_impersonating_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap()._0; assert_eq!("Hello World!", greeting); } @@ -182,33 +203,33 @@ async fn can_impersonate_gnosis_safe() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); - // - let safe: Address = "0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6".parse().unwrap(); + // + let safe = address!("A063Cb7CFd8E57c30c788A0572CBbf2129ae56B6"); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe).await.unwrap(); assert!(!code.is_empty()); api.anvil_impersonate_account(safe).await.unwrap(); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe).await.unwrap(); assert!(!code.is_empty()); let balance = U256::from(1e18 as u64); // fund the impersonated account api.anvil_set_balance(safe, balance).await.unwrap(); - let on_chain_balance = provider.get_balance(safe, None).await.unwrap(); + let on_chain_balance = provider.get_balance(safe).await.unwrap(); assert_eq!(on_chain_balance, balance); api.anvil_stop_impersonating_account(safe).await.unwrap(); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe).await.unwrap(); // code is added back after stop impersonating assert!(!code.is_empty()); } #[tokio::test(flavor = "multi_thread")] -async fn can_impersonate_multiple_account() { +async fn can_impersonate_multiple_accounts() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); @@ -216,42 +237,44 @@ async fn can_impersonate_multiple_account() { let impersonate1 = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated accounts api.anvil_set_balance(impersonate0, funding).await.unwrap(); api.anvil_set_balance(impersonate1, funding).await.unwrap(); - let tx = TransactionRequest::new().from(impersonate0).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate0).to(to).with_value(val); + let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(impersonate0).await.unwrap(); api.anvil_impersonate_account(impersonate1).await.unwrap(); - let res0 = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res0 = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res0.from, impersonate0); - let nonce = provider.get_transaction_count(impersonate0, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate0).await.unwrap(); + assert_eq!(nonce, 1); let receipt = provider.get_transaction_receipt(res0.transaction_hash).await.unwrap().unwrap(); - assert_eq!(res0, receipt); + assert_eq!(res0.inner, receipt.inner); let res1 = provider - .send_transaction(tx.from(impersonate1), None) + .send_transaction(tx.with_from(impersonate1)) .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + assert_eq!(res1.from, impersonate1); - let nonce = provider.get_transaction_count(impersonate1, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate1).await.unwrap(); + assert_eq!(nonce, 1); let receipt = provider.get_transaction_receipt(res1.transaction_hash).await.unwrap().unwrap(); - assert_eq!(res1, receipt); + assert_eq!(res1.inner, receipt.inner); - assert_ne!(res0, res1); + assert_ne!(res0.inner, res1.inner); } #[tokio::test(flavor = "multi_thread")] @@ -261,10 +284,10 @@ async fn can_mine_manually() { let start_num = provider.get_block_number().await.unwrap(); - for (idx, _) in std::iter::repeat(()).take(10).enumerate() { + for (idx, _) in std::iter::repeat_n((), 10).enumerate() { api.evm_mine(None).await.unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, start_num + idx + 1); + assert_eq!(num, start_num + idx as u64 + 1); } } @@ -282,17 +305,17 @@ async fn test_set_next_timestamp() { api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1); - assert_eq!(block.timestamp.as_u64(), next_timestamp.as_secs()); + assert_eq!(block.header.number, 1); + assert_eq!(block.header.timestamp, next_timestamp.as_secs()); api.evm_mine(None).await.unwrap(); - let next = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(next.number.unwrap().as_u64(), 2); + let next = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); + assert_eq!(next.header.number, 2); - assert!(next.timestamp > block.timestamp); + assert!(next.header.timestamp >= block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -309,14 +332,14 @@ async fn test_evm_set_time() { // mine a block api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert!(block.timestamp.as_u64() >= timestamp.as_secs()); + assert!(block.header.timestamp >= timestamp.as_secs()); api.evm_mine(None).await.unwrap(); - let next = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let next = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert!(next.timestamp > block.timestamp); + assert!(next.header.timestamp >= block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -333,10 +356,10 @@ async fn test_evm_set_time_in_past() { // mine a block api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert!(block.timestamp.as_u64() >= timestamp.as_secs()); - assert!(block.timestamp.as_u64() < now.as_secs()); + assert!(block.header.timestamp >= timestamp.as_secs()); + assert!(block.header.timestamp < now.as_secs()); } #[tokio::test(flavor = "multi_thread")] @@ -348,44 +371,46 @@ async fn test_timestamp_interval() { let interval = 10; for _ in 0..5 { - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // mock timestamp api.evm_set_block_timestamp_interval(interval).unwrap(); api.evm_mine(None).await.unwrap(); - let new_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let new_block = + provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(new_block.timestamp, block.timestamp + interval); + assert_eq!(new_block.header.timestamp, block.header.timestamp + interval); } - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - let next_timestamp = block.timestamp + 50; - api.evm_set_next_block_timestamp(next_timestamp.as_u64()).unwrap(); + let next_timestamp = block.header.timestamp + 50; + api.evm_set_next_block_timestamp(next_timestamp).unwrap(); api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.timestamp, next_timestamp); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, next_timestamp); api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // interval also works after setting the next timestamp manually - assert_eq!(block.timestamp, next_timestamp + interval); + assert_eq!(block.header.timestamp, next_timestamp + interval); assert!(api.evm_remove_block_timestamp_interval().unwrap()); api.evm_mine(None).await.unwrap(); - let new_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let new_block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // offset is applied correctly after resetting the interval - assert!(new_block.timestamp > block.timestamp); + assert!(new_block.header.timestamp > block.header.timestamp); api.evm_mine(None).await.unwrap(); - let another_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let another_block = + provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // check interval is disabled - assert!(another_block.timestamp - new_block.timestamp < U256::from(interval)); + assert!(another_block.header.timestamp - new_block.header.timestamp < interval); } // @@ -393,26 +418,25 @@ async fn test_timestamp_interval() { async fn test_can_set_storage_bsc_fork() { let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; - let provider = Arc::new(handle.http_provider()); + let provider = handle.http_provider(); - let busd_addr: Address = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse().unwrap(); - let idx: U256 = - "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49".parse().unwrap(); - let value: H256 = - "0x0000000000000000000000000000000000000000000000000000000000003039".parse().unwrap(); + let busd_addr = address!("e9e7CEA3DedcA5984780Bafc599bD69ADd087D56"); + let idx = U256::from_str("0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49") + .unwrap(); + let value = fixed_bytes!("0000000000000000000000000000000000000000000000000000000000003039"); api.anvil_set_storage_at(busd_addr, idx, value).await.unwrap(); let storage = api.storage_at(busd_addr, idx, None).await.unwrap(); assert_eq!(storage, value); - let input = - hex::decode("70a082310000000000000000000000000000000000000000000000000000000000000000") - .unwrap(); + let busd_contract = BUSD::new(busd_addr, &provider); - let busd = BUSD::new(busd_addr, provider); - let call = busd::BalanceOfCall::decode(&input).unwrap(); - - let balance = busd.balance_of(call.0).call().await.unwrap(); + let BUSD::balanceOfReturn { _0 } = busd_contract + .balanceOf(address!("0000000000000000000000000000000000000000")) + .call() + .await + .unwrap(); + let balance = _0; assert_eq!(balance, U256::from(12345u64)); } @@ -425,19 +449,21 @@ async fn can_get_node_info() { let provider = handle.http_provider(); let block_number = provider.get_block_number().await.unwrap(); - let block = provider.get_block(block_number).await.unwrap().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([0]), + current_block_number: 0_u64, current_block_timestamp: 1, - current_block_hash: block.hash.unwrap(), - hard_fork: SpecId::SHANGHAI, + current_block_hash: block.header.hash, + hard_fork: hard_fork.to_string(), transaction_order: "fees".to_owned(), environment: NodeEnvironment { - base_fee: U256::from_str("0x3b9aca00").unwrap(), - chain_id: U256::from_str("0x7a69").unwrap(), - gas_limit: U256::from_str("0x1c9c380").unwrap(), - gas_price: U256::from_str("0x77359400").unwrap(), + base_fee: U256::from_str("0x3b9aca00").unwrap().to(), + chain_id: 0x7a69, + gas_limit: U256::from_str("0x1c9c380").unwrap().to(), + gas_price: U256::from_str("0x77359400").unwrap().to(), }, fork_config: NodeForkConfig { fork_url: None, @@ -449,32 +475,589 @@ async fn can_get_node_info() { assert_eq!(node_info, expected_node_info); } +#[tokio::test(flavor = "multi_thread")] +async fn can_get_metadata() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let metadata = api.anvil_metadata().await.unwrap(); + + let provider = handle.http_provider(); + + let block_number = provider.get_block_number().await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let block = + provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); + + let expected_metadata = Metadata { + latest_block_hash: block.header.hash, + latest_block_number: block_number, + chain_id, + client_version: CLIENT_VERSION.to_string(), + instance_id: api.instance_id(), + forked_network: None, + snapshots: Default::default(), + }; + + assert_eq!(metadata, expected_metadata); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_metadata_on_fork() { + let (api, handle) = + spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; + let provider = handle.http_provider(); + + let metadata = api.anvil_metadata().await.unwrap(); + + let block_number = provider.get_block_number().await.unwrap(); + let chain_id = provider.get_chain_id().await.unwrap(); + let block = + provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); + + let expected_metadata = Metadata { + latest_block_hash: block.header.hash, + latest_block_number: block_number, + chain_id, + client_version: CLIENT_VERSION.to_string(), + instance_id: api.instance_id(), + forked_network: Some(ForkedNetwork { + chain_id, + fork_block_number: block_number, + fork_block_hash: block.header.hash, + }), + snapshots: Default::default(), + }; + + assert_eq!(metadata, expected_metadata); +} + +#[tokio::test(flavor = "multi_thread")] +async fn metadata_changes_on_reset() { + let (api, _) = + spawn(NodeConfig::test().with_eth_rpc_url(Some("https://bsc-dataseed.binance.org/"))).await; + + let metadata = api.anvil_metadata().await.unwrap(); + let instance_id = metadata.instance_id; + + api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: None })).await.unwrap(); + + let new_metadata = api.anvil_metadata().await.unwrap(); + let new_instance_id = new_metadata.instance_id; + + assert_ne!(instance_id, new_instance_id); +} + #[tokio::test(flavor = "multi_thread")] async fn test_get_transaction_receipt() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); // set the base fee - let new_base_fee = U256::from(1_000); + let new_base_fee = U256::from(1000); api.anvil_set_next_block_base_fee_per_gas(new_base_fee).await.unwrap(); // send a EIP-1559 transaction - let tx = - TypedTransaction::Eip1559(Eip1559TransactionRequest::new().gas(U256::from(30_000_000))); - let receipt = - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let to = Address::random(); + let val = U256::from(1337); + let tx = TransactionRequest::default().with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); // the block should have the new base fee - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.base_fee_per_gas.unwrap().as_u64(), new_base_fee.as_u64()); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.base_fee_per_gas.unwrap(), new_base_fee.to::()); - // mine block + // mine blocks api.evm_mine(None).await.unwrap(); // the transaction receipt should have the original effective gas price let new_receipt = provider.get_transaction_receipt(receipt.transaction_hash).await.unwrap(); - assert_eq!( - receipt.effective_gas_price.unwrap().as_u64(), - new_receipt.unwrap().effective_gas_price.unwrap().as_u64() - ); + assert_eq!(receipt.effective_gas_price, new_receipt.unwrap().effective_gas_price); +} + +// test can set chain id +#[tokio::test(flavor = "multi_thread")] +async fn test_set_chain_id() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 31337); + + let chain_id = 1234; + api.anvil_set_chain_id(chain_id).await.unwrap(); + + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 1234); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_revert_next_block_timestamp() { + let (api, _handle) = spawn(fork_config()).await; + + // Mine a new block, and check the new block gas limit + api.mine_one().await; + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + let state_snapshot = api.evm_snapshot().await.unwrap(); + api.mine_one().await; + api.evm_revert(state_snapshot).await.unwrap(); + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block, latest_block); + + api.mine_one().await; + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert!(block.header.timestamp >= latest_block.header.timestamp); +} + +// test that after a snapshot revert, the env block is reset +// to its correct value (block number, etc.) +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_revert_call_latest_block_timestamp() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + // Mine a new block, and check the new block gas limit + api.mine_one().await; + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + let state_snapshot = api.evm_snapshot().await.unwrap(); + api.mine_one().await; + api.evm_revert(state_snapshot).await.unwrap(); + + let multicall_contract = + Multicall::new(address!("eefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); + + let Multicall::getCurrentBlockTimestampReturn { timestamp } = + multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); + assert_eq!(timestamp, U256::from(latest_block.header.timestamp)); + + let Multicall::getCurrentBlockDifficultyReturn { difficulty } = + multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); + assert_eq!(difficulty, U256::from(latest_block.header.difficulty)); + + let Multicall::getCurrentBlockGasLimitReturn { gaslimit } = + multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); + assert_eq!(gaslimit, U256::from(latest_block.header.gas_limit)); + + let Multicall::getCurrentBlockCoinbaseReturn { coinbase } = + multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); + assert_eq!(coinbase, latest_block.header.beneficiary); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_remove_pool_transactions() { + 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(); + let from = wallet.address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let sender = Address::random(); + let to = Address::random(); + let val = U256::from(1337); + let tx = TransactionRequest::default().with_from(sender).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.with_from(from)).await.unwrap().register().await.unwrap(); + + let initial_txs = provider.txpool_inspect().await.unwrap(); + assert_eq!(initial_txs.pending.len(), 1); + + api.anvil_remove_pool_transactions(wallet.address()).await.unwrap(); + + let final_txs = provider.txpool_inspect().await.unwrap(); + assert_eq!(final_txs.pending.len(), 0); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_reorg() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.ws_provider(); + + let accounts = handle.dev_wallets().collect::>(); + + // Test calls + // Populate chain + for i in 0..10 { + let tx = TransactionRequest::default() + .to(accounts[0].address()) + .value(U256::from(i)) + .from(accounts[1].address()); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + + let tx = TransactionRequest::default() + .to(accounts[1].address()) + .value(U256::from(i)) + .from(accounts[2].address()); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + // Define transactions + let mut txs = vec![]; + for i in 0..3 { + let from = accounts[i].address(); + let to = accounts[i + 1].address(); + for j in 0..5 { + let tx = TransactionRequest::default().from(from).to(to).value(U256::from(j)); + txs.push((TransactionData::JSON(tx), i as u64)); + } + } + + let prev_height = provider.get_block_number().await.unwrap(); + api.anvil_reorg(ReorgOptions { depth: 7, tx_block_pairs: txs }).await.unwrap(); + + let reorged_height = provider.get_block_number().await.unwrap(); + assert_eq!(reorged_height, prev_height); + + // The first 3 reorged blocks should have 5 transactions each + for num in 14..17 { + let block = + provider.get_block_by_number(num.into(), BlockTransactionsKind::Full).await.unwrap(); + let block = block.unwrap(); + assert_eq!(block.transactions.len(), 5); + } + + // Verify that historic blocks are still accessible + for num in (0..14).rev() { + let _ = + provider.get_block_by_number(num.into(), BlockTransactionsKind::Full).await.unwrap(); + } + + // Send a few more transaction to verify the chain can still progress + for i in 0..3 { + let tx = TransactionRequest::default() + .to(accounts[0].address()) + .value(U256::from(i)) + .from(accounts[1].address()); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + // Test reverting code + let greeter = abi::Greeter::deploy(provider.clone(), "Reorg".to_string()).await.unwrap(); + api.anvil_reorg(ReorgOptions { depth: 5, tx_block_pairs: vec![] }).await.unwrap(); + let code = api.get_code(*greeter.address(), Some(BlockId::latest())).await.unwrap(); + assert_eq!(code, Bytes::default()); + + // Test reverting contract storage + let storage = + abi::SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); + api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(5) })).await.unwrap(); + let _ = storage + .setValue("ReorgMe".to_string()) + .from(accounts[0].address()) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + api.anvil_reorg(ReorgOptions { depth: 3, tx_block_pairs: vec![] }).await.unwrap(); + let value = storage.getValue().call().await.unwrap()._0; + assert_eq!("initial value".to_string(), value); + + api.mine_one().await; + api.mine_one().await; + + // Test raw transaction data + let mut tx = TxEip1559 { + chain_id: api.chain_id(), + to: TxKind::Call(accounts[1].address()), + value: U256::from(100), + max_priority_fee_per_gas: 1000000000000, + max_fee_per_gas: 10000000000000, + gas_limit: 21000, + ..Default::default() + }; + let signature = accounts[5].sign_transaction_sync(&mut tx).unwrap(); + let tx = tx.into_signed(signature); + let mut encoded = vec![]; + tx.eip2718_encode(&mut encoded); + + let pre_bal = provider.get_balance(accounts[5].address()).await.unwrap(); + api.anvil_reorg(ReorgOptions { + depth: 1, + tx_block_pairs: vec![(TransactionData::Raw(encoded.into()), 0)], + }) + .await + .unwrap(); + let post_bal = provider.get_balance(accounts[5].address()).await.unwrap(); + assert_ne!(pre_bal, post_bal); + + // Test reorg depth exceeding current height + let res = api.anvil_reorg(ReorgOptions { depth: 100, tx_block_pairs: vec![] }).await; + assert!(res.is_err()); + + // Test reorg tx pairs exceeds chain length + let res = api + .anvil_reorg(ReorgOptions { + depth: 1, + tx_block_pairs: vec![(TransactionData::JSON(TransactionRequest::default()), 10)], + }) + .await; + assert!(res.is_err()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_rollback() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + // Mine 5 blocks + for _ in 0..5 { + api.mine_one().await; + } + + // Get block 4 for later comparison + let block4 = provider.get_block(4.into(), false.into()).await.unwrap().unwrap(); + + // Rollback with None should rollback 1 block + api.anvil_rollback(None).await.unwrap(); + + // Assert we're at block 4 and the block contents are kept the same + let head = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(head, block4); + + // Get block 1 for comparison + let block1 = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); + + // Rollback to block 1 + let depth = 3; // from block 4 to block 1 + api.anvil_rollback(Some(depth)).await.unwrap(); + + // Assert we're at block 1 and the block contents are kept the same + let head = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(head, block1); +} + +// === wallet endpoints === // +#[tokio::test(flavor = "multi_thread")] +async fn can_get_wallet_capabilities() { + let (api, handle) = spawn(NodeConfig::test().with_odyssey(true)).await; + + let provider = handle.http_provider(); + + let init_sponsor_bal = provider.get_balance(EXECUTOR).await.unwrap(); + + let expected_bal = Unit::ETHER.wei().saturating_mul(U256::from(10_000)); + assert_eq!(init_sponsor_bal, expected_bal); + + let p256_code = provider.get_code_at(P256_DELEGATION_CONTRACT).await.unwrap(); + + assert_eq!(p256_code, Bytes::from_static(P256_DELEGATION_RUNTIME_CODE)); + + let capabilities = api.get_capabilities().unwrap(); + + let mut expect_caps = WalletCapabilities::default(); + let cap: Capabilities = Capabilities { + delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, + }; + expect_caps.insert(api.chain_id(), cap); + + assert_eq!(capabilities, expect_caps); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_add_capability() { + let (api, _handle) = spawn(NodeConfig::test().with_odyssey(true)).await; + + let init_capabilities = api.get_capabilities().unwrap(); + + let mut expect_caps = WalletCapabilities::default(); + let cap: Capabilities = Capabilities { + delegation: DelegationCapability { addresses: vec![P256_DELEGATION_CONTRACT] }, + }; + expect_caps.insert(api.chain_id(), cap); + + assert_eq!(init_capabilities, expect_caps); + + let new_cap_addr = Address::with_last_byte(1); + + api.anvil_add_capability(new_cap_addr).unwrap(); + + let capabilities = api.get_capabilities().unwrap(); + + let cap: Capabilities = Capabilities { + delegation: DelegationCapability { + addresses: vec![P256_DELEGATION_CONTRACT, new_cap_addr], + }, + }; + expect_caps.insert(api.chain_id(), cap); + + assert_eq!(capabilities, expect_caps); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_set_executor() { + let (api, _handle) = spawn(NodeConfig::test().with_odyssey(true)).await; + + let expected_addr = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let pk = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); + + let executor = api.anvil_set_executor(pk).unwrap(); + + assert_eq!(executor, expected_addr); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_arb_get_block() { + let (api, _handle) = spawn(NodeConfig::test().with_chain_id(Some(421611u64))).await; + + // Mine two blocks + api.mine_one().await; + api.mine_one().await; + + let best_number = api.block_number().unwrap().to::(); + + assert_eq!(best_number, 2); + + let block = api.block_by_number(1.into()).await.unwrap().unwrap(); + + assert_eq!(block.header.number, 1); +} + +// Set next_block_timestamp same as previous block +// api.evm_set_next_block_timestamp(0).unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn test_mine_blk_with_prev_timestamp() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let init_blk = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let init_number = init_blk.header.number; + let init_timestamp = init_blk.header.timestamp; + + // mock timestamp + api.evm_set_next_block_timestamp(init_timestamp).unwrap(); + + api.mine_one().await; + + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let next_blk_num = block.header.number; + let next_blk_timestamp = block.header.timestamp; + + assert_eq!(next_blk_num, init_number + 1); + assert_eq!(next_blk_timestamp, init_timestamp); + + // Sleep for 1 second + tokio::time::sleep(Duration::from_secs(1)).await; + + // Subsequent block should have a greater timestamp than previous block + api.mine_one().await; + + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let third_blk_num = block.header.number; + let third_blk_timestmap = block.header.timestamp; + + assert_eq!(third_blk_num, init_number + 2); + assert_ne!(third_blk_timestmap, next_blk_timestamp); + assert!(third_blk_timestmap > next_blk_timestamp); +} + +// increase time by 0 seconds i.e next_block_timestamp = prev_block_timestamp +// api.evm_increase_time(0).unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn test_increase_time_by_zero() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let init_blk = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let init_number = init_blk.header.number; + let init_timestamp = init_blk.header.timestamp; + + let _ = api.evm_increase_time(U256::ZERO).await; + + api.mine_one().await; + + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let next_blk_num = block.header.number; + let next_blk_timestamp = block.header.timestamp; + + assert_eq!(next_blk_num, init_number + 1); + assert_eq!(next_blk_timestamp, init_timestamp); +} + +// evm_mine(MineOptions::Timestamp(prev_block_timestamp)) +#[tokio::test(flavor = "multi_thread")] +async fn evm_mine_blk_with_same_timestamp() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let init_blk = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let init_number = init_blk.header.number; + let init_timestamp = init_blk.header.timestamp; + + api.evm_mine(Some(MineOptions::Timestamp(Some(init_timestamp)))).await.unwrap(); + + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let next_blk_num = block.header.number; + let next_blk_timestamp = block.header.timestamp; + + assert_eq!(next_blk_num, init_number + 1); + assert_eq!(next_blk_timestamp, init_timestamp); +} + +// mine 4 blocks instantly. +#[tokio::test(flavor = "multi_thread")] +async fn test_mine_blks_with_same_timestamp() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let init_blk = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + let init_number = init_blk.header.number; + let init_timestamp = init_blk.header.timestamp; + + // Mine 4 blocks instantly + let _ = api.anvil_mine(Some(U256::from(4)), None).await; + + let latest_blk_num = api.block_number().unwrap().to::(); + + assert_eq!(latest_blk_num, init_number + 4); + + let mut blk_futs = vec![]; + for i in 1..=4 { + blk_futs.push(provider.get_block(i.into(), false.into())); + } + + let blks = futures::future::join_all(blk_futs) + .await + .into_iter() + .map(|blk| blk.unwrap().unwrap().header.timestamp) + .collect::>(); + + // timestamps should be equal + assert_eq!(blks, vec![init_timestamp; 4]); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_mine_first_block_with_interval() { + let (api, _) = spawn(NodeConfig::test()).await; + + let init_block = api.block_by_number(0.into()).await.unwrap().unwrap(); + let init_timestamp = init_block.header.timestamp; + + // Mine 2 blocks with interval of 60. + let _ = api.anvil_mine(Some(U256::from(2)), Some(U256::from(60))).await; + + let first_block = api.block_by_number(1.into()).await.unwrap().unwrap(); + assert_eq!(first_block.header.timestamp, init_timestamp + 60); + + let second_block = api.block_by_number(2.into()).await.unwrap().unwrap(); + assert_eq!(second_block.header.timestamp, init_timestamp + 120); } diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index 5a7e97218ed34..946118af8c07b 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -1,32 +1,35 @@ //! general eth api tests -use anvil::{ - eth::{api::CLIENT_VERSION, EthApi}, - spawn, NodeConfig, CHAIN_ID, +use crate::{ + abi::{Multicall, SimpleStorage}, + utils::{connect_pubsub_with_wallet, http_provider_with_signer}, }; -use anvil_core::eth::{state::AccountOverride, transaction::EthTransactionRequest}; -use ethers::{ - abi::{Address, Tokenizable}, - prelude::{builders::ContractCall, decode_function_data, Middleware, SignerMiddleware}, - signers::Signer, - types::{Block, BlockNumber, Chain, Transaction, TransactionRequest, H256, U256}, - utils::get_contract_address, +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{ + map::{AddressHashMap, B256HashMap, HashMap}, + Address, ChainId, B256, U256, }; -use std::{collections::HashMap, sync::Arc, time::Duration}; - -use crate::abi::{MulticallContract, SimpleStorage}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + request::TransactionRequest, state::AccountOverride, BlockId, BlockNumberOrTag, + BlockTransactions, +}; +use alloy_serde::WithOtherFields; +use anvil::{eth::api::CLIENT_VERSION, spawn, NodeConfig, CHAIN_ID}; +use futures::join; +use std::time::Duration; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number() { let (api, handle) = spawn(NodeConfig::test()).await; let block_num = api.block_number().unwrap(); - assert_eq!(block_num, U256::zero()); + assert_eq!(block_num, U256::from(0)); let provider = handle.http_provider(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.as_u64().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] @@ -36,7 +39,7 @@ async fn can_dev_get_balance() { let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { - let balance = provider.get_balance(acc, None).await.unwrap(); + let balance = provider.get_balance(acc).await.unwrap(); assert_eq!(balance, genesis_balance); } } @@ -62,7 +65,7 @@ async fn can_get_client_version() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let version = provider.client_version().await.unwrap(); + let version = provider.get_client_version().await.unwrap(); assert_eq!(CLIENT_VERSION, version); } @@ -71,20 +74,21 @@ async fn can_get_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, CHAIN_ID.into()); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, CHAIN_ID); } #[tokio::test(flavor = "multi_thread")] async fn can_modify_chain_id() { - let (_api, handle) = spawn(NodeConfig::test().with_chain_id(Some(Chain::Goerli))).await; + let (_api, handle) = + spawn(NodeConfig::test().with_chain_id(Some(ChainId::from(777_u64)))).await; let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, Chain::Goerli.into()); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 777); let chain_id = provider.get_net_version().await.unwrap(); - assert_eq!(chain_id, (Chain::Goerli as u64).to_string()); + assert_eq!(chain_id, 777); } #[tokio::test(flavor = "multi_thread")] @@ -98,238 +102,307 @@ async fn can_get_network_id() { #[tokio::test(flavor = "multi_thread")] async fn can_get_block_by_number() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - // send a dummy transactions - let tx = TransactionRequest::new().to(to).value(amount).from(from); - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - let block: Block = provider.get_block_with_txs(1u64).await.unwrap().unwrap(); - assert_eq!(block.transactions.len(), 1); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let val = handle.genesis_balance().checked_div(U256::from(2)).unwrap(); - let block = provider.get_block(1u64).await.unwrap().unwrap(); + // send a dummy transaction + let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(val); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let block = provider.get_block(BlockId::number(1), true.into()).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); - let block = provider.get_block(block.hash.unwrap()).await.unwrap().unwrap(); + let block = + provider.get_block(BlockId::hash(block.header.hash), true.into()).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn can_get_pending_block() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); - let block = provider.get_block(BlockNumber::Pending).await.unwrap().unwrap(); + let provider = connect_pubsub_with_wallet(&handle.http_endpoint(), signer).await; - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.number, 1); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + assert_eq!(num, 0); api.anvil_set_auto_mine(false).await.unwrap(); - let from = accounts[0].address(); - let to = accounts[1].address(); - let tx = TransactionRequest::new().to(to).value(100u64).from(from); + let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(100)); - let tx = provider.send_transaction(tx, None).await.unwrap(); + let pending = provider.send_transaction(tx.clone()).await.unwrap().register().await.unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + assert_eq!(num, 0); - let block = provider.get_block(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.number, 1); assert_eq!(block.transactions.len(), 1); - assert_eq!(block.transactions, vec![tx.tx_hash()]); + assert_eq!(block.transactions, BlockTransactions::Hashes(vec![*pending.tx_hash()])); - let block = provider.get_block_with_txs(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), true.into()).await.unwrap().unwrap(); + assert_eq!(block.header.number, 1); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_on_pending_block() { +async fn can_estimate_gas_with_undersized_max_fee_per_gas() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); - let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); - api.anvil_set_auto_mine(false).await.unwrap(); + api.anvil_set_auto_mine(true).await.unwrap(); + + let init_value = "toto".to_string(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); + + let undersized_max_fee_per_gas = 1; + + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + let latest_block_base_fee_per_gas = latest_block.header.base_fee_per_gas.unwrap(); + + assert!(undersized_max_fee_per_gas < latest_block_base_fee_per_gas); + + let estimated_gas = simple_storage_contract + .setValue("new_value".to_string()) + .max_fee_per_gas(undersized_max_fee_per_gas.into()) + .from(wallet.address()) + .estimate_gas() + .await + .unwrap(); + + assert!(estimated_gas > 0); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_call_on_pending_block() { + let (api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); - client.send_transaction(deploy_tx, None).await.unwrap(); + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, 0); - let pending_contract = MulticallContract::new(pending_contract_address, client.clone()); + api.anvil_set_auto_mine(false).await.unwrap(); - let num = client.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + let _contract_pending = Multicall::deploy_builder(&provider) + .from(wallet.address()) + .send() + .await + .unwrap() + .register() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = Multicall::new(contract_address, &provider); + + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, 0); // Ensure that we can get the block_number from the pending contract - let (ret_block_number, _) = - pending_contract.aggregate(vec![]).block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(ret_block_number.as_u64(), 1u64); + let Multicall::aggregateReturn { blockNumber: ret_block_number, .. } = + contract.aggregate(vec![]).block(BlockId::pending()).call().await.unwrap(); + assert_eq!(ret_block_number, U256::from(1)); let accounts: Vec

= handle.dev_wallets().map(|w| w.address()).collect(); + for i in 1..10 { api.anvil_set_coinbase(accounts[i % accounts.len()]).await.unwrap(); - api.evm_set_block_gas_limit((30_000_000 + i).into()).unwrap(); + api.evm_set_block_gas_limit(U256::from(30_000_000 + i)).unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; } + // Ensure that the right header values are set when calling a past block - for block_number in 1..(api.block_number().unwrap().as_usize() + 1) { - let block_number = BlockNumber::Number(block_number.into()); + for anvil_block_number in 1..(api.block_number().unwrap().to::() + 1) { + let block_number = BlockNumberOrTag::Number(anvil_block_number as u64); let block = api.block_by_number(block_number).await.unwrap().unwrap(); - let block_timestamp = pending_contract - .get_current_block_timestamp() - .block(block_number) + let Multicall::getCurrentBlockTimestampReturn { timestamp: ret_timestamp, .. } = contract + .getCurrentBlockTimestamp() + .block(BlockId::number(anvil_block_number as u64)) .call() .await .unwrap(); - assert_eq!(block.timestamp, block_timestamp); + assert_eq!(block.header.timestamp, ret_timestamp.to::()); - let block_gas_limit = pending_contract - .get_current_block_gas_limit() - .block(block_number) + let Multicall::getCurrentBlockGasLimitReturn { gaslimit: ret_gas_limit, .. } = contract + .getCurrentBlockGasLimit() + .block(BlockId::number(anvil_block_number as u64)) .call() .await .unwrap(); - assert_eq!(block.gas_limit, block_gas_limit); + assert_eq!(block.header.gas_limit, ret_gas_limit.to::()); - let block_coinbase = - pending_contract.get_current_block_coinbase().block(block_number).call().await.unwrap(); - assert_eq!(block.author.unwrap(), block_coinbase); + let Multicall::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = contract + .getCurrentBlockCoinbase() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); + assert_eq!(block.header.beneficiary, ret_coinbase); } } -async fn call_with_override( - api: &EthApi, - call: ContractCall, - to: Address, - overrides: HashMap, -) -> D -where - D: Tokenizable, -{ - let result = api - .call( - EthTransactionRequest { - data: call.tx.data().cloned(), - to: Some(to), - ..Default::default() - }, - None, - Some(overrides), - ) +#[tokio::test(flavor = "multi_thread")] +async fn can_call_with_undersized_max_fee_per_gas() { + let (api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + api.anvil_set_auto_mine(true).await.unwrap(); + + let init_value = "toto".to_string(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); + + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + let latest_block_base_fee_per_gas = latest_block.header.base_fee_per_gas.unwrap(); + let undersized_max_fee_per_gas = 1; + + assert!(undersized_max_fee_per_gas < latest_block_base_fee_per_gas); + + let last_sender = simple_storage_contract + .lastSender() + .max_fee_per_gas(undersized_max_fee_per_gas.into()) + .from(wallet.address()) + .call() .await - .unwrap(); - decode_function_data(&call.function, result.as_ref(), false).unwrap() + .unwrap() + ._0; + assert_eq!(last_sender, Address::ZERO); } #[tokio::test(flavor = "multi_thread")] async fn can_call_with_state_override() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + let account = wallet.address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); api.anvil_set_auto_mine(true).await.unwrap(); - let wallet = handle.dev_wallets().next().unwrap(); - let account = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let init_value = "toto".to_string(); - let multicall = - MulticallContract::deploy(Arc::clone(&client), ()).unwrap().send().await.unwrap(); - let simple_storage = SimpleStorage::deploy(Arc::clone(&client), init_value.clone()) - .unwrap() - .send() - .await - .unwrap(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); // Test the `balance` account override - let balance = 42u64.into(); - let result = call_with_override( - &api, - multicall.get_eth_balance(account), - multicall.address(), - HashMap::from([( - account, - AccountOverride { balance: Some(balance), ..Default::default() }, - )]), - ) - .await; + let balance = U256::from(42u64); + let mut overrides = AddressHashMap::default(); + overrides.insert(account, AccountOverride { balance: Some(balance), ..Default::default() }); + let result = + multicall_contract.getEthBalance(account).state(overrides).call().await.unwrap().balance; assert_eq!(result, balance); // Test the `state_diff` account override - let overrides = HashMap::from([( - simple_storage.address(), + let mut state_diff = B256HashMap::default(); + state_diff.insert(B256::ZERO, account.into_word()); + let mut overrides = AddressHashMap::default(); + overrides.insert( + *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state_diff: Some(HashMap::from([(H256::from_low_u64_be(0), account.into())])), + state_diff: Some(state_diff), ..Default::default() }, - )]); - - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - Default::default(), - ) - .await; + ); + + let last_sender = + simple_storage_contract.lastSender().state(HashMap::default()).call().await.unwrap()._0; // No `sender` set without override - assert_eq!(last_sender, Address::zero()); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - overrides.clone(), - ) - .await; + assert_eq!(last_sender, Address::ZERO); + + let last_sender = + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; // `sender` *is* set with override assert_eq!(last_sender, account); - let value = - call_with_override(&api, simple_storage.get_value(), simple_storage.address(), overrides) - .await; + + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; // `value` *is not* changed with state-diff assert_eq!(value, init_value); // Test the `state` account override - let overrides = HashMap::from([( - simple_storage.address(), + let mut state = B256HashMap::default(); + state.insert(B256::ZERO, account.into_word()); + let mut overrides = AddressHashMap::default(); + overrides.insert( + *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state: Some(HashMap::from([(H256::from_low_u64_be(0), account.into())])), + state: Some(state), ..Default::default() }, - )]); - - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - overrides.clone(), - ) - .await; + ); + + let last_sender = + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; // `sender` *is* set with override assert_eq!(last_sender, account); - let value = - call_with_override(&api, simple_storage.get_value(), simple_storage.address(), overrides) - .await; + + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; // `value` *is* changed with state assert_eq!(value, ""); } + +#[tokio::test(flavor = "multi_thread")] +async fn can_mine_while_mining() { + let (api, _) = spawn(NodeConfig::test()).await; + + let total_blocks = 200; + + let block_number = + api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap().header.number; + assert_eq!(block_number, 0); + + let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); + assert_eq!(block.header.number, 0); + + let result = join!( + api.anvil_mine(Some(U256::from(total_blocks / 2)), None), + api.anvil_mine(Some(U256::from(total_blocks / 2)), None) + ); + result.0.unwrap(); + result.1.unwrap(); + tokio::time::sleep(Duration::from_millis(100)).await; + + let block_number = + api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap().header.number; + assert_eq!(block_number, total_blocks); + + let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); + assert_eq!(block.header.number, total_blocks); +} diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs new file mode 100644 index 0000000000000..65bdba61163d2 --- /dev/null +++ b/crates/anvil/tests/it/eip4844.rs @@ -0,0 +1,301 @@ +use crate::utils::{http_provider, http_provider_with_signer}; +use alloy_consensus::{SidecarBuilder, SimpleCoder, Transaction}; +use alloy_eips::eip4844::{BLOB_TX_MIN_BLOB_GASPRICE, DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}; +use alloy_network::{EthereumWallet, TransactionBuilder, TransactionBuilder4844}; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, BlockTransactionsKind, TransactionRequest}; +use alloy_serde::WithOtherFields; +use anvil::{spawn, EthereumHardfork, NodeConfig}; + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + let from = wallets[0].address(); + let to = wallets[1].address(); + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Hello World"); + + let sidecar = sidecar.build().unwrap(); + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar) + .value(U256::from(5)); + + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert_eq!(receipt.blob_gas_used, Some(131072)); + assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_multiple_blobs_in_one_tx() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let large_data = vec![1u8; DATA_GAS_PER_BLOB as usize * 5]; // 131072 is DATA_GAS_PER_BLOB and also BYTE_PER_BLOB + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&large_data); + + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert_eq!(receipt.blob_gas_used, Some(MAX_DATA_GAS_PER_BLOCK)); + assert_eq!(receipt.blob_gas_price, Some(0x1)); // 1 wei +} + +#[tokio::test(flavor = "multi_thread")] +async fn cannot_exceed_six_blobs() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let wallets = handle.dev_wallets().collect::>(); + + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let large_data = vec![1u8; DATA_GAS_PER_BLOB as usize * 6]; // 131072 is DATA_GAS_PER_BLOB and also BYTE_PER_BLOB + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&large_data); + + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let err = provider.send_transaction(tx).await.unwrap_err(); + + assert!(err.to_string().contains("too many blobs")); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_mine_blobs_when_exceeds_max_blobs() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (api, handle) = spawn(node_config).await; + api.anvil_set_auto_mine(false).await.unwrap(); + + let wallets = handle.dev_wallets().collect::>(); + + let from = wallets[0].address(); + let to = wallets[1].address(); + + let provider = http_provider(&handle.http_endpoint()); + + let eip1559_est = provider.estimate_eip1559_fees(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let first_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 3]; + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&first_batch); + + let num_blobs_first = sidecar.clone().take().len() as u64; + + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_nonce(0) + .with_max_fee_per_blob_gas(gas_price + 1) + .with_max_fee_per_gas(eip1559_est.max_fee_per_gas) + .with_max_priority_fee_per_gas(eip1559_est.max_priority_fee_per_gas) + .with_blob_sidecar(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let first_tx = provider.send_transaction(tx.clone()).await.unwrap(); + + let second_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 2]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(&second_batch); + + let num_blobs_second = sidecar.clone().take().len() as u64; + + let sidecar = sidecar.build().unwrap(); + tx.set_blob_sidecar(sidecar); + tx.set_nonce(1); + tx.populate_blob_hashes(); + let second_tx = provider.send_transaction(tx).await.unwrap(); + + api.mine_one().await; + + let first_receipt = first_tx.get_receipt().await.unwrap(); + + api.mine_one().await; + let second_receipt = second_tx.get_receipt().await.unwrap(); + + let (first_block, second_block) = tokio::join!( + provider.get_block_by_number( + first_receipt.block_number.unwrap().into(), + BlockTransactionsKind::Hashes + ), + provider.get_block_by_number( + second_receipt.block_number.unwrap().into(), + BlockTransactionsKind::Hashes + ) + ); + assert_eq!( + first_block.unwrap().unwrap().header.blob_gas_used, + Some(DATA_GAS_PER_BLOB * num_blobs_first) + ); + + assert_eq!( + second_block.unwrap().unwrap().header.blob_gas_used, + Some(DATA_GAS_PER_BLOB * num_blobs_second) + ); + // Mined in two different blocks + assert_eq!(first_receipt.block_number.unwrap() + 1, second_receipt.block_number.unwrap()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_check_blob_fields_on_genesis() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let provider = http_provider(&handle.http_endpoint()); + + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + assert_eq!(block.header.blob_gas_used, Some(0)); + assert_eq!(block.header.excess_blob_gas, Some(0)); +} + +#[allow(clippy::disallowed_macros)] +#[tokio::test(flavor = "multi_thread")] +async fn can_correctly_estimate_blob_gas_with_recommended_fillers() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let provider = http_provider(&handle.http_endpoint()); + + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar(sidecar); + let tx = WithOtherFields::new(tx); + + // Send the transaction and wait for the broadcast. + let pending_tx = provider.send_transaction(tx).await.unwrap(); + + println!("Pending transaction... {}", pending_tx.tx_hash()); + + // Wait for the transaction to be included and get the receipt. + let receipt = pending_tx.get_receipt().await.unwrap(); + + // Grab the processed transaction. + let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); + + println!( + "Transaction included in block {}", + receipt.block_number.expect("Failed to get block number") + ); + + assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); + assert_eq!(receipt.from, alice); + assert_eq!(receipt.to, Some(bob)); + assert_eq!( + receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), + DATA_GAS_PER_BLOB + ); +} + +#[allow(clippy::disallowed_macros)] +#[tokio::test(flavor = "multi_thread")] +async fn can_correctly_estimate_blob_gas_with_recommended_fillers_with_signer() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Cancun.into())); + let (_api, handle) = spawn(node_config).await; + + let signer = handle.dev_wallets().next().unwrap(); + let wallet: EthereumWallet = signer.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), wallet); + + let accounts = provider.get_accounts().await.unwrap(); + let alice = accounts[0]; + let bob = accounts[1]; + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice(b"Blobs are fun!"); + let sidecar = sidecar.build().unwrap(); + + let tx = TransactionRequest::default().with_to(bob).with_blob_sidecar(sidecar); + let tx = WithOtherFields::new(tx); + + // Send the transaction and wait for the broadcast. + let pending_tx = provider.send_transaction(tx).await.unwrap(); + + println!("Pending transaction... {}", pending_tx.tx_hash()); + + // Wait for the transaction to be included and get the receipt. + let receipt = pending_tx.get_receipt().await.unwrap(); + + // Grab the processed transaction. + let tx = provider.get_transaction_by_hash(receipt.transaction_hash).await.unwrap().unwrap(); + + println!( + "Transaction included in block {}", + receipt.block_number.expect("Failed to get block number") + ); + + assert!(tx.max_fee_per_blob_gas().unwrap() >= BLOB_TX_MIN_BLOB_GASPRICE); + assert_eq!(receipt.from, alice); + assert_eq!(receipt.to, Some(bob)); + assert_eq!( + receipt.blob_gas_used.expect("Expected to be EIP-4844 transaction"), + DATA_GAS_PER_BLOB + ); +} diff --git a/crates/anvil/tests/it/eip7702.rs b/crates/anvil/tests/it/eip7702.rs new file mode 100644 index 0000000000000..dfc93bfe08aa8 --- /dev/null +++ b/crates/anvil/tests/it/eip7702.rs @@ -0,0 +1,78 @@ +use crate::utils::http_provider; +use alloy_consensus::{transaction::TxEip7702, SignableTransaction}; +use alloy_network::{ReceiptResponse, TransactionBuilder, TxSignerSync}; +use alloy_primitives::{bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{Authorization, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_signer::SignerSync; +use anvil::{spawn, EthereumHardfork, NodeConfig}; + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip7702_tx() { + let node_config = NodeConfig::test().with_hardfork(Some(EthereumHardfork::Prague.into())); + let (_api, handle) = spawn(node_config).await; + let provider = http_provider(&handle.http_endpoint()); + + let wallets = handle.dev_wallets().collect::>(); + + // deploy simple contract forwarding calldata to LOG0 + // PUSH7(CALLDATASIZE PUSH0 PUSH0 CALLDATACOPY CALLDATASIZE PUSH0 LOG0) PUSH0 MSTORE PUSH1(7) + // PUSH1(25) RETURN + let logger_bytecode = bytes!("66365f5f37365fa05f5260076019f3"); + + let eip1559_est = provider.estimate_eip1559_fees(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: U256::from(31337u64), + address: contract, + nonce: 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: from, + input: bytes!("11112222"), + authorization_list: vec![authorization], + ..Default::default() + }; + let signature = wallets[1].sign_transaction_sync(&mut tx).unwrap(); + + let tx = tx.into_signed(signature); + let mut encoded = Vec::new(); + tx.eip2718_encode(&mut encoded); + + let receipt = + provider.send_raw_transaction(&encoded).await.unwrap().get_receipt().await.unwrap(); + let log = &receipt.inner.inner.logs()[0]; + // assert that log was from EOA which signed authorization + assert_eq!(log.address(), from); + assert_eq!(log.topics().len(), 0); + assert_eq!(log.data().data, log_data); +} diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index c539445be2b6d..e1dc19f6f94de 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1,26 +1,29 @@ //! various fork related test -use crate::{abi::*, utils}; -use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; -use anvil_core::{eth::transaction::EthTransactionRequest, types::Forking}; -use ethers::{ - core::rand, - prelude::{Bytes, LocalWallet, Middleware, SignerMiddleware}, - providers::{Http, Provider}, - signers::Signer, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Chain, TransactionRequest, - U256, - }, +use crate::{ + abi::{Greeter, ERC721}, + utils::{http_provider, http_provider_with_signer}, +}; +use alloy_chains::NamedChain; +use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder, TransactionResponse}; +use alloy_primitives::{address, b256, bytes, uint, Address, Bytes, TxHash, TxKind, U256, U64}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + anvil::Forking, + request::{TransactionInput, TransactionRequest}, + BlockId, BlockNumberOrTag, BlockTransactionsKind, }; -use foundry_common::get_http_provider; +use alloy_serde::WithOtherFields; +use alloy_signer_local::PrivateKeySigner; +use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; +use foundry_common::provider::get_http_provider; use foundry_config::Config; -use foundry_evm::utils::h160_to_b160; -use foundry_utils::{rpc, rpc::next_http_rpc_endpoint}; +use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_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; const BLOCK_TIMESTAMP: u64 = 1_650_274_250u64; @@ -33,7 +36,6 @@ pub struct LocalFork { fork_handle: NodeHandle, } -// === impl LocalFork === #[allow(dead_code)] impl LocalFork { /// Spawns two nodes with the test config @@ -53,9 +55,39 @@ impl LocalFork { pub fn fork_config() -> NodeConfig { NodeConfig::test() - .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())) + .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())) .with_fork_block_number(Some(BLOCK_NUMBER)) - .silent() +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_gas_limit_applied_from_config() { + let (api, _handle) = spawn(fork_config().with_gas_limit(Some(10_000_000_u128))).await; + + assert_eq!(api.gas_limit(), uint!(10_000_000_U256)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_gas_limit_disabled_from_config() { + let (api, handle) = spawn(fork_config().disable_block_gas_limit(true)).await; + + // see https://github.com/foundry-rs/foundry/pull/8933 + assert_eq!(api.gas_limit(), U256::from(U64::MAX)); + + // try to mine a couple blocks + let provider = handle.http_provider(); + let tx = TransactionRequest::default() + .to(Address::random()) + .value(U256::from(1337u64)) + .from(handle.dev_wallets().next().unwrap().address()); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let tx = TransactionRequest::default() + .to(Address::random()) + .value(U256::from(1337u64)) + .from(handle.dev_wallets().next().unwrap().address()); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -64,7 +96,7 @@ async fn test_spawn_fork() { assert!(api.is_fork()); let head = api.block_number().unwrap(); - assert_eq!(head, BLOCK_NUMBER.into()) + assert_eq!(head, U256::from(BLOCK_NUMBER)) } #[tokio::test(flavor = "multi_thread")] @@ -74,7 +106,7 @@ async fn test_fork_eth_get_balance() { for _ in 0..10 { let addr = Address::random(); let balance = api.balance(addr, None).await.unwrap(); - let provider_balance = provider.get_balance(addr, None).await.unwrap(); + let provider_balance = provider.get_balance(addr).await.unwrap(); assert_eq!(balance, provider_balance) } } @@ -90,17 +122,11 @@ async fn test_fork_eth_get_balance_after_mine() { let address = Address::random(); - let _balance = provider - .get_balance(address, Some(BlockNumber::Number(number.into()).into())) - .await - .unwrap(); + let _balance = provider.get_balance(address).await.unwrap(); api.evm_mine(None).await.unwrap(); - let _balance = provider - .get_balance(address, Some(BlockNumber::Number(number.into()).into())) - .await - .unwrap(); + let _balance = provider.get_balance(address).await.unwrap(); } // @@ -114,13 +140,11 @@ async fn test_fork_eth_get_code_after_mine() { let address = Address::random(); - let _code = - provider.get_code(address, Some(BlockNumber::Number(number.into()).into())).await.unwrap(); + let _code = provider.get_code_at(address).block_id(BlockId::number(1)).await.unwrap(); api.evm_mine(None).await.unwrap(); - let _code = - provider.get_code(address, Some(BlockNumber::Number(number.into()).into())).await.unwrap(); + let _code = provider.get_code_at(address).block_id(BlockId::number(1)).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -130,17 +154,24 @@ async fn test_fork_eth_get_code() { for _ in 0..10 { let addr = Address::random(); let code = api.get_code(addr, None).await.unwrap(); - let provider_code = provider.get_code(addr, None).await.unwrap(); + let provider_code = provider.get_code_at(addr).await.unwrap(); assert_eq!(code, provider_code) } - for address in utils::contract_addresses(Chain::Mainnet) { + let addresses: Vec
= vec![ + "0x6b175474e89094c44da98b954eedeac495271d0f".parse().unwrap(), + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48".parse().unwrap(), + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse().unwrap(), + "0x1F98431c8aD98523631AE4a59f267346ea31F984".parse().unwrap(), + "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45".parse().unwrap(), + ]; + for address in addresses { let prev_code = api - .get_code(address, Some(BlockNumber::Number((BLOCK_NUMBER - 10).into()).into())) + .get_code(address, Some(BlockNumberOrTag::Number(BLOCK_NUMBER - 10).into())) .await .unwrap(); let code = api.get_code(address, None).await.unwrap(); - let provider_code = provider.get_code(address, None).await.unwrap(); + let provider_code = provider.get_code_at(address).await.unwrap(); assert_eq!(code, prev_code); assert_eq!(code, provider_code); assert!(!code.as_ref().is_empty()); @@ -154,25 +185,47 @@ async fn test_fork_eth_get_nonce() { for _ in 0..10 { let addr = Address::random(); - let api_nonce = api.transaction_count(addr, None).await.unwrap(); - let provider_nonce = provider.get_transaction_count(addr, None).await.unwrap(); + let api_nonce = api.transaction_count(addr, None).await.unwrap().to::(); + let provider_nonce = provider.get_transaction_count(addr).await.unwrap(); assert_eq!(api_nonce, provider_nonce); } let addr = Config::DEFAULT_SENDER; - let api_nonce = api.transaction_count(addr, None).await.unwrap(); - let provider_nonce = provider.get_transaction_count(addr, None).await.unwrap(); + let api_nonce = api.transaction_count(addr, None).await.unwrap().to::(); + let provider_nonce = provider.get_transaction_count(addr).await.unwrap(); assert_eq!(api_nonce, provider_nonce); } +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_optimism_with_transaction_hash() { + use std::str::FromStr; + + // Fork to a block with a specific transaction + let fork_tx_hash = + TxHash::from_str("fcb864b5a50f0f0b111dbbf9e9167b2cb6179dfd6270e1ad53aac6049c0ec038") + .unwrap(); + let (api, _handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(rpc::next_rpc_endpoint(NamedChain::Optimism))) + .with_fork_transaction_hash(Some(fork_tx_hash)), + ) + .await; + + // Make sure the fork starts from previous block + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, 125777954 - 1); +} + #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_fee_history() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); let count = 10u64; - let _history = api.fee_history(count.into(), BlockNumber::Latest, vec![]).await.unwrap(); - let _provider_history = provider.fee_history(count, BlockNumber::Latest, &[]).await.unwrap(); + let _history = + api.fee_history(U256::from(count), BlockNumberOrTag::Latest, vec![]).await.unwrap(); + let _provider_history = + provider.get_fee_history(count, BlockNumberOrTag::Latest, &[]).await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -184,36 +237,33 @@ async fn test_fork_reset() { let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let initial_nonce = provider.get_transaction_count(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.transaction_index, 0u64.into()); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + assert_eq!(tx.transaction_index, Some(0)); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); - api.anvil_reset(Some(Forking { - json_rpc_url: None, - block_number: Some(block_number.as_u64()), - })) - .await - .unwrap(); + api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(block_number) })) + .await + .unwrap(); // reset block number assert_eq!(block_number, provider.get_block_number().await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); // reset to latest @@ -224,41 +274,166 @@ async fn test_fork_reset() { } #[tokio::test(flavor = "multi_thread")] -async fn test_fork_snapshotting() { - let (api, handle) = spawn(fork_config()).await; +async fn test_fork_reset_setup() { + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let snapshot = api.evm_snapshot().await.unwrap(); + let dead_addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); + + let block_number = provider.get_block_number().await.unwrap(); + assert_eq!(block_number, 0); + + let local_balance = provider.get_balance(dead_addr).await.unwrap(); + assert_eq!(local_balance, U256::ZERO); + + api.anvil_reset(Some(Forking { + json_rpc_url: Some(rpc::next_http_archive_rpc_url()), + block_number: Some(BLOCK_NUMBER), + })) + .await + .unwrap(); + + let block_number = provider.get_block_number().await.unwrap(); + assert_eq!(block_number, BLOCK_NUMBER); + + let remote_balance = provider.get_balance(dead_addr).await.unwrap(); + assert_eq!(remote_balance, U256::from(DEAD_BALANCE_AT_BLOCK_NUMBER)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_state_snapshotting() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + let state_snapshot = api.evm_snapshot().await.unwrap(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let initial_nonce = provider.get_transaction_count(from).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let provider = handle.http_provider(); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let provider = handle.http_provider(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); - assert!(api.evm_revert(snapshot).await.unwrap()); + assert!(api.evm_revert(state_snapshot).await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); } +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_state_snapshotting_repeated() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + let state_snapshot = api.evm_snapshot().await.unwrap(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let block_number = provider.get_block_number().await.unwrap(); + + let initial_nonce = provider.get_transaction_count(from).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(92u64)).unwrap(); + + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let tx_provider = handle.http_provider(); + let _ = tx_provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + let to_balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance_before.saturating_add(amount), to_balance); + + let _second_state_snapshot = api.evm_snapshot().await.unwrap(); + + assert!(api.evm_revert(state_snapshot).await.unwrap()); + + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce, initial_nonce); + let balance = provider.get_balance(from).await.unwrap(); + assert_eq!(balance, handle.genesis_balance()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, handle.genesis_balance()); + assert_eq!(block_number, provider.get_block_number().await.unwrap()); + + // invalidated + // TODO enable after + // assert!(!api.evm_revert(second_snapshot).await.unwrap()); + + // nothing is reverted, snapshot gone + assert!(!api.evm_revert(state_snapshot).await.unwrap()); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_state_snapshotting_blocks() { + let (api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + let state_snapshot = api.evm_snapshot().await.unwrap(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let from = accounts[0].address(); + let to = accounts[1].address(); + let block_number = provider.get_block_number().await.unwrap(); + + let initial_nonce = provider.get_transaction_count(from).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); + + // send the transaction + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let block_number_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_number_after, block_number + 1); + + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + let to_balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance_before.saturating_add(amount), to_balance); + + assert!(api.evm_revert(state_snapshot).await.unwrap()); + + assert_eq!(initial_nonce, provider.get_transaction_count(from).await.unwrap()); + let block_number_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_number_after, block_number); + + // repeat transaction + let _ = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + + // revert again: nothing to revert since state snapshot gone + assert!(!api.evm_revert(state_snapshot).await.unwrap()); + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert_eq!(nonce, initial_nonce + 1); + let block_number_after = provider.get_block_number().await.unwrap(); + assert_eq!(block_number_after, block_number + 1); +} + /// tests that the remote state and local state are kept separate. /// changes don't make into the read only Database that holds the remote state, which is flushed to /// a cache file. @@ -269,42 +444,46 @@ async fn test_separate_states() { let addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); - let remote_balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(remote_balance, 12556104082473169733500u128.into()); + let remote_balance = provider.get_balance(addr).await.unwrap(); + assert_eq!(remote_balance, U256::from(12556104082473169733500u128)); - api.anvil_set_balance(addr, 1337u64.into()).await.unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, 1337u64.into()); + api.anvil_set_balance(addr, U256::from(1337u64)).await.unwrap(); + let balance = provider.get_balance(addr).await.unwrap(); + assert_eq!(balance, U256::from(1337u64)); let fork = api.get_fork().unwrap(); let fork_db = fork.database.read().await; - let acc = fork_db.inner().db().accounts.read().get(&h160_to_b160(addr)).cloned().unwrap(); + let acc = fork_db + .maybe_inner() + .expect("could not get fork db inner") + .db() + .accounts + .read() + .get(&addr) + .cloned() + .unwrap(); - assert_eq!(acc.balance, remote_balance.into()) + assert_eq!(acc.balance, remote_balance); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_on_fork() { let (_api, handle) = spawn(fork_config().with_fork_block_number(Some(14723772u64))).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumWallet = wallet.into(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let greeter_contract = Greeter::deploy(&provider, "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -312,36 +491,69 @@ async fn can_reset_properly() { let (origin_api, origin_handle) = spawn(NodeConfig::test()).await; let account = origin_handle.dev_accounts().next().unwrap(); let origin_provider = origin_handle.http_provider(); - let origin_nonce = 1u64.into(); - origin_api.anvil_set_nonce(account, origin_nonce).await.unwrap(); + let origin_nonce = 1u64; + origin_api.anvil_set_nonce(account, U256::from(origin_nonce)).await.unwrap(); - assert_eq!(origin_nonce, origin_provider.get_transaction_count(account, None).await.unwrap()); + assert_eq!(origin_nonce, origin_provider.get_transaction_count(account).await.unwrap()); let (fork_api, fork_handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(origin_handle.http_endpoint()))).await; let fork_provider = fork_handle.http_provider(); - assert_eq!(origin_nonce, fork_provider.get_transaction_count(account, None).await.unwrap()); + let fork_tx_provider = http_provider(&fork_handle.http_endpoint()); + assert_eq!(origin_nonce, fork_provider.get_transaction_count(account).await.unwrap()); let to = Address::random(); - let to_balance = fork_provider.get_balance(to, None).await.unwrap(); - let tx = TransactionRequest::new().from(account).to(to).value(1337u64); - let tx = fork_provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let to_balance = fork_provider.get_balance(to).await.unwrap(); + let tx = TransactionRequest::default().from(account).to(to).value(U256::from(1337u64)); + let tx = WithOtherFields::new(tx); + let tx = fork_tx_provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // nonce incremented by 1 - assert_eq!(origin_nonce + 1, fork_provider.get_transaction_count(account, None).await.unwrap()); + assert_eq!(origin_nonce + 1, fork_provider.get_transaction_count(account).await.unwrap()); // resetting to origin state fork_api.anvil_reset(Some(Forking::default())).await.unwrap(); // nonce reset to origin - assert_eq!(origin_nonce, fork_provider.get_transaction_count(account, None).await.unwrap()); + assert_eq!(origin_nonce, fork_provider.get_transaction_count(account).await.unwrap()); // balance is reset - assert_eq!(to_balance, fork_provider.get_balance(to, None).await.unwrap()); + assert_eq!(to_balance, fork_provider.get_balance(to).await.unwrap()); // tx does not exist anymore - assert!(fork_provider.get_transaction(tx.transaction_hash).await.unwrap().is_none()) + assert!(fork_tx_provider.get_transaction_by_hash(tx.transaction_hash).await.unwrap().is_none()) +} + +// Ref: +#[tokio::test(flavor = "multi_thread")] +async fn can_reset_fork_to_new_fork() { + let eth_rpc_url = next_rpc_endpoint(NamedChain::Mainnet); + let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(eth_rpc_url))).await; + let provider = handle.http_provider(); + + let op = address!("C0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007"); // L2CrossDomainMessenger - Dead on mainnet. + + let tx = TransactionRequest::default().with_to(op).with_input("0x54fd4d50"); + + let tx = WithOtherFields::new(tx); + + let mainnet_call_output = provider.call(&tx).await.unwrap(); + + assert_eq!(mainnet_call_output, Bytes::new()); // 0x + + let optimism = next_rpc_endpoint(NamedChain::Optimism); + + api.anvil_reset(Some(Forking { + json_rpc_url: Some(optimism.to_string()), + block_number: Some(124659890), + })) + .await + .unwrap(); + + let code = provider.get_code_at(op).await.unwrap(); + + assert_ne!(code, Bytes::new()); } #[tokio::test(flavor = "multi_thread")] @@ -351,39 +563,52 @@ async fn test_fork_timestamp() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); - let block = provider.get_block(BLOCK_NUMBER).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP); + let block = provider + .get_block(BlockId::Number(BLOCK_NUMBER.into()), false.into()) + .await + .unwrap() + .unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); - let elapsed = start.elapsed().as_secs(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let elapsed = start.elapsed().as_secs() + 1; // ensure the diff between the new mined block and the original block is within the elapsed time - let diff = block.timestamp - BLOCK_TIMESTAMP; - assert!(diff <= elapsed.into(), "diff={diff}, elapsed={elapsed}"); + let diff = block.header.timestamp - BLOCK_TIMESTAMP; + assert!(diff <= elapsed, "diff={diff}, elapsed={elapsed}"); let start = std::time::Instant::now(); // reset to check timestamp works after resetting api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(BLOCK_NUMBER) })) .await .unwrap(); - let block = provider.get_block(BLOCK_NUMBER).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP); + let block = provider + .get_block(BlockId::Number(BLOCK_NUMBER.into()), false.into()) + .await + .unwrap() + .unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); // FIXME: Awaits endlessly here. - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; - let diff = block.timestamp - BLOCK_TIMESTAMP; - assert!(diff <= elapsed.into()); + let diff = block.header.timestamp - BLOCK_TIMESTAMP; + assert!(diff <= elapsed); // ensure that after setting a timestamp manually, then next block time is correct let start = std::time::Instant::now(); @@ -391,19 +616,23 @@ async fn test_fork_timestamp() { .await .unwrap(); api.evm_set_next_block_timestamp(BLOCK_TIMESTAMP + 1).unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let _tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP + 1); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP + 1); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; - let diff = block.timestamp - (BLOCK_TIMESTAMP + 1); - assert!(diff <= elapsed.into()); + let diff = block.header.timestamp - (BLOCK_TIMESTAMP + 1); + assert!(diff <= elapsed); } #[tokio::test(flavor = "multi_thread")] @@ -422,21 +651,25 @@ async fn test_fork_can_send_tx() { let (api, handle) = spawn(fork_config().with_blocktime(Some(std::time::Duration::from_millis(800)))).await; - let wallet = LocalWallet::new(&mut rand::thread_rng()); - - api.anvil_set_balance(wallet.address(), U256::from(1e18 as u64)).await.unwrap(); + let wallet = PrivateKeySigner::random(); + let signer = wallet.address(); + let provider = handle.http_provider(); + // let provider = SignerMiddleware::new(provider, wallet); - let provider = SignerMiddleware::new(handle.http_provider(), wallet); + api.anvil_set_balance(signer, U256::MAX).await.unwrap(); + api.anvil_impersonate_account(signer).await.unwrap(); // Added until WalletFiller for alloy-provider is fixed. + let balance = provider.get_balance(signer).await.unwrap(); + assert_eq!(balance, U256::MAX); let addr = Address::random(); - let val = 1337u64; - let tx = TransactionRequest::new().to(addr).value(val); - + let val = U256::from(1337u64); + let tx = TransactionRequest::default().to(addr).value(val).from(signer); + let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(addr).await.unwrap(); + assert_eq!(balance, val); } // @@ -451,39 +684,50 @@ async fn test_fork_nft_set_approve_all() { .await; // create and fund a random wallet - let wallet = LocalWallet::new(&mut rand::thread_rng()); - api.anvil_set_balance(wallet.address(), U256::from(1000e18 as u64)).await.unwrap(); + let wallet = PrivateKeySigner::random(); + let signer = wallet.address(); + api.anvil_set_balance(signer, U256::from(1000e18)).await.unwrap(); - let provider = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet.clone())); + let provider = handle.http_provider(); // pick a random nft let nouns_addr: Address = "0x9c8ff314c9bc7f6e59a9d9225fb22946427edc03".parse().unwrap(); let owner: Address = "0x052564eb0fd8b340803df55def89c25c432f43f4".parse().unwrap(); - let token_id: U256 = 154u64.into(); - - let nouns = Erc721::new(nouns_addr, Arc::clone(&provider)); - - let real_onwer = nouns.owner_of(token_id).call().await.unwrap(); - assert_eq!(real_onwer, owner); - let approval = nouns.set_approval_for_all(nouns_addr, true); - let tx = approval.send().await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let token_id: U256 = U256::from(154u64); + + let nouns = ERC721::new(nouns_addr, provider.clone()); + + let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); + assert_eq!(real_owner._0, owner); + let approval = nouns.setApprovalForAll(nouns_addr, true); + let tx = TransactionRequest::default() + .from(owner) + .to(nouns_addr) + .with_input(approval.calldata().to_owned()); + let tx = WithOtherFields::new(tx); + api.anvil_impersonate_account(owner).await.unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); // transfer: impersonate real owner and transfer nft - api.anvil_impersonate_account(real_onwer).await.unwrap(); - - api.anvil_set_balance(real_onwer, U256::from(10000e18 as u64)).await.unwrap(); - - let call = nouns.transfer_from(real_onwer, wallet.address(), token_id); - let mut tx: TypedTransaction = call.tx; - tx.set_from(real_onwer); - provider.fill_transaction(&mut tx, None).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); - - let real_onwer = nouns.owner_of(token_id).call().await.unwrap(); - assert_eq!(real_onwer, wallet.address()); + api.anvil_impersonate_account(real_owner._0).await.unwrap(); + + api.anvil_set_balance(real_owner._0, U256::from(10000e18 as u64)).await.unwrap(); + + let call = nouns.transferFrom(real_owner._0, signer, token_id); + let tx = TransactionRequest::default() + .from(real_owner._0) + .to(nouns_addr) + .with_input(call.calldata().to_owned()); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); + + let real_owner = nouns.ownerOf(token_id).call().await.unwrap(); + assert_eq!(real_owner._0, wallet.address()); } // @@ -506,7 +750,7 @@ async fn test_fork_with_custom_chain_id() { let config_chain_id = handle.config().chain_id; // check that the chainIds are the same - assert_eq!(eth_chain_id.unwrap().unwrap().as_u64(), 3145u64); + assert_eq!(eth_chain_id.unwrap().unwrap().to::(), 3145u64); assert_eq!(txn_chain_id, 3145u64); assert_eq!(config_chain_id, Some(3145u64)); } @@ -530,16 +774,18 @@ async fn test_fork_can_send_opensea_tx() { let input: Bytes = "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ff2e795f5000000000000000000000000000023f28ae3e9756ba982a6290f9081b6a84900b758000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000003235b597a78eabcb08ffcb4d97411073211dbcb0000000000000000000000000000000000000000000000000000000000000e72000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062ad47c20000000000000000000000000000000000000000000000000000000062d43104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df44e65d2a2cf40000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc900000000000000000000000000000000000000000000000000005543df729c0000000000000000000000000006eb234847a9e3a546539aac57a071c01dc3f398600000000000000000000000000000000000000000000000000000000000000416d39b5352353a22cf2d44faa696c2089b03137a13b5acfee0366306f2678fede043bc8c7e422f6f13a3453295a4a063dac7ee6216ab7bade299690afc77397a51c00000000000000000000000000000000000000000000000000000000000000".parse().unwrap(); let to: Address = "0x00000000006c3852cbef3e08e8df289169ede581".parse().unwrap(); - let tx = TransactionRequest::new() + let tx = TransactionRequest::default() .from(sender) .to(to) - .value(20000000000000000u64) - .data(input) - .gas_price(22180711707u64) - .gas(150_000u64); - - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + .value(U256::from(20000000000000000u64)) + .with_input(input) + .with_gas_price(22180711707u128) + .with_gas_limit(150_000); + let tx = WithOtherFields::new(tx); + + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); } #[tokio::test(flavor = "multi_thread")] @@ -551,13 +797,13 @@ async fn test_fork_base_fee() { let provider = handle.http_provider(); - api.anvil_set_next_block_base_fee_per_gas(U256::zero()).await.unwrap(); + api.anvil_set_next_block_base_fee_per_gas(U256::ZERO).await.unwrap(); let addr = Address::random(); - let val = 1337u64; - let tx = TransactionRequest::new().from(from).to(addr).value(val); - - let _res = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let val = U256::from(1337u64); + let tx = TransactionRequest::default().from(from).to(addr).value(val); + let tx = WithOtherFields::new(tx); + let _res = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -566,40 +812,43 @@ async fn test_fork_init_base_fee() { let provider = handle.http_provider(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); // - assert_eq!(block.number.unwrap().as_u64(), 13184859u64); - let init_base_fee = block.base_fee_per_gas.unwrap(); - assert_eq!(init_base_fee, 63739886069u64.into()); + assert_eq!(block.header.number, 13184859u64); + let init_base_fee = block.header.base_fee_per_gas.unwrap(); + assert_eq!(init_base_fee, 63739886069); api.mine_one().await; - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); - let next_base_fee = block.base_fee_per_gas.unwrap(); + let next_base_fee = block.header.base_fee_per_gas.unwrap(); assert!(next_base_fee < init_base_fee); } #[tokio::test(flavor = "multi_thread")] async fn test_reset_fork_on_new_blocks() { - let (api, handle) = spawn( - NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())).silent(), - ) - .await; + let (api, handle) = + spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await; let anvil_provider = handle.http_provider(); - let endpoint = next_http_rpc_endpoint(); - let provider = Arc::new(get_http_provider(&endpoint).interval(Duration::from_secs(2))); + let provider = Arc::new(get_http_provider(&endpoint)); let current_block = anvil_provider.get_block_number().await.unwrap(); handle.task_manager().spawn_reset_on_new_polled_blocks(provider.clone(), api); - let mut stream = provider.watch_blocks().await.unwrap(); + let mut stream = provider + .watch_blocks() + .await + .unwrap() + .with_poll_interval(Duration::from_secs(2)) + .into_stream() + .flat_map(futures::stream::iter); // the http watcher may fetch multiple blocks at once, so we set a timeout here to offset edge // cases where the stream immediately returns a block - tokio::time::sleep(Chain::Mainnet.average_blocktime_hint().unwrap()).await; + tokio::time::sleep(Duration::from_secs(12)).await; stream.next().await.unwrap(); stream.next().await.unwrap(); @@ -614,17 +863,20 @@ async fn test_fork_call() { let to: Address = "0x99d1Fa417f94dcD62BfE781a1213c092a47041Bc".parse().unwrap(); let block_number = 14746300u64; - let provider = Provider::::try_from(rpc::next_http_archive_rpc_endpoint()).unwrap(); - let mut tx = TypedTransaction::default(); - tx.set_to(to).set_data(input.clone()); - let res0 = - provider.call(&tx, Some(BlockNumber::Number(block_number.into()).into())).await.unwrap(); + let provider = http_provider(rpc::next_http_archive_rpc_url().as_str()); + let tx = TransactionRequest::default().to(to).with_input(input.clone()); + let tx = WithOtherFields::new(tx); + let res0 = provider.call(&tx).block(BlockId::Number(block_number.into())).await.unwrap(); let (api, _) = spawn(fork_config().with_fork_block_number(Some(block_number))).await; let res1 = api .call( - EthTransactionRequest { to: Some(to), data: Some(input), ..Default::default() }, + WithOtherFields::new(TransactionRequest { + to: Some(TxKind::from(to)), + input: input.into(), + ..Default::default() + }), None, None, ) @@ -638,11 +890,11 @@ async fn test_fork_call() { async fn test_fork_block_timestamp() { let (api, _) = spawn(fork_config()).await; - let initial_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let initial_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert!(initial_block.timestamp.as_u64() < latest_block.timestamp.as_u64()); + assert!(initial_block.header.timestamp <= latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -650,14 +902,14 @@ async fn test_fork_snapshot_block_timestamp() { let (api, _) = spawn(fork_config()).await; let snapshot_id = api.evm_snapshot().await.unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - let initial_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + let initial_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); api.evm_revert(snapshot_id).await.unwrap(); - api.evm_set_next_block_timestamp(initial_block.timestamp.as_u64()).unwrap(); - api.anvil_mine(Some(1.into()), None).await.unwrap(); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + api.evm_set_next_block_timestamp(initial_block.header.timestamp).unwrap(); + api.anvil_mine(Some(U256::from(1)), None).await.unwrap(); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert_eq!(initial_block.timestamp.as_u64(), latest_block.timestamp.as_u64()); + assert_eq!(initial_block.header.timestamp, latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -669,32 +921,33 @@ async fn test_fork_uncles_fetch() { let block_with_uncles = 190u64; let block = - api.block_by_number(BlockNumber::Number(block_with_uncles.into())).await.unwrap().unwrap(); + api.block_by_number(BlockNumberOrTag::Number(block_with_uncles)).await.unwrap().unwrap(); assert_eq!(block.uncles.len(), 2); - let count = provider.get_uncle_count(block_with_uncles).await.unwrap(); - assert_eq!(count.as_usize(), block.uncles.len()); + let count = provider.get_uncle_count(block_with_uncles.into()).await.unwrap(); + assert_eq!(count as usize, block.uncles.len()); - let count = provider.get_uncle_count(block.hash.unwrap()).await.unwrap(); - assert_eq!(count.as_usize(), block.uncles.len()); + let hash = BlockId::hash(block.header.hash); + let count = provider.get_uncle_count(hash).await.unwrap(); + assert_eq!(count as usize, block.uncles.len()); for (uncle_idx, uncle_hash) in block.uncles.iter().enumerate() { // Try with block number let uncle = provider - .get_uncle(block_with_uncles, (uncle_idx as u64).into()) + .get_uncle(BlockId::number(block_with_uncles), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.hash.unwrap()); + assert_eq!(*uncle_hash, uncle.header.hash); // Try with block hash let uncle = provider - .get_uncle(block.hash.unwrap(), (uncle_idx as u64).into()) + .get_uncle(BlockId::hash(block.header.hash), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.hash.unwrap()); + assert_eq!(*uncle_hash, uncle.header.hash); } } @@ -711,34 +964,36 @@ async fn test_fork_block_transaction_count() { // transfer: impersonate real sender api.anvil_impersonate_account(sender).await.unwrap(); - let tx = TransactionRequest::new().from(sender).value(42u64).gas(100_000); - provider.send_transaction(tx, None).await.unwrap(); + let tx = + TransactionRequest::default().from(sender).value(U256::from(42u64)).with_gas_limit(100_000); + let tx = WithOtherFields::new(tx); + let _ = provider.send_transaction(tx).await.unwrap(); let pending_txs = - api.block_transaction_count_by_number(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(pending_txs.as_usize(), 1); + api.block_transaction_count_by_number(BlockNumberOrTag::Pending).await.unwrap().unwrap(); + assert_eq!(pending_txs.to::(), 1); // mine a new block api.anvil_mine(None, None).await.unwrap(); let pending_txs = - api.block_transaction_count_by_number(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(pending_txs.as_usize(), 0); + api.block_transaction_count_by_number(BlockNumberOrTag::Pending).await.unwrap().unwrap(); + assert_eq!(pending_txs.to::(), 0); let latest_txs = - api.block_transaction_count_by_number(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(latest_txs.as_usize(), 1); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + api.block_transaction_count_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(latest_txs.to::(), 1); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let latest_txs = - api.block_transaction_count_by_hash(latest_block.hash.unwrap()).await.unwrap().unwrap(); - assert_eq!(latest_txs.as_usize(), 1); + api.block_transaction_count_by_hash(latest_block.header.hash).await.unwrap().unwrap(); + assert_eq!(latest_txs.to::(), 1); // check txs count on an older block: 420000 has 3 txs on mainnet let count_txs = api - .block_transaction_count_by_number(BlockNumber::Number(420000.into())) + .block_transaction_count_by_number(BlockNumberOrTag::Number(420000)) .await .unwrap() .unwrap(); - assert_eq!(count_txs.as_usize(), 3); + assert_eq!(count_txs.to::(), 3); let count_txs = api .block_transaction_count_by_hash( "0xb3b0e3e0c64e23fb7f1ccfd29245ae423d2f6f1b269b63b70ff882a983ce317c".parse().unwrap(), @@ -746,7 +1001,7 @@ async fn test_fork_block_transaction_count() { .await .unwrap() .unwrap(); - assert_eq!(count_txs.as_usize(), 3); + assert_eq!(count_txs.to::(), 3); } // @@ -757,51 +1012,53 @@ async fn can_impersonate_in_fork() { let token_holder: Address = "0x2f0b23f53734252bda2277357e97e1517d6b042a".parse().unwrap(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337u64); // fund the impersonated account - api.anvil_set_balance(token_holder, U256::from(1e18 as u64)).await.unwrap(); + api.anvil_set_balance(token_holder, U256::from(1e18)).await.unwrap(); - let tx = TransactionRequest::new().from(token_holder).to(to).value(val); - - let res = provider.send_transaction(tx.clone(), None).await; + let tx = TransactionRequest::default().from(token_holder).to(to).value(val); + let tx = WithOtherFields::new(tx); + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); api.anvil_impersonate_account(token_holder).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, token_holder); - assert_eq!(res.status, Some(1u64.into())); + let status = res.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); api.anvil_stop_impersonating_account(token_holder).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); } // #[tokio::test(flavor = "multi_thread")] +#[ignore] async fn test_total_difficulty_fork() { let (api, handle) = spawn(fork_config()).await; - let total_difficulty: U256 = 46_673_965_560_973_856_260_636u128.into(); - let difficulty: U256 = 13_680_435_288_526_144u128.into(); + let total_difficulty = U256::from(46_673_965_560_973_856_260_636u128); + let difficulty = U256::from(13_680_435_288_526_144u128); let provider = handle.http_provider(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.total_difficulty, Some(total_difficulty)); - assert_eq!(block.difficulty, difficulty); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.total_difficulty, Some(total_difficulty)); + assert_eq!(block.header.difficulty, difficulty); api.mine_one().await; api.mine_one().await; let next_total_difficulty = total_difficulty + difficulty; - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.total_difficulty, Some(next_total_difficulty)); - assert_eq!(block.difficulty, U256::zero()); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.total_difficulty, Some(next_total_difficulty)); + assert_eq!(block.header.difficulty, U256::ZERO); } // @@ -828,6 +1085,26 @@ async fn test_transaction_receipt() { assert!(receipt.is_none()); } +// +#[tokio::test(flavor = "multi_thread")] +async fn test_block_receipts() { + let (api, _) = spawn(fork_config()).await; + + // Receipts from the forked block (14608400) + let receipts = api.block_receipts(BlockNumberOrTag::Number(BLOCK_NUMBER).into()).await.unwrap(); + assert!(receipts.is_some()); + + // Receipts from a block in the future (14608401) + let receipts = + api.block_receipts(BlockNumberOrTag::Number(BLOCK_NUMBER + 1).into()).await.unwrap(); + assert!(receipts.is_none()); + + // Receipts from a block hash (14608400) + let hash = b256!("4c1c76f89cfe4eb503b09a0993346dd82865cac9d76034efc37d878c66453f0a"); + let receipts = api.block_receipts(BlockId::Hash(hash.into())).await.unwrap(); + assert!(receipts.is_some()); +} + #[tokio::test(flavor = "multi_thread")] async fn can_override_fork_chain_id() { let chain_id_override = 5u64; @@ -837,27 +1114,402 @@ async fn can_override_fork_chain_id() { .with_chain_id(Some(chain_id_override)), ) .await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumWallet = wallet.into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) + let greeter_contract = + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap(); + + assert_eq!("Hello World!", greeting._0); + let greeter_contract = + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); + + let provider = handle.http_provider(); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, chain_id_override); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_reset_moonbeam() { + crate::init_tracing(); + let (api, handle) = spawn( + fork_config() + .with_eth_rpc_url(Some("https://rpc.api.moonbeam.network".to_string())) + .with_fork_block_number(None::), + ) + .await; + let provider = handle.http_provider(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let from = accounts[0].address(); + + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + api.anvil_impersonate_account(from).await.unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); + + // reset to check timestamp works after resetting + api.anvil_reset(Some(Forking { + json_rpc_url: Some("https://rpc.api.moonbeam.network".to_string()), + block_number: None, + })) + .await + .unwrap(); + + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(1337u64)).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); +} + +// + let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(18835000u64))).await; + + api.mine_one().await; + let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + // basefee of +1 block: + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59455969592u64); + + // now reset to block 18835000 -1 + api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(18835000u64 - 1) })) + .await + .unwrap(); + + api.mine_one().await; + let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + // basefee of the forked block: + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59017001138); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_arbitrum_fork_dev_balance() { + let (api, handle) = spawn( + fork_config() + .with_fork_block_number(None::) + .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Arbitrum))), + ) + .await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + for acc in accounts { + let balance = api.balance(acc.address(), Some(Default::default())).await.unwrap(); + assert_eq!(balance, U256::from(100000000000000000000u128)); + } +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_arb_fork_mining() { + let fork_block_number = 266137031u64; + let fork_rpc = next_rpc_endpoint(NamedChain::Arbitrum); + let (api, _handle) = spawn( + fork_config() + .with_fork_block_number(Some(fork_block_number)) + .with_eth_rpc_url(Some(fork_rpc)), + ) + .await; + + let init_blk_num = api.block_number().unwrap().to::(); + + // Mine one + api.mine_one().await; + let mined_blk_num = api.block_number().unwrap().to::(); + + assert_eq!(mined_blk_num, init_blk_num + 1); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_arbitrum_fork_block_number() { + // fork to get initial block for test + let (_, handle) = spawn( + fork_config() + .with_fork_block_number(None::) + .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Arbitrum))), + ) + .await; + let provider = handle.http_provider(); + let initial_block_number = provider.get_block_number().await.unwrap(); + + // fork again at block number returned by `eth_blockNumber` + // if wrong block number returned (e.g. L1) then fork will fail with error code -32000: missing + // trie node + let (api, _) = spawn( + fork_config() + .with_fork_block_number(Some(initial_block_number)) + .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Arbitrum))), + ) + .await; + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number); + + // take snapshot at initial block number + let snapshot_state = api.evm_snapshot().await.unwrap(); + + // mine new block and check block number returned by `eth_blockNumber` + api.mine_one().await; + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number + 1); + + // test block by number API call returns proper block number and `l1BlockNumber` is set + let block_by_number = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block_by_number.header.number, initial_block_number + 1); + assert!(block_by_number.other.get("l1BlockNumber").is_some()); + + // revert to recorded snapshot and check block number + assert!(api.evm_revert(snapshot_state).await.unwrap()); + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number); + + // reset fork to different block number and compare with block returned by `eth_blockNumber` + api.anvil_reset(Some(Forking { + json_rpc_url: Some(next_rpc_endpoint(NamedChain::Arbitrum)), + block_number: Some(initial_block_number - 2), + })) + .await + .unwrap(); + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number - 2); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_base_fork_gas_limit() { + // fork to get initial block for test + let (api, handle) = spawn( + fork_config() + .with_fork_block_number(None::) + .with_eth_rpc_url(Some(next_rpc_endpoint(NamedChain::Base))), + ) + .await; + + let provider = handle.http_provider(); + let block = provider + .get_block(BlockId::Number(BlockNumberOrTag::Latest), BlockTransactionsKind::Hashes) + .await .unwrap() - .send() + .unwrap(); + + assert!(api.gas_limit() >= uint!(96_000_000_U256)); + assert!(block.header.gas_limit >= 96_000_000_u64); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_execution_reverted() { + let target = 16681681u64; + let (api, _handle) = spawn(fork_config().with_fork_block_number(Some(target + 1))).await; + + let resp = api + .call( + WithOtherFields::new(TransactionRequest { + to: Some(TxKind::from(address!("Fd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377"))), + input: TransactionInput::new(bytes!("8f283b3c")), + ..Default::default() + }), + Some(target.into()), + None, + ) + .await; + + assert!(resp.is_err()); + 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); + } - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + // 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)) + .map(|b| b.header.hash) + .and_then(|hash| { + api.backend.mined_transaction_by_block_hash_and_index(hash, expected.1.into()) + }) + .unwrap(); + assert_eq!(tx.tx_hash().to_string(), expected.0.to_string()); + } +} - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); +// +#[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 greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + 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 chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id.as_u64(), chain_id_override); + 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(21000), + )) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + assert!(receipt.status()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_get_account() { + let (_api, handle) = spawn(fork_config()).await; + let provider = handle.http_provider(); + + let accounts = handle.dev_accounts().collect::>(); + + let alice = accounts[0]; + let bob = accounts[1]; + + let init_block = provider.get_block_number().await.unwrap(); + let alice_bal = provider.get_balance(alice).await.unwrap(); + let alice_nonce = provider.get_transaction_count(alice).await.unwrap(); + let alice_acc_init = provider.get_account(alice).await.unwrap(); + + assert_eq!(alice_acc_init.balance, alice_bal); + assert_eq!(alice_acc_init.nonce, alice_nonce); + + let tx = TransactionRequest::default().from(alice).to(bob).value(U256::from(142)); + + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + assert_eq!(init_block + 1, receipt.block_number.unwrap()); + + let alice_acc = provider.get_account(alice).await.unwrap(); + + assert_eq!( + alice_acc.balance, + alice_bal - + (U256::from(142) + + U256::from(receipt.gas_used as u128 * receipt.effective_gas_price)), + ); + assert_eq!(alice_acc.nonce, alice_nonce + 1); + + let alice_acc_prev_block = provider.get_account(alice).number(init_block).await.unwrap(); + + assert_eq!(alice_acc_init, alice_acc_prev_block); } diff --git a/crates/anvil/tests/it/ganache.rs b/crates/anvil/tests/it/ganache.rs deleted file mode 100644 index 3ea0e84f508fc..0000000000000 --- a/crates/anvil/tests/it/ganache.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! tests against local ganache for local debug purposes -#![allow(unused)] -use crate::init_tracing; -use ethers::{ - abi::Address, - contract::{Contract, ContractFactory, ContractInstance}, - core::k256::SecretKey, - prelude::{abigen, Middleware, Signer, SignerMiddleware, TransactionRequest, Ws}, - providers::{Http, Provider}, - signers::LocalWallet, - types::{BlockNumber, U256}, - utils::hex, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; - -// the mnemonic used to start the local ganache instance -const MNEMONIC: &str = - "amazing discover palace once resource choice flush horn wink shift planet relief"; - -fn ganache_wallet() -> LocalWallet { - wallet("552dd2534c4984f892191997d6b1dd9e6a23c7e07b908a6cebfad1d3f2af4c4c") -} - -fn ganache_wallet2() -> LocalWallet { - wallet("305b526d493844b63466be6d48a424ab83f5216011eef860acc6db4c1821adc9") -} - -fn wallet(key_str: &str) -> LocalWallet { - let key_hex = hex::decode(key_str).expect("could not parse as hex"); - let key = SecretKey::from_bytes(key_hex.as_slice().into()).expect("did not get private key"); - key.into() -} - -fn http_client() -> Arc, LocalWallet>> { - let provider = Provider::::try_from("http://127.0.0.1:8545").unwrap(); - Arc::new(SignerMiddleware::new(provider, ganache_wallet())) -} - -async fn ws_client() -> Arc, LocalWallet>> { - let provider = Provider::::connect("ws://127.0.0.1:8545").await.unwrap(); - Arc::new(SignerMiddleware::new(provider, ganache_wallet())) -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_block_number() { - let client = http_client(); - let balance = client - .get_balance(Address::random(), Some(BlockNumber::Number(100u64.into()).into())) - .await; -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_deploy() { - abigen!(Greeter, "test-data/greeter.json"); - let client = http_client(); - - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().legacy().send().await.unwrap(); - - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_emit_logs() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let client = ws_client().await; - - let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); - - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); - - let val = contract - .set_value("Next Message".to_string()) - .legacy() - .send() - .await - .unwrap() - .await - .unwrap() - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_deploy_reverting() { - let client = http_client(); - - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - constructor() { - require(false, ""); - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - println!("{compiled}"); - assert!(!compiled.has_compiler_errors()); - - let contract = compiled.remove_first("Contract").unwrap(); - - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let factory = ContractFactory::new(abi.unwrap(), bytecode.unwrap(), Arc::clone(&client)); - let contract = factory.deploy(()).unwrap().legacy().send().await; - contract.unwrap_err(); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_tx_reverting() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address owner; - constructor() public { - owner = msg.sender; - } - modifier onlyOwner() { - require(msg.sender == owner, "!authorized"); - _; - } - function getSecret() public onlyOwner view returns(uint256 secret) { - return 123; - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let client = Arc::new(http_client()); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().legacy().send().await.unwrap(); - let provider = SignerMiddleware::new( - Provider::::try_from("http://127.0.0.1:8545").unwrap(), - ganache_wallet2(), - ); - let contract = ContractInstance::new(contract.address(), abi.unwrap(), provider); - let resp = contract.method::<_, U256>("getSecret", ()).unwrap().legacy().call().await; - resp.unwrap_err(); - - /* Ganache rpc errors look like: - < { - < "id": 1627277502538, - < "jsonrpc": "2.0", - < "error": { - < "message": "VM Exception while processing transaction: revert !authorized", - < "code": -32000, - < "data": { - < "0x90264de254689f1d4e7f8670cd97f60d9bc803874fdecb34d249bd1cc3ca823a": { - < "error": "revert", - < "program_counter": 223, - < "return": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b21617574686f72697a6564000000000000000000000000000000000000000000", - < "reason": "!authorized" - < }, - < "stack": "c: VM Exception while processing transaction: revert !authorized\n at Function.c.fromResults (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:4:192416)\n at /usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:42:50402", - < "name": "c" - < } - < } - */ -} diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index 27c6055be3ee3..55f8321997239 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -1,15 +1,29 @@ //! Gas related tests +use crate::utils::http_provider_with_signer; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{uint, Address, U256, U64}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_serde::WithOtherFields; use anvil::{eth::fees::INITIAL_BASE_FEE, spawn, NodeConfig}; -use ethers::{ - prelude::Middleware, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest, - TransactionRequest, - }, -}; -const GAS_TRANSFER: u64 = 21_000u64; +const GAS_TRANSFER: u128 = 21_000; + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_limit_applied_from_config() { + let (api, _handle) = spawn(NodeConfig::test().with_gas_limit(Some(10_000_000))).await; + + assert_eq!(api.gas_limit(), uint!(10_000_000_U256)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_gas_limit_disabled_from_config() { + let (api, _handle) = spawn(NodeConfig::test().disable_block_gas_limit(true)).await; + + // see https://github.com/foundry-rs/foundry/pull/8933 + assert_eq!(api.gas_limit(), U256::from(U64::MAX)); +} #[tokio::test(flavor = "multi_thread")] async fn test_basefee_full_block() { @@ -17,19 +31,41 @@ async fn test_basefee_full_block() { NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE)).with_gas_limit(Some(GAS_TRANSFER)), ) .await; - let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let base_fee = provider + .get_block(BlockId::latest(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let next_base_fee = provider + .get_block(BlockId::latest(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); assert!(next_base_fee > base_fee); + // max increase, full block - assert_eq!(next_base_fee.as_u64(), INITIAL_BASE_FEE + 125_000_000); + assert_eq!(next_base_fee, INITIAL_BASE_FEE + 125_000_000); } #[tokio::test(flavor = "multi_thread")] @@ -40,32 +76,69 @@ async fn test_basefee_half_block() { .with_gas_limit(Some(GAS_TRANSFER * 2)), ) .await; - let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let tx = TransactionRequest::default().to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let next_base_fee = provider + .get_block(BlockId::latest(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // unchanged, half block - assert_eq!(next_base_fee.as_u64(), INITIAL_BASE_FEE); + assert_eq!(next_base_fee, { INITIAL_BASE_FEE }); } + #[tokio::test(flavor = "multi_thread")] async fn test_basefee_empty_block() { let (api, handle) = spawn(NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE))).await; - let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - let base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let tx = TransactionRequest::default().with_to(Address::random()).with_value(U256::from(1337)); + let tx = WithOtherFields::new(tx); + + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); + + let base_fee = provider + .get_block(BlockId::latest(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // mine empty block api.mine_one().await; - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + let next_base_fee = provider + .get_block(BlockId::latest(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // empty block, decreased base fee assert!(next_base_fee < base_fee); @@ -73,40 +146,73 @@ async fn test_basefee_empty_block() { #[tokio::test(flavor = "multi_thread")] async fn test_respect_base_fee() { - let base_fee = 50u64; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let base_fee = 50u128; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; + let provider = handle.http_provider(); - let mut tx = TypedTransaction::default(); - tx.set_value(100u64); - tx.set_to(Address::random()); + + let tx = TransactionRequest::default().with_to(Address::random()).with_value(U256::from(100)); + let mut tx = WithOtherFields::new(tx); let mut underpriced = tx.clone(); underpriced.set_gas_price(base_fee - 1); - let res = provider.send_transaction(underpriced, None).await; + + let res = provider.send_transaction(underpriced).await; assert!(res.is_err()); assert!(res.unwrap_err().to_string().contains("max fee per gas less than block base fee")); tx.set_gas_price(base_fee); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] async fn test_tip_above_fee_cap() { - let base_fee = 50u64; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let base_fee = 50u128; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; + let provider = handle.http_provider(); - let tx = TypedTransaction::Eip1559( - Eip1559TransactionRequest::new() - .max_fee_per_gas(base_fee) - .max_priority_fee_per_gas(base_fee + 1) - .to(Address::random()) - .value(100u64), - ); - let res = provider.send_transaction(tx, None).await; + + let tx = TransactionRequest::default() + .max_fee_per_gas(base_fee) + .max_priority_fee_per_gas(base_fee + 1) + .with_to(Address::random()) + .with_value(U256::from(100)); + let tx = WithOtherFields::new(tx); + + let res = provider.send_transaction(tx.clone()).await; assert!(res.is_err()); assert!(res .unwrap_err() .to_string() .contains("max priority fee per gas higher than max fee per gas")); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_can_use_fee_history() { + let base_fee = 50u128; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; + let provider = handle.http_provider(); + + for _ in 0..10 { + let fee_history = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); + let next_base_fee = *fee_history.base_fee_per_gas.last().unwrap(); + + let tx = TransactionRequest::default() + .with_to(Address::random()) + .with_value(U256::from(100)) + .with_gas_price(next_base_fee); + let tx = WithOtherFields::new(tx); + + 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() as u64; + 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 as u64); + } +} diff --git a/crates/anvil/tests/it/genesis.rs b/crates/anvil/tests/it/genesis.rs index 6c028599fc146..139a2fe209d5b 100644 --- a/crates/anvil/tests/it/genesis.rs +++ b/crates/anvil/tests/it/genesis.rs @@ -1,7 +1,10 @@ //! genesis.json tests -use anvil::{genesis::Genesis, spawn, NodeConfig}; -use ethers::{abi::Address, prelude::Middleware, types::U256}; +use alloy_genesis::Genesis; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use anvil::{spawn, NodeConfig}; +use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn can_apply_genesis() { @@ -37,11 +40,11 @@ async fn can_apply_genesis() { let provider = handle.http_provider(); - assert_eq!(provider.get_chainid().await.unwrap(), 19763u64.into()); + assert_eq!(provider.get_chain_id().await.unwrap(), 19763u64); - let addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); + let addr: Address = Address::from_str("71562b71999873db5b286df957af199ec94617f7").unwrap(); + let balance = provider.get_balance(addr).await.unwrap(); - let expected: U256 = "ffffffffffffffffffffffffff".parse().unwrap(); + let expected: U256 = U256::from_str_radix("ffffffffffffffffffffffffff", 16).unwrap(); assert_eq!(balance, expected); } diff --git a/crates/anvil/tests/it/geth.rs b/crates/anvil/tests/it/geth.rs deleted file mode 100644 index 31429d0bd4167..0000000000000 --- a/crates/anvil/tests/it/geth.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! tests against local geth for local debug purposes - -use crate::abi::VENDING_MACHINE_CONTRACT; -use ethers::{ - abi::Address, - contract::{Contract, ContractFactory}, - prelude::{Middleware, TransactionRequest}, - providers::Provider, - types::U256, - utils::WEI_IN_ETHER, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use futures::StreamExt; -use std::sync::Arc; -use tokio::time::timeout; - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_geth_pending_transaction() { - let client = Provider::try_from("http://127.0.0.1:8545").unwrap(); - let accounts = client.get_accounts().await.unwrap(); - let tx = TransactionRequest::new() - .from(accounts[0]) - .to(Address::random()) - .value(1337u64) - .nonce(2u64); - - let mut watch_tx_stream = - client.watch_pending_transactions().await.unwrap().transactions_unordered(1).fuse(); - - let _res = client.send_transaction(tx, None).await.unwrap(); - - let pending = timeout(std::time::Duration::from_secs(3), watch_tx_stream.next()).await; - pending.unwrap_err(); -} - -// check how geth returns reverts -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_geth_revert_transaction() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source("VendingMachine", VENDING_MACHINE_CONTRACT).unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("VendingMachine").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let client = Arc::new(Provider::try_from("http://127.0.0.1:8545").unwrap()); - - let account = client.get_accounts().await.unwrap().remove(0); - - // deploy successfully - let factory = - ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), Arc::clone(&client)); - - let mut tx = factory.deploy(()).unwrap().tx; - tx.set_from(account); - - let resp = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - - let contract = - Contract::>::new(resp.contract_address.unwrap(), abi.unwrap(), client); - - let ten = WEI_IN_ETHER.saturating_mul(10u64.into()); - let call = contract.method::<_, ()>("buyRevert", ten).unwrap().value(ten).from(account); - let resp = call.call().await; - let err = resp.unwrap_err().to_string(); - assert!(err.contains("execution reverted: Not enough Ether provided.")); - assert!(err.contains("code: 3")); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_geth_low_gas_limit() { - let provider = Arc::new(Provider::try_from("http://127.0.0.1:8545").unwrap()); - - let account = provider.get_accounts().await.unwrap().remove(0); - - let gas = 21_000u64 - 1; - let tx = TransactionRequest::new() - .to(Address::random()) - .value(U256::from(1337u64)) - .from(account) - .gas(gas); - - let resp = provider.send_transaction(tx, None).await; - - let err = resp.unwrap_err().to_string(); - assert!(err.contains("intrinsic gas too low")); -} diff --git a/crates/anvil/tests/it/ipc.rs b/crates/anvil/tests/it/ipc.rs index f5bb19e1de923..e5d99e01b333d 100644 --- a/crates/anvil/tests/it/ipc.rs +++ b/crates/anvil/tests/it/ipc.rs @@ -1,48 +1,60 @@ //! IPC tests +use crate::{init_tracing, utils::connect_pubsub}; +use alloy_primitives::U256; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::{core::rand, prelude::Middleware, types::U256}; use futures::StreamExt; - -pub fn rand_ipc_endpoint() -> String { - let num: u64 = rand::Rng::gen(&mut rand::thread_rng()); - if cfg!(windows) { - format!(r"\\.\pipe\anvil-ipc-{num}") +use tempfile::TempDir; + +fn ipc_config() -> (Option, NodeConfig) { + let path; + let dir; + if cfg!(unix) { + let tmp = tempfile::tempdir().unwrap(); + path = tmp.path().join("anvil.ipc").to_string_lossy().into_owned(); + dir = Some(tmp); } else { - format!(r"/tmp/anvil-ipc-{num}") + dir = None; + path = format!(r"\\.\pipe\anvil_test_{}.ipc", rand::random::()); } -} - -fn ipc_config() -> NodeConfig { - NodeConfig::test().with_ipc(Some(Some(rand_ipc_endpoint()))) + let config = NodeConfig::test().with_ipc(Some(Some(path))); + (dir, config) } #[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "TODO")] async fn can_get_block_number_ipc() { - let (api, handle) = spawn(ipc_config()).await; + init_tracing(); + + let (_dir, config) = ipc_config(); + let (api, handle) = spawn(config).await; let block_num = api.block_number().unwrap(); - assert_eq!(block_num, U256::zero()); + assert_eq!(block_num, U256::ZERO); - let provider = handle.ipc_provider().await.unwrap(); + let provider = handle.ipc_provider().unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.as_u64().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "TODO")] async fn test_sub_new_heads_ipc() { - let (api, handle) = spawn(ipc_config()).await; - - let provider = handle.ipc_provider().await.unwrap(); + init_tracing(); - let blocks = provider.subscribe_blocks().await.unwrap(); + let (_dir, config) = ipc_config(); + let (api, handle) = spawn(config).await; + let provider = connect_pubsub(handle.ipc_path().unwrap().as_str()).await; // mine a block every 1 seconds api.anvil_set_interval_mining(1).unwrap(); + let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); + let blocks = blocks.take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + let block_numbers = blocks.into_iter().map(|b| b.number).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } diff --git a/crates/anvil/tests/it/logs.rs b/crates/anvil/tests/it/logs.rs index f0b0877599f76..ac644e6e24769 100644 --- a/crates/anvil/tests/it/logs.rs +++ b/crates/anvil/tests/it/logs.rs @@ -1,175 +1,205 @@ //! log/event related tests -use crate::abi::*; -use anvil::{spawn, NodeConfig}; -use ethers::{ - middleware::SignerMiddleware, - prelude::{BlockNumber, Filter, FilterKind, Middleware, Signer, H256}, - types::Log, +use crate::{ + abi::SimpleStorage::{self}, + utils::{http_provider_with_signer, ws_provider_with_signer}, }; +use alloy_network::EthereumWallet; +use alloy_primitives::{map::B256HashSet, B256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, BlockTransactionsKind, Filter}; +use anvil::{spawn, NodeConfig}; use futures::StreamExt; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn get_past_events() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let address = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let account = wallet.address(); + let signer: EthereumWallet = wallet.into(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let contract = + SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); + let _ = contract + .setValue("hi".to_string()) + .from(account) .send() .await + .unwrap() + .get_receipt() + .await .unwrap(); + let simple_storage_address = *contract.address(); - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); - let _receipt = tx.await.unwrap(); + let filter = Filter::new() + .address(simple_storage_address) + .topic1(B256::from(account.into_word())) + .from_block(BlockNumberOrTag::from(0)); - // and we can fetch the events - let logs: Vec = - contract.event().from_block(0u64).topic1(address).query().await.unwrap(); + let logs = provider + .get_logs(&filter) + .await + .unwrap() + .into_iter() + .map(|log| log.log_decode::().unwrap()) + .collect::>(); // 2 events, 1 in constructor, 1 in call - assert_eq!(logs[0].new_value, "initial value"); - assert_eq!(logs[1].new_value, "hi"); + assert_eq!(logs[0].inner.newValue, "initial value"); + assert_eq!(logs[1].inner.newValue, "hi"); assert_eq!(logs.len(), 2); // and we can fetch the events at a block hash - let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); + // let hash = provider.get_block(1).await.unwrap().unwrap().hash.unwrap(); + let hash = provider + .get_block_by_number(BlockNumberOrTag::from(1), BlockTransactionsKind::Hashes) + .await + .unwrap() + .unwrap() + .header + .hash; + + let filter = Filter::new() + .address(simple_storage_address) + .topic1(B256::from(account.into_word())) + .at_block_hash(hash); + + let logs = provider + .get_logs(&filter) + .await + .unwrap() + .into_iter() + .map(|log| log.log_decode::().unwrap()) + .collect::>(); - let logs: Vec = - contract.event().at_block_hash(hash).topic1(address).query().await.unwrap(); - assert_eq!(logs[0].new_value, "initial value"); + assert_eq!(logs[0].inner.newValue, "initial value"); assert_eq!(logs.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn get_all_events() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let account = wallet.address(); + let signer: EthereumWallet = wallet.into(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let contract = + SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); api.anvil_set_auto_mine(false).await.unwrap(); - let pre_logs = client.get_logs(&Filter::new().from_block(BlockNumber::Earliest)).await.unwrap(); + let pre_logs = + provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Earliest)).await.unwrap(); assert_eq!(pre_logs.len(), 1); let pre_logs = - client.get_logs(&Filter::new().from_block(BlockNumber::Number(0u64.into()))).await.unwrap(); + provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Number(0))).await.unwrap(); assert_eq!(pre_logs.len(), 1); // spread logs across several blocks let num_tx = 10; + let tx = contract.setValue("hi".to_string()).from(account); for _ in 0..num_tx { - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); + let tx = tx.send().await.unwrap(); api.mine_one().await; - let _receipt = tx.await.unwrap(); + tx.get_receipt().await.unwrap(); } - let logs = client.get_logs(&Filter::new().from_block(BlockNumber::Earliest)).await.unwrap(); + + let logs = + provider.get_logs(&Filter::new().from_block(BlockNumberOrTag::Earliest)).await.unwrap(); let num_logs = num_tx + pre_logs.len(); assert_eq!(logs.len(), num_logs); -} -#[tokio::test(flavor = "multi_thread")] -async fn can_install_filter() { - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + // test that logs returned from get_logs and get_transaction_receipt have + // the same log_index, block_number, and transaction_hash + let mut tasks = vec![]; + let mut seen_tx_hashes = B256HashSet::default(); + for log in &logs { + if seen_tx_hashes.contains(&log.transaction_hash.unwrap()) { + continue; + } + tasks.push(provider.get_transaction_receipt(log.transaction_hash.unwrap())); + seen_tx_hashes.insert(log.transaction_hash.unwrap()); + } - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() + let receipt_logs = futures::future::join_all(tasks) .await - .unwrap(); - - let filter = Filter::new().from_block(BlockNumber::Number(0u64.into())); - - let filter = client.new_filter(FilterKind::Logs(&filter)).await.unwrap(); - - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert_eq!(logs.len(), 1); - - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert!(logs.is_empty()); - api.anvil_set_auto_mine(false).await.unwrap(); - // create some logs - let num_logs = 10; - for _ in 0..num_logs { - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); - api.mine_one().await; - let _receipt = tx.await.unwrap(); - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert_eq!(logs.len(), 1); + .into_iter() + .collect::, _>>() + .unwrap() + .into_iter() + .flat_map(|receipt| receipt.unwrap().inner.inner.inner.receipt.logs) + .collect::>(); + + assert_eq!(receipt_logs.len(), logs.len()); + for (receipt_log, log) in receipt_logs.iter().zip(logs.iter()) { + assert_eq!(receipt_log.transaction_hash, log.transaction_hash); + assert_eq!(receipt_log.block_number, log.block_number); + assert_eq!(receipt_log.log_index, log.log_index); } - let all_logs = api - .get_filter_logs(&serde_json::to_string(&filter).unwrap().replace('\"', "")) - .await - .unwrap(); - - assert_eq!(all_logs.len(), num_logs + 1); } #[tokio::test(flavor = "multi_thread")] async fn watch_events() { let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet)); - - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); - - // We spawn the event listener: - let event = contract.event::(); - let mut stream = event.stream().await.unwrap(); - // Also set up a subscription for the same thing - let ws = Arc::new(handle.ws_provider().await); - let contract2 = SimpleStorage::new(contract.address(), ws); - let event2 = contract2.event::(); - let mut subscription = event2.subscribe().await.unwrap(); - - let mut subscription_meta = event2.subscribe().await.unwrap().with_meta(); - - let num_calls = 3u64; - - // and we make a few calls - let num = client.get_block_number().await.unwrap(); - for i in 0..num_calls { - let call = contract.method::<_, H256>("setValue", i.to_string()).unwrap().legacy(); - let pending_tx = call.send().await.unwrap(); - let _receipt = pending_tx.await.unwrap(); - } - - for i in 0..num_calls { - // unwrap the option of the stream, then unwrap the decoding result - let log = stream.next().await.unwrap().unwrap(); - let log2 = subscription.next().await.unwrap().unwrap(); - let (log3, meta) = subscription_meta.next().await.unwrap().unwrap(); - assert_eq!(log.new_value, log3.new_value); - assert_eq!(log.new_value, log2.new_value); - assert_eq!(log.new_value, i.to_string()); - assert_eq!(meta.block_number, num + i + 1); - let hash = client.get_block(num + i + 1).await.unwrap().unwrap().hash.unwrap(); - assert_eq!(meta.block_hash, hash); + let wallet = handle.dev_wallets().next().unwrap(); + let account = wallet.address(); + let signer: EthereumWallet = wallet.into(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); + + let contract1 = + SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); + + // Spawn the event listener. + let event1 = contract1.event_filter::(); + let mut stream1 = event1.watch().await.unwrap().into_stream(); + + // Also set up a subscription for the same thing. + let ws = ws_provider_with_signer(&handle.ws_endpoint(), signer.clone()); + let contract2 = SimpleStorage::new(*contract1.address(), ws); + let event2 = contract2.event_filter::(); + let mut stream2 = event2.watch().await.unwrap().into_stream(); + + let num_tx = 3; + + let starting_block_number = provider.get_block_number().await.unwrap(); + for i in 0..num_tx { + contract1 + .setValue(i.to_string()) + .from(account) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + let log = stream1.next().await.unwrap().unwrap(); + let log2 = stream2.next().await.unwrap().unwrap(); + + assert_eq!(log.0.newValue, log2.0.newValue); + assert_eq!(log.0.newValue, i.to_string()); + assert_eq!(log.1.block_number.unwrap(), starting_block_number + i + 1); + + let hash = provider + .get_block_by_number( + BlockNumberOrTag::from(starting_block_number + i + 1), + false.into(), + ) + .await + .unwrap() + .unwrap() + .header + .hash; + assert_eq!(log.1.block_hash.unwrap(), hash); } } diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index cd99a9f15a011..f3f5eca157707 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -2,18 +2,20 @@ mod abi; mod anvil; mod anvil_api; mod api; +mod eip4844; +mod eip7702; mod fork; -mod ganache; mod gas; mod genesis; -mod geth; mod ipc; mod logs; +mod optimism; +mod otterscan; mod proof; mod pubsub; -// mod revert; // TODO uncomment -mod otterscan; +mod revert; mod sign; +mod state; mod traces; mod transaction; mod txpool; diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs new file mode 100644 index 0000000000000..17246f0927d3b --- /dev/null +++ b/crates/anvil/tests/it/optimism.rs @@ -0,0 +1,235 @@ +//! Tests for OP chain support. + +use crate::utils::{http_provider, http_provider_with_signer}; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{b256, Address, TxHash, TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use anvil::{spawn, EthereumHardfork, NodeConfig}; +use anvil_core::eth::transaction::optimism::DepositTransaction; +use op_alloy_rpc_types::OpTransactionFields; + +#[tokio::test(flavor = "multi_thread")] +async fn test_deposits_not_supported_if_optimism_disabled() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(U256::from(1234)) + .with_gas_limit(21000); + + let op_fields = OpTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(0), + is_system_tx: Some(true), + deposit_receipt_version: None, + }; + + // TODO: Test this + let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); + + let tx = WithOtherFields { inner: tx, other }; + + let err = provider.send_transaction(tx).await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("op-stack deposit tx received but is not supported"), "{s:?}"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_send_value_deposit_transaction() { + // enable the Optimism flag + let (api, handle) = spawn( + NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), + ) + .await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let send_value = U256::from(1234); + let before_balance_to = provider.get_balance(to).await.unwrap(); + + let op_fields = OpTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(0), + is_system_tx: Some(true), + deposit_receipt_version: None, + }; + + let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); + let tx = TransactionRequest::default() + .with_from(from) + .with_to(to) + .with_value(send_value) + .with_gas_limit(21000); + let tx: WithOtherFields = WithOtherFields { inner: tx, other }; + + let pending = provider.send_transaction(tx).await.unwrap().register().await.unwrap(); + + // mine block + api.evm_mine(None).await.unwrap(); + + let receipt = + provider.get_transaction_receipt(pending.tx_hash().to_owned()).await.unwrap().unwrap(); + assert_eq!(receipt.from, from); + assert_eq!(receipt.to, Some(to)); + + // the recipient should have received the value + let after_balance_to = provider.get_balance(to).await.unwrap(); + assert_eq!(after_balance_to, before_balance_to + send_value); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_send_value_raw_deposit_transaction() { + // enable the Optimism flag + let (api, handle) = spawn( + NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), + ) + .await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); + + let send_value = U256::from(1234); + let before_balance_to = provider.get_balance(to).await.unwrap(); + + let tx = TransactionRequest::default() + .with_chain_id(31337) + .with_nonce(0) + .with_from(from) + .with_to(to) + .with_value(send_value) + .with_gas_limit(21_000) + .with_max_fee_per_gas(20_000_000_000) + .with_max_priority_fee_per_gas(1_000_000_000); + + let op_fields = OpTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(0), + is_system_tx: Some(true), + deposit_receipt_version: None, + }; + let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); + let tx = WithOtherFields { inner: tx, other }; + let tx_envelope = tx.build(&signer).await.unwrap(); + let mut tx_buffer = Vec::with_capacity(tx_envelope.encode_2718_len()); + tx_envelope.encode_2718(&mut tx_buffer); + let tx_encoded = tx_buffer.as_slice(); + + let pending = + provider.send_raw_transaction(tx_encoded).await.unwrap().register().await.unwrap(); + + // mine block + api.evm_mine(None).await.unwrap(); + + let receipt = + provider.get_transaction_receipt(pending.tx_hash().to_owned()).await.unwrap().unwrap(); + assert_eq!(receipt.from, from); + assert_eq!(receipt.to, Some(to)); + + // the recipient should have received the value + let after_balance_to = provider.get_balance(to).await.unwrap(); + assert_eq!(after_balance_to, before_balance_to + send_value); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_deposit_transaction_hash_matches_sepolia() { + // enable the Optimism flag + let (_api, handle) = spawn( + NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), + ) + .await; + + let accounts: Vec<_> = handle.dev_wallets().collect(); + let signer: EthereumWallet = accounts[0].clone().into(); + // https://sepolia-optimism.etherscan.io/tx/0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let tx_hash: TxHash = "0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7" + .parse::() + .unwrap(); + + // https://sepolia-optimism.etherscan.io/getRawTx?tx=0xbf8b5f08c43e4b860715cd64fc0849bbce0d0ea20a76b269e7bc8886d112fca7 + let raw_deposit_tx = alloy_primitives::hex::decode( + "7ef861a0dfd7ae78bf3c414cfaa77f13c0205c82eb9365e217b2daa3448c3156b69b27ac94778f2146f48179643473b82931c4cd7b8f153efd94778f2146f48179643473b82931c4cd7b8f153efd872386f26fc10000872386f26fc10000830186a08080", + ) + .unwrap(); + + let provider = http_provider_with_signer(&handle.http_endpoint(), signer.clone()); + + let receipt = provider + .send_raw_transaction(raw_deposit_tx.as_slice()) + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + assert_eq!(receipt.transaction_hash, tx_hash); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_deposit_tx_checks_sufficient_funds_after_applying_deposited_value() { + // enable the Optimism flag + let (_api, handle) = spawn( + NodeConfig::test().with_optimism(true).with_hardfork(Some(EthereumHardfork::Paris.into())), + ) + .await; + + let provider = http_provider(&handle.http_endpoint()); + + let sender = Address::random(); + let recipient = Address::random(); + let send_value = 1_000_000_000_u128; + + let sender_prev_balance = provider.get_balance(sender).await.unwrap(); + assert_eq!(sender_prev_balance, U256::from(0)); + + let recipient_prev_balance = provider.get_balance(recipient).await.unwrap(); + assert_eq!(recipient_prev_balance, U256::from(0)); + + let deposit_tx = DepositTransaction { + source_hash: b256!("0000000000000000000000000000000000000000000000000000000000000000"), + from: sender, + nonce: 0, + kind: TxKind::Call(recipient), + mint: U256::from(send_value), + value: U256::from(send_value), + gas_limit: 21_000, + is_system_tx: false, + input: Vec::new().into(), + }; + + let mut tx_buffer = Vec::new(); + deposit_tx.encode_2718(&mut tx_buffer); + + provider.send_raw_transaction(&tx_buffer).await.unwrap().get_receipt().await.unwrap(); + + let sender_new_balance = provider.get_balance(sender).await.unwrap(); + // sender should've sent the entire deposited value to recipient + assert_eq!(sender_new_balance, U256::from(0)); + + let recipient_new_balance = provider.get_balance(recipient).await.unwrap(); + // recipient should've received the entire deposited value + assert_eq!(recipient_new_balance, U256::from(send_value)); +} diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index f4c4913ca7081..37d21a29e0b7d 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,320 +1,383 @@ -//! tests for otterscan endpoints -use crate::abi::MulticallContract; -use anvil::{ - eth::otterscan::types::{ - OtsInternalOperation, OtsInternalOperationType, OtsTrace, OtsTraceType, - }, - spawn, NodeConfig, +//! Tests for otterscan endpoints. + +use crate::abi::Multicall; +use alloy_network::TransactionResponse; +use alloy_primitives::{address, Address, Bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + trace::otterscan::{InternalOperation, OperationType, TraceEntry}, + BlockNumberOrTag, TransactionRequest, }; -use ethers::{ - abi::Address, - prelude::{ContractFactory, ContractInstance, Middleware, SignerMiddleware}, - signers::Signer, - types::{BlockNumber, Bytes, TransactionRequest, U256}, - utils::get_contract_address, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::{collections::VecDeque, str::FromStr, sync::Arc}; +use alloy_serde::WithOtherFields; +use alloy_sol_types::{sol, SolCall, SolError, SolValue}; +use anvil::{spawn, EthereumHardfork, NodeConfig}; +use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] -async fn can_call_erigon_get_header_by_number() { +async fn erigon_get_header_by_number() { let (api, _handle) = spawn(NodeConfig::test()).await; api.mine_one().await; let res0 = api.erigon_get_header_by_number(0.into()).await.unwrap().unwrap(); - let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); + assert_eq!(res0.header.number, 0); - assert_eq!(res0.number, Some(0.into())); - assert_eq!(res1.number, Some(1.into())); + let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); + assert_eq!(res1.header.number, 1); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_api_level() { +async fn ots_get_api_level() { let (api, _handle) = spawn(NodeConfig::test()).await; assert_eq!(api.ots_get_api_level().await.unwrap(), 8); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_internal_operations_contract_deploy() { +async fn ots_get_internal_operations_contract_deploy() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); - let wallet = handle.dev_wallets().next().unwrap(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - let contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); - - let receipt = client.send_transaction(deploy_tx, None).await.unwrap().await.unwrap().unwrap(); - - let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); + let contract_receipt = + Multicall::deploy_builder(&provider).send().await.unwrap().get_receipt().await.unwrap(); - assert_eq!(res.len(), 1); + let res = api.ots_get_internal_operations(contract_receipt.transaction_hash).await.unwrap(); assert_eq!( - res[0], - OtsInternalOperation { - r#type: OtsInternalOperationType::Create, + res, + [InternalOperation { + r#type: OperationType::OpCreate, from: sender, - to: contract_address, - value: 0.into() - } + to: contract_receipt.contract_address.unwrap(), + value: U256::from(0) + }], ); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_has_code() { +async fn ots_get_internal_operations_contract_transfer() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - api.mine_one().await; + let accounts: Vec<_> = handle.dev_wallets().collect(); + let from = accounts[0].address(); + let to = accounts[1].address(); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); - // no code in the address before deploying - assert!(!api - .ots_has_code(pending_contract_address, BlockNumber::Number(1.into())) - .await - .unwrap()); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); + assert_eq!( + res, + [InternalOperation { r#type: OperationType::OpTransfer, from, to, value: amount }], + ); +} - client.send_transaction(deploy_tx, None).await.unwrap(); +#[tokio::test(flavor = "multi_thread")] +async fn ots_get_internal_operations_contract_create2() { + sol!( + #[sol(rpc, bytecode = "60808060405234601557610147908161001a8239f35b5f80fdfe6080600436101561000e575f80fd5b5f3560e01c636cd5c39b14610021575f80fd5b346100d0575f3660031901126100d0575f602082810191825282526001600160401b03916040810191838311828410176100d4578261008960405f959486958252606081019486865281518091608084015e81018660808201520360208101845201826100ee565b519082734e59b44847b379578588920ca78fbf26c0b4956c5af1903d156100e8573d9081116100d4576040516100c991601f01601f1916602001906100ee565b156100d057005b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b506100c9565b601f909101601f19168101906001600160401b038211908210176100d45760405256fea2646970667358221220f76968e121fc002b537029df51a2aecca0793282491baf84b872ffbfbfb1c9d764736f6c63430008190033")] + contract Contract { + address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + function deployContract() public { + uint256 salt = 0; + uint256 code = 0; + bytes memory creationCode = abi.encodePacked(code); + (bool success,) = address(CREATE2_DEPLOYER).call(abi.encodePacked(salt, creationCode)); + require(success); + } + } + ); - let num = client.get_block_number().await.unwrap(); - // code is detected after deploying - assert!(api.ots_has_code(pending_contract_address, BlockNumber::Number(num)).await.unwrap()); + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); - // code is not detected for the previous block - assert!(!api - .ots_has_code(pending_contract_address, BlockNumber::Number(num - 1)) - .await - .unwrap()); + let contract = Contract::deploy(&provider).await.unwrap(); + + let receipt = contract.deployContract().send().await.unwrap().get_receipt().await.unwrap(); + + let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); + assert_eq!( + res, + [InternalOperation { + r#type: OperationType::OpCreate2, + from: address!("4e59b44847b379578588920cA78FbF26c0B4956C"), + to: address!("347bcdad821abc09b8c275881b368de36476b62c"), + value: U256::from(0), + }], + ); } #[tokio::test(flavor = "multi_thread")] -async fn test_call_call_ots_trace_transaction() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function run() payable public { - this.do_staticcall(); - this.do_call(); - } - - function do_staticcall() external view returns (bool) { - return true; - } +async fn ots_get_internal_operations_contract_selfdestruct_london() { + ots_get_internal_operations_contract_selfdestruct(EthereumHardfork::London).await; +} - function do_call() external { - owner.call{value: address(this).balance}(""); - address(this).delegatecall(abi.encodeWithSignature("do_delegatecall()")); - } - - function do_delegatecall() internal { - } +#[tokio::test(flavor = "multi_thread")] +async fn ots_get_internal_operations_contract_selfdestruct_cancun() { + ots_get_internal_operations_contract_selfdestruct(EthereumHardfork::Cancun).await; } -"#, - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +async fn ots_get_internal_operations_contract_selfdestruct(hardfork: EthereumHardfork) { + sol!( + #[sol(rpc, bytecode = "608080604052607f908160108239f3fe6004361015600c57600080fd5b6000803560e01c6375fc8e3c14602157600080fd5b346046578060031936011260465773dcdd539da22bffaa499dbea4d37d086dde196e75ff5b80fdfea264697066735822122080a9ad005cc408b2d4e30ca11216d8e310700fbcdf58a629d6edbb91531f9c6164736f6c63430008190033")] + contract Contract { + constructor() payable {} + function goodbye() public { + selfdestruct(payable(0xDcDD539DA22bfFAa499dBEa4d37d086Dde196E75)); + } + } + ); - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(hardfork.into()))).await; + let provider = handle.http_provider(); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let sender = handle.dev_accounts().next().unwrap(); + let value = U256::from(69); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("run", ()).unwrap().value(1337); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + let contract_address = + Contract::deploy_builder(&provider).from(sender).value(value).deploy().await.unwrap(); + let contract = Contract::new(contract_address, &provider); - let res = api.ots_trace_transaction(receipt.transaction_hash).await.unwrap(); + let receipt = contract.goodbye().send().await.unwrap().get_receipt().await.unwrap(); + + let expected_to = address!("DcDD539DA22bfFAa499dBEa4d37d086Dde196E75"); + let expected_value = value; + let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( res, - vec![ - OtsTrace { - r#type: OtsTraceType::Call, - depth: 0, - from: wallets[1].address(), - to: contract.address(), - value: 1337.into(), - input: Bytes::from_str("0xc0406226").unwrap() - }, - OtsTrace { - r#type: OtsTraceType::StaticCall, - depth: 1, - from: contract.address(), - to: contract.address(), - value: U256::zero(), - input: Bytes::from_str("0x6a6758fe").unwrap() - }, - OtsTrace { - r#type: OtsTraceType::Call, - depth: 1, - from: contract.address(), - to: contract.address(), - value: U256::zero(), - input: Bytes::from_str("0x96385e39").unwrap() - }, - OtsTrace { - r#type: OtsTraceType::Call, - depth: 2, - from: contract.address(), - to: wallets[0].address(), - value: 1337.into(), - input: Bytes::from_str("0x").unwrap() - }, - OtsTrace { - r#type: OtsTraceType::DelegateCall, - depth: 2, - from: contract.address(), - to: contract.address(), - value: U256::zero(), - input: Bytes::from_str("0xa1325397").unwrap() - }, - ] + [InternalOperation { + r#type: OperationType::OpSelfDestruct, + from: contract_address, + to: expected_to, + value: expected_value, + }], ); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_transaction_error() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - error CustomError(string msg); - - function trigger_revert() public { - revert CustomError("RevertStringFooBar"); - } +async fn ots_has_code() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); + + api.mine_one().await; + + let contract_address = sender.create(0); + + // no code in the address before deploying + assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(1)).await.unwrap()); + + let contract_builder = Multicall::deploy_builder(&provider); + let contract_receipt = contract_builder.send().await.unwrap().get_receipt().await.unwrap(); + + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, contract_receipt.block_number.unwrap()); + + // code is detected after deploying + assert!(api.ots_has_code(contract_address, BlockNumberOrTag::Number(num)).await.unwrap()); + + // code is not detected for the previous block + assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(num - 1)).await.unwrap()); } -"#, - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +#[tokio::test(flavor = "multi_thread")] +async fn test_call_ots_trace_transaction() { + sol!( + #[sol(rpc, bytecode = "608080604052346026575f80546001600160a01b0319163317905561025e908161002b8239f35b5f80fdfe6080604081815260049081361015610015575f80fd5b5f925f3560e01c9081636a6758fe1461019a5750806396385e3914610123578063a1325397146101115763c04062261461004d575f80fd5b5f3660031901126100d5578051633533ac7f60e11b81526020818481305afa80156100cb576100d9575b50303b156100d55780516396385e3960e01b8152915f83828183305af180156100cb576100a2578380f35b919250906001600160401b0383116100b8575052005b604190634e487b7160e01b5f525260245ffd5b82513d5f823e3d90fd5b5f80fd5b6020813d602011610109575b816100f2602093836101b3565b810103126100d55751801515036100d5575f610077565b3d91506100e5565b346100d5575f3660031901126100d557005b5090346100d5575f3660031901126100d5575f805481908190819047906001600160a01b03165af1506101546101ea565b50815163a132539760e01b6020820190815282825292909182820191906001600160401b038311848410176100b8575f8086868686525190305af4506101986101ea565b005b346100d5575f3660031901126100d55780600160209252f35b601f909101601f19168101906001600160401b038211908210176101d657604052565b634e487b7160e01b5f52604160045260245ffd5b3d15610223573d906001600160401b0382116101d65760405191610218601f8201601f1916602001846101b3565b82523d5f602084013e565b60609056fea264697066735822122099817ea378044f1f6434272aeb1f3f01a734645e599e69b4caf2ba7a4fb65f9d64736f6c63430008190033")] + contract Contract { + address private owner; + + constructor() { + owner = msg.sender; + } + + function run() payable public { + this.do_staticcall(); + this.do_call(); + } + + function do_staticcall() external view returns (bool) { + return true; + } + + function do_call() external { + owner.call{value: address(this).balance}(""); + address(this).delegatecall(abi.encodeWithSignature("do_delegatecall()")); + } + + function do_delegatecall() external {} + } + ); let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.http_provider(); + let wallets = handle.dev_wallets().collect::>(); + let sender = wallets[0].address(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let contract_address = Contract::deploy_builder(&provider).from(sender).deploy().await.unwrap(); + let contract = Contract::new(contract_address, &provider); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let receipt = + contract.run().value(U256::from(1337)).send().await.unwrap().get_receipt().await.unwrap(); - let call = contract.method::<_, ()>("trigger_revert", ()).unwrap().gas(150_000u64); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + let res = api.ots_trace_transaction(receipt.transaction_hash).await.unwrap(); + let expected = vec![ + TraceEntry { + r#type: "CALL".to_string(), + depth: 0, + from: sender, + to: contract_address, + value: U256::from(1337), + input: Contract::runCall::SELECTOR.into(), + output: Bytes::new(), + }, + TraceEntry { + r#type: "STATICCALL".to_string(), + depth: 1, + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Contract::do_staticcallCall::SELECTOR.into(), + output: true.abi_encode().into(), + }, + TraceEntry { + r#type: "CALL".to_string(), + depth: 1, + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Contract::do_callCall::SELECTOR.into(), + output: Bytes::new(), + }, + TraceEntry { + r#type: "CALL".to_string(), + depth: 2, + from: contract_address, + to: sender, + value: U256::from(1337), + input: Bytes::new(), + output: Bytes::new(), + }, + TraceEntry { + r#type: "DELEGATECALL".to_string(), + depth: 2, + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Contract::do_delegatecallCall::SELECTOR.into(), + output: Bytes::new(), + }, + ]; + assert_eq!(res, expected); +} - let block = api.block_by_number_full(BlockNumber::Latest).await.unwrap().unwrap(); - dbg!(block); - // let tx = block.transactions[0].hashVg +#[tokio::test(flavor = "multi_thread")] +async fn ots_get_transaction_error() { + sol!( + #[sol(rpc, bytecode = "6080806040523460135760a3908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63f67f4650146023575f80fd5b346069575f3660031901126069576346b7545f60e11b81526020600482015260126024820152712932bb32b93a29ba3934b733a337b7a130b960711b6044820152606490fd5b5f80fdfea264697066735822122069222918090d4d3ddc6a9c8b6ef282464076c71f923a0e8618ed25489b87f12b64736f6c63430008190033")] + contract Contract { + error CustomError(string msg); + + function trigger_revert() public { + revert CustomError("RevertStringFooBar"); + } + } + ); - let res = api.ots_get_transaction_error(receipt.transaction_hash).await.unwrap().unwrap(); - assert_eq!(res, Bytes::from_str("0x8d6ea8be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012526576657274537472696e67466f6f4261720000000000000000000000000000").unwrap()); + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let contract = Contract::deploy(&provider).await.unwrap(); + + let receipt = contract.trigger_revert().send().await.unwrap().get_receipt().await.unwrap(); + + let err = api.ots_get_transaction_error(receipt.transaction_hash).await.unwrap(); + let expected = Contract::CustomError { msg: String::from("RevertStringFooBar") }.abi_encode(); + assert_eq!(err, Bytes::from(expected)); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_block_details() { +async fn ots_get_transaction_error_no_error() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + // Send a successful transaction + 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(); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let res = api.ots_get_transaction_error(receipt.transaction_hash).await.unwrap(); + assert!(res.is_empty(), "{res}"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn ots_get_block_details() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); + + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); + let tx = WithOtherFields::new(tx); + 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); - assert_eq!(result.block.block.transactions[0].hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_block_details_by_hash() { +async fn ots_get_block_details_by_hash() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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(); let block_hash = receipt.block_hash.unwrap(); let result = api.ots_get_block_details_by_hash(block_hash).await.unwrap(); assert_eq!(result.block.transaction_count, 1); - assert_eq!(result.block.block.transactions[0].hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_block_transactions() { +async fn ots_get_block_transactions() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - // disable automine api.anvil_set_auto_mine(false).await.unwrap(); let mut hashes = VecDeque::new(); for i in 0..10 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap(); - hashes.push_back(receipt.tx_hash()); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(100)).nonce(i); + let tx = WithOtherFields::new(tx); + let pending_receipt = + provider.send_transaction(tx).await.unwrap().register().await.unwrap(); + hashes.push_back(*pending_receipt.tx_hash()); } - dbg!(&hashes); api.mine_one().await; let page_size = 3; for page in 0..4 { let result = api.ots_get_block_transactions(1, page, page_size).await.unwrap(); - dbg!(&result); assert!(result.receipts.len() <= page_size); - assert!(result.fullblock.block.transactions.len() <= page_size); + let len = result.receipts.len(); + assert!(len <= page_size); assert!(result.fullblock.transaction_count == result.receipts.len()); result.receipts.iter().enumerate().for_each(|(i, receipt)| { let expected = hashes.pop_front(); - assert_eq!(expected, Some(receipt.transaction_hash)); - assert_eq!(expected, Some(result.fullblock.block.transactions[i].hash)); + assert_eq!(expected, Some(receipt.receipt.transaction_hash)); + assert_eq!(expected, result.fullblock.block.transactions.hashes().nth(i)); }); } @@ -322,116 +385,119 @@ async fn can_call_ots_get_block_transactions() { } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_search_transactions_before() { +async fn ots_search_transactions_before() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - - let wallet = handle.dev_wallets().next().unwrap(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let sender = handle.dev_accounts().next().unwrap(); let mut hashes = vec![]; for i in 0..7 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(100)).nonce(i); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); hashes.push(receipt.transaction_hash); } let page_size = 2; let mut block = 0; - for _ in 0..4 { + for i in 0..4 { let result = api.ots_search_transactions_before(sender, block, page_size).await.unwrap(); - assert!(result.txs.len() <= page_size); + assert_eq!(result.first_page, i == 0); + assert_eq!(result.last_page, i == 3); // check each individual hash result.txs.iter().for_each(|tx| { - assert_eq!(hashes.pop(), Some(tx.hash)); + assert_eq!(hashes.pop(), Some(tx.tx_hash())); }); - block = result.txs.last().unwrap().block_number.unwrap().as_u64() - 1; + block = result.txs.last().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_search_transactions_after() { +async fn ots_search_transactions_after() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - - let wallet = handle.dev_wallets().next().unwrap(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let sender = handle.dev_accounts().next().unwrap(); let mut hashes = VecDeque::new(); for i in 0..7 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = + TransactionRequest::default().to(Address::random()).value(U256::from(100)).nonce(i); + let tx = WithOtherFields::new(tx); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); hashes.push_front(receipt.transaction_hash); } let page_size = 2; let mut block = 0; - for _ in 0..4 { + for i in 0..4 { let result = api.ots_search_transactions_after(sender, block, page_size).await.unwrap(); - assert!(result.txs.len() <= page_size); + assert_eq!(result.first_page, i == 3); + assert_eq!(result.last_page, i == 0); // check each individual hash - result.txs.iter().for_each(|tx| { - assert_eq!(hashes.pop_back(), Some(tx.hash)); + result.txs.iter().rev().for_each(|tx| { + assert_eq!(hashes.pop_back(), Some(tx.tx_hash())); }); - block = result.txs.last().unwrap().block_number.unwrap().as_u64() + 1; + block = result.txs.first().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_transaction_by_sender_and_nonce() { +async fn ots_get_transaction_by_sender_and_nonce() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - api.mine_one().await; - - let wallet = handle.dev_wallets().next().unwrap(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let tx1 = TransactionRequest::new().to(Address::random()).value(100u64); - let tx2 = TransactionRequest::new().to(Address::random()).value(100u64); + let sender = handle.dev_accounts().next().unwrap(); + + let tx1 = WithOtherFields::new( + TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(0), + ); + let tx2 = WithOtherFields::new( + TransactionRequest::default() + .from(sender) + .to(Address::random()) + .value(U256::from(100)) + .nonce(1), + ); - let receipt1 = client.send_transaction(tx1, None).await.unwrap().await.unwrap().unwrap(); - let receipt2 = client.send_transaction(tx2, None).await.unwrap().await.unwrap().unwrap(); + let receipt1 = provider.send_transaction(tx1).await.unwrap().get_receipt().await.unwrap(); + let receipt2 = provider.send_transaction(tx2).await.unwrap().get_receipt().await.unwrap(); - let result1 = api.ots_get_transaction_by_sender_and_nonce(sender, 0.into()).await.unwrap(); - let result2 = api.ots_get_transaction_by_sender_and_nonce(sender, 1.into()).await.unwrap(); + let result1 = + api.ots_get_transaction_by_sender_and_nonce(sender, U256::from(0)).await.unwrap().unwrap(); + let result2 = + api.ots_get_transaction_by_sender_and_nonce(sender, U256::from(1)).await.unwrap().unwrap(); - assert_eq!(result1.unwrap().hash, receipt1.transaction_hash); - assert_eq!(result2.unwrap().hash, receipt2.transaction_hash); + assert_eq!(result1, receipt1.transaction_hash); + assert_eq!(result2, receipt2.transaction_hash); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_contract_creator() { +async fn ots_get_contract_creator() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - api.mine_one().await; - - let wallet = handle.dev_wallets().next().unwrap(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let sender = handle.dev_accounts().next().unwrap(); - let receipt = client.send_transaction(deploy_tx, None).await.unwrap().await.unwrap().unwrap(); + let receipt = + Multicall::deploy_builder(&provider).send().await.unwrap().get_receipt().await.unwrap(); + let contract_address = receipt.contract_address.unwrap(); - let creator = api.ots_get_contract_creator(pending_contract_address).await.unwrap().unwrap(); + let creator = api.ots_get_contract_creator(contract_address).await.unwrap().unwrap(); assert_eq!(creator.creator, sender); assert_eq!(creator.hash, receipt.transaction_hash); diff --git a/crates/anvil/tests/it/proof.rs b/crates/anvil/tests/it/proof.rs new file mode 100644 index 0000000000000..757d36082696d --- /dev/null +++ b/crates/anvil/tests/it/proof.rs @@ -0,0 +1,133 @@ +//! tests for `eth_getProof` + +use alloy_primitives::{address, fixed_bytes, Address, Bytes, B256, U256}; +use anvil::{eth::EthApi, spawn, NodeConfig}; +use std::{collections::BTreeMap, str::FromStr}; + +async fn verify_account_proof( + api: &EthApi, + address: Address, + proof: impl IntoIterator, +) { + let expected_proof = + proof.into_iter().map(Bytes::from_str).collect::, _>>().unwrap(); + let proof = api.get_proof(address, Vec::new(), None).await.unwrap(); + + assert_eq!(proof.account_proof, expected_proof); +} + +async fn verify_storage_proof( + api: &EthApi, + address: Address, + slot: B256, + proof: impl IntoIterator, +) { + let expected_proof = + proof.into_iter().map(Bytes::from_str).collect::, _>>().unwrap(); + let proof = api.get_proof(address, vec![slot], None).await.unwrap(); + + assert_eq!(proof.storage_proof[0].proof, expected_proof); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_account_proof() { + let (api, _handle) = spawn(NodeConfig::empty_state()).await; + + api.anvil_set_balance( + address!("2031f89b3ea8014eb51a78c316e42af3e0d7695f"), + U256::from(45000000000000000000_u128), + ) + .await + .unwrap(); + api.anvil_set_balance(address!("33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), U256::from(1)) + .await + .unwrap(); + api.anvil_set_balance( + address!("62b0dd4aab2b1a0a04e279e2b828791a10755528"), + U256::from(1100000000000000000_u128), + ) + .await + .unwrap(); + api.anvil_set_balance( + address!("1ed9b1dd266b607ee278726d324b855a093394a6"), + U256::from(120000000000000000_u128), + ) + .await + .unwrap(); + + verify_account_proof(&api, address!("2031f89b3ea8014eb51a78c316e42af3e0d7695f"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ]).await; + + verify_account_proof(&api, address!("33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", + "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", + "0xf8679e207781e762f3577784bab7491fcc43e291ce5a356b9bc517ac52eed3a37ab846f8448001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]).await; + + verify_account_proof(&api, address!("62b0dd4aab2b1a0a04e279e2b828791a10755528"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xf8709f3936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446eb84ef84c80880f43fc2c04ee0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]).await; + + verify_account_proof(&api, address!("1ed9b1dd266b607ee278726d324b855a093394a6"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", + "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", + "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ]).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_storage_proof() { + let target = address!("1ed9b1dd266b607ee278726d324b855a093394a6"); + + let (api, _handle) = spawn(NodeConfig::empty_state()).await; + let storage: BTreeMap = + serde_json::from_str(include_str!("../../test-data/storage_sample.json")).unwrap(); + + for (key, value) in storage { + api.anvil_set_storage_at(target, key, value).await.unwrap(); + } + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000022"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf85180a0776aa456ba9c5008e03b82b841a9cf2fc1e8578cfacd5c9015804eae315f17fb80808080808080808080808080a072e3e284d47badbb0a5ca1421e1179d3ea90cc10785b26b74fb8a81f0f9e841880", + "0xf843a020035b26e3e9eee00e0d72fd1ee8ddca6894550dca6916ea2ac6baa90d11e510a1a0f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b" + ]).await; + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000023"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf8518080808080a0d546c4ca227a267d29796643032422374624ed109b3d94848c5dc06baceaee76808080808080a027c48e210ccc6e01686be2d4a199d35f0e1e8df624a8d3a17c163be8861acd6680808080", + "0xf843a0207b2b5166478fd4318d2acc6cc2c704584312bdd8781b32d5d06abda57f4230a1a0db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71" + ]).await; + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000024"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf85180808080a030263404acfee103d0b1019053ff3240fce433c69b709831673285fa5887ce4c80808080808080a0f8f1fbb1f7b482d9860480feebb83ff54a8b6ec1ead61cc7d2f25d7c01659f9c80808080", + "0xf843a020d332d19b93bcabe3cce7ca0c18a052f57e5fd03b4758a09f30f5ddc4b22ec4a1a0c78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + ]).await; + + verify_storage_proof(&api, target, fixed_bytes!("0000000000000000000000000000000000000000000000000000000000000100"), [ + "0xf9019180a0aafd5b14a6edacd149e110ba6776a654f2dbffca340902be933d011113f2750380a0a502c93b1918c4c6534d4593ae03a5a23fa10ebc30ffb7080b297bff2446e42da02eb2bf45fd443bd1df8b6f9c09726a4c6252a0f7896a131a081e39a7f644b38980a0a9cf7f673a0bce76fd40332afe8601542910b48dea44e93933a3e5e930da5d19a0ddf79db0a36d0c8134ba143bcb541cd4795a9a2bae8aca0ba24b8d8963c2a77da0b973ec0f48f710bf79f63688485755cbe87f9d4c68326bb83c26af620802a80ea0f0855349af6bf84afc8bca2eda31c8ef8c5139be1929eeb3da4ba6b68a818cb0a0c271e189aeeb1db5d59d7fe87d7d6327bbe7cfa389619016459196497de3ccdea0e7503ba5799e77aa31bbe1310c312ca17b2c5bcc8fa38f266675e8f154c2516ba09278b846696d37213ab9d20a5eb42b03db3173ce490a2ef3b2f3b3600579fc63a0e9041059114f9c910adeca12dbba1fef79b2e2c8899f2d7213cd22dfe4310561a047c59da56bb2bf348c9dd2a2e8f5538a92b904b661cfe54a4298b85868bbe4858080", + "0xf891a090bacef44b189ddffdc5f22edc70fe298c58e5e523e6e1dfdf7dbc6d657f7d1b80a026eed68746028bc369eb456b7d3ee475aa16f34e5eaa0c98fdedb9c59ebc53b0808080a09ce86197173e14e0633db84ce8eea32c5454eebe954779255644b45b717e8841808080a0328c7afb2c58ef3f8c4117a8ebd336f1a61d24591067ed9c5aae94796cac987d808080808080", + ]).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_get_random_account_proofs() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + for acc in std::iter::repeat_with(Address::random).take(10) { + let _ = api + .get_proof(acc, Vec::new(), None) + .await + .unwrap_or_else(|_| panic!("Failed to get proof for {acc:?}")); + } +} diff --git a/crates/anvil/tests/it/proof/eip1186.rs b/crates/anvil/tests/it/proof/eip1186.rs deleted file mode 100644 index 2c6875ecaa4da..0000000000000 --- a/crates/anvil/tests/it/proof/eip1186.rs +++ /dev/null @@ -1,297 +0,0 @@ -/// Taken from https://github.com/paritytech/trie/blob/aa3168d6de01793e71ebd906d3a82ae4b363db59/trie-eip1186/src/eip1186.rs -use hash_db::Hasher; -use trie_db::{ - node::{decode_hash, Node, NodeHandle, Value}, - CError, NibbleSlice, NodeCodec, TrieHash, TrieLayout, -}; - -/// Errors that may occur during proof verification. Most of the errors types simply indicate that -/// the proof is invalid with respect to the statement being verified, and the exact error type can -/// be used for debugging. -#[derive(PartialEq, Eq, Debug)] -pub enum VerifyError<'a, HO, CE> { - /// The proof does not contain any value for the given key - /// the error carries the nibbles left after traversing the trie - NonExistingValue(NibbleSlice<'a>), - /// The proof contains a value for the given key - /// while we were expecting to find a non-existence proof - ExistingValue(Vec), - /// The proof indicates that the trie contains a different value. - /// the error carries the value contained in the trie - ValueMismatch(Vec), - /// The proof is missing trie nodes required to verify. - IncompleteProof, - /// The node hash computed from the proof is not matching. - HashMismatch(HO), - /// One of the proof nodes could not be decoded. - DecodeError(CE), - /// Error in converting a plain hash into a HO - HashDecodeError(&'a [u8]), -} - -#[cfg(feature = "std")] -impl<'a, HO: std::fmt::Debug, CE: std::error::Error> std::fmt::Display for VerifyError<'a, HO, CE> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - VerifyError::NonExistingValue(key) => { - write!(f, "Key does not exist in trie: reaming key={:?}", key) - } - VerifyError::ExistingValue(value) => { - write!(f, "trie contains a value for given key value={:?}", value) - } - VerifyError::ValueMismatch(key) => { - write!(f, "Expected value was not found in the trie: key={:?}", key) - } - VerifyError::IncompleteProof => write!(f, "Proof is incomplete -- expected more nodes"), - VerifyError::HashMismatch(hash) => write!(f, "hash mismatch found: hash={:?}", hash), - VerifyError::DecodeError(err) => write!(f, "Unable to decode proof node: {}", err), - VerifyError::HashDecodeError(plain_hash) => { - write!(f, "Unable to decode hash value plain_hash: {:?}", plain_hash) - } - } - } -} - -#[cfg(feature = "std")] -impl<'a, HO: std::fmt::Debug, CE: std::error::Error + 'static> std::error::Error - for VerifyError<'a, HO, CE> -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - VerifyError::DecodeError(err) => Some(err), - _ => None, - } - } -} - -/// Verify a compact proof for key-value pairs in a trie given a root hash. -pub fn verify_proof<'a, L>( - root: &::Out, - proof: &'a [Vec], - raw_key: &'a [u8], - expected_value: Option<&[u8]>, -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if proof.is_empty() { - return Err(VerifyError::IncompleteProof) - } - let key = NibbleSlice::new(raw_key); - process_node::(Some(root), &proof[0], key, expected_value, &proof[1..]) -} - -fn process_node<'a, L>( - expected_node_hash: Option<&::Out>, - encoded_node: &'a [u8], - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if let Some(value) = expected_value { - if encoded_node == value { - return Ok(()) - } - } - if let Some(expected) = expected_node_hash { - let calculated_node_hash = ::hash(encoded_node); - if calculated_node_hash != *expected { - return Err(VerifyError::HashMismatch(calculated_node_hash)) - } - } - let node = ::decode(encoded_node).map_err(VerifyError::DecodeError)?; - match node { - Node::Empty => process_empty::(key, expected_value, proof), - Node::Leaf(nib, data) => process_leaf::(nib, data, key, expected_value, proof), - Node::Extension(nib, handle) => { - process_extension::(&nib, handle, key, expected_value, proof) - } - Node::Branch(children, maybe_data) => { - process_branch::(children, maybe_data, key, expected_value, proof) - } - Node::NibbledBranch(nib, children, maybe_data) => { - process_nibbledbranch::(nib, children, maybe_data, key, expected_value, proof) - } - } -} - -fn process_empty<'a, L>( - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - _: &[Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if expected_value.is_none() { - Ok(()) - } else { - Err(VerifyError::NonExistingValue(key)) - } -} - -fn process_leaf<'a, L>( - nib: NibbleSlice, - data: Value<'a>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if key != nib && expected_value.is_none() { - return Ok(()) - } else if key != nib { - return Err(VerifyError::NonExistingValue(key)) - } - match_value::(Some(data), key, expected_value, proof) -} -fn process_extension<'a, L>( - nib: &NibbleSlice, - handle: NodeHandle<'a>, - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if !key.starts_with(nib) && expected_value.is_none() { - return Ok(()) - } else if !key.starts_with(nib) { - return Err(VerifyError::NonExistingValue(key)) - } - key.advance(nib.len()); - - match handle { - NodeHandle::Inline(encoded_node) => { - process_node::(None, encoded_node, key, expected_value, proof) - } - NodeHandle::Hash(plain_hash) => { - let new_root = decode_hash::(plain_hash) - .ok_or_else(|| VerifyError::HashDecodeError(plain_hash))?; - process_node::(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) - } - } -} - -fn process_nibbledbranch<'a, L>( - nib: NibbleSlice, - children: [Option>; 16], - maybe_data: Option>, - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if !key.starts_with(&nib) && expected_value.is_none() { - return Ok(()) - } else if !key.starts_with(&nib) && expected_value.is_some() { - return Err(VerifyError::NonExistingValue(key)) - } - key.advance(nib.len()); - - if key.is_empty() { - match_value::(maybe_data, key, expected_value, proof) - } else { - match_children::(children, key, expected_value, proof) - } -} - -fn process_branch<'a, L>( - children: [Option>; 16], - maybe_data: Option>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if key.is_empty() { - match_value::(maybe_data, key, expected_value, proof) - } else { - match_children::(children, key, expected_value, proof) - } -} -fn match_children<'a, L>( - children: [Option>; 16], - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - match children.get(key.at(0) as usize) { - Some(Some(NodeHandle::Hash(hash))) => { - if proof.is_empty() { - Err(VerifyError::IncompleteProof) - } else { - key.advance(1); - let new_root = decode_hash::(hash) - .ok_or_else(|| VerifyError::HashDecodeError(hash))?; - process_node::(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) - } - } - Some(Some(NodeHandle::Inline(encoded_node))) => { - key.advance(1); - process_node::(None, encoded_node, key, expected_value, proof) - } - Some(None) => { - if expected_value.is_none() { - Ok(()) - } else { - Err(VerifyError::NonExistingValue(key)) - } - } - None => panic!("key index is out of range in children array"), - } -} - -fn match_value<'a, L>( - maybe_data: Option>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - match (maybe_data, proof.first(), expected_value) { - (None, _, None) => Ok(()), - (None, _, Some(_)) => Err(VerifyError::NonExistingValue(key)), - (Some(Value::Inline(inline_data)), _, Some(value)) => { - if inline_data == value { - Ok(()) - } else { - Err(VerifyError::ValueMismatch(inline_data.to_vec())) - } - } - (Some(Value::Inline(inline_data)), _, None) => { - Err(VerifyError::ExistingValue(inline_data.to_vec())) - } - (Some(Value::Node(plain_hash, _)), Some(next_proof_item), Some(value)) => { - let value_hash = L::Hash::hash(value); - let node_hash = decode_hash::(plain_hash) - .ok_or_else(|| VerifyError::HashDecodeError(plain_hash))?; - if node_hash != value_hash { - Err(VerifyError::HashMismatch(node_hash)) - } else if next_proof_item != value { - Err(VerifyError::ValueMismatch(next_proof_item.to_vec())) - } else { - Ok(()) - } - } - (Some(Value::Node(_, _)), None, _) => Err(VerifyError::IncompleteProof), - (Some(Value::Node(_, _)), Some(proof_item), None) => { - Err(VerifyError::ExistingValue(proof_item.to_vec())) - } - } -} diff --git a/crates/anvil/tests/it/proof/mod.rs b/crates/anvil/tests/it/proof/mod.rs deleted file mode 100644 index c82de8a2f5d8f..0000000000000 --- a/crates/anvil/tests/it/proof/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -//! tests for `eth_getProof` - -use anvil::{spawn, NodeConfig}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - types::{Address, H256, U256}, -}; - -use anvil_core::eth::proof::{AccountProof, BasicAccount}; - -use crate::proof::eip1186::verify_proof; -use anvil_core::eth::trie::ExtensionLayout; -use ethers::utils::{keccak256, rlp}; -use foundry_evm::revm::primitives::KECCAK_EMPTY; - -mod eip1186; - -#[tokio::test(flavor = "multi_thread")] -async fn can_get_proof() { - let (api, _handle) = spawn(NodeConfig::test()).await; - - let acc: Address = "0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa".parse().unwrap(); - - let key = U256::zero(); - let value = U256::one(); - - api.anvil_set_storage_at(acc, key, H256::from_uint(&value)).await.unwrap(); - - let proof: AccountProof = api.get_proof(acc, vec![H256::from_uint(&key)], None).await.unwrap(); - - let account = BasicAccount { - nonce: 0.into(), - balance: 0.into(), - storage_root: proof.storage_hash, - code_hash: KECCAK_EMPTY.into(), - }; - - let rlp_account = rlp::encode(&account); - - let root: H256 = api.state_root().await.unwrap(); - let acc_proof: Vec> = proof.account_proof.into_iter().map(|b| b.to_vec()).collect(); - - verify_proof::( - &root.0, - &acc_proof, - &keccak256(acc.as_bytes())[..], - Some(rlp_account.as_ref()), - ) - .unwrap(); - - assert_eq!(proof.storage_proof.len(), 1); - let expected_value = rlp::encode(&value); - let proof = proof.storage_proof[0].clone(); - let storage_proof: Vec> = proof.proof.into_iter().map(|b| b.to_vec()).collect(); - verify_proof::( - &account.storage_root.0, - &storage_proof, - proof.key.as_bytes(), - Some(expected_value.as_ref()), - ) - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn can_get_random_account_proofs() { - let (api, _handle) = spawn(NodeConfig::test()).await; - - for acc in std::iter::repeat_with(Address::random).take(10) { - let _ = api - .get_proof(acc, Vec::new(), None) - .await - .unwrap_or_else(|_| panic!("Failed to get proof for {acc:?}")); - } -} diff --git a/crates/anvil/tests/it/pubsub.rs b/crates/anvil/tests/it/pubsub.rs index 49b814113df8e..948456055f240 100644 --- a/crates/anvil/tests/it/pubsub.rs +++ b/crates/anvil/tests/it/pubsub.rs @@ -1,117 +1,125 @@ //! tests for subscriptions +use crate::utils::{connect_pubsub, connect_pubsub_with_wallet}; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_pubsub::Subscription; +use alloy_rpc_types::{Block as AlloyBlock, Filter, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::abigen, - middleware::SignerMiddleware, - prelude::{Middleware, Ws}, - providers::{JsonRpcClient, PubsubClient}, - signers::Signer, - types::{Address, Block, Filter, TransactionRequest, TxHash, ValueOrArray, U256}, -}; use futures::StreamExt; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = connect_pubsub(&handle.ws_endpoint()).await; let blocks = provider.subscribe_blocks().await.unwrap(); // mine a block every 1 seconds api.anvil_set_interval_mining(1).unwrap(); - let blocks = blocks.take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + let blocks = blocks.into_stream().take(3).collect::>().await; + let block_numbers = blocks.into_iter().map(|b| b.number).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } +sol!( + #[sol(rpc)] + EmitLogs, + "test-data/emit_logs.json" +); +// FIXME: Use .legacy() in tx when implemented in alloy #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_legacy() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); + let contract_addr = EmitLogs::deploy_builder(provider.clone(), msg.clone()) + .from(wallet.address()) + .deploy() + .await + .unwrap(); + let contract = EmitLogs::new(contract_addr, provider.clone()); - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let val = contract.getValue().call().await.unwrap(); + assert_eq!(val._0, msg); // subscribe to events from the contract - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + let filter = Filter::new().address(contract.address().to_owned()); + let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event + // FIXME: Use .legacy() in tx let receipt = contract - .set_value("Next Message".to_string()) - .legacy() + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream - assert_eq!(receipt.logs[0], log); + assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); + let contract_addr = EmitLogs::deploy_builder(provider.clone(), msg.clone()) + .from(wallet.address()) + .deploy() + .await + .unwrap(); + let contract = EmitLogs::new(contract_addr, provider.clone()); - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let val = contract.getValue().call().await.unwrap(); + assert_eq!(val._0, msg); // subscribe to events from the contract - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + let filter = Filter::new().address(contract.address().to_owned()); + let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event let receipt = contract - .set_value("Next Message".to_string()) + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream - assert_eq!(receipt.logs[0], log); + assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_impersonated() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) + .await; // impersonate account let impersonate = Address::random(); @@ -119,155 +127,146 @@ async fn test_sub_logs_impersonated() { api.anvil_set_balance(impersonate, funding).await.unwrap(); api.anvil_impersonate_account(impersonate).await.unwrap(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); - let _val = contract.get_value().call().await.unwrap(); + let _val = contract.getValue().call().await.unwrap(); // subscribe to events from the impersonated account - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + let filter = Filter::new().address(contract.address().to_owned()); + let logs_sub = provider.subscribe_logs(&filter).await.unwrap(); // send a tx triggering an event - let data = contract.set_value("Next Message".to_string()).tx.data().cloned().unwrap(); + let data = contract.setValue("Next Message".to_string()); + let data = data.calldata().clone(); - let tx = TransactionRequest::new().from(impersonate).to(contract.address()).data(data); + let tx = + TransactionRequest::default().from(impersonate).to(*contract.address()).with_input(data); + let tx = WithOtherFields::new(tx); let provider = handle.http_provider(); - let receipt = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let mut logs_sub = logs_sub.into_stream(); // get the emitted event let log = logs_sub.next().await.unwrap(); // ensure the log in the receipt is the same as received via subscription stream - assert_eq!(receipt.logs[0], log); + assert_eq!(receipt.inner.inner.logs()[0], log); } +// FIXME: Use legacy() in tx when implemented in alloy #[tokio::test(flavor = "multi_thread")] async fn test_filters_legacy() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) + .await; + let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); - let filter = contract.value_changed_filter(); - let mut stream = filter.stream().await.unwrap(); + // FIXME: Use legacy() in tx when implemented in alloy + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); + + let stream = contract.ValueChanged_filter().subscribe().await.unwrap(); // send a tx triggering an event + // FIXME: Use legacy() in tx when implemented in alloy let _receipt = contract - .set_value("Next Message".to_string()) - .legacy() + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut log = stream.into_stream(); // get the emitted event - let log = stream.next().await.unwrap().unwrap(); - assert_eq!( - log, - ValueChangedFilter { - author: from, - old_value: "First Message".to_string(), - new_value: "Next Message".to_string(), - }, - ); + let (value_changed, _log) = log.next().await.unwrap().unwrap(); + + assert_eq!(value_changed.author, from); + assert_eq!(value_changed.oldValue, "First Message".to_string()); + assert_eq!(value_changed.newValue, "Next Message".to_string()); } #[tokio::test(flavor = "multi_thread")] async fn test_filters() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) + .await; + let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); - let filter = contract.value_changed_filter(); - let mut stream = filter.stream().await.unwrap(); + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); + + let stream = contract.ValueChanged_filter().subscribe().await.unwrap(); // send a tx triggering an event let _receipt = contract - .set_value("Next Message".to_string()) + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut log = stream.into_stream(); // get the emitted event - let log = stream.next().await.unwrap().unwrap(); - assert_eq!( - log, - ValueChangedFilter { - author: from, - old_value: "First Message".to_string(), - new_value: "Next Message".to_string(), - }, - ); + let (value_changed, _log) = log.next().await.unwrap().unwrap(); + + assert_eq!(value_changed.author, from); + assert_eq!(value_changed.oldValue, "First Message".to_string()); + assert_eq!(value_changed.newValue, "Next Message".to_string()); } #[tokio::test(flavor = "multi_thread")] async fn test_subscriptions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(std::time::Duration::from_secs(1)))).await; - let ws = Ws::connect(handle.ws_endpoint()).await.unwrap(); - - // Subscribing requires sending the sub request and then subscribing to - // the returned sub_id - let sub_id: U256 = ws.request("eth_subscribe", ["newHeads"]).await.unwrap(); - let mut stream = ws.subscribe(sub_id).unwrap(); - - let mut blocks = Vec::new(); - for _ in 0..3 { - let item = stream.next().await.unwrap(); - let block: Block = serde_json::from_str(item.get()).unwrap(); - blocks.push(block.number.unwrap_or_default().as_u64()); - } + let provider = connect_pubsub(&handle.ws_endpoint()).await; + let sub_id = provider.raw_request("eth_subscribe".into(), ["newHeads"]).await.unwrap(); + let stream: Subscription = provider.get_subscription(sub_id).await.unwrap(); + let blocks = stream + .into_stream() + .take(3) + .collect::>() + .await + .into_iter() + .map(|b| b.header.number) + .collect::>(); assert_eq!(blocks, vec![1, 2, 3]) } +#[allow(clippy::disallowed_macros)] #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads_fast() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = connect_pubsub(&handle.ws_endpoint()).await; let blocks = provider.subscribe_blocks().await.unwrap(); + let mut blocks = blocks.into_stream(); + + let num = 1000u64; + + let mut block_numbers = Vec::new(); + for _ in 0..num { + api.mine_one().await; + let block_number = blocks.next().await.unwrap().number; + block_numbers.push(block_number); + } - let num = 1_000u64; - let mine_api = api.clone(); - tokio::task::spawn(async move { - for _ in 0..num { - mine_api.mine_one().await; - } - }); - - // collect all the blocks - let blocks = blocks.take(num as usize).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + println!("Collected {} blocks", block_numbers.len()); let numbers = (1..=num).collect::>(); assert_eq!(block_numbers, numbers); diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index c58302c1f153a..55762fd0f91be 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -1,225 +1,126 @@ -use crate::abi::VENDING_MACHINE_CONTRACT; +use crate::abi::VendingMachine; +use alloy_network::TransactionBuilder; +use alloy_primitives::{bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::{ContractFactory, ContractInstance}, - middleware::SignerMiddleware, - types::U256, - utils::WEI_IN_ETHER, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn test_deploy_reverting() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - constructor() { - require(false, ""); - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let factory = ContractFactory::new(abi.unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await; - assert!(contract.is_err()); - - // should catch the revert during estimation which results in an err - let err = contract.unwrap_err(); - assert!(err.to_string().contains("execution reverted")); + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); + + let code = bytes!("5f5ffd"); // PUSH0 PUSH0 REVERT + let tx = TransactionRequest::default().from(sender).with_deploy_code(code); + let tx = WithOtherFields::new(tx); + + // Calling/estimating gas fails early. + let err = provider.call(&tx).await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("execution reverted"), "{s:?}"); + + // Sending the transaction is successful but reverts on chain. + let tx = provider.send_transaction(tx).await.unwrap(); + let receipt = tx.get_receipt().await.unwrap(); + assert!(!receipt.inner.inner.status()); } #[tokio::test(flavor = "multi_thread")] async fn test_revert_messages() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address owner; - constructor() public { - owner = msg.sender; - } - modifier onlyOwner() { - require(msg.sender == owner, "!authorized"); - _; - } - function getSecret() public onlyOwner view returns(uint256 secret) { - return 123; - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + sol!( + #[sol(rpc, bytecode = "608080604052346025575f80546001600160a01b031916600117905560b69081602a8239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c635b9fdc30146023575f80fd5b34607c575f366003190112607c575f546001600160a01b03163303604c576020604051607b8152f35b62461bcd60e51b815260206004820152600b60248201526a08585d5d1a1bdc9a5e995960aa1b6044820152606490fd5b5f80fdfea2646970667358221220f593e5ccd46935f623185de62a72d9f1492d8d15075a111b0fa4d7e16acf4a7064736f6c63430008190033")] + contract Contract { + address private owner; + + constructor() { + owner = address(1); + } + + modifier onlyOwner() { + require(msg.sender == owner, "!authorized"); + _; + } + + #[derive(Debug)] + function getSecret() public onlyOwner view returns(uint256 secret) { + return 123; + } + } + ); let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); + let provider = handle.http_provider(); - let resp = contract.method::<_, U256>("getSecret", ()).unwrap().call().await; + let contract = Contract::deploy(&provider).await.unwrap(); - let err = resp.unwrap_err(); - let msg = err.to_string(); - assert!(msg.contains("execution reverted: !authorized")); + let err = contract.getSecret().call().await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("!authorized"), "{s:?}"); } #[tokio::test(flavor = "multi_thread")] async fn test_solc_revert_example() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source("VendingMachine", VENDING_MACHINE_CONTRACT).unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("VendingMachine").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); + let sender = handle.dev_accounts().next().unwrap(); + let provider = handle.http_provider(); - for fun in ["buyRevert", "buyRequire"] { - let resp = contract.method::<_, ()>(fun, U256::zero()).unwrap().call().await; - resp.unwrap(); + let contract = VendingMachine::deploy(&provider).await.unwrap(); - let ten = WEI_IN_ETHER.saturating_mul(10u64.into()); - let call = contract.method::<_, ()>(fun, ten).unwrap().value(ten); - - let resp = call.clone().call().await; - let err = resp.unwrap_err().to_string(); - assert!(err.contains("execution reverted: Not enough Ether provided.")); - assert!(err.contains("code: 3")); - } + let err = + contract.buy(U256::from(100)).value(U256::from(1)).from(sender).call().await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("Not enough Ether provided."), "{s:?}"); } // #[tokio::test(flavor = "multi_thread")] async fn test_another_revert_message() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - uint256 public number; - - function setNumber(uint256 num) public { - require(num != 0, "RevertStringFooBar"); - number = num; - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + sol!( + #[sol(rpc, bytecode = "6080806040523460135760d7908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c9081633fb5c1cb14604d5750638381f58a14602f575f80fd5b346049575f36600319011260495760205f54604051908152f35b5f80fd5b346049576020366003190112604957600435908115606a57505f55005b62461bcd60e51b81526020600482015260126024820152712932bb32b93a29ba3934b733a337b7a130b960711b6044820152606490fdfea2646970667358221220314bf8261cc467619137c071584f8d3bd8d9d97bf2846c138c0567040cf9828a64736f6c63430008190033")] + contract Contract { + uint256 public number; + + #[derive(Debug)] + function setNumber(uint256 num) public { + require(num != 0, "RevertStringFooBar"); + number = num; + } + } + ); let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("setNumber", U256::zero()).unwrap(); - let resp = call.send().await; + let provider = handle.http_provider(); + + let contract = Contract::deploy(&provider).await.unwrap(); - let err = resp.unwrap_err(); - let msg = err.to_string(); - assert!(msg.contains("execution reverted: RevertStringFooBar")); + let err = contract.setNumber(U256::from(0)).call().await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("RevertStringFooBar"), "{s:?}"); } #[tokio::test(flavor = "multi_thread")] async fn test_solc_revert_custom_errors() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - uint256 public number; - error AddressRevert(address); - - function revertAddress() public { - revert AddressRevert(address(1)); - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + sol!( + #[sol(rpc, bytecode = "608080604052346013576081908160188239f35b5f80fdfe60808060405260043610156011575f80fd5b5f3560e01c63e57207e6146023575f80fd5b346047575f3660031901126047576373ea2a7f60e01b815260016004820152602490fd5b5f80fdfea26469706673582212202a8d69545801394af36c56ca229b52ae0b22d7b8f938b107dca8ebbf655464f764736f6c63430008190033")] + contract Contract { + error AddressRevert(address); + + #[derive(Debug)] + function revertAddress() public { + revert AddressRevert(address(1)); + } + } + ); let (_api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); - let provider = handle.ws_provider().await; - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = - ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), Arc::clone(&client)); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let call = contract.method::<_, ()>("revertAddress", ()).unwrap().gas(150000); - - let resp = call.call().await; + let contract = Contract::deploy(&provider).await.unwrap(); - let _ = resp.unwrap_err(); + let err = contract.revertAddress().call().await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("execution reverted"), "{s:?}"); } diff --git a/crates/anvil/tests/it/sign.rs b/crates/anvil/tests/it/sign.rs index 368a34da17633..0ff56f3641199 100644 --- a/crates/anvil/tests/it/sign.rs +++ b/crates/anvil/tests/it/sign.rs @@ -1,9 +1,12 @@ +use crate::utils::http_provider_with_signer; +use alloy_dyn_abi::TypedData; +use alloy_network::EthereumWallet; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; use anvil::{spawn, NodeConfig}; -use ethers::{ - prelude::{Middleware, SignerMiddleware}, - signers::Signer, - types::{transaction::eip712::TypedData, Address, Chain, TransactionRequest}, -}; #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_data() { @@ -281,28 +284,51 @@ async fn can_sign_typed_data_os() { } #[tokio::test(flavor = "multi_thread")] -async fn rejects_different_chain_id() { - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); +async fn can_sign_transaction() { + let (api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = SignerMiddleware::new(provider, wallet.with_chain_id(Chain::Mainnet)); + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + // craft the tx + // specify the `from` field so that the client knows which account to use + let tx = TransactionRequest::default() + .nonce(10) + .max_fee_per_gas(100) + .max_priority_fee_per_gas(101) + .to(to) + .value(U256::from(1001u64)) + .from(from); + let tx = WithOtherFields::new(tx); + // sign it via the eth_signTransaction API + let signed_tx = api.sign_transaction(tx).await.unwrap(); + + assert_eq!(signed_tx, "0x02f866827a690a65648252089470997970c51812dc3a010c7d01b50e0d17dc79c88203e980c001a0e4de88aefcf87ccb04466e60de66a83192e46aa26177d5ea35efbfd43fd0ecdca00e3148e0e8e0b9a6f9b329efd6e30c4a461920f3a27497be3dbefaba996601da"); +} - let tx = TransactionRequest::new().to(Address::random()).value(100u64); +#[tokio::test(flavor = "multi_thread")] +async fn rejects_different_chain_id() { + let (_api, handle) = spawn(NodeConfig::test()).await; + let wallet = handle.dev_wallets().next().unwrap().with_chain_id(Some(1)); + let provider = http_provider_with_signer(&handle.http_endpoint(), EthereumWallet::from(wallet)); - let res = client.send_transaction(tx, None).await; + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100)); + let tx = WithOtherFields::new(tx); + let res = provider.send_transaction(tx).await; let err = res.unwrap_err(); - assert!(err.to_string().contains("signed for another chain")); + assert!(err.to_string().contains("does not match the signer's"), "{}", err.to_string()); } #[tokio::test(flavor = "multi_thread")] async fn rejects_invalid_chain_id() { let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap().with_chain_id(99u64); - let provider = handle.http_provider(); - let client = SignerMiddleware::new(provider, wallet); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let res = client.send_transaction(tx, None).await; + let wallet = handle.dev_wallets().next().unwrap(); + let wallet = wallet.with_chain_id(Some(99u64)); + let provider = http_provider_with_signer(&handle.http_endpoint(), EthereumWallet::from(wallet)); + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(100u64)); + let tx = WithOtherFields::new(tx); + let res = provider.send_transaction(tx).await; let _err = res.unwrap_err(); } diff --git a/crates/anvil/tests/it/state.rs b/crates/anvil/tests/it/state.rs new file mode 100644 index 0000000000000..5337b36b561f5 --- /dev/null +++ b/crates/anvil/tests/it/state.rs @@ -0,0 +1,278 @@ +//! general eth api tests + +use crate::abi::Greeter; +use alloy_network::{ReceiptResponse, TransactionBuilder}; +use alloy_primitives::{address, utils::Unit, Bytes, Uint, U256, U64}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_serde::WithOtherFields; +use anvil::{spawn, NodeConfig}; +use foundry_test_utils::rpc::next_http_rpc_endpoint; + +#[tokio::test(flavor = "multi_thread")] +async fn can_load_state() { + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, _handle) = spawn(NodeConfig::test()).await; + + api.mine_one().await; + api.mine_one().await; + + let num = api.block_number().unwrap(); + + let state = api.serialized_state(false).await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &state).unwrap(); + + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + + let num2 = api.block_number().unwrap(); + + // Ref: https://github.com/foundry-rs/foundry/issues/9017 + // Check responses of eth_blockNumber and eth_getBlockByNumber don't deviate after loading state + let num_from_tag = api + .block_by_number(alloy_eips::BlockNumberOrTag::Latest) + .await + .unwrap() + .unwrap() + .header + .number; + assert_eq!(num, num2); + + assert_eq!(num, U256::from(num_from_tag)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_load_existing_state_legacy() { + let state_file = "test-data/state-dump-legacy.json"; + + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, Uint::from(2)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_load_existing_state_legacy_stress() { + let state_file = "test-data/state-dump-legacy-stress.json"; + + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, Uint::from(5)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_load_existing_state() { + let state_file = "test-data/state-dump.json"; + + let (api, _handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, Uint::from(2)); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_make_sure_historical_state_is_not_cleared_on_dump() { + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, handle) = spawn(NodeConfig::test()).await; + + let provider = handle.http_provider(); + + let greeter = Greeter::deploy(&provider, "Hello".to_string()).await.unwrap(); + + let address = greeter.address(); + + let _tx = greeter + .setGreeting("World!".to_string()) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + api.mine_one().await; + + let ser_state = api.serialized_state(true).await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &ser_state).unwrap(); + + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, Uint::from(3)); + + // Makes sure historical states of the new instance are not cleared. + let code = provider.get_code_at(*address).block_id(BlockId::number(2)).await.unwrap(); + + assert_ne!(code, Bytes::new()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_preserve_historical_states_between_dump_and_load() { + let tmp = tempfile::tempdir().unwrap(); + let state_file = tmp.path().join("state.json"); + + let (api, handle) = spawn(NodeConfig::test()).await; + + let provider = handle.http_provider(); + + let greeter = Greeter::deploy(&provider, "Hello".to_string()).await.unwrap(); + + let address = greeter.address(); + + let deploy_blk_num = provider.get_block_number().await.unwrap(); + + let tx = greeter + .setGreeting("World!".to_string()) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + + let change_greeting_blk_num = tx.block_number.unwrap(); + + api.mine_one().await; + + let ser_state = api.serialized_state(true).await.unwrap(); + foundry_common::fs::write_json_file(&state_file, &ser_state).unwrap(); + + let (api, handle) = spawn(NodeConfig::test().with_init_state_path(state_file)).await; + + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, Uint::from(3)); + + let provider = handle.http_provider(); + + let greeter = Greeter::new(*address, provider); + + let greeting_at_init = + greeter.greet().block(BlockId::number(deploy_blk_num)).call().await.unwrap()._0; + + assert_eq!(greeting_at_init, "Hello"); + + let greeting_after_change = + greeter.greet().block(BlockId::number(change_greeting_blk_num)).call().await.unwrap()._0; + + assert_eq!(greeting_after_change, "World!"); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_load_state() { + let (api, handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(next_http_rpc_endpoint())) + .with_fork_block_number(Some(21070682u64)), + ) + .await; + + let bob = address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"); + let alice = address!("9276449EaC5b4f7Bc17cFC6700f7BeeB86F9bCd0"); + + let provider = handle.http_provider(); + + let init_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let init_balance_alice = provider.get_balance(alice).await.unwrap(); + + let value = Unit::ETHER.wei().saturating_mul(U256::from(1)); // 1 ether + let tx = TransactionRequest::default().with_to(alice).with_value(value).with_from(bob); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + let serialized_state = api.serialized_state(false).await.unwrap(); + + let state_dump_block = api.block_number().unwrap(); + + let (api, handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(next_http_rpc_endpoint())) + .with_fork_block_number(Some(21070686u64)) // Forked chain has moved forward + .with_init_state(Some(serialized_state)), + ) + .await; + + // Ensure the initial block number is the fork_block_number and not the state_dump_block + let block_number = api.block_number().unwrap(); + assert_eq!(block_number, U256::from(21070686u64)); + assert_ne!(block_number, state_dump_block); + + let provider = handle.http_provider(); + + let restart_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let restart_balance_alice = provider.get_balance(alice).await.unwrap(); + + assert_eq!(init_nonce_bob + 1, restart_nonce_bob); + + assert_eq!(init_balance_alice + value, restart_balance_alice); + + // Send another tx to check if the state is preserved + + let tx = TransactionRequest::default().with_to(alice).with_value(value).with_from(bob); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + let nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let balance_alice = provider.get_balance(alice).await.unwrap(); + + let tx = TransactionRequest::default() + .with_to(alice) + .with_value(value) + .with_from(bob) + .with_nonce(nonce_bob); + let tx = WithOtherFields::new(tx); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + let latest_nonce_bob = provider.get_transaction_count(bob).await.unwrap(); + + let latest_balance_alice = provider.get_balance(alice).await.unwrap(); + + assert_eq!(nonce_bob + 1, latest_nonce_bob); + + assert_eq!(balance_alice + value, latest_balance_alice); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn test_fork_load_state_with_greater_state_block() { + let (api, _handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(next_http_rpc_endpoint())) + .with_fork_block_number(Some(21070682u64)), + ) + .await; + + api.mine_one().await; + + let block_number = api.block_number().unwrap(); + + let serialized_state = api.serialized_state(false).await.unwrap(); + + assert_eq!(serialized_state.best_block_number, Some(block_number.to::())); + + let (api, _handle) = spawn( + NodeConfig::test() + .with_eth_rpc_url(Some(next_http_rpc_endpoint())) + .with_fork_block_number(Some(21070682u64)) // Forked chain has moved forward + .with_init_state(Some(serialized_state)), + ) + .await; + + let new_block_number = api.block_number().unwrap(); + + assert_eq!(new_block_number, block_number); +} diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 53c86b926c01d..79448ad836921 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1,36 +1,54 @@ -use crate::fork::fork_config; -use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::ContractInstance, - prelude::{ - Action, ContractFactory, GethTrace, GethTraceFrame, Middleware, Signer, SignerMiddleware, - TransactionRequest, +use crate::{ + abi::{Multicall, SimpleStorage}, + fork::fork_config, + utils::http_provider_with_signer, +}; +use alloy_eips::BlockId; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{ + hex::{self, FromHex}, + Address, Bytes, U256, +}; +use alloy_provider::{ + ext::{DebugApi, TraceApi}, + Provider, +}; +use alloy_rpc_types::{ + state::StateOverride, + trace::{ + filter::{TraceFilter, TraceFilterMode}, + geth::{ + CallConfig, GethDebugBuiltInTracerType, GethDebugTracerType, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, + }, + parity::{Action, LocalizedTransactionTrace}, }, - types::{ActionType, Address, GethDebugTracingCallOptions, Trace}, - utils::hex, + TransactionRequest, }; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; +use anvil::{spawn, EthereumHardfork, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); + let provider = handle.ws_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // specify the `from` field so that the client knows which account to use - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - match traces[0].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -46,109 +64,244 @@ async fn test_get_transfer_parity_traces() { assert_eq!(traces, block_traces); } +sol!( + #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")] + contract SuicideContract { + address payable private owner; + constructor() public { + owner = payable(msg.sender); + } + function goodbye() public { + selfdestruct(owner); + } + } +); + #[tokio::test(flavor = "multi_thread")] async fn test_parity_suicide_trace() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function goodbye() public { - selfdestruct(owner); - } + let (_api, handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Shanghai.into()))).await; + let provider = handle.ws_provider(); + let wallets = handle.dev_wallets().collect::>(); + let owner = wallets[0].address(); + let destructor = wallets[1].address(); + + let contract_addr = + SuicideContract::deploy_builder(provider.clone()).from(owner).deploy().await.unwrap(); + let contract = SuicideContract::new(contract_addr, provider.clone()); + let call = contract.goodbye().from(destructor); + let call = call.send().await.unwrap(); + let tx = call.get_receipt().await.unwrap(); + + let traces = handle.http_provider().trace_transaction(tx.transaction_hash).await.unwrap(); + assert!(!traces.is_empty()); + assert!(traces[1].trace.action.is_selfdestruct()); } -"#, - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +sol!( + #[sol(rpc, bytecode = "0x6080604052348015600f57600080fd5b50336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060a48061005e6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806375fc8e3c14602d575b600080fd5b60336035565b005b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16fffea26469706673582212205006867290df97c54f2df1cb94fc081197ab670e2adf5353071d2ecce1d694b864736f6c634300080d0033")] + contract DebugTraceContract { + address payable private owner; + constructor() public { + owner = payable(msg.sender); + } + function goodbye() public { + selfdestruct(owner); + } + } +); +#[tokio::test(flavor = "multi_thread")] +async fn test_transfer_debug_trace_call() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); + let deployer: EthereumWallet = wallets[0].clone().into(); + let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let contract_addr = DebugTraceContract::deploy_builder(provider.clone()) + .from(wallets[0].clone().address()) + .deploy() + .await + .unwrap(); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); - let tx = call.send().await.unwrap().await.unwrap().unwrap(); + let caller: EthereumWallet = wallets[1].clone().into(); + let caller_provider = http_provider_with_signer(&handle.http_endpoint(), caller); + let contract = DebugTraceContract::new(contract_addr, caller_provider); - let traces = handle.http_provider().trace_transaction(tx.transaction_hash).await.unwrap(); - assert!(!traces.is_empty()); - assert_eq!(traces[0].action_type, ActionType::Suicide); + let call = contract.goodbye().from(wallets[1].address()); + let calldata = call.calldata().to_owned(); + + let tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*contract.address()) + .with_input(calldata); + + let traces = handle + .http_provider() + .debug_trace_call(tx, BlockId::latest(), GethDebugTracingCallOptions::default()) + .await + .unwrap(); + + match traces { + GethTrace::Default(default_frame) => { + assert!(!default_frame.failed); + } + _ => { + unreachable!() + } + } } #[tokio::test(flavor = "multi_thread")] -async fn test_transfer_debug_trace_call() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); +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 = Multicall::deploy(&provider).await.unwrap(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); + + let set_value = simple_storage_contract.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + + let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { + target: *simple_storage_contract.address(), + callData: set_value_calldata.to_owned(), + }]); + + let internal_call_tx_calldata = internal_call_tx_builder.calldata().to_owned(); + + // 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(), + BlockId::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!() + } } - function goodbye() public { - selfdestruct(owner); + + // 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(), + BlockId::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!() + } } -} -"#, - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + // 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, + BlockId::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_debug_trace_call_state_override() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let tx = TransactionRequest::default() + .from(wallets[1].address()) + .to("0x1234567890123456789012345678901234567890".parse().unwrap()); - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); + let override_json = r#"{ + "0x1234567890123456789012345678901234567890": { + "balance": "0x01", + "code": "0x30315f5260205ff3" + } + }"#; - let traces = handle + let state_override: StateOverride = serde_json::from_str(override_json).unwrap(); + + let tx_traces = handle .http_provider() - .debug_trace_call(call.tx, None, GethDebugTracingCallOptions::default()) + .debug_trace_call( + tx.clone(), + BlockId::latest(), + GethDebugTracingCallOptions::default() + .with_tracing_options(GethDebugTracingOptions::default()) + .with_state_overrides(state_override), + ) .await .unwrap(); - match traces { - GethTrace::Known(traces) => match traces { - GethTraceFrame::Default(traces) => { - assert!(!traces.failed); - } - _ => { - unreachable!() - } - }, - GethTrace::Unknown(_) => { + + match tx_traces { + GethTrace::Default(trace_res) => { + assert_eq!( + trace_res.return_value, + Bytes::from_hex("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap() + ); + } + _ => { unreachable!() } } @@ -164,15 +317,20 @@ async fn test_trace_address_fork() { let from: Address = "0x2e4777139254ff76db957e284b186a4507ff8c67".parse().unwrap(); let to: Address = "0xe2f2a5c287993345a840db3b0845fbc70f5935a5".parse().unwrap(); - let tx = TransactionRequest::new().to(to).from(from).data(input).gas(300_000); + let tx = TransactionRequest::default() + .to(to) + .from(from) + .with_input::(input.into()) + .with_gas_limit(300_000); + let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - match traces[0].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -180,8 +338,7 @@ async fn test_trace_address_fork() { _ => unreachable!("unexpected action"), } - let json = serde_json::json!( - [ + let json = serde_json::json!([ { "action": { "callType": "call", @@ -219,9 +376,7 @@ async fn test_trace_address_fork() { "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3" }, "subtraces": 2, - "traceAddress": [ - 0 - ], + "traceAddress": [0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" @@ -242,10 +397,7 @@ async fn test_trace_address_fork() { "output": "0x0000000000000000000000000000000000000000000000e0e82ca52ec6e6a4d3" }, "subtraces": 0, - "traceAddress": [ - 0, - 0 - ], + "traceAddress": [0, 0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" @@ -266,10 +418,7 @@ async fn test_trace_address_fork() { "output": "0x" }, "subtraces": 2, - "traceAddress": [ - 0, - 1 - ], + "traceAddress": [0, 1], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" @@ -290,11 +439,7 @@ async fn test_trace_address_fork() { "output": "0x0000000000000000000000000000000000000000000020fe99f8898600d94750" }, "subtraces": 0, - "traceAddress": [ - 0, - 1, - 0 - ], + "traceAddress": [0, 1, 0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" @@ -315,11 +460,7 @@ async fn test_trace_address_fork() { "output": "0x" }, "subtraces": 1, - "traceAddress": [ - 0, - 1, - 1 - ], + "traceAddress": [0, 1, 1], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" @@ -340,26 +481,20 @@ async fn test_trace_address_fork() { "output": "0x0000000000000000000000000000000000000000000000000000000000000001" }, "subtraces": 0, - "traceAddress": [ - 0, - 1, - 1, - 0 - ], + "traceAddress": [0, 1, 1, 0], "transactionHash": "0x3255cce7312e9c4470e1a1883be13718e971f6faafb96199b8bd75e5b7c39e3a", "transactionPosition": 19, "type": "call" } - ] - ); + ]); - let expected_traces: Vec = serde_json::from_value(json).unwrap(); + let expected_traces: Vec = serde_json::from_value(json).unwrap(); // test matching traceAddress traces.into_iter().zip(expected_traces).for_each(|(a, b)| { - assert_eq!(a.trace_address, b.trace_address); - assert_eq!(a.subtraces, b.subtraces); - match (a.action, b.action) { + assert_eq!(a.trace.trace_address, b.trace.trace_address); + assert_eq!(a.trace.subtraces, b.trace.subtraces); + match (a.trace.action, b.trace.action) { (Action::Call(a), Action::Call(b)) => { assert_eq!(a.from, b.from); assert_eq!(a.to, b.to); @@ -380,17 +515,23 @@ async fn test_trace_address_fork2() { let from: Address = "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123".parse().unwrap(); let to: Address = "0x99999999d116ffa7d76590de2f427d8e15aeb0b8".parse().unwrap(); - let tx = TransactionRequest::new().to(to).from(from).data(input).gas(350_000); + let tx = TransactionRequest::default() + .to(to) + .from(from) + .with_input::(input.into()) + .with_gas_limit(350_000); + let tx = WithOtherFields::new(tx); api.anvil_impersonate_account(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + let status = tx.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); let traces = provider.trace_transaction(tx.transaction_hash).await.unwrap(); assert!(!traces.is_empty()); - match traces[0].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -398,261 +539,226 @@ async fn test_trace_address_fork2() { _ => unreachable!("unexpected action"), } - let json = serde_json::json!( - [ - - { - "action": { - "from": "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123", - "callType": "call", - "gas": "0x4fabc", - "input": "0x30000003000000000000000000000000adda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a300000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af210000000000000000000000000000000000000000000000000000000000", - "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x1d51b", - "output": "0x" - }, - "subtraces": 1, - "traceAddress": [], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "callType": "delegatecall", - "gas": "0x4d594", - "input": "0x00000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af21", - "to": "0xadda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x1c35f", - "output": "0x" - }, - "subtraces": 3, - "traceAddress": [ - 0 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "callType": "call", - "gas": "0x4b6d6", - "input": "0x16b2da82000000000000000000000000000000000000000000000000000000086a23af21", - "to": "0xd1663cfb8ceaf22039ebb98914a8c98264643710", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0xd6d", - "output": "0x0000000000000000000000000000000000000000000000000000000000000000" - }, - "subtraces": 0, - "traceAddress": [ - 0, - 0 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "callType": "staticcall", - "gas": "0x49c35", - "input": "0x3850c7bd", - "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0xa88", - "output": "0x000000000000000000000000000000000000004319b52bf08b65295d49117e7900000000000000000000000000000000000000000000000000000000000148a0000000000000000000000000000000000000000000000000000000000000010e000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" - }, - "subtraces": 0, - "traceAddress": [ - 0, - 1 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "callType": "call", - "gas": "0x48d01", - "input": "0x128acb0800000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b80000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb98000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000", - "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x18c20", - "output": "0x0000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980" - }, - "subtraces": 4, - "traceAddress": [ - 0, - 2 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "callType": "call", - "gas": "0x3802a", - "input": "0xa9059cbb00000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b8000000000000000000000000000000000000000000000986236e1301eaf04680", - "to": "0xf4d2888d29d722226fafa5d9b24f9164c092421e", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x31b6", - "output": "0x0000000000000000000000000000000000000000000000000000000000000001" - }, - "subtraces": 0, - "traceAddress": [ - 0, - 2, - 0 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "callType": "staticcall", - "gas": "0x34237", - "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x9e6", - "output": "0x000000000000000000000000000000000000000000000091cda6c1ce33e53b89" - }, - "subtraces": 0, - "traceAddress": [ - 0, - 2, - 1 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "callType": "call", - "gas": "0x3357e", - "input": "0xfa461e330000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb9800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000", - "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x2e8b", - "output": "0x" - }, - "subtraces": 1, - "traceAddress": [ - 0, - 2, - 2 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", - "callType": "call", - "gas": "0x324db", - "input": "0xa9059cbb0000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce0110000000000000000000000000000000000000000000000008b5116525f9edc3e", - "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x2a6e", - "output": "0x0000000000000000000000000000000000000000000000000000000000000001" - }, - "subtraces": 0, - "traceAddress": [ - 0, - 2, - 2, - 0 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - }, - { - "action": { - "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "callType": "staticcall", - "gas": "0x30535", - "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011", - "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - "value": "0x0" - }, - "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", - "blockNumber": 15314402, - "result": { - "gasUsed": "0x216", - "output": "0x00000000000000000000000000000000000000000000009258f7d820938417c7" - }, - "subtraces": 0, - "traceAddress": [ - 0, - 2, - 3 - ], - "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", - "transactionPosition": 289, - "type": "call" - } - ] - ); + let json = serde_json::json!([ + { + "action": { + "from": "0xa009fa1ac416ec02f6f902a3a4a584b092ae6123", + "callType": "call", + "gas": "0x4fabc", + "input": "0x30000003000000000000000000000000adda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a300000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af210000000000000000000000000000000000000000000000000000000000", + "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x1d51b", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "callType": "delegatecall", + "gas": "0x4d594", + "input": "0x00000004fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000004319b52bf08b65295d49117e790000000000000000000000000000000000000000000000008b6d9e8818d6141f000000000000000000000000000000000000000000000000000000086a23af21", + "to": "0xadda1059a6c6c102b0fa562b9bb2cb9a0de5b1f4", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x1c35f", + "output": "0x" + }, + "subtraces": 3, + "traceAddress": [0], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "callType": "call", + "gas": "0x4b6d6", + "input": "0x16b2da82000000000000000000000000000000000000000000000000000000086a23af21", + "to": "0xd1663cfb8ceaf22039ebb98914a8c98264643710", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0xd6d", + "output": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "subtraces": 0, + "traceAddress": [0, 0], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "callType": "staticcall", + "gas": "0x49c35", + "input": "0x3850c7bd", + "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0xa88", + "output": "0x000000000000000000000000000000000000004319b52bf08b65295d49117e7900000000000000000000000000000000000000000000000000000000000148a0000000000000000000000000000000000000000000000000000000000000010e000000000000000000000000000000000000000000000000000000000000012c000000000000000000000000000000000000000000000000000000000000012c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [0, 1], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "callType": "call", + "gas": "0x48d01", + "input": "0x128acb0800000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b80000000000000000000000000000000000000000000000000000000000000001fffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb98000000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000", + "to": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x18c20", + "output": "0x0000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb980" + }, + "subtraces": 4, + "traceAddress": [0, 2], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "callType": "call", + "gas": "0x3802a", + "input": "0xa9059cbb00000000000000000000000099999999d116ffa7d76590de2f427d8e15aeb0b8000000000000000000000000000000000000000000000986236e1301eaf04680", + "to": "0xf4d2888d29d722226fafa5d9b24f9164c092421e", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x31b6", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [0, 2, 0], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "callType": "staticcall", + "gas": "0x34237", + "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x9e6", + "output": "0x000000000000000000000000000000000000000000000091cda6c1ce33e53b89" + }, + "subtraces": 0, + "traceAddress": [0, 2, 1], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "callType": "call", + "gas": "0x3357e", + "input": "0xfa461e330000000000000000000000000000000000000000000000008b5116525f9edc3efffffffffffffffffffffffffffffffffffffffffffff679dc91ecfe150fb9800000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f4d2888d29d722226fafa5d9b24f9164c092421e000bb8000000000000000000000000000000000000000000", + "to": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x2e8b", + "output": "0x" + }, + "subtraces": 1, + "traceAddress": [0, 2, 2], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x99999999d116ffa7d76590de2f427d8e15aeb0b8", + "callType": "call", + "gas": "0x324db", + "input": "0xa9059cbb0000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce0110000000000000000000000000000000000000000000000008b5116525f9edc3e", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x2a6e", + "output": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "subtraces": 0, + "traceAddress": [0, 2, 2, 0], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + }, + { + "action": { + "from": "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "callType": "staticcall", + "gas": "0x30535", + "input": "0x70a082310000000000000000000000004b5ab61593a2401b1075b90c04cbcdd3f87ce011", + "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "value": "0x0" + }, + "blockHash": "0xf689ba7749648b8c5c8f5eedd73001033f0aed7ea50b7c81048ad1533b8d3d73", + "blockNumber": 15314402, + "result": { + "gasUsed": "0x216", + "output": "0x00000000000000000000000000000000000000000000009258f7d820938417c7" + }, + "subtraces": 0, + "traceAddress": [0, 2, 3], + "transactionHash": "0x2d951c5c95d374263ca99ad9c20c9797fc714330a8037429a3aa4c83d456f845", + "transactionPosition": 289, + "type": "call" + } + ]); - let expected_traces: Vec = serde_json::from_value(json).unwrap(); + let expected_traces: Vec = serde_json::from_value(json).unwrap(); // test matching traceAddress traces.into_iter().zip(expected_traces).for_each(|(a, b)| { - assert_eq!(a.trace_address, b.trace_address); - assert_eq!(a.subtraces, b.subtraces); - match (a.action, b.action) { + assert_eq!(a.trace.trace_address, b.trace.trace_address); + assert_eq!(a.trace.subtraces, b.trace.subtraces); + match (a.trace.action, b.trace.action) { (Action::Call(a), Action::Call(b)) => { assert_eq!(a.from, b.from); assert_eq!(a.to, b.to); @@ -661,3 +767,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); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + } + + let traces = api.trace_filter(tracer).await.unwrap(); + assert_eq!(traces.len(), 6); + + // 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); + provider.send_transaction(tx).await.unwrap().get_receipt().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); + provider.send_transaction(tx).await.unwrap().get_receipt().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 b52a058da081f..6aaada01048d9 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -1,18 +1,19 @@ -use crate::abi::*; -use anvil::{spawn, Hardfork, NodeConfig}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - prelude::{ - signer::SignerMiddlewareError, BlockId, Middleware, Signer, SignerMiddleware, - TransactionRequest, - }, - types::{ - transaction::eip2930::{AccessList, AccessListItem}, - Address, BlockNumber, Transaction, TransactionReceipt, H256, U256, - }, +use crate::{ + abi::{Greeter, Multicall, SimpleStorage}, + utils::{connect_pubsub, http_provider_with_signer}, }; +use alloy_network::{EthereumWallet, TransactionBuilder, TransactionResponse}; +use alloy_primitives::{map::B256HashSet, Address, Bytes, FixedBytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{ + state::{AccountOverride, StateOverride}, + AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockTransactions, TransactionRequest, +}; +use alloy_serde::WithOtherFields; +use anvil::{spawn, EthereumHardfork, NodeConfig}; +use eyre::Ok; use futures::{future::join_all, FutureExt, StreamExt}; -use std::{collections::HashSet, sync::Arc, time::Duration}; +use std::{str::FromStr, time::Duration}; use tokio::time::timeout; #[tokio::test(flavor = "multi_thread")] @@ -20,32 +21,34 @@ async fn can_transfer_eth() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - assert!(nonce.is_zero()); + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert!(nonce == 0); - let balance_before = provider.get_balance(to, None).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); // craft the tx // specify the `from` field so that the client knows which account to use - let tx = TransactionRequest::new().to(to).value(amount).from(from); - + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); // broadcast it via the eth_sendTransaction API - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap(); - assert_eq!(tx.block_number, Some(1u64.into())); - assert_eq!(tx.transaction_index, 0u64.into()); + let tx = tx.get_receipt().await.unwrap(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + assert_eq!(tx.block_number, Some(1)); + assert_eq!(tx.transaction_index, Some(0)); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(from).await.unwrap(); - let to_balance = provider.get_balance(to, None).await.unwrap(); + assert_eq!(nonce, 1); + + let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_before.saturating_add(amount), to_balance); } @@ -58,30 +61,38 @@ async fn can_order_transactions() { // disable automine api.anvil_set_auto_mine(false).await.unwrap(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); // craft the tx with lower price - let tx = TransactionRequest::new().to(to).from(from).value(amount).gas_price(gas_price); - let tx_lower = provider.send_transaction(tx, None).await.unwrap(); + let mut tx = TransactionRequest::default().to(to).from(from).value(amount); + + tx.set_gas_price(gas_price); + let tx = WithOtherFields::new(tx); + let tx_lower = provider.send_transaction(tx).await.unwrap(); // craft the tx with higher price - let tx = TransactionRequest::new().to(from).from(to).value(amount).gas_price(gas_price + 1); - let tx_higher = provider.send_transaction(tx, None).await.unwrap(); + let mut tx = TransactionRequest::default().to(from).from(to).value(amount); + + tx.set_gas_price(gas_price + 1); + let tx = WithOtherFields::new(tx); + let tx_higher = provider.send_transaction(tx).await.unwrap(); // manually mine the block with the transactions api.mine_one().await; + let higher_price = tx_higher.get_receipt().await.unwrap().transaction_hash; + let lower_price = tx_lower.get_receipt().await.unwrap().transaction_hash; + // get the block, await receipts - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - let lower_price = tx_lower.await.unwrap().unwrap().transaction_hash; - let higher_price = tx_higher.await.unwrap().unwrap().transaction_hash; - assert_eq!(block.transactions, vec![higher_price, lower_price]) + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + assert_eq!(block.transactions, BlockTransactions::Hashes(vec![higher_price, lower_price])) } #[tokio::test(flavor = "multi_thread")] @@ -89,34 +100,41 @@ async fn can_respect_nonces() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce + 1); + + let tx = WithOtherFields::new(tx); // send the transaction with higher nonce than on chain - let higher_pending_tx = - provider.send_transaction(tx.clone().nonce(nonce + 1u64), None).await.unwrap(); + let higher_pending_tx = provider.send_transaction(tx).await.unwrap(); // ensure the listener for ready transactions times out let mut listener = api.new_ready_transactions(); let res = timeout(Duration::from_millis(1500), listener.next()).await; res.unwrap_err(); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); + + let tx = WithOtherFields::new(tx); // send with the actual nonce which is mined immediately - let tx = - provider.send_transaction(tx.nonce(nonce), None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap(); + let tx = tx.get_receipt().await.unwrap(); // this will unblock the currently pending tx - let higher_tx = higher_pending_tx.await.unwrap().unwrap(); + let higher_tx = higher_pending_tx.get_receipt().await.unwrap(); // Awaits endlessly here due to alloy/#389 - let block = provider.get_block(1u64).await.unwrap().unwrap(); + let block = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); assert_eq!(2, block.transactions.len()); - assert_eq!(vec![tx.transaction_hash, higher_tx.transaction_hash], block.transactions); + assert_eq!( + BlockTransactions::Hashes(vec![tx.transaction_hash, higher_tx.transaction_hash]), + block.transactions + ); } #[tokio::test(flavor = "multi_thread")] @@ -128,37 +146,46 @@ async fn can_replace_transaction() { let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from).nonce(nonce); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); + let mut tx = WithOtherFields::new(tx); + + tx.set_gas_price(gas_price); // send transaction with lower gas price - let lower_priced_pending_tx = - provider.send_transaction(tx.clone().gas_price(gas_price), None).await.unwrap(); + let _lower_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap(); + tx.set_gas_price(gas_price + 1); // send the same transaction with higher gas price - let higher_priced_pending_tx = - provider.send_transaction(tx.gas_price(gas_price + 1u64), None).await.unwrap(); + let higher_priced_pending_tx = provider.send_transaction(tx).await.unwrap(); + let higher_tx_hash = *higher_priced_pending_tx.tx_hash(); // mine exactly one block api.mine_one().await; - // lower priced transaction was replaced - let lower_priced_receipt = lower_priced_pending_tx.await.unwrap(); - assert!(lower_priced_receipt.is_none()); + let block = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); - let higher_priced_receipt = higher_priced_pending_tx.await.unwrap().unwrap(); + assert_eq!(block.transactions.len(), 1); + assert_eq!(BlockTransactions::Hashes(vec![higher_tx_hash]), block.transactions); - // ensure that only the replacement tx was mined - let block = provider.get_block(1u64).await.unwrap().unwrap(); - assert_eq!(1, block.transactions.len()); - assert_eq!(vec![higher_priced_receipt.transaction_hash], block.transactions); + // FIXME: Unable to get receipt despite hotfix in https://github.com/alloy-rs/alloy/pull/614 + + // lower priced transaction was replaced + // let _lower_priced_receipt = lower_priced_pending_tx.get_receipt().await.unwrap(); + // let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); + + // assert_eq!(1, block.transactions.len()); + // assert_eq!( + // BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), + // block.transactions + // ); } #[tokio::test(flavor = "multi_thread")] @@ -166,22 +193,28 @@ async fn can_reject_too_high_gas_limits() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let gas_limit = api.gas_limit(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let gas_limit = api.gas_limit().to::(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); + + let tx = + TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit); - let tx = TransactionRequest::new().to(to).value(amount).from(from); + let mut tx = WithOtherFields::new(tx); // send transaction with the exact gas limit - let pending = provider.send_transaction(tx.clone().gas(gas_limit), None).await; + let pending = provider.send_transaction(tx.clone()).await.unwrap(); - pending.unwrap(); + let pending_receipt = pending.get_receipt().await; + assert!(pending_receipt.is_ok()); + + tx.set_gas_limit(gas_limit + 1); // send transaction with higher gas limit - let pending = provider.send_transaction(tx.clone().gas(gas_limit + 1u64), None).await; + let pending = provider.send_transaction(tx.clone()).await; assert!(pending.is_err()); let err = pending.unwrap_err(); @@ -189,8 +222,31 @@ async fn can_reject_too_high_gas_limits() { api.anvil_set_balance(from, U256::MAX).await.unwrap(); - let pending = provider.send_transaction(tx.gas(gas_limit), None).await; - pending.unwrap(); + tx.set_gas_limit(gas_limit); + let pending = provider.send_transaction(tx).await; + let _ = pending.unwrap(); +} + +// +#[tokio::test(flavor = "multi_thread")] +async fn can_mine_large_gas_limit() { + let (_, handle) = spawn(NodeConfig::test().disable_block_gas_limit(true)).await; + let provider = handle.http_provider(); + + let accounts = handle.dev_wallets().collect::>(); + let from = accounts[0].address(); + let to = accounts[1].address(); + + let gas_limit = anvil::DEFAULT_GAS_LIMIT as u64; + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); + + let tx = + TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit); + + // send transaction with higher gas limit + let pending = provider.send_transaction(WithOtherFields::new(tx)).await.unwrap(); + + let _resp = pending.get_receipt().await.unwrap(); } #[tokio::test(flavor = "multi_thread")] @@ -202,59 +258,63 @@ async fn can_reject_underpriced_replacement() { let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); let gas_price = provider.get_gas_price().await.unwrap(); - let amount = handle.genesis_balance().checked_div(3u64.into()).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from).nonce(nonce); + let tx = TransactionRequest::default().to(to).value(amount).from(from).nonce(nonce); + let mut tx = WithOtherFields::new(tx); + + tx.set_gas_price(gas_price + 1); // send transaction with higher gas price - let higher_priced_pending_tx = - provider.send_transaction(tx.clone().gas_price(gas_price + 1u64), None).await.unwrap(); + let higher_priced_pending_tx = provider.send_transaction(tx.clone()).await.unwrap(); + tx.set_gas_price(gas_price); // send the same transaction with lower gas price - let lower_priced_pending_tx = provider.send_transaction(tx.gas_price(gas_price), None).await; + let lower_priced_pending_tx = provider.send_transaction(tx).await; let replacement_err = lower_priced_pending_tx.unwrap_err(); assert!(replacement_err.to_string().contains("replacement transaction underpriced")); // mine exactly one block api.mine_one().await; - let higher_priced_receipt = higher_priced_pending_tx.await.unwrap().unwrap(); + let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.unwrap(); // ensure that only the higher priced tx was mined - let block = provider.get_block(1u64).await.unwrap().unwrap(); + let block = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); assert_eq!(1, block.transactions.len()); - assert_eq!(vec![higher_priced_receipt.transaction_hash], block.transactions); + assert_eq!( + BlockTransactions::Hashes(vec![higher_priced_receipt.transaction_hash]), + block.transactions + ); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_http() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() - .await - .unwrap(); + let signer: EthereumWallet = wallet.clone().into(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let alloy_provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let alloy_greeter_addr = + Greeter::deploy_builder(alloy_provider.clone(), "Hello World!".to_string()) + // .legacy() unimplemented! in alloy + .deploy() + .await + .unwrap(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let alloy_greeter = Greeter::new(alloy_greeter_addr, alloy_provider); + + let greeting = alloy_greeter.greet().call().await.unwrap(); + + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -271,31 +331,37 @@ async fn can_deploy_and_mine_manually() { let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let from = wallet.address(); + + let greeter_builder = + Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()).from(from); + let greeter_calldata = greeter_builder.calldata(); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; + let tx = TransactionRequest::default().from(from).with_input(greeter_calldata.to_owned()); - let tx = client.send_transaction(tx, None).await.unwrap(); + let tx = WithOtherFields::new(tx); + + let tx = provider.send_transaction(tx).await.unwrap(); // mine block with tx manually api.evm_mine(None).await.unwrap(); - let receipt = tx.await.unwrap().unwrap(); + let receipt = tx.get_receipt().await.unwrap(); let address = receipt.contract_address.unwrap(); - let greeter_contract = Greeter::new(address, Arc::clone(&client)); + let greeter_contract = Greeter::new(address, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let set_greeting = greeter_contract.set_greeting("Another Message".to_string()); + let set_greeting = greeter_contract.setGreeting("Another Message".to_string()); let tx = set_greeting.send().await.unwrap(); // mine block manually api.evm_mine(None).await.unwrap(); - let _tx = tx.await.unwrap(); + let _tx = tx.get_receipt().await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting); + assert_eq!("Another Message", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -307,94 +373,102 @@ async fn can_mine_automatically() { api.anvil_set_auto_mine(false).await.unwrap(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - let sent_tx = client.send_transaction(tx, None).await.unwrap(); + let greeter_builder = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()); + + let greeter_calldata = greeter_builder.calldata(); + + let tx = TransactionRequest::default() + .from(wallet.address()) + .with_input(greeter_calldata.to_owned()); + + let tx = WithOtherFields::new(tx); + + let sent_tx = provider.send_transaction(tx).await.unwrap(); // re-enable auto mine api.anvil_set_auto_mine(true).await.unwrap(); - let receipt = sent_tx.await.unwrap().unwrap(); - assert_eq!(receipt.status.unwrap().as_u64(), 1u64); + let receipt = sent_tx.get_receipt().await.unwrap(); + assert_eq!(receipt.block_number, Some(1)); } #[tokio::test(flavor = "multi_thread")] async fn can_call_greeter_historic() { - let (_api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + .deploy() .await .unwrap(); + let greeter_contract = Greeter::new(greeter_addr, provider.clone()); + let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let block = client.get_block_number().await.unwrap(); + let block_number = provider.get_block_number().await.unwrap(); - greeter_contract - .set_greeting("Another Message".to_string()) + let _receipt = greeter_contract + .setGreeting("Another Message".to_string()) .send() .await .unwrap() + .get_receipt() .await .unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting); + assert_eq!("Another Message", greeting._0); + + // min + api.mine_one().await; // returns previous state let greeting = - greeter_contract.greet().block(BlockId::Number(block.into())).call().await.unwrap(); - assert_eq!("Hello World!", greeting); + greeter_contract.greet().block(BlockId::Number(block_number.into())).call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_ws() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + // .legacy() unimplemented! in alloy + .deploy() .await .unwrap(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); - - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_get_code() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + .deploy() .await .unwrap(); - let code = client.get_code(greeter_contract.address(), None).await.unwrap(); + let code = provider.get_code_at(greeter_addr).await.unwrap(); assert!(!code.as_ref().is_empty()); } @@ -403,149 +477,159 @@ async fn get_blocktimestamp_works() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let contract = - MulticallContract::deploy(Arc::clone(&client), ()).unwrap().send().await.unwrap(); + let contract = Multicall::deploy(provider.clone()).await.unwrap(); - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; - assert!(timestamp > U256::one()); + assert!(timestamp > U256::from(1)); - let latest_block = api.block_by_number(BlockNumber::Latest).await.unwrap().unwrap(); + let latest_block = + api.block_by_number(alloy_rpc_types::BlockNumberOrTag::Latest).await.unwrap().unwrap(); - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); - assert_eq!(timestamp, latest_block.timestamp); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + assert_eq!(timestamp.to::(), latest_block.header.timestamp); // repeat call same result - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); - assert_eq!(timestamp, latest_block.timestamp); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + assert_eq!(timestamp.to::(), latest_block.header.timestamp); // mock timestamp - let next_timestamp = timestamp.as_u64() + 1337; + let next_timestamp = timestamp.to::() + 1337; api.evm_set_next_block_timestamp(next_timestamp).unwrap(); - let timestamp = - contract.get_current_block_timestamp().block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(timestamp, next_timestamp.into()); + let timestamp = contract + .getCurrentBlockTimestamp() + .block(BlockId::pending()) + .call() + .await + .unwrap() + .timestamp; + assert_eq!(timestamp, U256::from(next_timestamp)); // repeat call same result - let timestamp = - contract.get_current_block_timestamp().block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(timestamp, next_timestamp.into()); + let timestamp = contract + .getCurrentBlockTimestamp() + .block(BlockId::pending()) + .call() + .await + .unwrap() + .timestamp; + assert_eq!(timestamp, U256::from(next_timestamp)); } #[tokio::test(flavor = "multi_thread")] async fn call_past_state() { - let (_api, handle) = spawn(NodeConfig::test()).await; + let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let contract_addr = + SimpleStorage::deploy_builder(provider.clone(), "initial value".to_string()) + .from(wallet.address()) + .deploy() + .await + .unwrap(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let contract = SimpleStorage::new(contract_addr, provider.clone()); - let deployed_block = client.get_block_number().await.unwrap(); + let deployed_block = provider.get_block_number().await.unwrap(); - // assert initial state - let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap(); - assert_eq!(value, "initial value"); + let value = contract.getValue().call().await.unwrap(); + assert_eq!(value._0, "initial value"); - // make a call with `client` - let _tx_hash = contract - .method::<_, H256>("setValue", "hi".to_owned()) - .unwrap() - .send() - .await - .unwrap() - .await - .unwrap() - .unwrap(); + let gas_price = api.gas_price(); + let set_tx = contract.setValue("hi".to_string()).gas_price(gas_price + 1); + + let _receipt = set_tx.send().await.unwrap().get_receipt().await.unwrap(); // assert new value - let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap(); - assert_eq!(value, "hi"); + let value = contract.getValue().call().await.unwrap(); + assert_eq!(value._0, "hi"); // assert previous value - let value = contract - .method::<_, String>("getValue", ()) - .unwrap() - .block(BlockId::Number(deployed_block.into())) - .call() - .await - .unwrap(); - assert_eq!(value, "initial value"); + let value = + contract.getValue().block(BlockId::Number(deployed_block.into())).call().await.unwrap(); + assert_eq!(value._0, "initial value"); - let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); - let value = contract - .method::<_, String>("getValue", ()) - .unwrap() - .block(BlockId::Hash(hash)) - .call() + let hash = provider + .get_block(BlockId::Number(1.into()), false.into()) .await - .unwrap(); - assert_eq!(value, "initial value"); + .unwrap() + .unwrap() + .header + .hash; + let value = contract.getValue().block(BlockId::Hash(hash.into())).call().await.unwrap(); + assert_eq!(value._0, "initial value"); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_multiple_concurrent_transfers_with_same_nonce() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); // explicitly set the nonce - let tx = TransactionRequest::new().to(to).value(100u64).from(from).nonce(nonce).gas(21_000u64); + let tx = TransactionRequest::default() + .to(to) + .value(U256::from(100)) + .from(from) + .nonce(nonce) + .with_gas_limit(21000); + + let tx = WithOtherFields::new(tx); + let mut tasks = Vec::new(); for _ in 0..10 { - let provider = provider.clone(); let tx = tx.clone(); - let task = - tokio::task::spawn(async move { provider.send_transaction(tx, None).await?.await }); + let provider = provider.clone(); + let task = tokio::task::spawn(async move { + provider.send_transaction(tx).await.unwrap().get_receipt().await + }); tasks.push(task); } // only one succeeded let successful_tx = - join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); + join_all(tasks).await.into_iter().filter(|res| res.as_ref().is_ok()).count(); assert_eq!(successful_tx, 1); - assert_eq!(provider.get_transaction_count(from, None).await.unwrap(), 1u64.into()); + assert_eq!(provider.get_transaction_count(from).await.unwrap(), 1u64); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let nonce = client.get_transaction_count(from, None).await.unwrap(); - // explicitly set the nonce + let nonce = provider.get_transaction_count(from).await.unwrap(); + let mut tasks = Vec::new(); - let mut tx = - Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - tx.set_nonce(nonce); - tx.set_gas(300_000u64); + + let greeter = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + + let greeter_calldata = greeter.calldata(); + + let tx = TransactionRequest::default() + .from(from) + .with_input(greeter_calldata.to_owned()) + .nonce(nonce) + .with_gas_limit(300_000); + + let tx = WithOtherFields::new(tx); for _ in 0..10 { - let client = Arc::clone(&client); + let provider = provider.clone(); let tx = tx.clone(); let task = tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }); tasks.push(task); } @@ -554,51 +638,54 @@ async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); assert_eq!(successful_tx, 1); - assert_eq!(client.get_transaction_count(from, None).await.unwrap(), 1u64.into()); + assert_eq!(provider.get_transaction_count(from).await.unwrap(), 1u64); } #[tokio::test(flavor = "multi_thread")] async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let greeter_contract = + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); + + let nonce = provider.get_transaction_count(from).await.unwrap(); - let nonce = client.get_transaction_count(from, None).await.unwrap(); - // explicitly set the nonce let mut tasks = Vec::new(); - let mut deploy_tx = - Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - deploy_tx.set_nonce(nonce); - deploy_tx.set_gas(300_000u64); - let mut set_greeting_tx = greeter_contract.set_greeting("Hello".to_string()).tx; - set_greeting_tx.set_nonce(nonce); - set_greeting_tx.set_gas(300_000u64); + let deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let deploy_calldata = deploy.calldata(); + let deploy_tx = TransactionRequest::default() + .from(from) + .with_input(deploy_calldata.to_owned()) + .nonce(nonce) + .with_gas_limit(300_000); + let deploy_tx = WithOtherFields::new(deploy_tx); + + let set_greeting = greeter_contract.setGreeting("Hello".to_string()); + let set_greeting_calldata = set_greeting.calldata(); + + let set_greeting_tx = TransactionRequest::default() + .from(from) + .with_input(set_greeting_calldata.to_owned()) + .nonce(nonce) + .with_gas_limit(300_000); + let set_greeting_tx = WithOtherFields::new(set_greeting_tx); for idx in 0..10 { - let client = Arc::clone(&client); + let provider = provider.clone(); let task = if idx % 2 == 0 { let tx = deploy_tx.clone(); tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) } else { let tx = set_greeting_tx.clone(); tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) }; @@ -609,9 +696,8 @@ async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { let successful_tx = join_all(tasks).await.into_iter().filter(|res| res.as_ref().unwrap().is_ok()).count(); assert_eq!(successful_tx, 1); - assert_eq!(client.get_transaction_count(from, None).await.unwrap(), nonce + 1); + assert_eq!(provider.get_transaction_count(from).await.unwrap(), nonce + 1); } - #[tokio::test(flavor = "multi_thread")] async fn can_get_pending_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; @@ -622,33 +708,54 @@ async fn can_get_pending_transaction() { let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); - let tx = TransactionRequest::new().from(from).value(1337u64).to(Address::random()); - let tx = provider.send_transaction(tx, None).await.unwrap(); + let tx = TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap(); - let pending = provider.get_transaction(tx.tx_hash()).await.unwrap(); - assert!(pending.is_some()); + let pending = provider.get_transaction_by_hash(*tx.tx_hash()).await; + assert!(pending.is_ok()); api.mine_one().await; - let mined = provider.get_transaction(tx.tx_hash()).await.unwrap().unwrap(); + let mined = provider.get_transaction_by_hash(*tx.tx_hash()).await.unwrap().unwrap(); - assert_eq!(mined.hash, pending.unwrap().hash); + assert_eq!(mined.tx_hash(), pending.unwrap().unwrap().tx_hash()); } #[tokio::test(flavor = "multi_thread")] -async fn test_first_noce_is_zero() { +async fn can_get_raw_transaction() { let (api, handle) = spawn(NodeConfig::test()).await; + // first test the pending tx, disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); + let from = handle.dev_wallets().next().unwrap().address(); + let tx = TransactionRequest::default().from(from).value(U256::from(1488)).to(Address::random()); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap(); - let nonce = provider - .get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); + let res1 = api.raw_transaction(*tx.tx_hash()).await; + assert!(res1.is_ok()); + + api.mine_one().await; + let res2 = api.raw_transaction(*tx.tx_hash()).await; - assert_eq!(nonce, U256::zero()); + assert_eq!(res1.unwrap(), res2.unwrap()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_first_nonce_is_zero() { + let (api, handle) = spawn(NodeConfig::test()).await; + + api.anvil_set_auto_mine(false).await.unwrap(); + + let provider = handle.http_provider(); + let from = handle.dev_wallets().next().unwrap().address(); + + let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap(); + + assert_eq!(nonce, 0); } #[tokio::test(flavor = "multi_thread")] @@ -658,7 +765,7 @@ async fn can_handle_different_sender_nonce_calculation() { api.anvil_set_auto_mine(false).await.unwrap(); let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from_first = accounts[0].address(); let from_second = accounts[1].address(); @@ -666,23 +773,25 @@ async fn can_handle_different_sender_nonce_calculation() { // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { - let tx_from_first = - TransactionRequest::new().from(from_first).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx_from_first, None).await.unwrap(); - let nonce_from_first = provider - .get_transaction_count(from_first, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce_from_first, idx.into()); - - let tx_from_second = - TransactionRequest::new().from(from_second).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx_from_second, None).await.unwrap(); - let nonce_from_second = provider - .get_transaction_count(from_second, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce_from_second, idx.into()); + let tx_from_first = TransactionRequest::default() + .from(from_first) + .value(U256::from(1337u64)) + .to(Address::random()); + let tx_from_first = WithOtherFields::new(tx_from_first); + let _tx = provider.send_transaction(tx_from_first).await.unwrap(); + let nonce_from_first = + provider.get_transaction_count(from_first).block_id(BlockId::pending()).await.unwrap(); + assert_eq!(nonce_from_first, idx); + + let tx_from_second = TransactionRequest::default() + .from(from_second) + .value(U256::from(1337u64)) + .to(Address::random()); + let tx_from_second = WithOtherFields::new(tx_from_second); + let _tx = provider.send_transaction(tx_from_second).await.unwrap(); + let nonce_from_second = + provider.get_transaction_count(from_second).block_id(BlockId::pending()).await.unwrap(); + assert_eq!(nonce_from_second, idx); } } @@ -699,21 +808,18 @@ async fn includes_pending_tx_for_transaction_count() { // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { - let tx = TransactionRequest::new().from(from).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx, None).await.unwrap(); - let nonce = provider - .get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce, idx.into()); + let tx = + TransactionRequest::default().from(from).value(U256::from(1337)).to(Address::random()); + let tx = WithOtherFields::new(tx); + let _tx = provider.send_transaction(tx).await.unwrap(); + let nonce = + provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap(); + assert_eq!(nonce, idx); } api.mine_one().await; - let nonce = provider - .get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce, tx_count.into()); + let nonce = provider.get_transaction_count(from).block_id(BlockId::pending()).await.unwrap(); + assert_eq!(nonce, tx_count); } #[tokio::test(flavor = "multi_thread")] @@ -721,32 +827,30 @@ async fn can_get_historic_info() { let (_api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let accounts: Vec<_> = handle.dev_wallets().collect(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(2u64.into()).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap(); + let _ = tx.get_receipt().await.unwrap(); - let nonce_pre = provider - .get_transaction_count(from, Some(BlockNumber::Number(0.into()).into())) - .await - .unwrap(); + let nonce_pre = + provider.get_transaction_count(from).block_id(BlockId::number(0)).await.unwrap(); - let nonce_post = - provider.get_transaction_count(from, Some(BlockNumber::Latest.into())).await.unwrap(); + let nonce_post = provider.get_transaction_count(from).await.unwrap(); assert!(nonce_pre < nonce_post); - let balance_pre = - provider.get_balance(from, Some(BlockNumber::Number(0.into()).into())).await.unwrap(); + let balance_pre = provider.get_balance(from).block_id(BlockId::number(0)).await.unwrap(); - let balance_post = provider.get_balance(from, Some(BlockNumber::Latest.into())).await.unwrap(); + let balance_post = provider.get_balance(from).await.unwrap(); assert!(balance_post < balance_pre); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance_pre.saturating_add(amount), to_balance); } @@ -756,18 +860,25 @@ async fn test_tx_receipt() { let (_api, handle) = spawn(NodeConfig::test()).await; let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet)); + let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337)); - let tx = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(tx.to.is_some()); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; + let greeter_deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let greeter_calldata = greeter_deploy.calldata(); + + let tx = TransactionRequest::default() + .from(wallet.address()) + .with_input(greeter_calldata.to_owned()); - let tx = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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()); } @@ -777,32 +888,45 @@ async fn can_stream_pending_transactions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(Duration::from_secs(2)))).await; let num_txs = 5; + let provider = handle.http_provider(); - let ws_provider = handle.ws_provider().await; + let ws_provider = connect_pubsub(&handle.ws_endpoint()).await; let accounts = provider.get_accounts().await.unwrap(); - let tx = TransactionRequest::new().from(accounts[0]).to(accounts[0]).value(1e18 as u64); + let tx = + TransactionRequest::default().from(accounts[0]).to(accounts[0]).value(U256::from(1e18)); let mut sending = futures::future::join_all( - std::iter::repeat(tx.clone()) - .take(num_txs) + std::iter::repeat_n(tx.clone(), num_txs) .enumerate() - .map(|(nonce, tx)| tx.nonce(nonce)) + .map(|(nonce, tx)| tx.nonce(nonce as u64)) .map(|tx| async { - provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap() + let tx = WithOtherFields::new(tx); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap() }), ) .fuse(); - let mut watch_tx_stream = - provider.watch_pending_transactions().await.unwrap().transactions_unordered(num_txs).fuse(); + let mut watch_tx_stream = provider + .watch_pending_transactions() + .await + .unwrap() + .into_stream() + .flat_map(futures::stream::iter) + .take(num_txs) + .fuse(); - let mut sub_tx_stream = - ws_provider.subscribe_pending_txs().await.unwrap().transactions_unordered(2).fuse(); + let mut sub_tx_stream = ws_provider + .subscribe_pending_transactions() + .await + .unwrap() + .into_stream() + .take(num_txs) + .fuse(); - let mut sent: Option> = None; - let mut watch_received: Vec = Vec::with_capacity(num_txs); - let mut sub_received: Vec = Vec::with_capacity(num_txs); + let mut sent = None; + let mut watch_received = Vec::with_capacity(num_txs); + let mut sub_received = Vec::with_capacity(num_txs); loop { futures::select! { @@ -810,18 +934,24 @@ async fn can_stream_pending_transactions() { sent = Some(txs) }, tx = watch_tx_stream.next() => { - watch_received.push(tx.unwrap().unwrap()); + if let Some(tx) = tx { + watch_received.push(tx); + } }, tx = sub_tx_stream.next() => { - sub_received.push(tx.unwrap().unwrap()); + if let Some(tx) = tx { + sub_received.push(tx); + } }, + complete => unreachable!(), }; + if watch_received.len() == num_txs && sub_received.len() == num_txs { - if let Some(ref sent) = sent { + if let Some(sent) = &sent { assert_eq!(sent.len(), watch_received.len()); - let sent_txs = sent.iter().map(|tx| tx.transaction_hash).collect::>(); - assert_eq!(sent_txs, watch_received.iter().map(|tx| tx.hash).collect()); - assert_eq!(sent_txs, sub_received.iter().map(|tx| tx.hash).collect()); + let sent_txs = sent.iter().map(|tx| tx.transaction_hash).collect::(); + assert_eq!(sent_txs, watch_received.iter().copied().collect()); + assert_eq!(sent_txs, sub_received.iter().copied().collect()); break } } @@ -860,38 +990,51 @@ async fn test_tx_access_list() { // - The sender shouldn't be in the AL let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(handle.http_provider(), wallet)); + let provider = handle.http_provider(); let sender = Address::random(); let other_acc = Address::random(); - let multicall = MulticallContract::deploy(client.clone(), ()).unwrap().send().await.unwrap(); - let simple_storage = - SimpleStorage::deploy(client.clone(), "foo".to_string()).unwrap().send().await.unwrap(); + let multicall = Multicall::deploy(provider.clone()).await.unwrap(); + let simple_storage = SimpleStorage::deploy(provider.clone(), "foo".to_string()).await.unwrap(); // when calling `setValue` on SimpleStorage, both the `lastSender` and `_value` storages are // modified The `_value` is a `string`, so the storage slots here (small string) are `0x1` // and `keccak(0x1)` - let set_value_tx = simple_storage.set_value("bar".to_string()).from(sender).tx; - let access_list = client.create_access_list(&set_value_tx, None).await.unwrap(); + let set_value = simple_storage.setValue("bar".to_string()); + let set_value_calldata = set_value.calldata(); + let set_value_tx = TransactionRequest::default() + .from(sender) + .to(*simple_storage.address()) + .with_input(set_value_calldata.to_owned()); + let set_value_tx = WithOtherFields::new(set_value_tx); + let access_list = provider.create_access_list(&set_value_tx).await.unwrap(); + // let set_value_tx = simple_storage.set_value("bar".to_string()).from(sender).tx; + // let access_list = client.create_access_list(&set_value_tx, None).await.unwrap(); assert_access_list_eq( access_list.access_list, AccessList::from(vec![AccessListItem { - address: simple_storage.address(), + address: *simple_storage.address(), storage_keys: vec![ - H256::zero(), - H256::from_uint(&(1u64.into())), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - .parse() - .unwrap(), + FixedBytes::ZERO, + FixedBytes::with_last_byte(1), + FixedBytes::from_str( + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + ) + .unwrap(), ], }]), ); // With a subcall that fetches the balances of an account (`other_acc`), only the address // of this account should be in the Access List - let call_tx = multicall.get_eth_balance(other_acc).from(sender).tx; - let access_list = client.create_access_list(&call_tx, None).await.unwrap(); + let call_tx = multicall.getEthBalance(other_acc); + let call_tx_data = call_tx.calldata(); + let call_tx = TransactionRequest::default() + .from(sender) + .to(*multicall.address()) + .with_input(call_tx_data.to_owned()); + let call_tx = WithOtherFields::new(call_tx); + let access_list = provider.create_access_list(&call_tx).await.unwrap(); assert_access_list_eq( access_list.access_list, AccessList::from(vec![AccessListItem { address: other_acc, storage_keys: vec![] }]), @@ -899,24 +1042,31 @@ async fn test_tx_access_list() { // With a subcall to another contract, the AccessList should be the same as when calling the // subcontract directly (given that the proxy contract doesn't read/write any state) - let subcall_tx = multicall - .aggregate(vec![Call { - target: simple_storage.address(), - call_data: set_value_tx.data().unwrap().clone(), - }]) + let subcall_tx = multicall.aggregate(vec![Multicall::Call { + target: *simple_storage.address(), + callData: set_value_calldata.to_owned(), + }]); + + let subcall_tx_calldata = subcall_tx.calldata(); + + let subcall_tx = TransactionRequest::default() .from(sender) - .tx; - let access_list = client.create_access_list(&subcall_tx, None).await.unwrap(); + .to(*multicall.address()) + .with_input(subcall_tx_calldata.to_owned()); + let subcall_tx = WithOtherFields::new(subcall_tx); + let access_list = provider.create_access_list(&subcall_tx).await.unwrap(); assert_access_list_eq( access_list.access_list, + // H256::from_uint(&(1u64.into())), AccessList::from(vec![AccessListItem { - address: simple_storage.address(), + address: *simple_storage.address(), storage_keys: vec![ - H256::zero(), - H256::from_uint(&(1u64.into())), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - .parse() - .unwrap(), + FixedBytes::ZERO, + FixedBytes::with_last_byte(1), + FixedBytes::from_str( + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + ) + .unwrap(), ], }]), ); @@ -936,14 +1086,57 @@ async fn estimates_gas_on_pending_by_default() { let sender = wallet.address(); let recipient = Address::random(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); + let tx = WithOtherFields::new(tx); - let tx = TransactionRequest::new().from(sender).to(recipient).value(1e18 as u64); - client.send_transaction(tx, None).await.unwrap(); + let _pending = provider.send_transaction(tx).await.unwrap(); - let tx = - TransactionRequest::new().from(recipient).to(sender).value(1e10 as u64).data(vec![0x42]); - api.estimate_gas(tx.into(), None).await.unwrap(); + let tx = TransactionRequest::default() + .from(recipient) + .to(sender) + .value(U256::from(1e10)) + .input(Bytes::from(vec![0x42]).into()); + api.estimate_gas(WithOtherFields::new(tx), None, None).await.unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_estimate_gas() { + let (api, handle) = spawn(NodeConfig::test()).await; + + let wallet = handle.dev_wallets().next().unwrap(); + let sender = wallet.address(); + let recipient = Address::random(); + + let tx = TransactionRequest::default() + .from(recipient) + .to(sender) + .value(U256::from(1e10)) + .input(Bytes::from(vec![0x42]).into()); + // Expect the gas estimation to fail due to insufficient funds. + let error_result = api.estimate_gas(WithOtherFields::new(tx.clone()), None, None).await; + + assert!(error_result.is_err(), "Expected an error due to insufficient funds"); + let error_message = error_result.unwrap_err().to_string(); + assert!( + error_message.contains("Insufficient funds for gas * price + value"), + "Error message did not match expected: {error_message}" + ); + + // Setup state override to simulate sufficient funds for the recipient. + let addr = recipient; + let account_override = + AccountOverride { balance: Some(alloy_primitives::U256::from(1e18)), ..Default::default() }; + let mut state_override = StateOverride::default(); + state_override.insert(addr, account_override); + + // Estimate gas with state override implying sufficient funds. + let gas_estimate = api + .estimate_gas(WithOtherFields::new(tx), None, Some(state_override)) + .await + .expect("Failed to estimate gas with state override"); + + // Assert the gas estimate meets the expected minimum. + assert!(gas_estimate >= U256::from(21000), "Gas estimate is lower than expected minimum"); } #[tokio::test(flavor = "multi_thread")] @@ -954,13 +1147,14 @@ async fn test_reject_gas_too_low() { let account = handle.dev_accounts().next().unwrap(); let gas = 21_000u64 - 1; - let tx = TransactionRequest::new() + let tx = TransactionRequest::default() .to(Address::random()) .value(U256::from(1337u64)) .from(account) - .gas(gas); + .with_gas_limit(gas); + let tx = WithOtherFields::new(tx); - let resp = provider.send_transaction(tx, None).await; + let resp = provider.send_transaction(tx).await; let err = resp.unwrap_err().to_string(); assert!(err.contains("intrinsic gas too low")); @@ -969,50 +1163,75 @@ async fn test_reject_gas_too_low() { // #[tokio::test(flavor = "multi_thread")] async fn can_call_with_high_gas_limit() { - let (_api, handle) = - spawn(NodeConfig::test().with_gas_limit(Some(U256::from(100_000_000)))).await; + let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(100_000_000))).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let greeter_contract = Greeter::deploy(provider, "Hello World!".to_string()).await.unwrap(); - let greeting = greeter_contract.greet().gas(60_000_000u64).call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let greeting = greeter_contract.greet().gas(60_000_000).call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn test_reject_eip1559_pre_london() { - let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; + let (api, handle) = + spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let gas_limit = api.gas_limit().to::(); + let gas_price = api.gas_price(); - let gas_limit = api.gas_limit(); - let gas_price = api.gas_price().unwrap(); - let unsupported = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .gas(gas_limit) - .gas_price(gas_price) - .send() - .await - .unwrap_err() - .to_string(); + let unsupported_call_builder = + Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let unsupported_calldata = unsupported_call_builder.calldata(); + + let unsup_tx = TransactionRequest::default() + .from(handle.dev_accounts().next().unwrap()) + .with_input(unsupported_calldata.to_owned()) + .with_gas_limit(gas_limit) + .with_max_fee_per_gas(gas_price) + .with_max_priority_fee_per_gas(gas_price); + + let unsup_tx = WithOtherFields::new(unsup_tx); + + let unsupported = provider.send_transaction(unsup_tx).await.unwrap_err().to_string(); assert!(unsupported.contains("not supported by the current hardfork"), "{unsupported}"); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() - .await - .unwrap(); + let greeter_contract_addr = + Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .gas(gas_limit) + .gas_price(gas_price) + .deploy() + .await + .unwrap(); + + let greeter_contract = Greeter::new(greeter_contract_addr, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); +} + +// https://github.com/foundry-rs/foundry/issues/6931 +#[tokio::test(flavor = "multi_thread")] +async fn can_mine_multiple_in_block() { + let (api, _handle) = spawn(NodeConfig::test()).await; + + // disable auto mine + api.anvil_set_auto_mine(false).await.unwrap(); + + let tx = TransactionRequest { + from: Some("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()), + ..Default::default() + }; + + // broadcast it via the eth_sendTransaction API + let first = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap(); + let second = api.send_transaction(WithOtherFields::new(tx.clone())).await.unwrap(); + + api.anvil_mine(Some(U256::from(1)), Some(U256::ZERO)).await.unwrap(); + + let block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + + let txs = block.transactions.hashes().collect::>(); + assert_eq!(txs, vec![first, second]); } diff --git a/crates/anvil/tests/it/txpool.rs b/crates/anvil/tests/it/txpool.rs index ea0f6b4d246af..c329b27fa9130 100644 --- a/crates/anvil/tests/it/txpool.rs +++ b/crates/anvil/tests/it/txpool.rs @@ -1,33 +1,41 @@ //! txpool related tests +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; use anvil::{spawn, NodeConfig}; -use ethers::{ - prelude::Middleware, - types::{TransactionRequest, U256}, -}; #[tokio::test(flavor = "multi_thread")] async fn geth_txpool() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); + api.anvil_set_auto_mine(false).await.unwrap(); - let account = provider.get_accounts().await.unwrap()[0]; - let value: u64 = 42; - let gas_price: U256 = 221435145689u64.into(); - let tx = TransactionRequest::new().to(account).from(account).value(value).gas_price(gas_price); + let account = provider.get_accounts().await.unwrap().remove(0); + let value = U256::from(42); + let gas_price = 221435145689u128; + + let tx = TransactionRequest::default() + .with_to(account) + .with_from(account) + .with_value(value) + .with_gas_price(gas_price); + let tx = WithOtherFields::new(tx); // send a few transactions let mut txs = Vec::new(); for _ in 0..10 { - let tx_hash = provider.send_transaction(tx.clone(), None).await.unwrap(); + let tx_hash = provider.send_transaction(tx.clone()).await.unwrap(); txs.push(tx_hash); } // we gave a 20s block time, should be plenty for us to get the txpool's content let status = provider.txpool_status().await.unwrap(); - assert_eq!(status.pending.as_u64(), 10); - assert_eq!(status.queued.as_u64(), 0); + assert_eq!(status.pending, 10); + assert_eq!(status.queued, 0); let inspect = provider.txpool_inspect().await.unwrap(); assert!(inspect.queued.is_empty()); @@ -35,8 +43,8 @@ async fn geth_txpool() { for i in 0..10 { let tx_summary = summary.get(&i.to_string()).unwrap(); assert_eq!(tx_summary.gas_price, gas_price); - assert_eq!(tx_summary.value, value.into()); - assert_eq!(tx_summary.gas, 21000.into()); + assert_eq!(tx_summary.value, value); + assert_eq!(tx_summary.gas, 21000); assert_eq!(tx_summary.to.unwrap(), account); } diff --git a/crates/anvil/tests/it/utils.rs b/crates/anvil/tests/it/utils.rs index 0a566609300fe..abee87f26cfcc 100644 --- a/crates/anvil/tests/it/utils.rs +++ b/crates/anvil/tests/it/utils.rs @@ -1,15 +1,66 @@ -use ethers::{ - addressbook::contract, - types::{Address, Chain}, +use alloy_network::{Ethereum, EthereumWallet}; +use alloy_provider::{ + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + Identity, RootProvider, }; +use foundry_common::provider::{ + get_http_provider, ProviderBuilder, RetryProvider, RetryProviderWithSigner, +}; + +pub fn http_provider(http_endpoint: &str) -> RetryProvider { + get_http_provider(http_endpoint) +} + +pub fn http_provider_with_signer( + http_endpoint: &str, + signer: EthereumWallet, +) -> RetryProviderWithSigner { + ProviderBuilder::new(http_endpoint) + .build_with_wallet(signer) + .expect("failed to build Alloy HTTP provider with signer") +} + +pub fn ws_provider_with_signer( + ws_endpoint: &str, + signer: EthereumWallet, +) -> RetryProviderWithSigner { + ProviderBuilder::new(ws_endpoint) + .build_with_wallet(signer) + .expect("failed to build Alloy WS provider with signer") +} + +/// Currently required to get around +pub async fn connect_pubsub(conn_str: &str) -> RootProvider { + alloy_provider::ProviderBuilder::default().on_builtin(conn_str).await.unwrap() +} + +type PubsubSigner = FillProvider< + JoinFill< + JoinFill< + Identity, + JoinFill< + GasFiller, + JoinFill< + alloy_provider::fillers::BlobGasFiller, + JoinFill, + >, + >, + >, + WalletFiller, + >, + RootProvider, + Ethereum, +>; + +pub async fn connect_pubsub_with_wallet(conn_str: &str, wallet: EthereumWallet) -> PubsubSigner { + alloy_provider::ProviderBuilder::new().wallet(wallet).on_builtin(conn_str).await.unwrap() +} -/// Returns a set of various contract addresses -pub fn contract_addresses(chain: Chain) -> Vec
{ - vec![ - contract("dai").unwrap().address(chain).unwrap(), - contract("usdc").unwrap().address(chain).unwrap(), - contract("weth").unwrap().address(chain).unwrap(), - contract("uniswapV3Factory").unwrap().address(chain).unwrap(), - contract("uniswapV3SwapRouter02").unwrap().address(chain).unwrap(), - ] +pub async fn ipc_provider_with_wallet( + ipc_endpoint: &str, + wallet: EthereumWallet, +) -> RetryProviderWithSigner { + ProviderBuilder::new(ipc_endpoint) + .build_with_wallet(wallet) + .expect("failed to build Alloy IPC provider with signer") } diff --git a/crates/anvil/tests/it/wsapi.rs b/crates/anvil/tests/it/wsapi.rs index 9bcc0d810c39d..ebe853a7d8789 100644 --- a/crates/anvil/tests/it/wsapi.rs +++ b/crates/anvil/tests/it/wsapi.rs @@ -1,28 +1,29 @@ //! general eth api tests with websocket provider +use alloy_primitives::U256; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::{prelude::Middleware, types::U256}; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number_ws() { let (api, handle) = spawn(NodeConfig::test()).await; let block_num = api.block_number().unwrap(); - assert_eq!(block_num, U256::zero()); + assert_eq!(block_num, U256::ZERO); - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.as_u64().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] async fn can_dev_get_balance_ws() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider().await; + let provider = handle.ws_provider(); let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { - let balance = provider.get_balance(acc, None).await.unwrap(); + let balance = provider.get_balance(acc).await.unwrap(); assert_eq!(balance, genesis_balance); } } diff --git a/crates/binder/Cargo.toml b/crates/binder/Cargo.toml deleted file mode 100644 index cf761f7927523..0000000000000 --- a/crates/binder/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "foundry-binder" -description = "Generate rust bindings for solidity projects" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -foundry-config.workspace = true -ethers-solc = { workspace = true, features = ["async", "svm-solc", "project-util"] } -ethers-contract = { workspace = true, features = ["abigen"] } -curl = { version = "0.4", default-features = false, features = ["http2"] } -eyre = "0.6" -git2 = { version = "0.17", default-features = false } -url = "2" -tracing = "0.1" -tempfile = "3" diff --git a/crates/binder/README.md b/crates/binder/README.md deleted file mode 100644 index d69c970e682d5..0000000000000 --- a/crates/binder/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# foundry-binder - -Utilities for generating bindings for solidity projects in one step. - -First add `foundry-binder` to your cargo build-dependencies. - -```toml -[build-dependencies] -foundry-binder = { git = "https://github.com/foundry-rs/foundry" } -# required in order to enable ssh support in [libgit2](https://github.com/rust-lang/git2-rs) -git2 = "0.16.1" -``` - -```rust -use foundry_binder::{Binder, RepositoryBuilder, Url}; - -// github repository url -const REPO_URL: &str = ""; - -// the release tag for which to generate bindings for -const RELEASE_TAG: &str = "v3.0.0"; - -/// This clones the project, builds the project and generates rust bindings -fn generate() { - let binder = - Binder::new(RepositoryBuilder::new(Url::parse(REPO_URL).unwrap()) - // generate bindings for this release tag - // if not set, then the default branch will be used - .tag(RELEASE_TAG)) - // keep build artifacts in `artifacts` folder - .keep_artifacts("artifacts"); - - binder.generate().expect("Failed to generate bindings") -} - -fn main() { - // only generate if `FRESH_BINDINGS` env var is set - if std::env::var("FRESH_BINDINGS").is_ok() { - generate() - } -} -``` diff --git a/crates/binder/src/lib.rs b/crates/binder/src/lib.rs deleted file mode 100644 index e57d8c95a7cf9..0000000000000 --- a/crates/binder/src/lib.rs +++ /dev/null @@ -1,382 +0,0 @@ -//! Generate [ethers-rs]("https://github.com/gakonst/ethers-rs") bindings for solidity projects in a build script. - -use crate::utils::{GitReference, GitRemote}; -use ethers_contract::MultiAbigen; -pub use foundry_config::Config; -use std::{ - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; -use tempfile::{tempdir, TempDir}; -use tracing::trace; -pub use url::Url; - -pub mod utils; - -/// Contains all the options to configure the gen process -#[derive(Debug)] -pub struct Binder { - /// Where to find the project - location: SourceLocation, - /// Whether to include the bytecode in the bindings to be able to deploy them - deployable: bool, - /// Contains the directory where the artifacts should be written, if `None`, the artifacts will - /// be cleaned up - keep_artifacts: Option, - /// additional commands to run in the repo - commands: Vec>, - /// The foundry config to use in order to compile the project - config: Option, - /// Path to where the contract artifacts are stored - bindings: Option, -} - -// == impl Binder == - -impl Binder { - /// Creates a new `Binder` instance for the given location - /// - /// # Example - /// - /// ## Local repository - /// - /// ``` - /// # use foundry_binder::Binder; - /// # fn new() { - /// let binder = Binder::new("./aave-v3-core"); - /// # } - /// ``` - /// - /// ## Remote repository with default branch - /// - /// ``` - /// # use url::Url; - /// use foundry_binder::Binder; - /// # fn new() { - /// let binder = Binder::new(Url::parse("https://github.com/aave/aave-v3-core").unwrap()); - /// # } - /// ``` - pub fn new(location: impl Into) -> Self { - Self { - location: location.into(), - deployable: true, - keep_artifacts: None, - commands: vec![], - config: None, - bindings: None, - } - } - - /// Add a command to run in the project before generating the bindings - /// - /// # Example - /// - /// Add a `yarn install` command - /// - /// ``` - /// # use url::Url; - /// use foundry_binder::{Binder, RepositoryBuilder}; - /// # fn new() { - /// let binder = Binder::new( - /// RepositoryBuilder::new(Url::parse("https://github.com/aave/aave-v3-core").unwrap()) - /// .tag("v1.16.0"), - /// ).command(["yarn", "install"]); - /// # } - /// ``` - #[must_use] - pub fn command(mut self, cmd: I) -> Self - where - I: IntoIterator, - S: Into, - { - self.commands.push(cmd.into_iter().map(Into::into).collect()); - self - } - - /// If `deployable` set to `true` then the generated contract bindings will include the - /// generated bytecode which makes the contracts deployable - #[must_use] - pub fn set_deployable(mut self, deployable: bool) -> Self { - self.deployable = deployable; - self - } - - /// If set, the project's artifacts will be written there - #[must_use] - pub fn keep_artifacts(mut self, keep_artifacts: impl Into) -> Self { - self.keep_artifacts = Some(keep_artifacts.into()); - self - } - - /// Sets the path where to write the bindings to - #[must_use] - pub fn bindings(mut self, bindings: impl Into) -> Self { - self.bindings = Some(bindings.into()); - self - } - - /// Sets the config which contains all settings for how to compile the project - /// - /// ## Example - /// - /// ``` - /// # use url::Url; - /// use foundry_binder::{Binder, Config, RepositoryBuilder}; - /// # fn new() { - /// let binder = Binder::new( - /// RepositoryBuilder::new(Url::parse("https://github.com/aave/aave-v3-core").unwrap()) - /// .tag("v1.16.0"), - /// ) - /// .command(["yarn", "install"]) - /// .config(Config { - /// src: "src".into(), - /// out: "artifacts".into(), - /// ..Default::default() - /// }); - /// # } - /// ``` - #[must_use] - pub fn config(mut self, config: Config) -> Self { - self.config = Some(config); - self - } - - /// Generates the bindings - pub fn generate(&self) -> eyre::Result<()> { - let project = self.location.get()?; - - let config = if let Some(mut config) = self.config.clone() { - config.__root = project.into(); - config - } else { - foundry_config::load_config_with_root(Some(project)) - }; - - // run all commands - for mut args in self.commands.clone() { - eyre::ensure!(!args.is_empty(), "Command can't be empty"); - - let mut cmd = Command::new(args.remove(0)); - cmd.current_dir(&config.__root.0) - .args(args) - .stderr(Stdio::inherit()) - .stdout(Stdio::inherit()); - trace!("Executing command {:?}", cmd); - cmd.output()?; - } - - let mut project = config.project()?; - - // overwrite the artifacts dir - if let Some(keep_artifacts) = self.keep_artifacts.clone() { - let _ = std::fs::create_dir_all(&keep_artifacts); - project.paths.artifacts = keep_artifacts; - } - - let compiled = project.compile()?; - if compiled.has_compiler_errors() { - eyre::bail!("Compiled with errors:\n{compiled}"); - } - - trace!("Generating bindings"); - let bindings = MultiAbigen::from_json_files(project.artifacts_path())?.build()?; - trace!("Generated bindings"); - - trace!("Writing bindings to `src/contracts`"); - let module = self.bindings.clone().unwrap_or_else(|| "src/contracts".into()); - bindings.write_to_module(module, false)?; - - Ok(()) - } -} - -/// Where to find the source project -#[derive(Debug)] -pub enum SourceLocation { - Local(PathBuf), - Remote(Repository), -} - -// === impl SourceLocation === - -impl SourceLocation { - /// Returns the path to the project - /// - /// If this is a remote repository this will clone it - pub fn get(&self) -> eyre::Result { - let path = match self { - SourceLocation::Local(p) => p.clone(), - SourceLocation::Remote(r) => { - r.checkout()?; - r.dest.as_ref().to_path_buf() - } - }; - Ok(path) - } -} - -impl From for SourceLocation { - fn from(repo: Repository) -> Self { - SourceLocation::Remote(repo) - } -} - -impl From for SourceLocation { - fn from(builder: RepositoryBuilder) -> Self { - SourceLocation::Remote(builder.build()) - } -} - -impl From for SourceLocation { - fn from(url: Url) -> Self { - RepositoryBuilder::new(url).into() - } -} - -impl<'a> From<&'a str> for SourceLocation { - fn from(path: &'a str) -> Self { - SourceLocation::Local(path.into()) - } -} - -impl<'a> From<&'a String> for SourceLocation { - fn from(path: &'a String) -> Self { - SourceLocation::Local(path.into()) - } -} - -impl From for SourceLocation { - fn from(path: String) -> Self { - SourceLocation::Local(path.into()) - } -} - -#[derive(Debug)] -pub enum RepositoryDestination { - Path(PathBuf), - Temp(TempDir), -} - -impl AsRef for RepositoryDestination { - fn as_ref(&self) -> &Path { - match self { - RepositoryDestination::Path(p) => p, - RepositoryDestination::Temp(dir) => dir.path(), - } - } -} - -#[derive(Debug)] -pub struct Repository { - /// github project repository like - pub repo: GitRemote, - /// The version tag, branch or rev to checkout - pub rev: GitReference, - /// where to checkout the database - pub db_path: Option, - /// Where to clone into - pub dest: RepositoryDestination, -} - -// === impl Repository === - -impl Repository { - pub fn checkout(&self) -> eyre::Result<()> { - fn copy_to( - repo: &GitRemote, - rev: &GitReference, - db_path: &Path, - dest: &Path, - ) -> eyre::Result<()> { - let (local, oid) = repo.checkout(db_path, rev, None)?; - local.copy_to(oid, dest)?; - Ok(()) - } - - if let Some(ref db) = self.db_path { - copy_to(&self.repo, &self.rev, db, self.dest.as_ref()) - } else { - let tmp = tempdir()?; - let db = tmp.path().join(self.dest.as_ref().file_name().unwrap()); - copy_to(&self.repo, &self.rev, &db, self.dest.as_ref()) - } - } -} - -#[derive(Debug, Clone)] -#[must_use] -pub struct RepositoryBuilder { - repo: GitRemote, - rev: GitReference, - dest: Option, - db_path: Option, -} - -// === impl RepositoryBuilder === - -impl RepositoryBuilder { - pub fn new(url: Url) -> Self { - Self { repo: GitRemote::new(url), rev: Default::default(), dest: None, db_path: None } - } - - /// Specify the branch to checkout - pub fn branch(mut self, branch: impl Into) -> Self { - self.rev = GitReference::Branch(branch.into()); - self - } - - /// Specify the tag to checkout - pub fn tag(mut self, tag: impl Into) -> Self { - self.rev = GitReference::Tag(tag.into()); - self - } - - /// Specify the specific commit to checkout - pub fn rev(mut self, rev: impl Into) -> Self { - self.rev = GitReference::Rev(rev.into()); - self - } - - /// Specify a persistent location to clone into - pub fn dest(mut self, dest: impl Into) -> Self { - self.dest = Some(dest.into()); - self - } - - /// Sets the path to where to store the git database of the repo - /// - /// If None is provided a tempdir is used and the db is cleaned up after cloning - pub fn database(mut self, db_path: impl Into) -> Self { - self.db_path = Some(db_path.into()); - self - } - - pub fn build(self) -> Repository { - let RepositoryBuilder { repo, rev, dest, db_path } = self; - let dest = if let Some(dest) = dest { - RepositoryDestination::Path(dest) - } else { - let name = repo.url().path_segments().unwrap().last().unwrap(); - let dir = - tempfile::Builder::new().prefix(name).tempdir().expect("Failed to create tempdir"); - RepositoryDestination::Temp(dir) - }; - Repository { dest, repo, rev, db_path } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[ignore] - fn can_checkout_repo() { - let _dest = "./assets/aave-v3-core"; - - let repo = - RepositoryBuilder::new("https://github.com/aave/aave-v3-core".parse().unwrap()).build(); - - repo.checkout().unwrap(); - } -} diff --git a/crates/binder/src/utils.rs b/crates/binder/src/utils.rs deleted file mode 100644 index 1d73e942b56c3..0000000000000 --- a/crates/binder/src/utils.rs +++ /dev/null @@ -1,818 +0,0 @@ -//! Utilities for handling git repositories. - -// Adapted from https://github.com/rust-lang/cargo/blob/f51e799636fcba6aeb98dc2ca7e440ecd9afe909/src/cargo/sources/git/utils.rs - -use eyre::Context; -use git2::{self, ErrorClass, ObjectType}; -use std::{ - env, - fmt::Write, - fs, - path::{Path, PathBuf}, - process::Command, -}; -use tracing::{debug, info}; -use url::Url; - -/// Represents a remote repository. -/// It gets cloned into a local `GitLocal`. -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct GitRemote { - url: Url, -} - -// === impl GitRemote === - -impl GitRemote { - pub fn new(url: Url) -> GitRemote { - GitRemote { url } - } - - pub fn url(&self) -> &Url { - &self.url - } - - pub fn rev_for( - &self, - path: impl AsRef, - reference: &GitReference, - ) -> eyre::Result { - reference.resolve(&self.open_local(path)?.repo) - } - - /// opens a local repository - pub fn open_local(&self, path: impl AsRef) -> eyre::Result { - let path = path.as_ref(); - let repo = git2::Repository::open(path)?; - Ok(GitLocal { remote: self.clone(), path: path.to_path_buf(), repo }) - } - - pub fn checkout( - &self, - into: &Path, - reference: &GitReference, - db: Option, - ) -> eyre::Result<(GitLocal, git2::Oid)> { - // If we have a previous instance of `GitDatabase` then fetch into that - // if we can. If that can successfully load our revision then we've - // populated the database with the latest version of `reference`, so - // return that database and the rev we resolve to. - if let Some(mut db) = db { - fetch(&mut db.repo, self.url.as_str(), reference, false) - .context(format!("failed to fetch into: {}", into.display()))?; - - if let Ok(rev) = reference.resolve(&db.repo) { - return Ok((db, rev)) - } - } - - // Otherwise, start from scratch to handle corrupt git repositories. - // After our fetch (which is interpreted as a clone now) we do the same - // resolution to figure out what we cloned. - if into.exists() { - fs::remove_dir_all(into)?; - } - fs::create_dir_all(into)?; - let mut repo = init(into, true)?; - fetch(&mut repo, self.url.as_str(), reference, false) - .context(format!("failed to clone into: {}", into.display()))?; - - let rev = reference.resolve(&repo)?; - - Ok((GitLocal { remote: self.clone(), path: into.to_path_buf(), repo }, rev)) - } -} - -/// Represents a local clone of a remote repository's database. -/// -/// Supports multiple `GitCheckouts` than can be cloned from this type. -pub struct GitLocal { - pub remote: GitRemote, - pub path: PathBuf, - pub repo: git2::Repository, -} - -// === impl GitLocal === - -impl GitLocal { - pub fn contains(&self, oid: git2::Oid) -> bool { - self.repo.revparse_single(&oid.to_string()).is_ok() - } - - pub fn copy_to(&self, rev: git2::Oid, dest: impl AsRef) -> eyre::Result> { - let dest = dest.as_ref(); - let mut checkout = None; - if let Ok(repo) = git2::Repository::open(dest) { - let mut co = GitCheckout::new(dest, self, rev, repo); - // After a successful fetch operation the subsequent reset can - // fail sometimes for corrupt repositories where the fetch - // operation succeeds but the object isn't actually there in one - // way or another. In these situations just skip the error and - // try blowing away the whole repository and trying with a - // clone. - co.fetch()?; - match co.reset() { - Ok(()) => { - checkout = Some(co); - } - Err(e) => debug!("failed reset after fetch {:?}", e), - } - }; - let checkout = match checkout { - Some(c) => c, - None => GitCheckout::clone_into(dest, self, rev)?, - }; - checkout.update_submodules()?; - Ok(checkout) - } -} - -/// Represents a local checkout of a particular revision. Calling -/// `clone_into` with a reference will resolve the reference into a revision, -pub struct GitCheckout<'a> { - database: &'a GitLocal, - _location: PathBuf, - revision: git2::Oid, - repo: git2::Repository, -} - -// === impl GitCheckout === - -impl<'a> GitCheckout<'a> { - pub fn new( - location: impl Into, - database: &'a GitLocal, - revision: git2::Oid, - repo: git2::Repository, - ) -> GitCheckout<'a> { - GitCheckout { _location: location.into(), database, revision, repo } - } - - fn fetch(&mut self) -> eyre::Result<()> { - info!("fetch {}", self.repo.path().display()); - let url = Url::from_file_path(&self.database.path) - .map_err(|_| eyre::eyre!("Invalid file url {}", self.database.path.display()))?; - let reference = GitReference::Rev(self.revision.to_string()); - fetch(&mut self.repo, url.as_str(), &reference, false)?; - Ok(()) - } - - pub fn clone_into( - into: &Path, - local: &'a GitLocal, - revision: git2::Oid, - ) -> eyre::Result> { - let dirname = into.parent().unwrap(); - fs::create_dir_all(dirname)?; - if into.exists() { - fs::remove_dir_all(into)?; - } - - // we're doing a local filesystem-to-filesystem clone so there should - // be no need to respect global configuration options, so pass in - // an empty instance of `git2::Config` below. - let git_config = git2::Config::new()?; - - // Clone the repository, but make sure we use the "local" option in - // libgit2 which will attempt to use hardlinks to set up the database. - // This should speed up the clone operation quite a bit if it works. - // - // Note that we still use the same fetch options because while we don't - // need authentication information we may want progress bars and such. - let url = Url::from_file_path(&local.path) - .map_err(|_| eyre::eyre!("Invalid file url {}", local.path.display()))?; - - let mut checkout = git2::build::CheckoutBuilder::new(); - checkout.dry_run(); // we'll do this below during a `reset` - let mut checkout = Some(checkout); - let mut repo = None; - - with_retry(|| { - with_authentication(url.as_str(), &git_config, |_| { - let r = git2::build::RepoBuilder::new() - // use hard links and/or copy the database, we're doing a - // filesystem clone so this'll speed things up quite a bit. - .clone_local(git2::build::CloneLocal::Local) - .with_checkout(checkout.take().unwrap()) - .fetch_options(git2::FetchOptions::new()) - .clone(url.as_str(), into)?; - repo = Some(r); - Ok(()) - }) - })?; - - let repo = repo.unwrap(); - - let checkout = GitCheckout::new(into, local, revision, repo); - checkout.reset()?; - Ok(checkout) - } - - /// This will perform a reset - fn reset(&self) -> eyre::Result<()> { - info!("reset {} to {}", self.repo.path().display(), self.revision); - // Ensure libgit2 won't mess with newlines when we vendor. - if let Ok(mut git_config) = self.repo.config() { - git_config.set_bool("core.autocrlf", false)?; - } - - let object = self.repo.find_object(self.revision, None)?; - reset(&self.repo, &object)?; - Ok(()) - } - - fn update_submodules(&self) -> eyre::Result<()> { - fn update_submodules(repo: &git2::Repository) -> eyre::Result<()> { - debug!("update submodules for: {:?}", repo.workdir().unwrap()); - - for mut child in repo.submodules()? { - update_submodule(repo, &mut child).with_context(|| { - format!("failed to update submodule `{}`", child.name().unwrap_or("")) - })?; - } - Ok(()) - } - - fn update_submodule( - parent: &git2::Repository, - child: &mut git2::Submodule<'_>, - ) -> eyre::Result<()> { - child.init(false)?; - let url = child - .url() - .ok_or_else(|| eyre::eyre!("non-utf8 url for submodule {:?}?", child.path()))?; - - // A submodule which is listed in .gitmodules but not actually - // checked out will not have a head id, so we should ignore it. - let head = match child.head_id() { - Some(head) => head, - None => return Ok(()), - }; - - // If the submodule hasn't been checked out yet, we need to - // clone it. If it has been checked out and the head is the same - // as the submodule's head, then we can skip an update and keep - // recursing. - let head_and_repo = child.open().and_then(|repo| { - let target = repo.head()?.target(); - Ok((target, repo)) - }); - let mut repo = match head_and_repo { - Ok((head, repo)) => { - if child.head_id() == head { - return update_submodules(&repo) - } - repo - } - Err(..) => { - let path = parent.workdir().unwrap().join(child.path()); - let _ = fs::remove_dir_all(&path); - init(&path, false)? - } - }; - // Fetch data from origin and reset to the head commit - let reference = GitReference::Rev(head.to_string()); - - fetch(&mut repo, url, &reference, false).with_context(|| { - format!("failed to fetch submodule `{}` from {url}", child.name().unwrap_or("")) - })?; - - let obj = repo.find_object(head, None)?; - reset(&repo, &obj)?; - update_submodules(&repo) - } - - update_submodules(&self.repo) - } -} - -/// Represents a specific commit in a git repository -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub enum GitReference { - /// Tag, like a release v0.0.1 - Tag(String), - /// Specific Branch - Branch(String), - /// Specific revision. - Rev(String), - /// Default branch - #[default] - DefaultBranch, -} - -// === impl GitReference === - -impl GitReference { - /// Resolves the unique identify of the reference for the given [Repository](git2::Repository) - pub fn resolve(&self, repo: &git2::Repository) -> eyre::Result { - let id = match self { - GitReference::Tag(s) => { - let resolve_tag = move || -> eyre::Result { - let refname = format!("refs/remotes/origin/tags/{s}"); - let id = repo.refname_to_id(&refname)?; - let obj = repo.find_object(id, None)?; - let obj = obj.peel(ObjectType::Commit)?; - Ok(obj.id()) - }; - resolve_tag().with_context(|| format!("failed to find tag `{s}`"))? - } - GitReference::Branch(s) => { - let name = format!("origin/{s}"); - let b = repo - .find_branch(&name, git2::BranchType::Remote) - .with_context(|| format!("failed to find branch `{s}`"))?; - b.get().target().ok_or_else(|| eyre::eyre!("branch `{s}` did not have a target"))? - } - - // use the HEAD commit - GitReference::DefaultBranch => { - let head_id = repo.refname_to_id("refs/remotes/origin/HEAD")?; - let head = repo.find_object(head_id, None)?; - head.peel(ObjectType::Commit)?.id() - } - - GitReference::Rev(s) => { - let obj = repo.revparse_single(s)?; - if let Some(tag) = obj.as_tag() { - tag.target_id() - } else { - obj.id() - } - } - }; - Ok(id) - } -} - -fn reinitialize(repo: &mut git2::Repository) -> eyre::Result<()> { - // Here we want to drop the current repository object pointed to by `repo`, - // so we initialize temporary repository in a sub-folder, blow away the - // existing git folder, and then recreate the git repo. Finally we blow away - // the `tmp` folder we allocated. - let path = repo.path().to_path_buf(); - debug!("reinitializing git repo at {:?}", path); - let tmp = path.join("tmp"); - let bare = !repo.path().ends_with(".git"); - *repo = init(&tmp, false)?; - for entry in path.read_dir()? { - let entry = entry?; - if entry.file_name().to_str() == Some("tmp") { - continue - } - let path = entry.path(); - let _ = fs::remove_file(&path).or_else(|_| fs::remove_dir_all(&path)); - } - *repo = init(&path, bare)?; - fs::remove_dir_all(&tmp)?; - Ok(()) -} - -fn init(path: &Path, bare: bool) -> eyre::Result { - let mut opts = git2::RepositoryInitOptions::new(); - // Skip anything related to templates, they just call all sorts of issues as - // we really don't want to use them yet they insist on being used. See #6240 - // for an example issue that comes up. - opts.external_template(false); - opts.bare(bare); - Ok(git2::Repository::init_opts(path, &opts)?) -} - -fn reset(repo: &git2::Repository, obj: &git2::Object<'_>) -> eyre::Result<()> { - let mut opts = git2::build::CheckoutBuilder::new(); - debug!("doing git reset"); - repo.reset(obj, git2::ResetType::Hard, Some(&mut opts))?; - debug!("git reset done"); - Ok(()) -} - -pub struct Retry { - remaining: u32, -} - -impl Retry { - pub fn new(remaining: u32) -> Self { - Self { remaining } - } - - pub fn r#try(&mut self, f: impl FnOnce() -> eyre::Result) -> eyre::Result> { - match f() { - Err(ref e) if maybe_spurious(e) && self.remaining > 0 => { - let msg = format!( - "spurious network error ({} tries remaining): {}", - self.remaining, - e.root_cause(), - ); - println!("{msg}"); - self.remaining -= 1; - Ok(None) - } - other => other.map(Some), - } - } -} - -fn maybe_spurious(err: &eyre::Error) -> bool { - if let Some(git_err) = err.downcast_ref::() { - match git_err.class() { - git2::ErrorClass::Net | - git2::ErrorClass::Os | - git2::ErrorClass::Zlib | - git2::ErrorClass::Http => return true, - _ => (), - } - } - if let Some(curl_err) = err.downcast_ref::() { - if curl_err.is_couldnt_connect() || - curl_err.is_couldnt_resolve_proxy() || - curl_err.is_couldnt_resolve_host() || - curl_err.is_operation_timedout() || - curl_err.is_recv_error() || - curl_err.is_send_error() || - curl_err.is_http2_error() || - curl_err.is_http2_stream_error() || - curl_err.is_ssl_connect_error() || - curl_err.is_partial_file() - { - return true - } - } - false -} - -pub fn with_retry(mut callback: F) -> eyre::Result -where - F: FnMut() -> eyre::Result, -{ - let mut retry = Retry::new(2); - loop { - if let Some(ret) = retry.r#try(&mut callback)? { - return Ok(ret) - } - } -} - -/// Prepare the authentication callbacks for cloning a git repository. -/// -/// The main purpose of this function is to construct the "authentication -/// callback" which is used to clone a repository. This callback will attempt to -/// find the right authentication on the system (without user input) and will -/// guide libgit2 in doing so. -/// -/// The callback is provided `allowed` types of credentials, and we try to do as -/// much as possible based on that: -/// -/// * Prioritize SSH keys from the local ssh agent as they're likely the most reliable. The username -/// here is prioritized from the credential callback, then from whatever is configured in git -/// itself, and finally we fall back to the generic user of `git`. -/// -/// * If a username/password is allowed, then we fallback to git2-rs's implementation of the -/// credential helper. This is what is configured with `credential.helper` in git, and is the -/// interface for the macOS keychain, for example. -/// -/// * After the above two have failed, we just kinda grapple attempting to return *something*. -/// -/// If any form of authentication fails, libgit2 will repeatedly ask us for -/// credentials until we give it a reason to not do so. To ensure we don't -/// just sit here looping forever we keep track of authentications we've -/// attempted and we don't try the same ones again. -fn with_authentication(url: &str, cfg: &git2::Config, mut f: F) -> eyre::Result -where - F: FnMut(&mut git2::Credentials<'_>) -> eyre::Result, -{ - let mut cred_helper = git2::CredentialHelper::new(url); - cred_helper.config(cfg); - - let mut ssh_username_requested = false; - let mut cred_helper_bad = None; - let mut ssh_agent_attempts = Vec::new(); - let mut any_attempts = false; - let mut tried_sshkey = false; - let mut url_attempt = None; - - let orig_url = url; - let mut res = f(&mut |url, username, allowed| { - any_attempts = true; - if url != orig_url { - url_attempt = Some(url.to_string()); - } - // libgit2's "USERNAME" authentication actually means that it's just - // asking us for a username to keep going. This is currently only really - // used for SSH authentication and isn't really an authentication type. - // The logic currently looks like: - // - // let user = ...; - // if (user.is_null()) - // user = callback(USERNAME, null, ...); - // - // callback(SSH_KEY, user, ...) - // - // So if we're being called here then we know that (a) we're using ssh - // authentication and (b) no username was specified in the URL that - // we're trying to clone. We need to guess an appropriate username here, - // but that may involve a few attempts. Unfortunately we can't switch - // usernames during one authentication session with libgit2, so to - // handle this we bail out of this authentication session after setting - // the flag `ssh_username_requested`, and then we handle this below. - if allowed.contains(git2::CredentialType::USERNAME) { - debug_assert!(username.is_none()); - ssh_username_requested = true; - return Err(git2::Error::from_str("gonna try usernames later")) - } - - // An "SSH_KEY" authentication indicates that we need some sort of SSH - // authentication. This can currently either come from the ssh-agent - // process or from a raw in-memory SSH key. We only support using - // ssh-agent currently. - // - // If we get called with this then the only way that should be possible - // is if a username is specified in the URL itself (e.g., `username` is - // Some), hence the unwrap() here. We try custom usernames down below. - if allowed.contains(git2::CredentialType::SSH_KEY) && !tried_sshkey { - // If ssh-agent authentication fails, libgit2 will keep - // calling this callback asking for other authentication - // methods to try. Make sure we only try ssh-agent once, - // to avoid looping forever. - tried_sshkey = true; - let username = username.unwrap(); - debug_assert!(!ssh_username_requested); - ssh_agent_attempts.push(username.to_string()); - return git2::Cred::ssh_key_from_agent(username) - } - - // Sometimes libgit2 will ask for a username/password in plaintext. - // - // If ssh-agent authentication fails, libgit2 will keep calling this - // callback asking for other authentication methods to try. Check - // cred_helper_bad to make sure we only try the git credentail helper - // once, to avoid looping forever. - if allowed.contains(git2::CredentialType::USER_PASS_PLAINTEXT) && cred_helper_bad.is_none() - { - let r = git2::Cred::credential_helper(cfg, url, username); - cred_helper_bad = Some(r.is_err()); - return r - } - - // I'm... not sure what the DEFAULT kind of authentication is, but seems - // easy to support? - if allowed.contains(git2::CredentialType::DEFAULT) { - return git2::Cred::default() - } - - // Whelp, we tried our best - Err(git2::Error::from_str("no authentication available")) - }); - - // Ok, so if it looks like we're going to be doing ssh authentication, we - // want to try a few different usernames as one wasn't specified in the URL - // for us to use. In order, we'll try: - // - // * A credential helper's username for this URL, if available. - // * This account's username. - // * "git" - // - // We have to restart the authentication session each time (due to - // constraints in libssh2 I guess? maybe this is inherent to ssh?), so we - // call our callback, `f`, in a loop here. - if ssh_username_requested { - debug_assert!(res.is_err()); - let mut attempts = vec![String::from("git")]; - if let Ok(s) = env::var("USER").or_else(|_| env::var("USERNAME")) { - attempts.push(s); - } - if let Some(ref s) = cred_helper.username { - attempts.push(s.clone()); - } - - while let Some(s) = attempts.pop() { - // We should get `USERNAME` first, where we just return our attempt, - // and then after that we should get `SSH_KEY`. If the first attempt - // fails we'll get called again, but we don't have another option so - // we bail out. - let mut attempts = 0; - res = f(&mut |_url, username, allowed| { - if allowed.contains(git2::CredentialType::USERNAME) { - return git2::Cred::username(&s) - } - if allowed.contains(git2::CredentialType::SSH_KEY) { - debug_assert_eq!(Some(&s[..]), username); - attempts += 1; - if attempts == 1 { - ssh_agent_attempts.push(s.to_string()); - return git2::Cred::ssh_key_from_agent(&s) - } - } - Err(git2::Error::from_str("no authentication available")) - }); - - // If we made two attempts then that means: - // - // 1. A username was requested, we returned `s`. - // 2. An ssh key was requested, we returned to look up `s` in the ssh agent. - // 3. For whatever reason that lookup failed, so we were asked again for another mode of - // authentication. - // - // Essentially, if `attempts == 2` then in theory the only error was - // that this username failed to authenticate (e.g., no other network - // errors happened). Otherwise something else is funny so we bail - // out. - if attempts != 2 { - break - } - } - } - let mut err = match res { - Ok(e) => return Ok(e), - Err(e) => e, - }; - - // In the case of an authentication failure (where we tried something) then - // we try to give a more helpful error message about precisely what we - // tried. - if any_attempts { - let mut msg = "failed to authenticate when downloading \ - repository" - .to_string(); - - if let Some(attempt) = &url_attempt { - if url != attempt { - msg.push_str(": "); - msg.push_str(attempt); - } - } - msg.push('\n'); - if !ssh_agent_attempts.is_empty() { - let names = - ssh_agent_attempts.iter().map(|s| format!("`{s}`")).collect::>().join(", "); - write!( - &mut msg, - "\n* attempted ssh-agent authentication, but \ - no usernames succeeded: {names}" - ) - .expect("could not write to msg"); - } - if let Some(failed_cred_helper) = cred_helper_bad { - if failed_cred_helper { - msg.push_str( - "\n* attempted to find username/password via \ - git's `credential.helper` support, but failed", - ); - } else { - msg.push_str( - "\n* attempted to find username/password via \ - `credential.helper`, but maybe the found \ - credentials were incorrect", - ); - } - } - err = err.wrap_err(msg); - - // Otherwise if we didn't even get to the authentication phase them we may - // have failed to set up a connection, in these cases hint on the - // `net.git-fetch-with-cli` configuration option. - } else if let Some(e) = err.downcast_ref::() { - match e.class() { - ErrorClass::Net | - ErrorClass::Ssl | - ErrorClass::Submodule | - ErrorClass::FetchHead | - ErrorClass::Ssh | - ErrorClass::Callback | - ErrorClass::Http => { - err = err.wrap_err("network failure seems to have happened"); - } - _ => {} - } - } - - Err(err) -} - -pub fn fetch( - repo: &mut git2::Repository, - url: &str, - reference: &GitReference, - git_fetch_with_cli: bool, -) -> eyre::Result<()> { - // Translate the reference desired here into an actual list of refspecs - // which need to get fetched. Additionally record if we're fetching tags. - let mut refspecs = Vec::new(); - let mut tags = false; - // The `+` symbol on the refspec means to allow a forced (fast-forward) - // update which is needed if there is ever a force push that requires a - // fast-forward. - match reference { - // For branches and tags we can fetch simply one reference and copy it - // locally, no need to fetch other branches/tags. - GitReference::Branch(b) => { - refspecs.push(format!("+refs/heads/{b}:refs/remotes/origin/{b}")); - } - GitReference::Tag(t) => { - refspecs.push(format!("+refs/tags/{t}:refs/remotes/origin/tags/{t}")); - } - - GitReference::DefaultBranch => { - refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD")); - } - - GitReference::Rev(rev) => { - if rev.starts_with("refs/") { - refspecs.push(format!("+{rev}:{rev}")); - } else { - // We don't know what the rev will point to. To handle this - // situation we fetch all branches and tags, and then we pray - // it's somewhere in there. - refspecs.push(String::from("+refs/heads/*:refs/remotes/origin/*")); - refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD")); - tags = true; - } - } - } - - // Unfortunately `libgit2` is notably lacking in the realm of authentication - // when compared to the `git` command line. As a result, allow an escape - // hatch for users that would prefer to use `git`-the-CLI for fetching - // repositories instead of `libgit2`-the-library. This should make more - // flavors of authentication possible while also still giving us all the - // speed and portability of using `libgit2`. - if git_fetch_with_cli { - return fetch_with_cli(repo, url, &refspecs, tags) - } - - debug!("doing a fetch for {url}"); - let git_config = git2::Config::open_default()?; - - with_retry(|| { - with_authentication(url, &git_config, |f| { - let mut opts = git2::FetchOptions::new(); - let mut rcb = git2::RemoteCallbacks::new(); - rcb.credentials(f); - opts.remote_callbacks(rcb); - if tags { - opts.download_tags(git2::AutotagOption::All); - } - // The `fetch` operation here may fail spuriously due to a corrupt - // repository. It could also fail, however, for a whole slew of other - // reasons (aka network related reasons). - // - // Consequently, we save off the error of the `fetch` operation and if it - // looks like a "corrupt repo" error then we blow away the repo and try - // again. If it looks like any other kind of error, or if we've already - // blown away the repository, then we want to return the error as-is. - let mut repo_reinitialized = false; - loop { - debug!("initiating fetch of {:?} from {}", refspecs, url); - let res = repo.remote_anonymous(url)?.fetch(&refspecs, Some(&mut opts), None); - let err = match res { - Ok(()) => break, - Err(e) => e, - }; - debug!("fetch failed: {err}"); - - if !repo_reinitialized && - matches!(err.class(), ErrorClass::Reference | ErrorClass::Odb) - { - repo_reinitialized = true; - debug!( - "looks like this is a corrupt repository, reinitializing \ - and trying again" - ); - if reinitialize(repo).is_ok() { - continue - } - } - - return Err(err.into()) - } - Ok(()) - }) - })?; - Ok(()) -} - -fn fetch_with_cli( - repo: &git2::Repository, - url: &str, - refspecs: &[String], - tags: bool, -) -> eyre::Result<()> { - let mut cmd = Command::new("git"); - cmd.arg("fetch"); - if tags { - cmd.arg("--tags"); - } - cmd.arg("--force") // handle force pushes - .arg("--update-head-ok") - .arg(url) - .args(refspecs) - .env_remove("GIT_DIR") - // The reset of these may not be necessary, but I'm including them - // just to be extra paranoid and avoid any issues. - .env_remove("GIT_WORK_TREE") - .env_remove("GIT_INDEX_FILE") - .env_remove("GIT_OBJECT_DIRECTORY") - .env_remove("GIT_ALTERNATE_OBJECT_DIRECTORIES") - .current_dir(repo.path()); - - cmd.output()?; - Ok(()) -} diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index d73c1cc1c0d7f..bf51e20f32c1d 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -10,69 +10,94 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + +[lib] +name = "cast" + [[bin]] name = "cast" path = "bin/main.rs" -[build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } - [dependencies] # lib -foundry-utils.workspace = true -foundry-evm.workspace = true -foundry-config.workspace = true +foundry-block-explorers.workspace = true foundry-common.workspace = true +foundry-compilers.workspace = true +foundry-config.workspace = true +foundry-evm.workspace = true +foundry-wallets.workspace = true + +alloy-chains.workspace = true +alloy-consensus = { workspace = true, features = ["serde", "kzg"] } +alloy-contract.workspace = true +alloy-dyn-abi.workspace = true +alloy-json-abi.workspace = true +alloy-json-rpc.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-provider = { workspace = true, features = [ + "reqwest", + "ws", + "ipc", + "trace-api", +] } +alloy-rlp.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth", "trace"] } +alloy-serde.workspace = true +alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } +alloy-signer.workspace = true +alloy-sol-types.workspace = true +alloy-transport.workspace = true -ethers-etherscan.workspace = true -ethers-core.workspace = true -ethers-providers.workspace = true chrono.workspace = true -evm-disassembler = "0.2" -eyre = "0.6" -futures = "0.3" -hex.workspace = true -rayon = "1" -serde = "1" -serde_json = "1" +eyre.workspace = true +futures.workspace = true +rand.workspace = true +rayon.workspace = true +serde_json.workspace = true +serde.workspace = true -# aws -rusoto_core = { version = "0.48", default-features = false } -rusoto_kms = { version = "0.48", default-features = false } +# aws-kms +aws-sdk-kms = { version = "1", default-features = false, optional = true } # bin -# foundry internal foundry-cli.workspace = true -# eth -ethers = { workspace = true, features = ["rustls"] } -eth-keystore = "0.5" - -bytes = "1.4" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" -comfy-table = "6" -dunce = "1" +comfy-table.workspace = true +dunce.workspace = true indicatif = "0.17" itertools.workspace = true -regex = { version = "1", default-features = false } +regex = { workspace = true, default-features = false } rpassword = "7" -semver = "1" -tempfile = "3" -tokio = { version = "1", features = ["macros"] } -tracing = "0.1" -yansi = "0.5" +semver.workspace = true +tempfile.workspace = true +tokio = { workspace = true, features = ["macros", "signal"] } +tracing.workspace = true +yansi.workspace = true +evmole.workspace = true + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] +anvil.workspace = true foundry-test-utils.workspace = true -async-trait = "0.1" -criterion = "0.5" +alloy-node-bindings.workspace = true + +async-trait.workspace = true +divan.workspace = true [features] -default = ["rustls"] -rustls = ["foundry-cli/rustls"] -openssl = ["foundry-cli/openssl"] +default = ["jemalloc"] +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/README.md b/crates/cast/README.md deleted file mode 100644 index 32de3fd74c215..0000000000000 --- a/crates/cast/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# `cast` - -Cast is a command-line tool for performing Ethereum RPC calls. You can make smart contract calls, send transactions, or retrieve any type of chain data - all from your command-line! - -For more information, see the [📖 Foundry Book (Cast Guide)](https://book.getfoundry.sh/cast/index.html). diff --git a/crates/cast/benches/vanity.rs b/crates/cast/benches/vanity.rs index 4311b5283a74e..a39e4d8bcabfd 100644 --- a/crates/cast/benches/vanity.rs +++ b/crates/cast/benches/vanity.rs @@ -1,51 +1,22 @@ -use criterion::{criterion_group, criterion_main, Criterion}; use rayon::prelude::*; -use std::{hint::black_box, time::Duration}; +use std::hint::black_box; #[path = "../bin/cmd/wallet/mod.rs"] #[allow(unused)] mod wallet; use wallet::vanity::*; -/// Benches `cast wallet vanity` -/// -/// Left or right matchers, with or without nonce do not change the outcome. -/// -/// Regex matchers get optimised away even with a black_box. -fn vanity(c: &mut Criterion) { - let mut g = c.benchmark_group("vanity"); - - g.sample_size(500); - g.noise_threshold(0.04); - g.measurement_time(Duration::from_secs(30)); - - g.bench_function("wallet generator", |b| b.iter(|| black_box(generate_wallet()))); - - // 1 - - g.sample_size(100); - g.noise_threshold(0.02); - - g.bench_function("match 1", |b| { - let m = LeftHexMatcher { left: vec![0] }; - let matcher = create_matcher(m); - b.iter(|| wallet_generator().find_any(|x| black_box(matcher(x)))) - }); - - // 2 - - g.sample_size(10); - g.noise_threshold(0.01); - g.measurement_time(Duration::from_secs(60)); - - g.bench_function("match 2", |b| { - let m = LeftHexMatcher { left: vec![0, 0] }; - let matcher = create_matcher(m); - b.iter(|| wallet_generator().find_any(|x| black_box(matcher(x)))) - }); +#[divan::bench] +fn vanity_wallet_generator() -> GeneratedWallet { + generate_wallet() +} - g.finish(); +#[divan::bench(args = [&[0][..]])] +fn vanity_match(bencher: divan::Bencher<'_, '_>, arg: &[u8]) { + let matcher = create_matcher(LeftHexMatcher { left: arg.to_vec() }); + bencher.bench_local(|| wallet_generator().find_any(|x| black_box(matcher(x)))); } -criterion_group!(vanity_benches, vanity); -criterion_main!(vanity_benches); +fn main() { + divan::main(); +} diff --git a/crates/cast/bin/args.rs b/crates/cast/bin/args.rs new file mode 100644 index 0000000000000..eeeb5d8531d16 --- /dev/null +++ b/crates/cast/bin/args.rs @@ -0,0 +1,1204 @@ +use crate::cmd::{ + access_list::AccessListArgs, artifact::ArtifactArgs, bind::BindArgs, call::CallArgs, + constructor_args::ConstructorArgsArgs, create2::Create2Args, creation_code::CreationCodeArgs, + estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, + mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, + wallet::WalletSubcommands, +}; +use alloy_primitives::{Address, B256, U256}; +use alloy_rpc_types::BlockId; +use clap::{Parser, Subcommand, ValueHint}; +use eyre::Result; +use foundry_cli::opts::{EtherscanOpts, GlobalArgs, RpcOpts}; +use foundry_common::{ + ens::NameOrAddress, + version::{LONG_VERSION, SHORT_VERSION}, +}; +use std::{path::PathBuf, str::FromStr}; + +/// A Swiss Army knife for interacting with Ethereum applications from the command line. +#[derive(Parser)] +#[command( + name = "cast", + version = SHORT_VERSION, + long_version = LONG_VERSION, + after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html", + next_display_order = None, +)] +pub struct Cast { + /// Include the global arguments. + #[command(flatten)] + pub global: GlobalArgs, + + #[command(subcommand)] + pub cmd: CastSubcommand, +} + +#[derive(Subcommand)] +pub enum CastSubcommand { + /// Prints the maximum value of the given integer type. + #[command(visible_aliases = &["--max-int", "maxi"])] + MaxInt { + /// The integer type to get the maximum value of. + #[arg(default_value = "int256")] + r#type: String, + }, + + /// Prints the minimum value of the given integer type. + #[command(visible_aliases = &["--min-int", "mini"])] + MinInt { + /// The integer type to get the minimum value of. + #[arg(default_value = "int256")] + r#type: String, + }, + + /// Prints the maximum value of the given integer type. + #[command(visible_aliases = &["--max-uint", "maxu"])] + MaxUint { + /// The unsigned integer type to get the maximum value of. + #[arg(default_value = "uint256")] + r#type: String, + }, + + /// Prints the zero address. + #[command(visible_aliases = &["--address-zero", "az"])] + AddressZero, + + /// Prints the zero hash. + #[command(visible_aliases = &["--hash-zero", "hz"])] + HashZero, + + /// Convert UTF8 text to hex. + #[command( + visible_aliases = &[ + "--from-ascii", + "--from-utf8", + "from-ascii", + "fu", + "fa"] + )] + FromUtf8 { + /// The text to convert. + text: Option, + }, + + /// Concatenate hex strings. + #[command(visible_aliases = &["--concat-hex", "ch"])] + ConcatHex { + /// The data to concatenate. + data: Vec, + }, + + /// Convert binary data into hex data. + #[command(visible_aliases = &["--from-bin", "from-binx", "fb"])] + FromBin, + + /// Normalize the input to lowercase, 0x-prefixed hex. + /// + /// The input can be: + /// - mixed case hex with or without 0x prefix + /// - 0x prefixed hex, concatenated with a ':' + /// - an absolute path to file + /// - @tag, where the tag is defined in an environment variable + #[command(visible_aliases = &["--to-hexdata", "thd", "2hd"])] + ToHexdata { + /// The input to normalize. + input: Option, + }, + + /// Convert an address to a checksummed format (EIP-55). + #[command( + visible_aliases = &["--to-checksum-address", + "--to-checksum", + "to-checksum", + "ta", + "2a"] + )] + ToCheckSumAddress { + /// The address to convert. + address: Option
, + }, + + /// Convert hex data to an ASCII string. + #[command(visible_aliases = &["--to-ascii", "tas", "2as"])] + ToAscii { + /// The hex data to convert. + hexdata: Option, + }, + + /// Convert hex data to a utf-8 string. + #[command(visible_aliases = &["--to-utf8", "tu8", "2u8"])] + ToUtf8 { + /// The hex data to convert. + hexdata: Option, + }, + + /// Convert a fixed point number into an integer. + #[command(visible_aliases = &["--from-fix", "ff"])] + FromFixedPoint { + /// The number of decimals to use. + decimals: Option, + + /// The value to convert. + #[arg(allow_hyphen_values = true)] + value: Option, + }, + + /// Right-pads hex data to 32 bytes. + #[command(visible_aliases = &["--to-bytes32", "tb", "2b"])] + ToBytes32 { + /// The hex data to convert. + bytes: Option, + }, + + /// Convert an integer into a fixed point number. + #[command(visible_aliases = &["--to-fix", "tf", "2f"])] + ToFixedPoint { + /// The number of decimals to use. + decimals: Option, + + /// The value to convert. + #[arg(allow_hyphen_values = true)] + value: Option, + }, + + /// Convert a number to a hex-encoded uint256. + #[command(name = "to-uint256", visible_aliases = &["--to-uint256", "tu", "2u"])] + ToUint256 { + /// The value to convert. + value: Option, + }, + + /// Convert a number to a hex-encoded int256. + #[command(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] + ToInt256 { + /// The value to convert. + value: Option, + }, + + /// Perform a left shifting operation + #[command(name = "shl")] + LeftShift { + /// The value to shift. + value: String, + + /// The number of bits to shift. + bits: String, + + /// The input base. + #[arg(long)] + base_in: Option, + + /// The output base. + #[arg(long, default_value = "16")] + base_out: String, + }, + + /// Perform a right shifting operation + #[command(name = "shr")] + RightShift { + /// The value to shift. + value: String, + + /// The number of bits to shift. + bits: String, + + /// The input base, + #[arg(long)] + base_in: Option, + + /// The output base, + #[arg(long, default_value = "16")] + base_out: String, + }, + + /// Convert an ETH amount into another unit (ether, gwei or wei). + /// + /// Examples: + /// - 1ether wei + /// - "1 ether" wei + /// - 1ether + /// - 1 gwei + /// - 1gwei ether + #[command(visible_aliases = &["--to-unit", "tun", "2un"])] + ToUnit { + /// The value to convert. + value: Option, + + /// The unit to convert to (ether, gwei, wei). + #[arg(default_value = "wei")] + unit: String, + }, + + /// Convert a number from decimal to smallest unit with arbitrary decimals. + /// + /// Examples: + /// - 1.0 6 (for USDC, result: 1000000) + /// - 2.5 12 (for 12 decimals token, result: 2500000000000) + /// - 1.23 3 (for 3 decimals token, result: 1230) + #[command(visible_aliases = &["--parse-units", "pun"])] + ParseUnits { + /// The value to convert. + value: Option, + + /// The unit to convert to. + #[arg(default_value = "18")] + unit: u8, + }, + + /// Format a number from smallest unit to decimal with arbitrary decimals. + /// + /// Examples: + /// - 1000000 6 (for USDC, result: 1.0) + /// - 2500000000000 12 (for 12 decimals, result: 2.5) + /// - 1230 3 (for 3 decimals, result: 1.23) + #[command(visible_aliases = &["--format-units", "fun"])] + FormatUnits { + /// The value to format. + value: Option, + + /// The unit to format to. + #[arg(default_value = "18")] + unit: u8, + }, + + /// Convert an ETH amount to wei. + /// + /// Consider using --to-unit. + #[command(visible_aliases = &["--to-wei", "tw", "2w"])] + ToWei { + /// The value to convert. + #[arg(allow_hyphen_values = true)] + value: Option, + + /// The unit to convert from (ether, gwei, wei). + #[arg(default_value = "eth")] + unit: String, + }, + + /// Convert wei into an ETH amount. + /// + /// Consider using --to-unit. + #[command(visible_aliases = &["--from-wei", "fw"])] + FromWei { + /// The value to convert. + #[arg(allow_hyphen_values = true)] + value: Option, + + /// The unit to convert from (ether, gwei, wei). + #[arg(default_value = "eth")] + unit: String, + }, + + /// 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 hex-encoded data. + #[command(visible_aliases = &["--from-rlp"])] + FromRlp { + /// The RLP hex-encoded data. + value: Option, + + /// Decode the RLP data as int + #[arg(long, alias = "int")] + as_int: bool, + }, + + /// Converts a number of one base to another + #[command(visible_aliases = &["--to-hex", "th", "2h"])] + ToHex(ToBaseArgs), + + /// Converts a number of one base to decimal + #[command(visible_aliases = &["--to-dec", "td", "2d"])] + ToDec(ToBaseArgs), + + /// Converts a number of one base to another + #[command( + visible_aliases = &["--to-base", + "--to-radix", + "to-radix", + "tr", + "2r"] + )] + ToBase { + #[command(flatten)] + base: ToBaseArgs, + + /// The output base. + #[arg(value_name = "BASE")] + base_out: Option, + }, + /// Create an access list for a transaction. + #[command(visible_aliases = &["ac", "acl"])] + AccessList(AccessListArgs), + /// Get logs by signature or topic. + #[command(visible_alias = "l")] + Logs(LogsArgs), + /// Get information about a block. + #[command(visible_alias = "bl")] + Block { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + block: Option, + + /// If specified, only get the given field of the block. + #[arg(long, short)] + field: Option, + + #[arg(long, env = "CAST_FULL_BLOCK")] + full: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the latest block number. + #[command(visible_alias = "bn")] + BlockNumber { + /// The hash or tag to query. If not specified, the latest number is returned. + block: Option, + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Perform a call on an account without publishing a transaction. + #[command(visible_alias = "c")] + Call(CallArgs), + + /// ABI-encode a function with arguments. + #[command(name = "calldata", visible_alias = "cd")] + CalldataEncode { + /// The function signature in the format `()()` + sig: String, + + /// The arguments to encode. + #[arg(allow_hyphen_values = true)] + args: Vec, + }, + + /// Get the symbolic name of the current chain. + Chain { + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the Ethereum chain ID. + #[command(visible_aliases = &["ci", "cid"])] + ChainId { + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the current client version. + #[command(visible_alias = "cl")] + Client { + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Compute the contract address from a given nonce and deployer address. + #[command(visible_alias = "ca")] + ComputeAddress { + /// The deployer address. + address: Option
, + + /// The nonce of the deployer address. + #[arg(long)] + nonce: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Disassembles a hex-encoded bytecode into a human-readable representation. + #[command(visible_alias = "da")] + Disassemble { + /// The hex-encoded bytecode. + bytecode: Option, + }, + + /// Build and sign a transaction. + #[command(name = "mktx", visible_alias = "m")] + MakeTx(MakeTxArgs), + + /// Calculate the ENS namehash of a name. + #[command(visible_aliases = &["na", "nh"])] + Namehash { name: Option }, + + /// Get information about a transaction. + #[command(visible_alias = "t")] + Tx { + /// The transaction hash. + tx_hash: String, + + /// If specified, only get the given field of the transaction. If "raw", the RLP encoded + /// transaction will be printed. + field: Option, + + /// Print the raw RLP encoded transaction. + #[arg(long, conflicts_with = "field")] + raw: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the transaction receipt for a transaction. + #[command(visible_alias = "re")] + Receipt { + /// The transaction hash. + tx_hash: String, + + /// If specified, only get the given field of the transaction. + field: Option, + + /// The number of confirmations until the receipt is fetched + #[arg(long, default_value = "1")] + confirmations: u64, + + /// Exit immediately if the transaction was not found. + #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] + cast_async: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Sign and publish a transaction. + #[command(name = "send", visible_alias = "s")] + SendTx(SendTxArgs), + + /// Publish a raw transaction to the network. + #[command(name = "publish", visible_alias = "p")] + PublishTx { + /// The raw transaction + raw_tx: String, + + /// Only print the transaction hash and exit immediately. + #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] + cast_async: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Estimate the gas cost of a transaction. + #[command(visible_alias = "e")] + Estimate(EstimateArgs), + + /// Decode ABI-encoded input data. + /// + /// Similar to `abi-decode --input`, but function selector MUST be prefixed in `calldata` + /// string + #[command(visible_aliases = &["calldata-decode", "--calldata-decode", "cdd"])] + DecodeCalldata { + /// The function signature in the format `()()`. + sig: String, + + /// The ABI-encoded calldata. + calldata: String, + }, + + /// Decode ABI-encoded string. + /// + /// Similar to `calldata-decode --input`, but the function argument is a `string` + #[command(visible_aliases = &["string-decode", "--string-decode", "sd"])] + DecodeString { + /// The ABI-encoded string. + data: String, + }, + + /// Decode event data. + #[command(visible_aliases = &["event-decode", "--event-decode", "ed"])] + DecodeEvent { + /// The event signature. If none provided then tries to decode from local cache or `https://api.openchain.xyz`. + #[arg(long, visible_alias = "event-sig")] + sig: Option, + /// The event data to decode. + data: String, + }, + + /// Decode custom error data. + #[command(visible_aliases = &["error-decode", "--error-decode", "erd"])] + DecodeError { + /// The error signature. If none provided then tries to decode from local cache or `https://api.openchain.xyz`. + #[arg(long, visible_alias = "error-sig")] + sig: Option, + /// The error data to decode. + data: String, + }, + + /// Decode ABI-encoded input or output data. + /// + /// Defaults to decoding output data. To decode input data pass --input. + /// + /// When passing `--input`, function selector must NOT be prefixed in `calldata` string + #[command(name = "decode-abi", visible_aliases = &["abi-decode", "--abi-decode", "ad"])] + DecodeAbi { + /// The function signature in the format `()()`. + sig: String, + + /// The ABI-encoded calldata. + calldata: String, + + /// Whether to decode the input or output data. + #[arg(long, short, help_heading = "Decode input data instead of output data")] + input: bool, + }, + + /// ABI encode the given function argument, excluding the selector. + #[command(visible_alias = "ae")] + AbiEncode { + /// The function signature. + sig: String, + + /// Whether to use packed encoding. + #[arg(long)] + packed: bool, + + /// The arguments of the function. + #[arg(allow_hyphen_values = true)] + args: Vec, + }, + + /// Compute the storage slot for an entry in a mapping. + #[command(visible_alias = "in")] + Index { + /// The mapping key type. + key_type: String, + + /// The mapping key. + key: String, + + /// The storage slot of the mapping. + slot_number: String, + }, + + /// Compute storage slots as specified by `ERC-7201: Namespaced Storage Layout`. + #[command(name = "index-erc7201", alias = "index-erc-7201", visible_aliases = &["index7201", "in7201"])] + IndexErc7201 { + /// The arbitrary identifier. + id: Option, + /// The formula ID. Currently the only supported formula is `erc7201`. + #[arg(long, default_value = "erc7201")] + formula_id: String, + }, + + /// Fetch the EIP-1967 implementation for a contract + /// Can read from the implementation slot or the beacon slot. + #[command(visible_alias = "impl")] + Implementation { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// Fetch the implementation from the beacon slot. + /// + /// If not specified, the implementation slot is used. + #[arg(long)] + beacon: bool, + + /// The address for which the implementation will be fetched. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Fetch the EIP-1967 admin account + #[command(visible_alias = "adm")] + Admin { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The address from which the admin account will be fetched. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the function signatures for the given selector from https://openchain.xyz. + #[command(name = "4byte", visible_aliases = &["4", "4b"])] + FourByte { + /// The function selector. + selector: Option, + }, + + /// Decode ABI-encoded calldata using https://openchain.xyz. + #[command(name = "4byte-decode", visible_aliases = &["4d", "4bd"])] + FourByteDecode { + /// The ABI-encoded calldata. + calldata: Option, + }, + + /// Get the event signature for a given topic 0 from https://openchain.xyz. + #[command(name = "4byte-event", visible_aliases = &["4e", "4be", "topic0-event", "t0e"])] + FourByteEvent { + /// Topic 0 + #[arg(value_name = "TOPIC_0")] + topic: Option, + }, + + /// Upload the given signatures to https://openchain.xyz. + /// + /// Example inputs: + /// - "transfer(address,uint256)" + /// - "function transfer(address,uint256)" + /// - "function transfer(address,uint256)" "event Transfer(address,address,uint256)" + /// - "./out/Contract.sol/Contract.json" + #[command(visible_aliases = &["ups"])] + UploadSignature { + /// The signatures to upload. + /// + /// Prefix with 'function', 'event', or 'error'. Defaults to function if no prefix given. + /// Can also take paths to contract artifact JSON. + signatures: Vec, + }, + + /// Pretty print calldata. + /// + /// Tries to decode the calldata using https://openchain.xyz unless --offline is passed. + #[command(visible_alias = "pc")] + PrettyCalldata { + /// The calldata. + calldata: Option, + + /// Skip the https://openchain.xyz lookup. + #[arg(long, short)] + offline: bool, + }, + + /// Get the timestamp of a block. + #[command(visible_alias = "a")] + Age { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the balance of an account in wei. + #[command(visible_alias = "b")] + Balance { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The account to query. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + /// Format the balance in ether. + #[arg(long, short)] + ether: bool, + + #[command(flatten)] + rpc: RpcOpts, + + /// erc20 address to query, with the method `balanceOf(address) return (uint256)`, alias + /// with '--erc721' + #[arg(long, alias = "erc721")] + erc20: Option
, + }, + + /// Get the basefee of a block. + #[command(visible_aliases = &["ba", "fee", "basefee"])] + BaseFee { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the runtime bytecode of a contract. + #[command(visible_alias = "co")] + Code { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The contract address. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + /// Disassemble bytecodes. + #[arg(long, short)] + disassemble: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the runtime bytecode size of a contract. + #[command(visible_alias = "cs")] + Codesize { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The contract address. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the current gas price. + #[command(visible_alias = "g")] + GasPrice { + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Generate event signatures from event string. + #[command(visible_alias = "se")] + SigEvent { + /// The event string. + event_string: Option, + }, + + /// Hash arbitrary data using Keccak-256. + #[command(visible_aliases = &["k", "keccak256"])] + Keccak { + /// The data to hash. + data: Option, + }, + + /// Hash a message according to EIP-191. + #[command(visible_aliases = &["--hash-message", "hm"])] + HashMessage { + /// The message to hash. + message: Option, + }, + + /// Perform an ENS lookup. + #[command(visible_alias = "rn")] + ResolveName { + /// The name to lookup. + who: Option, + + /// Perform a reverse lookup to verify that the name is correct. + #[arg(long)] + verify: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Perform an ENS reverse lookup. + #[command(visible_alias = "la")] + LookupAddress { + /// The account to perform the lookup for. + who: Option
, + + /// Perform a normal lookup to verify that the address is correct. + #[arg(long)] + verify: bool, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the raw value of a contract's storage slot. + #[command(visible_alias = "st")] + Storage(StorageArgs), + + /// Generate a storage proof for a given storage slot. + #[command(visible_alias = "pr")] + Proof { + /// The contract address. + #[arg(value_parser = NameOrAddress::from_str)] + address: NameOrAddress, + + /// The storage slot numbers (hex or decimal). + #[arg(value_parser = parse_slot)] + slots: Vec, + + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the nonce for an account. + #[command(visible_alias = "n")] + Nonce { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The address to get the nonce for. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the codehash for an account. + #[command()] + Codehash { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The address to get the codehash for. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + /// The storage slot numbers (hex or decimal). + #[arg(value_parser = parse_slot)] + slots: Vec, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the storage root for an account. + #[command(visible_alias = "sr")] + StorageRoot { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The address to get the storage root for. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + /// The storage slot numbers (hex or decimal). + #[arg(value_parser = parse_slot)] + slots: Vec, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the source code of a contract from a block explorer. + #[command(visible_aliases = &["et", "src"])] + Source { + /// The contract's address. + address: String, + + /// Whether to flatten the source code. + #[arg(long, short)] + flatten: bool, + + /// The output directory/file to expand source tree into. + #[arg(short, value_hint = ValueHint::DirPath, alias = "path")] + directory: Option, + + #[command(flatten)] + etherscan: EtherscanOpts, + + /// Alternative explorer API URL to use that adheres to the Etherscan API. If not provided, + /// defaults to Etherscan. + #[arg(long, env = "EXPLORER_API_URL")] + explorer_api_url: Option, + + /// Alternative explorer browser URL. + #[arg(long, env = "EXPLORER_URL")] + explorer_url: Option, + }, + + /// Wallet management utilities. + #[command(visible_alias = "w")] + Wallet { + #[command(subcommand)] + command: WalletSubcommands, + }, + + /// Download a contract creation code from Etherscan and RPC. + #[command(visible_alias = "cc")] + CreationCode(CreationCodeArgs), + + /// Generate an artifact file, that can be used to deploy a contract locally. + #[command(visible_alias = "ar")] + Artifact(ArtifactArgs), + + /// Display constructor arguments used for the contract initialization. + #[command(visible_alias = "cra")] + ConstructorArgs(ConstructorArgsArgs), + + /// Generate a Solidity interface from a given ABI. + /// + /// Currently does not support ABI encoder v2. + #[command(visible_alias = "i")] + Interface(InterfaceArgs), + + /// Generate a rust binding from a given ABI. + #[command(visible_alias = "bi")] + Bind(BindArgs), + + /// Get the selector for a function. + #[command(visible_alias = "si")] + Sig { + /// The function signature, e.g. transfer(address,uint256). + sig: Option, + + /// Optimize signature to contain provided amount of leading zeroes in selector. + optimize: Option, + }, + + /// Generate a deterministic contract address using CREATE2. + #[command(visible_alias = "c2")] + Create2(Create2Args), + + /// Get the block number closest to the provided timestamp. + #[command(visible_alias = "f")] + FindBlock(FindBlockArgs), + + /// Generate shell completions script. + #[command(visible_alias = "com")] + Completions { + #[arg(value_enum)] + shell: clap_complete::Shell, + }, + + /// Generate Fig autocompletion spec. + #[command(visible_alias = "fig")] + GenerateFigSpec, + + /// Runs a published transaction in a local environment and prints the trace. + #[command(visible_alias = "r")] + Run(RunArgs), + + /// Perform a raw JSON-RPC request. + #[command(visible_alias = "rp")] + Rpc(RpcArgs), + + /// Formats a string into bytes32 encoding. + #[command(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] + FormatBytes32String { + /// The string to format. + string: Option, + }, + + /// Parses a string from bytes32 encoding. + #[command(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] + ParseBytes32String { + /// The string to parse. + bytes: Option, + }, + #[command(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] + #[command(about = "Parses a checksummed address from bytes32 encoding.")] + ParseBytes32Address { + #[arg(value_name = "BYTES")] + bytes: Option, + }, + + /// Decodes a raw signed EIP 2718 typed transaction + #[command(visible_aliases = &["dt", "decode-tx"])] + DecodeTransaction { tx: Option }, + + /// Extracts function selectors and arguments from bytecode + #[command(visible_alias = "sel")] + Selectors { + /// The hex-encoded bytecode. + bytecode: Option, + + /// Resolve the function signatures for the extracted selectors using https://openchain.xyz + #[arg(long, short)] + resolve: bool, + }, + + /// Decodes EOF container bytes + #[command()] + DecodeEof { eof: Option }, +} + +/// CLI arguments for `cast --to-base`. +#[derive(Debug, Parser)] +pub struct ToBaseArgs { + /// The value to convert. + #[arg(allow_hyphen_values = true)] + pub value: Option, + + /// The input base. + #[arg(long, short = 'i')] + pub base_in: Option, +} + +pub fn parse_slot(s: &str) -> Result { + let slot = U256::from_str(s).map_err(|e| eyre::eyre!("Could not parse slot number: {e}"))?; + Ok(B256::from(slot)) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rpc_types::{BlockNumberOrTag, RpcBlockHash}; + use cast::SimpleCast; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Cast::command().debug_assert(); + } + + #[test] + fn parse_proof_slot() { + let args: Cast = Cast::parse_from([ + "foundry-cli", + "proof", + "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + "0", + "1", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x1", + "0x01", + ]); + match args.cmd { + CastSubcommand::Proof { slots, .. } => { + assert_eq!( + slots, + vec![ + B256::ZERO, + U256::from(1).into(), + B256::ZERO, + U256::from(1).into(), + U256::from(1).into() + ] + ); + } + _ => unreachable!(), + }; + } + + #[test] + fn parse_call_data() { + let args: Cast = Cast::parse_from([ + "foundry-cli", + "calldata", + "f()", + "5c9d55b78febcc2061715ba4f57ecf8ea2711f2c", + "2", + ]); + match args.cmd { + CastSubcommand::CalldataEncode { args, .. } => { + assert_eq!( + args, + vec!["5c9d55b78febcc2061715ba4f57ecf8ea2711f2c".to_string(), "2".to_string()] + ) + } + _ => unreachable!(), + }; + } + + // + #[test] + fn parse_signature() { + let args: Cast = Cast::parse_from([ + "foundry-cli", + "sig", + "__$_$__$$$$$__$$_$$$_$$__$$___$$(address,address,uint256)", + ]); + match args.cmd { + CastSubcommand::Sig { sig, .. } => { + let sig = sig.unwrap(); + assert_eq!( + sig, + "__$_$__$$$$$__$$_$$$_$$__$$___$$(address,address,uint256)".to_string() + ); + + let selector = SimpleCast::get_selector(&sig, 0).unwrap(); + assert_eq!(selector.0, "0x23b872dd".to_string()); + } + _ => unreachable!(), + }; + } + + #[test] + fn parse_block_ids() { + struct TestCase { + input: String, + expect: BlockId, + } + + let test_cases = [ + TestCase { + input: "0".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Number(0u64)), + }, + TestCase { + input: "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" + .to_string(), + expect: BlockId::Hash(RpcBlockHash::from_hash( + "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" + .parse() + .unwrap(), + None, + )), + }, + TestCase { + input: "latest".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Latest), + }, + TestCase { + input: "earliest".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Earliest), + }, + TestCase { + input: "pending".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Pending), + }, + TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumberOrTag::Safe) }, + TestCase { + input: "finalized".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Finalized), + }, + ]; + + for test in test_cases { + let result: BlockId = test.input.parse().unwrap(); + assert_eq!(result, test.expect); + } + } +} diff --git a/crates/cast/bin/cmd/access_list.rs b/crates/cast/bin/cmd/access_list.rs index 261975a14f7cd..4184912ffa697 100644 --- a/crates/cast/bin/cmd/access_list.rs +++ b/crates/cast/bin/cmd/access_list.rs @@ -1,115 +1,69 @@ -use cast::{Cast, TxBuilder}; +use crate::tx::{CastTxBuilder, SenderKind}; +use alloy_rpc_types::BlockId; +use cast::Cast; use clap::Parser; -use ethers::{ - providers::Middleware, - types::{BlockId, NameOrAddress}, -}; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils, + utils::{self, LoadConfig}, }; -use foundry_config::{Chain, Config}; +use foundry_common::ens::NameOrAddress; use std::str::FromStr; /// CLI arguments for `cast access-list`. #[derive(Debug, Parser)] pub struct AccessListArgs { /// The destination of the transaction. - #[clap( + #[arg( value_name = "TO", value_parser = NameOrAddress::from_str )] to: Option, /// The signature of the function to call. - #[clap(value_name = "SIG")] + #[arg(value_name = "SIG")] sig: Option, /// The arguments of the function to call. - #[clap(value_name = "ARGS")] + #[arg(value_name = "ARGS")] args: Vec, - /// The data for the transaction. - #[clap( - long, - value_name = "DATA", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - conflicts_with_all = &["sig", "args"] - )] - data: Option, - /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, - /// Print the access list as JSON. - #[clap(long, short, help_heading = "Display options")] - json: bool, - - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } impl AccessListArgs { pub async fn run(self) -> Result<()> { - let AccessListArgs { to, sig, args, data, tx, eth, block, json: to_json } = self; + let Self { to, sig, args, tx, eth, block } = self; - let config = Config::from(ð); + let config = eth.load_config()?; let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain_id, &provider).await?; - let sender = eth.wallet.sender().await; + let sender = SenderKind::from_wallet_opts(eth.wallet).await?; - access_list(&provider, sender, to, sig, args, data, tx, chain, block, to_json).await?; - Ok(()) - } -} + let (tx, _) = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_to(to) + .await? + .with_code_sig_and_args(None, sig, args) + .await? + .build_raw(sender) + .await?; -#[allow(clippy::too_many_arguments)] -async fn access_list, T: Into>( - provider: M, - from: F, - to: Option, - sig: Option, - args: Vec, - data: Option, - tx: TransactionOpts, - chain: Chain, - block: Option, - to_json: bool, -) -> Result<()> -where - M::Error: 'static, -{ - let mut builder = TxBuilder::new(&provider, from, to, chain, tx.legacy).await?; - builder - .gas(tx.gas_limit) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .nonce(tx.nonce); - - builder.value(tx.value); - - if let Some(sig) = sig { - builder.set_args(sig.as_str(), args).await?; - } - if let Some(data) = data { - // Note: `sig+args` and `data` are mutually exclusive - builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?); - } + let cast = Cast::new(&provider); - let builder_output = builder.peek(); + let access_list: String = cast.access_list(&tx, block).await?; - let cast = Cast::new(&provider); + sh_println!("{access_list}")?; - let access_list: String = cast.access_list(builder_output, block, to_json).await?; - - println!("{}", access_list); - - Ok(()) + Ok(()) + } } diff --git a/crates/cast/bin/cmd/artifact.rs b/crates/cast/bin/cmd/artifact.rs new file mode 100644 index 0000000000000..c95bd4d898da9 --- /dev/null +++ b/crates/cast/bin/cmd/artifact.rs @@ -0,0 +1,94 @@ +use alloy_primitives::Address; +use alloy_provider::Provider; +use clap::{command, Parser}; +use eyre::Result; +use foundry_block_explorers::Client; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils::{self, LoadConfig}, +}; +use foundry_common::fs; +use serde_json::json; +use std::path::PathBuf; + +use super::{ + creation_code::{fetch_creation_code, parse_code_output}, + interface::{fetch_abi_from_etherscan, load_abi_from_file}, +}; + +/// CLI arguments for `cast artifact`. +#[derive(Parser)] +pub struct ArtifactArgs { + /// An Ethereum address, for which the artifact will be produced. + contract: Address, + + /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is + /// not verified on Etherscan. + #[arg(long)] + abi_path: Option, + + /// The path to the output file. + /// + /// If not specified, the artifact will be output to stdout. + #[arg( + short, + long, + value_hint = clap::ValueHint::FilePath, + value_name = "PATH", + )] + output: Option, + + #[command(flatten)] + etherscan: EtherscanOpts, + + #[command(flatten)] + rpc: RpcOpts, +} + +impl ArtifactArgs { + pub async fn run(self) -> Result<()> { + let Self { contract, etherscan, rpc, output: output_location, abi_path } = self; + + let mut etherscan = etherscan; + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let api_key = etherscan.key().unwrap_or_default(); + let chain = provider.get_chain_id().await?; + etherscan.chain = Some(chain.into()); + let client = Client::new(chain.into(), api_key)?; + + let abi = if let Some(ref abi_path) = abi_path { + load_abi_from_file(abi_path, None)? + } else { + fetch_abi_from_etherscan(contract, ðerscan).await? + }; + + let (abi, _) = abi.first().ok_or_else(|| eyre::eyre!("No ABI found"))?; + + let bytecode = fetch_creation_code(contract, client, provider).await?; + let bytecode = + parse_code_output(bytecode, contract, ðerscan, abi_path.as_deref(), true, false) + .await?; + + let artifact = json!({ + "abi": abi, + "bytecode": { + "object": bytecode + } + }); + + let artifact = serde_json::to_string_pretty(&artifact)?; + + if let Some(loc) = output_location { + if let Some(parent) = loc.parent() { + fs::create_dir_all(parent)?; + } + fs::write(&loc, artifact)?; + sh_println!("Saved artifact at {}", loc.display())?; + } else { + sh_println!("{artifact}")?; + } + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/bind.rs b/crates/cast/bin/cmd/bind.rs index 16794711b0eed..159302344e528 100644 --- a/crates/cast/bin/cmd/bind.rs +++ b/crates/cast/bin/cmd/bind.rs @@ -1,15 +1,13 @@ use clap::{Parser, ValueHint}; -use ethers::prelude::{errors::EtherscanError, Abigen, Client, MultiAbigen}; use eyre::Result; use foundry_cli::opts::EtherscanOpts; -use foundry_config::Config; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -static DEFAULT_CRATE_NAME: &str = "foundry-contracts"; -static DEFAULT_CRATE_VERSION: &str = "0.0.1"; +const DEFAULT_CRATE_NAME: &str = "foundry-contracts"; +const DEFAULT_CRATE_VERSION: &str = "0.0.1"; /// CLI arguments for `cast bind`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct BindArgs { /// The contract address, or the path to an ABI Directory /// @@ -17,7 +15,7 @@ pub struct BindArgs { path_or_address: String, /// Path to where bindings will be stored - #[clap( + #[arg( short, long, value_hint = ValueHint::DirPath, @@ -29,7 +27,7 @@ pub struct BindArgs { /// /// This should be a valid crates.io crate name. However, this is currently not validated by /// this command. - #[clap( + #[arg( long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME" @@ -40,7 +38,7 @@ pub struct BindArgs { /// /// This should be a standard semver version string. However, it is not currently validated by /// this command. - #[clap( + #[arg( long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION" @@ -48,60 +46,19 @@ pub struct BindArgs { crate_version: String, /// Generate bindings as separate files. - #[clap(long)] + #[arg(long)] separate_files: bool, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, } impl BindArgs { pub async fn run(self) -> Result<()> { - let path = Path::new(&self.path_or_address); - let multi = if path.exists() { - MultiAbigen::from_json_files(path) - } else { - self.abigen_etherscan().await - }?; - - println!("Generating bindings for {} contracts", multi.len()); - let bindings = multi.build()?; - - let out = self - .output_dir - .clone() - .unwrap_or_else(|| std::env::current_dir().unwrap().join("bindings")); - bindings.write_to_crate(self.crate_name, self.crate_version, out, !self.separate_files)?; - Ok(()) - } - - async fn abigen_etherscan(&self) -> Result { - let config = Config::from(&self.etherscan); - - let chain = config.chain_id.unwrap_or_default(); - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let chain = chain.named()?; - - let client = Client::new(chain, api_key)?; - let address = self.path_or_address.parse()?; - let source = match client.contract_source_code(address).await { - Ok(source) => source, - Err(EtherscanError::InvalidApiKey) => { - eyre::bail!("Invalid Etherscan API key. Did you set it correctly? You may be using an API key for another Etherscan API chain (e.g. Etherscan API key for Polygonscan).") - } - Err(EtherscanError::ContractCodeNotVerified(address)) => { - eyre::bail!("Contract source code at {:?} on {} not verified. Maybe you have selected the wrong chain?", address, chain) - } - Err(err) => { - eyre::bail!(err) - } - }; - let abigens = source - .items - .into_iter() - .map(|item| Abigen::new(item.contract_name, item.abi).unwrap()) - .collect::>(); - - Ok(MultiAbigen::from_abigens(abigens)) + Err(eyre::eyre!( + "`cast bind` has been removed.\n\ + Please use `cast source` to create a Forge project from a block explorer source\n\ + and `forge bind` to generate the bindings to it instead." + )) } } diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 5c3ff7a9e4334..949e165c1dbc4 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -1,26 +1,35 @@ -use cast::{Cast, TxBuilder}; +use crate::tx::{CastTxBuilder, SenderKind}; +use alloy_primitives::{TxKind, U256}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag}; +use cast::{traces::TraceKind, Cast}; use clap::Parser; -use ethers::{ - solc::EvmVersion, - types::{BlockId, NameOrAddress, U256}, -}; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, handle_traces, parse_ether_value, TraceResult}, }; -use foundry_config::{find_project_root_path, Config}; -use foundry_evm::{executor::opts::EvmOpts, trace::TracingExecutor}; +use foundry_common::{ens::NameOrAddress, shell}; +use foundry_compilers::artifacts::EvmVersion; +use foundry_config::{ + figment::{ + self, + value::{Dict, Map}, + Figment, Metadata, Profile, + }, + Config, +}; +use foundry_evm::{ + executors::TracingExecutor, + opts::EvmOpts, + traces::{InternalTraceMode, TraceMode}, +}; use std::str::FromStr; -type Provider = - ethers::providers::Provider>; - /// CLI arguments for `cast call`. #[derive(Debug, Parser)] pub struct CallArgs { /// The destination of the transaction. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -30,62 +39,62 @@ pub struct CallArgs { args: Vec, /// Data for the transaction. - #[clap( + #[arg( long, - value_parser = foundry_common::clap_helpers::strip_0x_prefix, conflicts_with_all = &["sig", "args"] )] data: Option, /// Forks the remote rpc, executes the transaction locally and prints a trace - #[clap(long, default_value_t = false)] + #[arg(long, default_value_t = false)] trace: bool, - /// Can only be used with "--trace" - /// - /// opens an interactive debugger - #[clap(long, requires = "trace")] + /// Opens an interactive debugger. + /// Can only be used with `--trace`. + #[arg(long, requires = "trace")] debug: bool, - /// Can only be used with "--trace" - /// - /// prints a more verbose trace - #[clap(long, requires = "trace")] - verbose: bool, + #[arg(long, requires = "trace")] + decode_internal: bool, - /// Can only be used with "--trace" - /// Labels to apply to the traces. - /// - /// Format: `address:label` - #[clap(long, requires = "trace")] + /// Labels to apply to the traces; format: `address:label`. + /// Can only be used with `--trace`. + #[arg(long, requires = "trace")] labels: Vec, - /// Can only be used with "--trace" - /// /// The EVM Version to use. - #[clap(long, requires = "trace")] + /// Can only be used with `--trace`. + #[arg(long, requires = "trace")] evm_version: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short)] + #[arg(long, short)] block: Option, - #[clap(subcommand)] + /// Enable Odyssey features. + #[arg(long, alias = "alphanet")] + pub odyssey: bool, + + #[command(subcommand)] command: Option, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, + + /// Use current project artifacts for trace decoding. + #[arg(long, visible_alias = "la")] + pub with_local_artifacts: bool, } #[derive(Debug, Parser)] pub enum CallSubcommands { /// ignores the address field and simulates creating a contract - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// Bytecode of contract. code: String, @@ -101,188 +110,172 @@ pub enum CallSubcommands { /// Either specified in wei, or as a string with a unit type. /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, }, } impl CallArgs { pub async fn run(self) -> Result<()> { - let CallArgs { + let figment = Into::::into(&self.eth).merge(&self); + let evm_opts = figment.extract::()?; + let mut config = Config::from_provider(figment)?.sanitized(); + + let Self { to, - sig, - args, - data, - tx, + mut sig, + mut args, + mut tx, eth, command, block, trace, evm_version, debug, - verbose, + decode_internal, labels, + data, + with_local_artifacts, + .. } = self; - let config = Config::from(ð); - let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain_id, &provider).await?; - let sender = eth.wallet.sender().await; - - let mut builder: TxBuilder<'_, Provider> = - TxBuilder::new(&provider, sender, to, chain, tx.legacy).await?; - - builder - .gas(tx.gas_limit) - .etherscan_api_key(config.get_etherscan_api_key(Some(chain))) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .nonce(tx.nonce); - - match command { - Some(CallSubcommands::Create { code, sig, args, value }) => { - if trace { - let figment = Config::figment_with_root(find_project_root_path(None).unwrap()) - .merge(eth.rpc); - - let evm_opts = figment.extract::()?; - - let (env, fork, chain) = - TracingExecutor::get_fork_material(&config, evm_opts).await?; - - let mut executor = - foundry_evm::trace::TracingExecutor::new(env, fork, evm_version, debug) - .await; - - let trace = match executor.deploy( - sender, - code.into(), - value.unwrap_or(U256::zero()), - None, - ) { - Ok(deploy_result) => TraceResult::from(deploy_result), - Err(evm_err) => TraceResult::try_from(evm_err)?, - }; - - handle_traces(trace, &config, chain, labels, verbose, debug).await?; - - return Ok(()) - } - - // fill the builder after the conditional so we dont move values - fill_create(&mut builder, value, code, sig, args).await?; - } - _ => { - // fill first here because we need to use the builder in the conditional - fill_tx(&mut builder, tx.value, sig, args, data).await?; - - if trace { - let figment = Config::figment_with_root(find_project_root_path(None).unwrap()) - .merge(eth.rpc); - - let evm_opts = figment.extract::()?; - - let (env, fork, chain) = - TracingExecutor::get_fork_material(&config, evm_opts).await?; + if let Some(data) = data { + sig = Some(data); + } - let mut executor = - foundry_evm::trace::TracingExecutor::new(env, fork, evm_version, debug) - .await; - - let (tx, _) = builder.build(); - - let trace = TraceResult::from(executor.call_raw_committing( - sender, - tx.to_addr().copied().expect("an address to be here"), - tx.data().cloned().unwrap_or_default().to_vec().into(), - tx.value().copied().unwrap_or_default(), - )?); - - handle_traces(trace, &config, chain, labels, verbose, debug).await?; - - return Ok(()) - } + let provider = utils::get_provider(&config)?; + let sender = SenderKind::from_wallet_opts(eth.wallet).await?; + let from = sender.address(); + + let code = if let Some(CallSubcommands::Create { + code, + sig: create_sig, + args: create_args, + value, + }) = command + { + sig = create_sig; + args = create_args; + if let Some(value) = value { + tx.value = Some(value); } + Some(code) + } else { + None }; - let builder_output: ( - ethers::types::transaction::eip2718::TypedTransaction, - Option, - ) = builder.build(); + let (tx, func) = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_to(to) + .await? + .with_code_sig_and_args(code, sig, args) + .await? + .build_raw(sender) + .await?; + + if trace { + if let Some(BlockId::Number(BlockNumberOrTag::Number(block_number))) = self.block { + // Override Config `fork_block_number` (if set) with CLI value. + config.fork_block_number = Some(block_number); + } - println!("{}", Cast::new(provider).call(builder_output, block).await?); + let create2_deployer = evm_opts.create2_deployer; + let (mut env, fork, chain, odyssey) = + 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 trace_mode = TraceMode::Call + .with_debug(debug) + .with_decode_internal(if decode_internal { + InternalTraceMode::Full + } else { + InternalTraceMode::None + }) + .with_state_changes(shell::verbosity() > 4); + let mut executor = + TracingExecutor::new(env, fork, evm_version, trace_mode, odyssey, create2_deployer); + + let value = tx.value.unwrap_or_default(); + let input = tx.inner.input.into_input().unwrap_or_default(); + let tx_kind = tx.inner.to.expect("set by builder"); + + let trace = match tx_kind { + TxKind::Create => { + let deploy_result = executor.deploy(from, input, value, None); + TraceResult::try_from(deploy_result)? + } + TxKind::Call(to) => TraceResult::from_raw( + executor.transact_raw(from, to, input, value)?, + TraceKind::Execution, + ), + }; + + handle_traces( + trace, + &config, + chain, + labels, + with_local_artifacts, + debug, + decode_internal, + ) + .await?; + + return Ok(()); + } + + sh_println!("{}", Cast::new(provider).call(&tx, func.as_ref(), block).await?)?; Ok(()) } } -/// fills the builder from create arg -async fn fill_create( - builder: &mut TxBuilder<'_, Provider>, - value: Option, - code: String, - sig: Option, - args: Vec, -) -> Result<()> { - builder.value(value); - - let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?; - - if let Some(s) = sig { - let (mut sigdata, _func) = builder.create_args(&s, args).await?; - data.append(&mut sigdata); +impl figment::Provider for CallArgs { + fn metadata(&self) -> Metadata { + Metadata::named("CallArgs") } - builder.set_data(data); + fn data(&self) -> Result, figment::Error> { + let mut map = Map::new(); - Ok(()) -} - -/// fills the builder from args -async fn fill_tx( - builder: &mut TxBuilder<'_, Provider>, - value: Option, - sig: Option, - args: Vec, - data: Option, -) -> Result<()> { - builder.value(value); + if self.odyssey { + map.insert("odyssey".into(), self.odyssey.into()); + } - if let Some(sig) = sig { - builder.set_args(sig.as_str(), args).await?; - } + if let Some(evm_version) = self.evm_version { + map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); + } - if let Some(data) = data { - // Note: `sig+args` and `data` are mutually exclusive - builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?); + Ok(Map::from([(Config::selected_profile(), map)])) } - - Ok(()) } #[cfg(test)] mod tests { use super::*; - use ethers::types::Address; + use alloy_primitives::{hex, Address}; #[test] fn can_parse_call_data() { let data = hex::encode("hello"); - let args: CallArgs = - CallArgs::parse_from(["foundry-cli", "--data", format!("0x{data}").as_str()]); - assert_eq!(args.data, Some(data.clone())); + let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); + assert_eq!(args.data, Some(data)); - let args: CallArgs = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); + let data = hex::encode_prefixed("hello"); + let args = CallArgs::parse_from(["foundry-cli", "--data", data.as_str()]); assert_eq!(args.data, Some(data)); } #[test] fn call_sig_and_data_exclusive() { let data = hex::encode("hello"); - let to = Address::zero(); + let to = Address::ZERO; let args = CallArgs::try_parse_from([ "foundry-cli", - format!("{to:?}").as_str(), + to.to_string().as_str(), "signature", "--data", format!("0x{data}").as_str(), diff --git a/crates/cast/bin/cmd/constructor_args.rs b/crates/cast/bin/cmd/constructor_args.rs new file mode 100644 index 0000000000000..2775e2e99ecfd --- /dev/null +++ b/crates/cast/bin/cmd/constructor_args.rs @@ -0,0 +1,98 @@ +use super::{ + creation_code::fetch_creation_code, + interface::{fetch_abi_from_etherscan, load_abi_from_file}, +}; +use alloy_dyn_abi::DynSolType; +use alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider; +use clap::{command, Parser}; +use eyre::{eyre, OptionExt, Result}; +use foundry_block_explorers::Client; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils::{self, LoadConfig}, +}; + +/// CLI arguments for `cast creation-args`. +#[derive(Parser)] +pub struct ConstructorArgsArgs { + /// An Ethereum address, for which the bytecode will be fetched. + contract: Address, + + /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is + /// not verified on Etherscan + #[arg(long)] + abi_path: Option, + + #[command(flatten)] + etherscan: EtherscanOpts, + + #[command(flatten)] + rpc: RpcOpts, +} + +impl ConstructorArgsArgs { + pub async fn run(self) -> Result<()> { + let Self { contract, mut etherscan, rpc, abi_path } = self; + + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let api_key = etherscan.key().unwrap_or_default(); + let chain = provider.get_chain_id().await?; + etherscan.chain = Some(chain.into()); + let client = Client::new(chain.into(), api_key)?; + + let bytecode = fetch_creation_code(contract, client, provider).await?; + + let args_arr = parse_constructor_args(bytecode, contract, ðerscan, abi_path).await?; + for arg in args_arr { + let _ = sh_println!("{arg}"); + } + + Ok(()) + } +} + +/// Fetches the constructor arguments values and types from the creation bytecode and ABI. +async fn parse_constructor_args( + bytecode: Bytes, + contract: Address, + etherscan: &EtherscanOpts, + abi_path: Option, +) -> Result> { + let abi = if let Some(abi_path) = abi_path { + load_abi_from_file(&abi_path, None)? + } else { + fetch_abi_from_etherscan(contract, etherscan).await? + }; + + let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; + let (abi, _) = abi; + + let constructor = abi.constructor.ok_or_else(|| eyre!("No constructor found."))?; + + if constructor.inputs.is_empty() { + return Err(eyre!("No constructor arguments found.")); + } + + let args_size = constructor.inputs.len() * 32; + let args_bytes = Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()); + + let display_args: Vec = args_bytes + .chunks(32) + .enumerate() + .map(|(i, arg)| { + format_arg(&constructor.inputs[i].ty, arg).expect("Failed to format argument.") + }) + .collect(); + + Ok(display_args) +} + +fn format_arg(ty: &str, arg: &[u8]) -> Result { + let arg_type: DynSolType = ty.parse().expect("Invalid ABI type."); + let decoded = arg_type.abi_decode(arg)?; + let bytes = Bytes::from(arg.to_vec()); + + Ok(format!("{bytes} → {decoded:?}")) +} diff --git a/crates/cast/bin/cmd/create2.rs b/crates/cast/bin/cmd/create2.rs index 65bad5f1c5877..f46066137dbb8 100644 --- a/crates/cast/bin/cmd/create2.rs +++ b/crates/cast/bin/cmd/create2.rs @@ -1,75 +1,132 @@ -use cast::SimpleCast; +use alloy_primitives::{hex, keccak256, Address, B256, U256}; use clap::Parser; -use ethers::{ - core::rand::thread_rng, - types::{Address, Bytes, H256, U256}, - utils::{get_create2_address_from_hash, keccak256}, -}; use eyre::{Result, WrapErr}; -use rayon::prelude::*; +use rand::{rngs::StdRng, RngCore, SeedableRng}; use regex::RegexSetBuilder; -use std::{str::FromStr, time::Instant}; +use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Instant, +}; + +// https://etherscan.io/address/0x4e59b44847b379578588920ca78fbf26c0b4956c#code +const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; /// CLI arguments for `cast create2`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct Create2Args { /// Prefix for the contract address. - #[clap( + #[arg( long, short, - required_unless_present_any = &["ends_with", "matching"], + required_unless_present_any = &["ends_with", "matching", "salt"], value_name = "HEX" )] starts_with: Option, /// Suffix for the contract address. - #[clap(long, short, value_name = "HEX")] + #[arg(long, short, value_name = "HEX")] ends_with: Option, /// Sequence that the address has to match. - #[clap(long, short, value_name = "HEX")] + #[arg(long, short, value_name = "HEX")] matching: Option, /// Case sensitive matching. - #[clap(short, long)] + #[arg(short, long)] case_sensitive: bool, /// Address of the contract deployer. - #[clap( + #[arg( short, long, - default_value = "0x4e59b44847b379578588920ca78fbf26c0b4956c", + default_value = DEPLOYER, value_name = "ADDRESS" )] deployer: Address, + /// Salt to be used for the contract deployment. This option separate from the default salt + /// mining with filters. + #[arg( + long, + conflicts_with_all = [ + "starts_with", + "ends_with", + "matching", + "case_sensitive", + "caller", + "seed", + "no_random" + ], + value_name = "HEX" + )] + salt: Option, + /// Init code of the contract to be deployed. - #[clap(short, long, value_name = "HEX", default_value = "")] - init_code: String, + #[arg(short, long, value_name = "HEX")] + init_code: Option, /// Init code hash of the contract to be deployed. - #[clap(alias = "ch", long, value_name = "HASH")] + #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")] init_code_hash: Option, + + /// Number of threads to use. Specifying 0 defaults to the number of logical cores. + #[arg(global = true, long, short = 'j', visible_alias = "jobs")] + threads: Option, + + /// Address of the caller. Used for the first 20 bytes of the salt. + #[arg(long, value_name = "ADDRESS")] + caller: Option
, + + /// The random number generator's seed, used to initialize the salt. + #[arg(long, value_name = "HEX")] + seed: Option, + + /// Don't initialize the salt with a random value, and instead use the default value of 0. + #[arg(long, conflicts_with = "seed")] + no_random: bool, } #[allow(dead_code)] pub struct Create2Output { pub address: Address, - pub salt: U256, + pub salt: B256, } impl Create2Args { pub fn run(self) -> Result { - let Create2Args { + let Self { starts_with, ends_with, matching, case_sensitive, deployer, + salt, init_code, init_code_hash, + threads, + caller, + seed, + no_random, } = self; + let init_code_hash = if let Some(init_code_hash) = init_code_hash { + hex::FromHex::from_hex(init_code_hash) + } else if let Some(init_code) = init_code { + hex::decode(init_code).map(keccak256) + } else { + unreachable!(); + }?; + + if let Some(salt) = salt { + let salt = hex::FromHex::from_hex(salt)?; + let address = deployer.create2(salt, init_code_hash); + sh_println!("{address}")?; + return Ok(Create2Output { address, salt }); + } + let mut regexs = vec![]; if let Some(matches) = matching { @@ -90,16 +147,16 @@ impl Create2Args { } if let Some(prefix) = starts_with { - let pad_width = prefix.len() + prefix.len() % 2; - hex::decode(format!("{prefix:0>pad_width$}")) - .wrap_err("invalid prefix hex provided")?; - regexs.push(format!(r"^{prefix}")); + regexs.push(format!( + r"^{}", + get_regex_hex_string(prefix).wrap_err("invalid prefix hex provided")? + )); } if let Some(suffix) = ends_with { - let pad_width = suffix.len() + suffix.len() % 2; - hex::decode(format!("{suffix:0>pad_width$}")) - .wrap_err("invalid suffix hex provided")?; - regexs.push(format!(r"{suffix}$")); + regexs.push(format!( + r"{}$", + get_regex_hex_string(suffix).wrap_err("invalid prefix hex provided")? + )) } debug_assert!( @@ -109,104 +166,194 @@ impl Create2Args { let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?; - let init_code_hash = if let Some(init_code_hash) = init_code_hash { - let mut a: [u8; 32] = [0; 32]; - let init_code_hash = init_code_hash.strip_prefix("0x").unwrap_or(&init_code_hash); - assert!(init_code_hash.len() == 64, "init code hash should be 32 bytes long"); // 32 bytes * 2 - a.copy_from_slice(&hex::decode(init_code_hash)?[..32]); - a + let mut n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); + if let Some(threads) = threads { + n_threads = n_threads.min(threads); + } + if cfg!(test) { + n_threads = n_threads.min(2); + } + + let mut salt = B256::ZERO; + let remaining = if let Some(caller_address) = caller { + salt[..20].copy_from_slice(&caller_address.into_array()); + &mut salt[20..] } else { - let init_code = init_code.strip_prefix("0x").unwrap_or(&init_code).as_bytes(); - keccak256(hex::decode(init_code)?) + &mut salt[..] }; - println!("Starting to generate deterministic contract address..."); + if !no_random { + let mut rng = match seed { + Some(seed) => StdRng::from_seed(seed.0), + None => StdRng::from_entropy(), + }; + rng.fill_bytes(remaining); + } + + sh_println!("Configuration:")?; + sh_println!("Init code hash: {init_code_hash}")?; + sh_println!("Regex patterns: {:?}\n", regex.patterns())?; + sh_println!( + "Starting to generate deterministic contract address with {n_threads} threads..." + )?; + let mut handles = Vec::with_capacity(n_threads); + let found = Arc::new(AtomicBool::new(false)); let timer = Instant::now(); - let (salt, addr) = std::iter::repeat(()) - .par_bridge() - .map(|_| { - let salt = H256::random_using(&mut thread_rng()); - let salt = Bytes::from(salt.to_fixed_bytes()); - - let addr = SimpleCast::to_checksum_address(&get_create2_address_from_hash( - deployer, - salt.clone(), - init_code_hash, - )); - - (salt, addr) - }) - .find_any(move |(_, addr)| { - let addr = addr.to_string(); - let addr = addr.strip_prefix("0x").unwrap(); - regex.matches(addr).into_iter().count() == regex.patterns().len() - }) - .unwrap(); - - let salt = U256::from(salt.to_vec().as_slice()); - let address = Address::from_str(&addr).unwrap(); - - println!( - "Successfully found contract address in {} seconds.\nAddress: {}\nSalt: {}", - timer.elapsed().as_secs(), - addr, - salt - ); + + // Loops through all possible salts in parallel until a result is found. + // Each thread iterates over `(i..).step_by(n_threads)`. + for i in 0..n_threads { + // Create local copies for the thread. + let increment = n_threads; + let regex = regex.clone(); + let regex_len = regex.patterns().len(); + let found = Arc::clone(&found); + handles.push(std::thread::spawn(move || { + // Read the first bytes of the salt as a usize to be able to increment it. + struct B256Aligned(B256, [usize; 0]); + let mut salt = B256Aligned(salt, []); + // SAFETY: B256 is aligned to `usize`. + let salt_word = unsafe { + &mut *salt.0.as_mut_ptr().add(32 - usize::BITS as usize / 8).cast::() + }; + // Important: add the thread index to the salt to avoid duplicate results. + *salt_word = salt_word.wrapping_add(i); + + let mut checksum = [0; 42]; + loop { + // Stop if a result was found in another thread. + if found.load(Ordering::Relaxed) { + break None; + } + + // Calculate the `CREATE2` address. + #[allow(clippy::needless_borrows_for_generic_args)] + let addr = deployer.create2(&salt.0, &init_code_hash); + + // Check if the regex matches the calculated address' checksum. + let _ = addr.to_checksum_raw(&mut checksum, None); + // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string + // is safe. + let s = unsafe { std::str::from_utf8_unchecked(checksum.get_unchecked(2..)) }; + if regex.matches(s).into_iter().count() == regex_len { + // Notify other threads that we found a result. + found.store(true, Ordering::Relaxed); + break Some((addr, salt.0)); + } + + // Increment the salt for the next iteration. + *salt_word = salt_word.wrapping_add(increment); + } + })); + } + + let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::>(); + let (address, salt) = results.into_iter().next().unwrap(); + sh_println!("Successfully found contract address in {:?}", timer.elapsed())?; + sh_println!("Address: {address}")?; + sh_println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0))?; Ok(Create2Output { address, salt }) } } +fn get_regex_hex_string(s: String) -> Result { + let s = s.strip_prefix("0x").unwrap_or(&s); + let pad_width = s.len() + s.len() % 2; + hex::decode(format!("{s:0) -> Address { - // let init_code_hash = keccak256(init_code); - get_create2_address(deployer, salt.encode(), init_code) + #[test] + fn deterministic_output() { + let args = Create2Args::parse_from([ + "foundry-cli", + "--starts-with=0x00", + "--init-code-hash=0x479d7e8f31234e208d704ba1a123c76385cea8a6981fd675b784fbd9cffb918d", + "--no-random", + "-j1", + ]); + let out = args.run().unwrap(); + assert_eq!(out.address, address!("00bF495b8b42fdFeb91c8bCEB42CA4eE7186AEd2")); + assert_eq!( + out.salt, + b256!("000000000000000000000000000000000000000000000000df00000000000000"), + ); } - fn verify_create2_hash(deployer: Address, salt: U256, init_code_hash: Vec) -> Address { - get_create2_address_from_hash(deployer, salt.encode(), init_code_hash) + #[test] + fn j0() { + let args = Create2Args::try_parse_from([ + "foundry-cli", + "--starts-with=00", + "--init-code-hash", + &B256::ZERO.to_string(), + "-j0", + ]) + .unwrap(); + assert_eq!(args.threads, Some(0)); } } diff --git a/crates/cast/bin/cmd/creation_code.rs b/crates/cast/bin/cmd/creation_code.rs new file mode 100644 index 0000000000000..727e5f0d67fb5 --- /dev/null +++ b/crates/cast/bin/cmd/creation_code.rs @@ -0,0 +1,171 @@ +use super::interface::{fetch_abi_from_etherscan, load_abi_from_file}; +use alloy_consensus::Transaction; +use alloy_primitives::{Address, Bytes}; +use alloy_provider::{ext::TraceApi, Provider}; +use alloy_rpc_types::trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; +use cast::SimpleCast; +use clap::{command, Parser}; +use eyre::{eyre, OptionExt, Result}; +use foundry_block_explorers::Client; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils::{self, LoadConfig}, +}; +use foundry_common::provider::RetryProvider; + +/// CLI arguments for `cast creation-code`. +#[derive(Parser)] +pub struct CreationCodeArgs { + /// An Ethereum address, for which the bytecode will be fetched. + contract: Address, + + /// Path to file containing the contract's JSON ABI. It's necessary if the target contract is + /// not verified on Etherscan. + #[arg(long)] + abi_path: Option, + + /// Disassemble bytecodes into individual opcodes. + #[arg(long)] + disassemble: bool, + + /// Return creation bytecode without constructor arguments appended. + #[arg(long, conflicts_with = "only_args")] + without_args: bool, + + /// Return only constructor arguments. + #[arg(long)] + only_args: bool, + + #[command(flatten)] + etherscan: EtherscanOpts, + + #[command(flatten)] + rpc: RpcOpts, +} + +impl CreationCodeArgs { + pub async fn run(self) -> Result<()> { + let Self { contract, mut etherscan, rpc, disassemble, without_args, only_args, abi_path } = + self; + + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let api_key = etherscan.key().unwrap_or_default(); + let chain = provider.get_chain_id().await?; + etherscan.chain = Some(chain.into()); + let client = Client::new(chain.into(), api_key)?; + + let bytecode = fetch_creation_code(contract, client, provider).await?; + + let bytecode = parse_code_output( + bytecode, + contract, + ðerscan, + abi_path.as_deref(), + without_args, + only_args, + ) + .await?; + + if disassemble { + let _ = sh_println!("{}", SimpleCast::disassemble(&bytecode)?); + } else { + let _ = sh_println!("{bytecode}"); + } + + Ok(()) + } +} + +/// Parses the creation bytecode and returns one of the following: +/// - The complete bytecode +/// - The bytecode without constructor arguments +/// - Only the constructor arguments +pub async fn parse_code_output( + bytecode: Bytes, + contract: Address, + etherscan: &EtherscanOpts, + abi_path: Option<&str>, + without_args: bool, + only_args: bool, +) -> Result { + if !without_args && !only_args { + return Ok(bytecode); + } + + let abi = if let Some(abi_path) = abi_path { + load_abi_from_file(abi_path, None)? + } else { + fetch_abi_from_etherscan(contract, etherscan).await? + }; + + let abi = abi.into_iter().next().ok_or_eyre("No ABI found.")?; + let (abi, _) = abi; + + if abi.constructor.is_none() { + if only_args { + return Err(eyre!("No constructor found.")); + } + return Ok(bytecode); + } + + let constructor = abi.constructor.unwrap(); + if constructor.inputs.is_empty() { + if only_args { + return Err(eyre!("No constructor arguments found.")); + } + return Ok(bytecode); + } + + let args_size = constructor.inputs.len() * 32; + + let bytecode = if without_args { + Bytes::from(bytecode[..bytecode.len() - args_size].to_vec()) + } else if only_args { + Bytes::from(bytecode[bytecode.len() - args_size..].to_vec()) + } else { + unreachable!(); + }; + + Ok(bytecode) +} + +/// Fetches the creation code of a contract from Etherscan and RPC. +pub async fn fetch_creation_code( + contract: Address, + client: Client, + provider: RetryProvider, +) -> Result { + let creation_data = client.contract_creation_data(contract).await?; + let creation_tx_hash = creation_data.transaction_hash; + let tx_data = provider.get_transaction_by_hash(creation_tx_hash).await?; + let tx_data = tx_data.ok_or_eyre("Could not find creation tx data.")?; + + let bytecode = if tx_data.to().is_none() { + // Contract was created using a standard transaction + tx_data.input().clone() + } else { + // Contract was created using a factory pattern or create2 + // Extract creation code from tx traces + let mut creation_bytecode = None; + + let traces = provider.trace_transaction(creation_tx_hash).await.map_err(|e| { + eyre!("Could not fetch traces for transaction {}: {}", creation_tx_hash, e) + })?; + + for trace in traces { + if let Some(TraceOutput::Create(CreateOutput { address, .. })) = trace.trace.result { + if address == contract { + creation_bytecode = match trace.trace.action { + Action::Create(CreateAction { init, .. }) => Some(init), + _ => None, + }; + } + } + } + + creation_bytecode.ok_or_else(|| eyre!("Could not find contract creation trace."))? + }; + + Ok(bytecode) +} diff --git a/crates/cast/bin/cmd/estimate.rs b/crates/cast/bin/cmd/estimate.rs index e2f992b2c9731..af08cd220a661 100644 --- a/crates/cast/bin/cmd/estimate.rs +++ b/crates/cast/bin/cmd/estimate.rs @@ -1,19 +1,21 @@ -use cast::{Cast, TxBuilder}; +use crate::tx::{CastTxBuilder, SenderKind}; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; use clap::Parser; -use ethers::types::{NameOrAddress, U256}; use eyre::Result; use foundry_cli::{ - opts::{EtherscanOpts, RpcOpts}, - utils::{self, parse_ether_value}, + opts::{EthereumOpts, TransactionOpts}, + utils::{self, parse_ether_value, LoadConfig}, }; -use foundry_config::{figment::Figment, Config}; +use foundry_common::ens::NameOrAddress; use std::str::FromStr; /// CLI arguments for `cast estimate`. #[derive(Debug, Parser)] pub struct EstimateArgs { /// The destination of the transaction. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -22,38 +24,26 @@ pub struct EstimateArgs { /// The arguments of the function to call. args: Vec, - /// The sender account. - #[clap( - short, - long, - value_parser = NameOrAddress::from_str, - default_value = "0x0000000000000000000000000000000000000000", - env = "ETH_FROM", - )] - from: NameOrAddress, - - /// Ether to send in the transaction. + /// The block height to query at. /// - /// Either specified in wei, or as a string with a unit type: - /// - /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] - value: Option, + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, - #[clap(flatten)] - rpc: RpcOpts, + #[command(subcommand)] + command: Option, - #[clap(flatten)] - etherscan: EtherscanOpts, + #[command(flatten)] + tx: TransactionOpts, - #[clap(subcommand)] - command: Option, + #[command(flatten)] + eth: EthereumOpts, } #[derive(Debug, Parser)] pub enum EstimateSubcommands { /// Estimate gas cost to deploy a smart contract - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// The bytecode of contract code: String, @@ -69,47 +59,58 @@ pub enum EstimateSubcommands { /// Either specified in wei, or as a string with a unit type: /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, }, } impl EstimateArgs { pub async fn run(self) -> Result<()> { - let EstimateArgs { from, to, sig, args, value, rpc, etherscan, command } = self; - - let figment = Figment::from(Config::figment()).merge(etherscan).merge(rpc); - let config = Config::from_provider(figment); + let Self { to, mut sig, mut args, mut tx, block, eth, command } = self; + let config = eth.load_config()?; let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain_id, &provider).await?; - let api_key = config.get_etherscan_api_key(Some(chain)); - - let mut builder = TxBuilder::new(&provider, from, to, chain, false).await?; - builder.etherscan_api_key(api_key); - - match command { - Some(EstimateSubcommands::Create { code, sig, args, value }) => { - builder.value(value); - - let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?; - - if let Some(s) = sig { - let (mut sigdata, _func) = builder.create_args(&s, args).await?; - data.append(&mut sigdata); - } - - builder.set_data(data); - } - _ => { - let sig = sig.ok_or_else(|| eyre::eyre!("Function signature must be provided."))?; - builder.value(value).set_args(sig.as_str(), args).await?; + let sender = SenderKind::from_wallet_opts(eth.wallet).await?; + + let code = if let Some(EstimateSubcommands::Create { + code, + sig: create_sig, + args: create_args, + value, + }) = command + { + sig = create_sig; + args = create_args; + if let Some(value) = value { + tx.value = Some(value); } + Some(code) + } else { + None }; - let builder_output = builder.peek(); - let gas = Cast::new(&provider).estimate(builder_output).await?; - println!("{gas}"); + let (tx, _) = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_to(to) + .await? + .with_code_sig_and_args(code, sig, args) + .await? + .build_raw(sender) + .await?; + + let gas = provider.estimate_gas(&tx).block(block.unwrap_or_default()).await?; + sh_println!("{gas}")?; Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_estimate_value() { + let args: EstimateArgs = EstimateArgs::parse_from(["foundry-cli", "--value", "100"]); + assert!(args.tx.value.is_some()); + } +} diff --git a/crates/cast/bin/cmd/find_block.rs b/crates/cast/bin/cmd/find_block.rs index 6f1ed25b02c52..b77845e189cbf 100644 --- a/crates/cast/bin/cmd/find_block.rs +++ b/crates/cast/bin/cmd/find_block.rs @@ -1,70 +1,72 @@ +use alloy_provider::Provider; use cast::Cast; use clap::Parser; -use ethers::prelude::*; use eyre::Result; -use foundry_cli::{opts::RpcOpts, utils}; -use foundry_config::Config; +use foundry_cli::{ + opts::RpcOpts, + utils::{self, LoadConfig}, +}; use futures::join; /// CLI arguments for `cast find-block`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct FindBlockArgs { /// The UNIX timestamp to search for, in seconds. timestamp: u64, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, } impl FindBlockArgs { pub async fn run(self) -> Result<()> { - let FindBlockArgs { timestamp, rpc } = self; + let Self { timestamp, rpc } = self; - let ts_target = U256::from(timestamp); - let config = Config::from(&rpc); + let ts_target = timestamp; + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let last_block_num = provider.get_block_number().await?; let cast_provider = Cast::new(provider); let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1)); - let ts_block_latest = res.0?; - let ts_block_1 = res.1?; + let ts_block_latest: u64 = res.0?.to(); + let ts_block_1: u64 = res.1?.to(); let block_num = if ts_block_latest < ts_target { // If the most recent block's timestamp is below the target, return it last_block_num } else if ts_block_1 > ts_target { // If the target timestamp is below block 1's timestamp, return that - U64::from(1_u64) + 1 } else { // Otherwise, find the block that is closest to the timestamp - let mut low_block = U64::from(1_u64); // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 + let mut low_block = 1_u64; // block 0 has a timestamp of 0: https://github.com/ethereum/go-ethereum/issues/17042#issuecomment-559414137 let mut high_block = last_block_num; - let mut matching_block: Option = None; + let mut matching_block = None; while high_block > low_block && matching_block.is_none() { // Get timestamp of middle block (this approach approach to avoids overflow) let high_minus_low_over_2 = high_block .checked_sub(low_block) .ok_or_else(|| eyre::eyre!("unexpected underflow")) .unwrap() - .checked_div(U64::from(2_u64)) + .checked_div(2_u64) .unwrap(); let mid_block = high_block.checked_sub(high_minus_low_over_2).unwrap(); - let ts_mid_block = cast_provider.timestamp(mid_block).await?; + let ts_mid_block = cast_provider.timestamp(mid_block).await?.to::(); // Check if we've found a match or should keep searching if ts_mid_block == ts_target { matching_block = Some(mid_block) - } else if high_block.checked_sub(low_block).unwrap() == U64::from(1_u64) { + } else if high_block.checked_sub(low_block).unwrap() == 1_u64 { // The target timestamp is in between these blocks. This rounds to the // highest block if timestamp is equidistant between blocks let res = join!( cast_provider.timestamp(high_block), cast_provider.timestamp(low_block) ); - let ts_high = res.0.unwrap(); - let ts_low = res.1.unwrap(); + let ts_high: u64 = res.0.unwrap().to(); + let ts_low: u64 = res.1.unwrap().to(); let high_diff = ts_high.checked_sub(ts_target).unwrap(); let low_diff = ts_target.checked_sub(ts_low).unwrap(); let is_low = low_diff < high_diff; @@ -77,7 +79,7 @@ impl FindBlockArgs { } matching_block.unwrap_or(low_block) }; - println!("{block_num}"); + sh_println!("{block_num}")?; Ok(()) } diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index 3bd52e07b3591..10b7e42500288 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -1,31 +1,44 @@ -use cast::{AbiPath, SimpleCast}; +use alloy_json_abi::{ContractObject, JsonAbi}; +use alloy_primitives::Address; use clap::Parser; -use eyre::Result; -use foundry_cli::opts::EtherscanOpts; -use foundry_common::fs; -use foundry_config::Config; -use std::path::{Path, PathBuf}; +use eyre::{Context, Result}; +use foundry_block_explorers::Client; +use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; +use foundry_common::{ + compile::{PathOrContractInfo, ProjectCompiler}, + find_target_path, fs, shell, ContractsByArtifact, +}; +use foundry_config::load_config; +use itertools::Itertools; +use serde_json::Value; +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; /// CLI arguments for `cast interface`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct InterfaceArgs { - /// The contract address, or the path to an ABI file. - /// - /// If an address is specified, then the ABI is fetched from Etherscan. - path_or_address: String, + /// The target contract, which can be one of: + /// - A file path to an ABI JSON file. + /// - A contract identifier in the form `:` or just ``. + /// - An Ethereum address, for which the ABI will be fetched from Etherscan. + contract: String, /// The name to use for the generated interface. - #[clap(long, short)] + /// + /// Only relevant when retrieving the ABI from a file. + #[arg(long, short)] name: Option, /// Solidity pragma version. - #[clap(long, short, default_value = "^0.8.10", value_name = "VERSION")] + #[arg(long, short, default_value = "^0.8.4", value_name = "VERSION")] pragma: String, /// The path to the output file. /// /// If not specified, the interface will be output to stdout. - #[clap( + #[arg( short, long, value_hint = clap::ValueHint::FilePath, @@ -33,39 +46,122 @@ pub struct InterfaceArgs { )] output: Option, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, } impl InterfaceArgs { pub async fn run(self) -> Result<()> { - let InterfaceArgs { path_or_address, name, pragma, output: output_location, etherscan } = - self; - let config = Config::from(ðerscan); - let chain = config.chain_id.unwrap_or_default(); - let source = if Path::new(&path_or_address).exists() { - AbiPath::Local { path: path_or_address, name } + let Self { contract, name, pragma, output: output_location, etherscan } = self; + + // Determine if the target contract is an ABI file, a local contract or an Ethereum address. + let abis = if Path::new(&contract).is_file() && + fs::read_to_string(&contract) + .ok() + .and_then(|content| serde_json::from_str::(&content).ok()) + .is_some() + { + load_abi_from_file(&contract, name)? } else { - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let chain = chain.named()?; - AbiPath::Etherscan { chain, api_key, address: path_or_address.parse()? } + match Address::from_str(&contract) { + Ok(address) => fetch_abi_from_etherscan(address, ðerscan).await?, + Err(_) => load_abi_from_artifact(&contract)?, + } }; - let interfaces = SimpleCast::generate_interface(source).await?; - // put it all together - let pragma = format!("pragma solidity {pragma};"); - let interfaces = - interfaces.iter().map(|iface| iface.source.to_string()).collect::>().join("\n"); - let res = format!("{pragma}\n\n{interfaces}"); + // Retrieve interfaces from the array of ABIs. + let interfaces = get_interfaces(abis)?; + + // Print result or write to file. + let res = if shell::is_json() { + // Format as JSON. + interfaces.iter().map(|iface| &iface.json_abi).format("\n").to_string() + } else { + // Format as Solidity. + format!( + "// SPDX-License-Identifier: UNLICENSED\n\ + pragma solidity {pragma};\n\n\ + {}", + interfaces.iter().map(|iface| &iface.source).format("\n") + ) + }; - // print or write to file if let Some(loc) = output_location { - fs::create_dir_all(loc.parent().unwrap())?; + if let Some(parent) = loc.parent() { + fs::create_dir_all(parent)?; + } fs::write(&loc, res)?; - println!("Saved interface at {}", loc.display()); + sh_println!("Saved interface at {}", loc.display())?; } else { - println!("{res}"); + sh_print!("{res}")?; } + Ok(()) } } + +struct InterfaceSource { + json_abi: String, + source: String, +} + +/// Load the ABI from a file. +pub fn load_abi_from_file(path: &str, name: Option) -> Result> { + let file = std::fs::read_to_string(path).wrap_err("unable to read abi file")?; + let obj: ContractObject = serde_json::from_str(&file)?; + let abi = obj.abi.ok_or_else(|| eyre::eyre!("could not find ABI in file {path}"))?; + let name = name.unwrap_or_else(|| "Interface".to_owned()); + Ok(vec![(abi, name)]) +} + +/// Load the ABI from the artifact of a locally compiled contract. +fn load_abi_from_artifact(path_or_contract: &str) -> Result> { + let config = load_config()?; + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet(true); + + let contract = PathOrContractInfo::from_str(path_or_contract)?; + + let target_path = find_target_path(&project, &contract)?; + let output = compiler.files([target_path.clone()]).compile(&project)?; + + let contracts_by_artifact = ContractsByArtifact::from(output); + + let maybe_abi = contracts_by_artifact + .find_abi_by_name_or_src_path(contract.name().unwrap_or(&target_path.to_string_lossy())); + + let (abi, name) = + maybe_abi.as_ref().ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?; + + Ok(vec![(abi.clone(), contract.name().unwrap_or(name).to_string())]) +} + +/// Fetches the ABI of a contract from Etherscan. +pub async fn fetch_abi_from_etherscan( + address: Address, + etherscan: &EtherscanOpts, +) -> Result> { + let config = etherscan.load_config()?; + let chain = config.chain.unwrap_or_default(); + let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, api_key)?; + let source = client.contract_source_code(address).await?; + source.items.into_iter().map(|item| Ok((item.abi()?, item.contract_name))).collect() +} + +/// Converts a vector of tuples containing the ABI and contract name into a vector of +/// `InterfaceSource` objects. +fn get_interfaces(abis: Vec<(JsonAbi, String)>) -> Result> { + abis.into_iter() + .map(|(contract_abi, name)| { + let source = match foundry_cli::utils::abi_to_solidity(&contract_abi, &name) { + Ok(generated_source) => generated_source, + Err(e) => { + warn!("Failed to format interface for {name}: {e}"); + contract_abi.to_sol(&name, None) + } + }; + Ok(InterfaceSource { json_abi: serde_json::to_string_pretty(&contract_abi)?, source }) + }) + .collect() +} diff --git a/crates/cast/bin/cmd/logs.rs b/crates/cast/bin/cmd/logs.rs index 433f90969469e..f6a756f69e622 100644 --- a/crates/cast/bin/cmd/logs.rs +++ b/crates/cast/bin/cmd/logs.rs @@ -1,16 +1,15 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue, Specifier}; +use alloy_json_abi::Event; +use alloy_network::AnyNetwork; +use alloy_primitives::{hex::FromHex, Address, B256}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; use cast::Cast; use clap::Parser; -use ethers::{ - abi::{Address, Event, RawTopicFilter, Topic, TopicFilter}, - providers::Middleware, - types::{BlockId, BlockNumber, Filter, FilterBlockOption, NameOrAddress, ValueOrArray, H256}, -}; use eyre::Result; -use foundry_cli::{opts::EthereumOpts, utils}; -use foundry_common::abi::{get_event, parse_tokens}; -use foundry_config::Config; +use foundry_cli::{opts::EthereumOpts, utils, utils::LoadConfig}; +use foundry_common::ens::NameOrAddress; use itertools::Itertools; -use std::str::FromStr; +use std::{io, str::FromStr}; /// CLI arguments for `cast logs`. #[derive(Debug, Parser)] @@ -18,17 +17,17 @@ pub struct LogsArgs { /// The block height to start query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long)] + #[arg(long)] from_block: Option, /// The block height to stop query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long)] + #[arg(long)] to_block: Option, /// The contract address to filter on. - #[clap( + #[arg( long, value_parser = NameOrAddress::from_str )] @@ -36,126 +35,100 @@ pub struct LogsArgs { /// The signature of the event to filter logs by which will be converted to the first topic or /// a topic to filter on. - #[clap(value_name = "SIG_OR_TOPIC")] + #[arg(value_name = "SIG_OR_TOPIC")] sig_or_topic: Option, /// If used with a signature, the indexed fields of the event to filter by. Otherwise, the /// remaining topics of the filter. - #[clap(value_name = "TOPICS_OR_ARGS")] + #[arg(value_name = "TOPICS_OR_ARGS")] topics_or_args: Vec, - /// Print the logs as JSON. - #[clap(long, short, help_heading = "Display options")] - json: bool, + /// If the RPC type and endpoints supports `eth_subscribe` stream logs instead of printing and + /// exiting. Will continue until interrupted or TO_BLOCK is reached. + #[arg(long)] + subscribe: bool, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } impl LogsArgs { pub async fn run(self) -> Result<()> { - let LogsArgs { - from_block, to_block, address, topics_or_args, sig_or_topic, json, eth, .. - } = self; + let Self { from_block, to_block, address, sig_or_topic, topics_or_args, subscribe, eth } = + self; - let config = Config::from(ð); + let config = eth.load_config()?; let provider = utils::get_provider(&config)?; + let cast = Cast::new(&provider); + let address = match address { - Some(address) => { - let address = match address { - NameOrAddress::Name(name) => provider.resolve_name(&name).await?, - NameOrAddress::Address(address) => address, - }; - Some(address) - } + Some(address) => Some(address.resolve(&provider).await?), None => None, }; - let from_block = convert_block_number(&provider, from_block).await?; - let to_block = convert_block_number(&provider, to_block).await?; - - let cast = Cast::new(&provider); + let from_block = + cast.convert_block_number(Some(from_block.unwrap_or_else(BlockId::earliest))).await?; + let to_block = + cast.convert_block_number(Some(to_block.unwrap_or_else(BlockId::latest))).await?; let filter = build_filter(from_block, to_block, address, sig_or_topic, topics_or_args)?; - let logs = cast.filter_logs(filter, json).await?; - - println!("{}", logs); + if !subscribe { + let logs = cast.filter_logs(filter).await?; + sh_println!("{logs}")?; + return Ok(()) + } + + // FIXME: this is a hotfix for + // currently the alloy `eth_subscribe` impl does not work with all transports, so we use + // the builtin transport here for now + let url = config.get_rpc_url_or_localhost_http()?; + let provider = alloy_provider::ProviderBuilder::<_, _, AnyNetwork>::default() + .on_builtin(url.as_ref()) + .await?; + let cast = Cast::new(&provider); + let mut stdout = io::stdout(); + cast.subscribe(filter, &mut stdout).await?; Ok(()) } } -/// Converts a block identifier into a block number. -/// -/// If the block identifier is a block number, then this function returns the block number. If the -/// block identifier is a block hash, then this function returns the block number of that block -/// hash. If the block identifier is `None`, then this function returns `None`. -async fn convert_block_number( - provider: M, - block: Option, -) -> Result, eyre::Error> -where - M::Error: 'static, -{ - match block { - Some(block) => match block { - BlockId::Number(block_number) => Ok(Some(block_number)), - BlockId::Hash(hash) => { - let block = provider.get_block(hash).await?; - Ok(block.map(|block| block.number.unwrap()).map(BlockNumber::from)) - } - }, - None => Ok(None), - } -} - -// First tries to parse the `sig_or_topic` as an event signature. If successful, `topics_or_args` is -// parsed as indexed inputs and converted to topics. Otherwise, `sig_or_topic` is prepended to -// `topics_or_args` and used as raw topics. +/// Builds a Filter by first trying to parse the `sig_or_topic` as an event signature. If +/// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise, +/// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics. fn build_filter( - from_block: Option, - to_block: Option, + from_block: Option, + to_block: Option, address: Option
, sig_or_topic: Option, topics_or_args: Vec, ) -> Result { let block_option = FilterBlockOption::Range { from_block, to_block }; - let topic_filter = match sig_or_topic { + let filter = match sig_or_topic { // Try and parse the signature as an event signature - Some(sig_or_topic) => match get_event(sig_or_topic.as_str()) { + Some(sig_or_topic) => match foundry_common::abi::get_event(sig_or_topic.as_str()) { Ok(event) => build_filter_event_sig(event, topics_or_args)?, Err(_) => { let topics = [vec![sig_or_topic], topics_or_args].concat(); build_filter_topics(topics)? } }, - None => TopicFilter::default(), + None => Filter::default(), }; - // Convert from TopicFilter to Filter - let topics = - vec![topic_filter.topic0, topic_filter.topic1, topic_filter.topic2, topic_filter.topic3] - .into_iter() - .map(|topic| match topic { - Topic::Any => None, - Topic::This(topic) => Some(ValueOrArray::Value(Some(topic))), - _ => unreachable!(), - }) - .collect::>(); - - let filter = Filter { - block_option, - address: address.map(ValueOrArray::Value), - topics: [topics[0].clone(), topics[1].clone(), topics[2].clone(), topics[3].clone()], - }; + let mut filter = filter.select(block_option); + + if let Some(address) = address { + filter = filter.address(address) + } Ok(filter) } -// Creates a TopicFilter for the given event signature and arguments. -fn build_filter_event_sig(event: Event, args: Vec) -> Result { +/// Creates a [Filter] from the given event signature and arguments. +fn build_filter_event_sig(event: Event, args: Vec) -> Result { let args = args.iter().map(|arg| arg.as_str()).collect::>(); // Match the args to indexed inputs. Enumerate so that the ordering can be restored @@ -165,57 +138,75 @@ fn build_filter_event_sig(event: Event, args: Vec) -> Result>>()? + .into_iter() .enumerate() .partition(|(_, (_, arg))| !arg.is_empty()); // Only parse the inputs with arguments - let indexed_tokens = - parse_tokens(with_args.clone().into_iter().map(|(_, p)| p).collect::>(), true)?; + let indexed_tokens = with_args + .iter() + .map(|(_, (kind, arg))| kind.coerce_str(arg)) + .collect::, _>>()?; // Merge the inputs restoring the original ordering - let mut tokens = with_args + let mut topics = with_args .into_iter() .zip(indexed_tokens) .map(|((i, _), t)| (i, Some(t))) .chain(without_args.into_iter().map(|(i, _)| (i, None))) .sorted_by(|(i1, _), (i2, _)| i1.cmp(i2)) - .map(|(_, token)| token) - .collect::>(); + .map(|(_, token)| { + token + .map(|token| Topic::from(B256::from_slice(token.abi_encode().as_slice()))) + .unwrap_or(Topic::default()) + }) + .collect::>(); - tokens.resize(3, None); + topics.resize(3, Topic::default()); - let raw = RawTopicFilter { - topic0: tokens[0].clone().map_or(Topic::Any, Topic::This), - topic1: tokens[1].clone().map_or(Topic::Any, Topic::This), - topic2: tokens[2].clone().map_or(Topic::Any, Topic::This), - }; + let filter = Filter::new() + .event_signature(event.selector()) + .topic1(topics[0].clone()) + .topic2(topics[1].clone()) + .topic3(topics[2].clone()); - // Let filter do the hardwork of converting arguments to topics - Ok(event.filter(raw)?) + Ok(filter) } -// Creates a TopicFilter from raw topic hashes. -fn build_filter_topics(topics: Vec) -> Result { +/// Creates a [Filter] from raw topic hashes. +fn build_filter_topics(topics: Vec) -> Result { let mut topics = topics .into_iter() - .map(|topic| if topic.is_empty() { Ok(None) } else { H256::from_str(&topic).map(Some) }) - .collect::, _>>()?; + .map(|topic| { + if topic.is_empty() { + Ok(Topic::default()) + } else { + Ok(Topic::from(B256::from_hex(topic.as_str())?)) + } + }) + .collect::>>>()?; - topics.resize(4, None); + topics.resize(4, Topic::default()); - Ok(TopicFilter { - topic0: topics[0].map_or(Topic::Any, Topic::This), - topic1: topics[1].map_or(Topic::Any, Topic::This), - topic2: topics[2].map_or(Topic::Any, Topic::This), - topic3: topics[3].map_or(Topic::Any, Topic::This), - }) + let filter = Filter::new() + .event_signature(topics[0].clone()) + .topic1(topics[1].clone()) + .topic2(topics[2].clone()) + .topic3(topics[3].clone()); + + Ok(filter) } #[cfg(test)] mod tests { use super::*; - use ethers::types::H160; + use alloy_primitives::{U160, U256}; + use alloy_rpc_types::ValueOrArray; const ADDRESS: &str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"; const TRANSFER_SIG: &str = "Transfer(address indexed,address indexed,uint256)"; @@ -224,13 +215,13 @@ mod tests { #[test] fn test_build_filter_basic() { - let from_block = Some(BlockNumber::from(1337)); - let to_block = Some(BlockNumber::Latest); + let from_block = Some(BlockNumberOrTag::from(1337)); + let to_block = Some(BlockNumberOrTag::Latest); let address = Address::from_str(ADDRESS).ok(); let expected = Filter { block_option: FilterBlockOption::Range { from_block, to_block }, - address: Some(ValueOrArray::Value(address.unwrap())), - topics: [None, None, None, None], + address: ValueOrArray::Value(address.unwrap()).into(), + topics: [vec![].into(), vec![].into(), vec![].into(), vec![].into()], }; let filter = build_filter(from_block, to_block, address, None, vec![]).unwrap(); assert_eq!(filter, expected) @@ -240,8 +231,13 @@ mod tests { fn test_build_filter_sig() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, - topics: [Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), None, None, None], + address: vec![].into(), + topics: [ + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], }; let filter = build_filter(None, None, None, Some(TRANSFER_SIG.to_string()), vec![]).unwrap(); @@ -252,8 +248,13 @@ mod tests { fn test_build_filter_mismatch() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, - topics: [Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), None, None, None], + address: vec![].into(), + topics: [ + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], }; let filter = build_filter( None, @@ -268,14 +269,16 @@ mod tests { #[test] fn test_build_filter_sig_with_arguments() { + let addr = Address::from_str(ADDRESS).unwrap(); + let addr = U256::from(U160::from_be_bytes(addr.0 .0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - Some(H160::from_str(ADDRESS).unwrap().into()), - None, - None, + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + addr.into(), + vec![].into(), + vec![].into(), ], }; let filter = build_filter( @@ -291,14 +294,16 @@ mod tests { #[test] fn test_build_filter_sig_with_skipped_arguments() { + let addr = Address::from_str(ADDRESS).unwrap(); + let addr = U256::from(U160::from_be_bytes(addr.0 .0)); let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - Some(H160::from_str(ADDRESS).unwrap().into()), - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + addr.into(), + vec![].into(), ], }; let filter = build_filter( @@ -306,7 +311,7 @@ mod tests { None, None, Some(TRANSFER_SIG.to_string()), - vec!["".to_string(), ADDRESS.to_string()], + vec![String::new(), ADDRESS.to_string()], ) .unwrap(); assert_eq!(filter, expected) @@ -316,12 +321,12 @@ mod tests { fn test_build_filter_with_topics() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + vec![].into(), ], }; let filter = build_filter( @@ -340,12 +345,12 @@ mod tests { fn test_build_filter_with_skipped_topic() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), ], }; let filter = build_filter( @@ -353,7 +358,7 @@ mod tests { None, None, Some(TRANSFER_TOPIC.to_string()), - vec!["".to_string(), TRANSFER_TOPIC.to_string()], + vec![String::new(), TRANSFER_TOPIC.to_string()], ) .unwrap(); @@ -371,9 +376,10 @@ mod tests { ) .err() .unwrap() - .to_string(); + .to_string() + .to_lowercase(); - assert_eq!(err, "Failed to parse `1234`, expected value of type: address"); + assert_eq!(err, "parser error:\n1234\n^\ninvalid string length"); } #[test] @@ -381,9 +387,10 @@ mod tests { let err = build_filter(None, None, None, Some("asdasdasd".to_string()), vec![]) .err() .unwrap() - .to_string(); + .to_string() + .to_lowercase(); - assert_eq!(err, "Invalid character 's' at position 1"); + assert_eq!(err, "odd number of digits"); } #[test] @@ -391,9 +398,10 @@ mod tests { let err = build_filter(None, None, None, Some(ADDRESS.to_string()), vec![]) .err() .unwrap() - .to_string(); + .to_string() + .to_lowercase(); - assert_eq!(err, "Invalid input length"); + assert_eq!(err, "invalid string length"); } #[test] @@ -407,8 +415,9 @@ mod tests { ) .err() .unwrap() - .to_string(); + .to_string() + .to_lowercase(); - assert_eq!(err, "Invalid input length"); + assert_eq!(err, "invalid string length"); } } diff --git a/crates/cast/bin/cmd/mktx.rs b/crates/cast/bin/cmd/mktx.rs new file mode 100644 index 0000000000000..085548d52d9fc --- /dev/null +++ b/crates/cast/bin/cmd/mktx.rs @@ -0,0 +1,111 @@ +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; +use foundry_cli::{ + opts::{EthereumOpts, TransactionOpts}, + utils::{get_provider, LoadConfig}, +}; +use foundry_common::ens::NameOrAddress; +use std::{path::PathBuf, str::FromStr}; + +/// CLI arguments for `cast mktx`. +#[derive(Debug, Parser)] +pub struct MakeTxArgs { + /// The destination of the transaction. + /// + /// If not provided, you must use `cast mktx --create`. + #[arg(value_parser = NameOrAddress::from_str)] + to: Option, + + /// The signature of the function to call. + sig: Option, + + /// The arguments of the function to call. + args: Vec, + + #[command(subcommand)] + command: Option, + + #[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, +} + +#[derive(Debug, Parser)] +pub enum MakeTxSubcommands { + /// Use to deploy raw contract bytecode. + #[command(name = "--create")] + Create { + /// The initialization bytecode of the contract to deploy. + code: String, + + /// The signature of the constructor. + sig: Option, + + /// The constructor arguments. + args: Vec, + }, +} + +impl MakeTxArgs { + pub async fn run(self) -> Result<()> { + 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, + sig: constructor_sig, + args: constructor_args, + }) = command + { + sig = constructor_sig; + args = constructor_args; + Some(code) + } else { + None + }; + + let config = eth.load_config()?; + + // Retrieve the signer, and bail if it can't be constructed. + let signer = eth.wallet.signer().await?; + let from = signer.address(); + + tx::validate_from_address(eth.wallet.from, from)?; + + let provider = get_provider(&config)?; + + let (tx, _) = CastTxBuilder::new(provider, tx, &config) + .await? + .with_to(to) + .await? + .with_code_sig_and_args(code, sig, args) + .await? + .with_blob_data(blob_data)? + .build(&signer) + .await?; + + let tx = tx.build(&EthereumWallet::new(signer)).await?; + + let signed_tx = hex::encode(tx.encoded_2718()); + sh_println!("0x{signed_tx}")?; + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index bd1c8ddf6a079..223d133f12905 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -1,4 +1,4 @@ -//! Subcommands for cast +//! `cast` subcommands. //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should @@ -6,13 +6,17 @@ //! [`foundry_config::Config`]. pub mod access_list; +pub mod artifact; pub mod bind; pub mod call; +pub mod constructor_args; pub mod create2; +pub mod creation_code; pub mod estimate; pub mod find_block; pub mod interface; pub mod logs; +pub mod mktx; pub mod rpc; pub mod run; pub mod send; diff --git a/crates/cast/bin/cmd/rpc.rs b/crates/cast/bin/cmd/rpc.rs index 78c0105de6c96..fa3facfd0879b 100644 --- a/crates/cast/bin/cmd/rpc.rs +++ b/crates/cast/bin/cmd/rpc.rs @@ -1,12 +1,11 @@ use cast::Cast; use clap::Parser; use eyre::Result; -use foundry_cli::{opts::RpcOpts, utils}; -use foundry_config::Config; +use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; use itertools::Itertools; /// CLI arguments for `cast rpc`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RpcArgs { /// RPC method name method: String, @@ -26,18 +25,18 @@ pub struct RpcArgs { /// /// cast rpc eth_getBlockByNumber '["0x123", false]' --raw /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } - #[clap(long, short = 'w')] + #[arg(long, short = 'w')] raw: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, } impl RpcArgs { pub async fn run(self) -> Result<()> { - let RpcArgs { raw, method, params, rpc } = self; + let Self { raw, method, params, rpc } = self; - let config = Config::from(&rpc); + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let params = if raw { @@ -53,7 +52,7 @@ impl RpcArgs { } else { serde_json::Value::Array(params.into_iter().map(value_or_string).collect()) }; - println!("{}", Cast::new(provider).rpc(&method, params).await?); + sh_println!("{}", Cast::new(provider).rpc(&method, params).await?)?; Ok(()) } } diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 902271adf76bf..4ffaa2b77f7d8 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -1,79 +1,97 @@ +use alloy_consensus::Transaction; +use alloy_network::TransactionResponse; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::BlockTransactions; +use cast::revm::primitives::EnvWithHandlerCfg; use clap::Parser; -use ethers::{prelude::Middleware, solc::EvmVersion, types::H160}; use eyre::{Result, WrapErr}; use foundry_cli::{ - init_progress, - opts::RpcOpts, - update_progress, utils, - utils::{handle_traces, TraceResult}, + opts::{EtherscanOpts, RpcOpts}, + utils::{handle_traces, init_progress, TraceResult}, +}; +use foundry_common::{is_known_system_sender, shell, SYSTEM_TRANSACTION_TYPE}; +use foundry_compilers::artifacts::EvmVersion; +use foundry_config::{ + figment::{ + self, + value::{Dict, Map}, + Figment, Metadata, Profile, + }, + Config, }; -use foundry_config::{find_project_root_path, Config}; use foundry_evm::{ - executor::{inspector::cheatcodes::util::configure_tx_env, opts::EvmOpts, EvmError}, - revm::primitives::U256 as rU256, - trace::TracingExecutor, - utils::h256_to_b256, + executors::{EvmError, TracingExecutor}, + opts::EvmOpts, + traces::{InternalTraceMode, TraceMode}, + utils::configure_tx_env, }; -use tracing::trace; - -const ARBITRUM_SENDER: H160 = H160([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0a, 0x4b, 0x05, -]); /// CLI arguments for `cast run`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RunArgs { /// The transaction hash. tx_hash: String, /// Opens the transaction in the debugger. - #[clap(long, short)] + #[arg(long, short)] debug: bool, + /// Whether to identify internal functions in traces. + #[arg(long)] + decode_internal: bool, + /// Print out opcode traces. - #[clap(long, short)] + #[arg(long, short)] trace_printer: bool, /// Executes the transaction only with the state from the previous block. /// /// May result in different results than the live execution! - #[clap(long, short)] + #[arg(long)] quick: bool, - /// Prints the full address of the contract. - #[clap(long, short)] - verbose: bool, - /// Label addresses in the trace. /// /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth - #[clap(long, short)] + #[arg(long, short)] label: Vec, - #[clap(flatten)] + #[command(flatten)] + etherscan: EtherscanOpts, + + #[command(flatten)] rpc: RpcOpts, - /// The evm version to use. + /// The EVM version to use. /// /// Overrides the version specified in the config. - #[clap(long, short)] + #[arg(long)] evm_version: Option, + /// Sets the number of assumed available compute units per second for this provider /// /// default value: 330 /// - /// See also, https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups - #[clap(long, alias = "cups", value_name = "CUPS")] + /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second + #[arg(long, alias = "cups", value_name = "CUPS")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. /// /// default value: false /// - /// See also, https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups - #[clap(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] + /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second + #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] pub no_rate_limit: bool, + + /// Enables Odyssey features. + #[arg(long, alias = "alphanet")] + pub odyssey: bool, + + /// Use current project artifacts for trace decoding. + #[arg(long, visible_alias = "la")] + pub with_local_artifacts: bool, } impl RunArgs { @@ -83,90 +101,146 @@ impl RunArgs { /// /// Note: This executes the transaction(s) as is: Cheatcodes are disabled pub async fn run(self) -> Result<()> { - let figment = - Config::figment_with_root(find_project_root_path(None).unwrap()).merge(self.rpc); + let figment = Into::::into(&self.rpc).merge(&self); let evm_opts = figment.extract::()?; - let mut config = Config::from_provider(figment).sanitized(); + let mut config = Config::from_provider(figment)?.sanitized(); let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; - let provider = utils::get_provider_builder(&config)? + let provider = foundry_cli::utils::get_provider_builder(&config)? .compute_units_per_second_opt(compute_units_per_second) .build()?; let tx_hash = self.tx_hash.parse().wrap_err("invalid tx hash")?; let tx = provider - .get_transaction(tx_hash) - .await? + .get_transaction_by_hash(tx_hash) + .await + .wrap_err_with(|| format!("tx not found: {tx_hash:?}"))? .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; - let tx_block_number = tx - .block_number - .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))? - .as_u64(); + // check if the tx is a system transaction + if is_known_system_sender(tx.from) || tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + { + return Err(eyre::eyre!( + "{:?} is a system transaction.\nReplaying system transactions is currently not supported.", + tx.tx_hash() + )); + } + + let tx_block_number = + tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; + // fetch the block the transaction was mined in + let block = provider.get_block(tx_block_number.into(), true.into()).await?; + + // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); - let (mut env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; + let create2_deployer = evm_opts.create2_deployer; + let (mut env, fork, chain, odyssey) = + TracingExecutor::get_fork_material(&config, evm_opts).await?; + + let mut evm_version = self.evm_version; - let mut executor = - TracingExecutor::new(env.clone(), fork, self.evm_version, self.debug).await; + env.block.number = U256::from(tx_block_number); - env.block.number = rU256::from(tx_block_number); + if let Some(block) = &block { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.beneficiary; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); - let block = provider.get_block_with_txs(tx_block_number).await?; - if let Some(ref block) = block { - env.block.timestamp = block.timestamp.into(); - env.block.coinbase = block.author.unwrap_or_default().into(); - env.block.difficulty = block.difficulty.into(); - env.block.prevrandao = block.mix_hash.map(h256_to_b256); - env.block.basefee = block.base_fee_per_gas.unwrap_or_default().into(); - env.block.gas_limit = block.gas_limit.into(); + // TODO: we need a smarter way to map the block to the corresponding evm_version for + // commonly used chains + if evm_version.is_none() { + // if the block has the excess_blob_gas field, we assume it's a Cancun block + if block.header.excess_blob_gas.is_some() { + evm_version = Some(EvmVersion::Cancun); + } + } } + let trace_mode = TraceMode::Call + .with_debug(self.debug) + .with_decode_internal(if self.decode_internal { + InternalTraceMode::Full + } else { + InternalTraceMode::None + }) + .with_state_changes(shell::verbosity() > 4); + let mut executor = TracingExecutor::new( + env.clone(), + fork, + evm_version, + trace_mode, + odyssey, + create2_deployer, + ); + let mut env = + EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); + // Set the state to the moment right before the transaction if !self.quick { - println!("Executing previous transactions from the block."); + if !shell::is_json() { + sh_println!("Executing previous transactions from the block.")?; + } if let Some(block) = block { - let pb = init_progress!(block.transactions, "tx"); + let pb = init_progress(block.transactions.len() as u64, "tx"); pb.set_position(0); - for (index, tx) in block.transactions.into_iter().enumerate() { - // arbitrum L1 transaction at the start of every block that has gas price 0 - // and gas limit 0 which causes reverts, so we skip it - if tx.from == ARBITRUM_SENDER { - update_progress!(pb, index); - continue + let BlockTransactions::Full(ref txs) = block.transactions else { + return Err(eyre::eyre!("Could not get block txs")) + }; + + for (index, tx) in txs.iter().enumerate() { + // System transactions such as on L2s don't contain any pricing info so + // we skip them otherwise this would cause + // reverts + if is_known_system_sender(tx.from) || + tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + { + pb.set_position((index + 1) as u64); + continue; } - if tx.hash().eq(&tx_hash) { - break + if tx.tx_hash() == tx_hash { + break; } - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env, &tx.inner); - if let Some(to) = tx.to { - trace!(tx=?tx.hash,?to, "executing previous call transaction"); - executor.commit_tx_with_env(env.clone()).wrap_err_with(|| { - format!("Failed to execute transaction: {:?}", tx.hash()) + if let Some(to) = Transaction::to(tx) { + trace!(tx=?tx.tx_hash(),?to, "executing previous call transaction"); + executor.transact_with_env(env.clone()).wrap_err_with(|| { + format!( + "Failed to execute transaction: {:?} in block {}", + tx.tx_hash(), + env.block.number + ) })?; } else { - trace!(tx=?tx.hash, "executing previous create transaction"); + trace!(tx=?tx.tx_hash(), "executing previous create transaction"); if let Err(error) = executor.deploy_with_env(env.clone(), None) { match error { // Reverted transactions should be skipped EvmError::Execution(_) => (), error => { return Err(error).wrap_err_with(|| { - format!("Failed to deploy transaction: {:?}", tx.hash()) + format!( + "Failed to deploy transaction: {:?} in block {}", + tx.tx_hash(), + env.block.number + ) }) } } } } - update_progress!(pb, index); + pb.set_position((index + 1) as u64); } } } @@ -175,22 +249,52 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env, &tx.inner); - if let Some(to) = tx.to { - trace!(tx=?tx.hash,to=?to, "executing call transaction"); - TraceResult::from(executor.commit_tx_with_env(env)?) + if let Some(to) = Transaction::to(&tx) { + trace!(tx=?tx.tx_hash(), to=?to, "executing call transaction"); + TraceResult::try_from(executor.transact_with_env(env))? } else { - trace!(tx=?tx.hash, "executing create transaction"); - match executor.deploy_with_env(env, None) { - Ok(res) => TraceResult::from(res), - Err(err) => TraceResult::try_from(err)?, - } + trace!(tx=?tx.tx_hash(), "executing create transaction"); + TraceResult::try_from(executor.deploy_with_env(env, None))? } }; - handle_traces(result, &config, chain, self.label, self.verbose, self.debug).await?; + handle_traces( + result, + &config, + chain, + self.label, + self.with_local_artifacts, + self.debug, + self.decode_internal, + ) + .await?; Ok(()) } } + +impl figment::Provider for RunArgs { + fn metadata(&self) -> Metadata { + Metadata::named("RunArgs") + } + + fn data(&self) -> Result, figment::Error> { + let mut map = Map::new(); + + if self.odyssey { + map.insert("odyssey".into(), self.odyssey.into()); + } + + if let Some(api_key) = &self.etherscan.key { + map.insert("etherscan_api_key".into(), api_key.as_str().into()); + } + + if let Some(evm_version) = self.evm_version { + map.insert("evm_version".into(), figment::value::Value::serialize(evm_version)?); + } + + Ok(Map::from([(Config::selected_profile(), map)])) + } +} diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 22e4f7d03c3a1..5f1df6704e019 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -1,16 +1,19 @@ -use cast::{Cast, TxBuilder}; +use crate::tx::{self, CastTxBuilder}; +use alloy_network::{AnyNetwork, EthereumWallet}; +use alloy_provider::{Provider, ProviderBuilder}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; +use cast::Cast; use clap::Parser; -use ethers::{ - prelude::MiddlewareBuilder, providers::Middleware, signers::Signer, types::NameOrAddress, -}; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, + utils::LoadConfig, }; -use foundry_common::cli_warn; -use foundry_config::{Chain, Config}; -use std::str::FromStr; +use foundry_common::ens::NameOrAddress; +use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast send`. #[derive(Debug, Parser)] @@ -18,7 +21,7 @@ pub struct SendTxArgs { /// The destination of the transaction. /// /// If not provided, you must use cast send --create. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -28,39 +31,45 @@ pub struct SendTxArgs { args: Vec, /// Only print the transaction hash and exit immediately. - #[clap(name = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] + #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] cast_async: bool, /// The number of confirmations until the receipt is fetched. - #[clap(long, default_value = "1")] - confirmations: usize, - - /// Print the transaction receipt as JSON. - #[clap(long, short, help_heading = "Display options")] - json: bool, - - /// Reuse the latest nonce for the sender account. - #[clap(long, conflicts_with = "nonce")] - resend: bool, + #[arg(long, default_value = "1")] + confirmations: u64, - #[clap(subcommand)] + #[command(subcommand)] command: Option, /// Send via `eth_sendTransaction using the `--from` argument or $ETH_FROM as sender - #[clap(long, requires = "from")] + #[arg(long, requires = "from")] unlocked: bool, - #[clap(flatten)] + /// Timeout for sending the transaction. + #[arg(long, env = "ETH_TIMEOUT")] + pub timeout: Option, + + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, + + /// 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, } #[derive(Debug, Parser)] pub enum SendTxSubcommands { /// Use to deploy raw contract bytecode. - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// The bytecode of the contract to deploy. code: String, @@ -74,25 +83,23 @@ pub enum SendTxSubcommands { } impl SendTxArgs { - pub async fn run(self) -> Result<()> { - let SendTxArgs { + #[allow(unknown_lints, dependency_on_unit_never_type_fallback)] + pub async fn run(self) -> eyre::Result<()> { + let Self { eth, to, - sig, + mut sig, cast_async, mut args, - mut tx, + tx, confirmations, - json: to_json, - resend, command, unlocked, + path, + timeout, } = self; - let config = Config::from(ð); - let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain_id, &provider).await?; - let api_key = config.get_etherscan_api_key(Some(chain)); - let mut sig = sig.unwrap_or_default(); + + let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; let code = if let Some(SendTxSubcommands::Create { code, @@ -100,29 +107,42 @@ impl SendTxArgs { args: constructor_args, }) = command { - sig = constructor_sig.unwrap_or_default(); + sig = constructor_sig; args = constructor_args; Some(code) } else { None }; + let config = eth.load_config()?; + let provider = utils::get_provider(&config)?; + + let builder = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_to(to) + .await? + .with_code_sig_and_args(code, sig, args) + .await? + .with_blob_data(blob_data)?; + + let timeout = timeout.unwrap_or(config.transaction_timeout); + // Case 1: // Default to sending via eth_sendTransaction if the --unlocked flag is passed. // This should be the only way this RPC method is used as it requires a local node // or remote RPC with unlocked accounts. if unlocked { // only check current chain id if it was specified in the config - if let Some(config_chain) = config.chain_id { - let current_chain_id = provider.get_chainid().await?.as_u64(); + if let Some(config_chain) = config.chain { + let current_chain_id = provider.get_chain_id().await?; let config_chain_id = config_chain.id(); // switch chain if current chain id is not the same as the one specified in the // config if config_chain_id != current_chain_id { - cli_warn!("Switching to chain {}", config_chain); + sh_warn!("Switching to chain {}", config_chain)?; provider - .request( - "wallet_switchEthereumChain", + .raw_request( + "wallet_switchEthereumChain".into(), [serde_json::json!({ "chainId": format!("0x{:x}", config_chain_id), })], @@ -131,123 +151,50 @@ impl SendTxArgs { } } - if resend { - tx.nonce = Some(provider.get_transaction_count(config.sender, None).await?); - } + let (tx, _) = builder.build(config.sender).await?; - cast_send( - provider, - config.sender, - to, - code, - (sig, args), - tx, - chain, - api_key, - cast_async, - confirmations, - to_json, - ) - .await + cast_send(provider, tx, cast_async, confirmations, timeout).await // Case 2: // An option to use a local signer was provided. - // If we cannot successfully instanciate a local signer, then we will assume we don't have + // If we cannot successfully instantiate a local signer, then we will assume we don't have // enough information to sign and we must bail. } else { // Retrieve the signer, and bail if it can't be constructed. - let signer = eth.wallet.signer(chain.id()).await?; + let signer = eth.wallet.signer().await?; let from = signer.address(); - // prevent misconfigured hwlib from sending a transaction that defies - // user-specified --from - if let Some(specified_from) = eth.wallet.from { - if specified_from != from { - eyre::bail!( - "\ -The specified sender via CLI/env vars does not match the sender configured via -the hardware wallet's HD Path. -Please use the `--hd-path ` parameter to specify the BIP32 Path which -corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." - ) - } - } + tx::validate_from_address(eth.wallet.from, from)?; - if resend { - tx.nonce = Some(provider.get_transaction_count(from, None).await?); - } + let (tx, _) = builder.build(&signer).await?; + + let wallet = EthereumWallet::from(signer); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .wallet(wallet) + .on_provider(&provider); - let provider = provider.with_signer(signer); - - cast_send( - provider, - from, - to, - code, - (sig, args), - tx, - chain, - api_key, - cast_async, - confirmations, - to_json, - ) - .await + cast_send(provider, tx, cast_async, confirmations, timeout).await } } } -#[allow(clippy::too_many_arguments)] -async fn cast_send, T: Into>( - provider: M, - from: F, - to: Option, - code: Option, - args: (String, Vec), - tx: TransactionOpts, - chain: Chain, - etherscan_api_key: Option, +async fn cast_send>( + provider: P, + tx: WithOtherFields, cast_async: bool, - confs: usize, - to_json: bool, -) -> Result<()> -where - M::Error: 'static, -{ - let (sig, params) = args; - let params = if !sig.is_empty() { Some((&sig[..], params)) } else { None }; - let mut builder = TxBuilder::new(&provider, from, to, chain, tx.legacy).await?; - builder - .etherscan_api_key(etherscan_api_key) - .gas(tx.gas_limit) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .value(tx.value) - .nonce(tx.nonce); - - if let Some(code) = code { - let mut data = hex::decode(code.strip_prefix("0x").unwrap_or(&code))?; - - if let Some((sig, args)) = params { - let (mut sigdata, _) = builder.create_args(sig, args).await?; - data.append(&mut sigdata); - } - - builder.set_data(data); - } else { - builder.args(params).await?; - }; - let builder_output = builder.build(); - + confs: u64, + timeout: u64, +) -> Result<()> { let cast = Cast::new(provider); + let pending_tx = cast.send(tx).await?; - let pending_tx = cast.send(builder_output).await?; - let tx_hash = *pending_tx; + let tx_hash = pending_tx.inner().tx_hash(); if cast_async { - println!("{tx_hash:#x}"); + sh_println!("{tx_hash:#x}")?; } else { - let receipt = cast.receipt(format!("{tx_hash:#x}"), None, confs, false, to_json).await?; - println!("{receipt}"); + let receipt = + cast.receipt(format!("{tx_hash:#x}"), None, confs, Some(timeout), false).await?; + sh_println!("{receipt}")?; } Ok(()) diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 3afc441360c32..c7a90ad2f06b5 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -1,27 +1,38 @@ -use crate::opts::parse_slot; +use crate::args::parse_slot; +use alloy_network::AnyNetwork; +use alloy_primitives::{Address, B256, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; use cast::Cast; use clap::Parser; -use comfy_table::{presets::ASCII_MARKDOWN, Table}; -use ethers::{ - abi::ethabi::ethereum_types::BigEndianHash, etherscan::Client, prelude::*, - solc::artifacts::StorageLayout, -}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; use eyre::Result; +use foundry_block_explorers::Client; use foundry_cli::{ - opts::{CoreBuildArgs, EtherscanOpts, RpcOpts}, + opts::{BuildOpts, EtherscanOpts, RpcOpts}, utils, + utils::LoadConfig, }; use foundry_common::{ abi::find_source, - compile::{compile, etherscan_project, suppress_compile}, - RetryProvider, + compile::{etherscan_project, ProjectCompiler}, + ens::NameOrAddress, + shell, +}; +use foundry_compilers::{ + artifacts::{ConfigurableContractArtifact, Contract, StorageLayout}, + compilers::{ + solc::{Solc, SolcCompiler}, + Compiler, + }, + Artifact, Project, }; use foundry_config::{ figment::{self, value::Dict, Metadata, Profile}, impl_figment_convert_cast, Config, }; -use futures::future::join_all; use semver::Version; +use serde::{Deserialize, Serialize}; use std::str::FromStr; /// The minimum Solc version for outputting storage layouts. @@ -30,30 +41,30 @@ use std::str::FromStr; const MIN_SOLC: Version = Version::new(0, 6, 5); /// CLI arguments for `cast storage`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct StorageArgs { /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, - /// The storage slot number. - #[clap(value_parser = parse_slot)] - slot: Option, + /// The storage slot number. If not provided, it gets the full storage layout. + #[arg(value_parser = parse_slot)] + slot: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short)] + #[arg(long, short)] block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, - #[clap(flatten)] - build: CoreBuildArgs, + #[command(flatten)] + build: BuildOpts, } impl_figment_convert_cast!(StorageArgs); @@ -74,26 +85,23 @@ impl figment::Provider for StorageArgs { impl StorageArgs { pub async fn run(self) -> Result<()> { - let config = Config::from(&self); + let config = self.load_config()?; let Self { address, slot, block, build, .. } = self; - let provider = utils::get_provider(&config)?; - let address = match address { - NameOrAddress::Name(name) => provider.resolve_name(&name).await?, - NameOrAddress::Address(address) => address, - }; + let address = address.resolve(&provider).await?; // Slot was provided, perform a simple RPC call if let Some(slot) = slot { let cast = Cast::new(provider); - println!("{}", cast.storage(address, slot, block).await?); - return Ok(()) + sh_println!("{}", cast.storage(address, slot, block).await?)?; + return Ok(()); } // No slot was provided // Get deployed bytecode at given address - let address_code = provider.get_code(address, block).await?; + let address_code = + provider.get_code_at(address).block_id(block.unwrap_or_default()).await?; if address_code.is_empty() { eyre::bail!("Provided address has no deployed code and thus no storage"); } @@ -103,26 +111,29 @@ impl StorageArgs { if project.paths.has_input_files() { // Find in artifacts and pretty print add_storage_layout_output(&mut project); - let out = compile(&project, false, false)?; - let match_code = |artifact: &ConfigurableContractArtifact| -> Option { - let bytes = - artifact.deployed_bytecode.as_ref()?.bytecode.as_ref()?.object.as_bytes()?; - Some(bytes == &address_code) - }; - let artifact = - out.artifacts().find(|(_, artifact)| match_code(artifact).unwrap_or_default()); + let out = ProjectCompiler::new().quiet(shell::is_json()).compile(&project)?; + let artifact = out.artifacts().find(|(_, artifact)| { + artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) + }); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage(provider, address, artifact, true).await + return fetch_and_print_storage( + provider, + address, + block, + artifact, + !shell::is_json(), + ) + .await; } } - // Not a forge project or artifact not found - // Get code from Etherscan - eprintln!("No matching artifacts found, fetching source code from Etherscan..."); + if !self.etherscan.has_key() { + eyre::bail!("You must provide an Etherscan API key if you're fetching a remote contract's storage."); + } - let chain = utils::get_chain(config.chain_id, &provider).await?; + let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let client = Client::new(chain.named()?, api_key)?; + let client = Client::new(chain, api_key)?; let source = find_source(client, address).await?; let metadata = source.items.first().unwrap(); if metadata.is_vyper() { @@ -138,10 +149,15 @@ impl StorageArgs { let root_path = root.path(); let mut project = etherscan_project(metadata, root_path)?; add_storage_layout_output(&mut project); - project.auto_detect = auto_detect; + + project.compiler = if auto_detect { + SolcCompiler::AutoDetect + } else { + SolcCompiler::Specific(Solc::find_or_install(&version)?) + }; // Compile - let mut out = suppress_compile(&project)?; + let mut out = ProjectCompiler::new().quiet(true).compile(&project)?; let artifact = { let (_, mut artifact) = out .artifacts() @@ -150,11 +166,10 @@ impl StorageArgs { if is_storage_layout_empty(&artifact.storage_layout) && auto_detect { // try recompiling with the minimum version - eprintln!("The requested contract was compiled with {version} while the minimum version for storage layouts is {MIN_SOLC} and as a result the output may be empty."); - let solc = Solc::find_or_install_svm_version(MIN_SOLC.to_string())?; - project.solc = solc; - project.auto_detect = false; - if let Ok(output) = suppress_compile(&project) { + sh_warn!("The requested contract was compiled with {version} while the minimum version for storage layouts is {MIN_SOLC} and as a result the output may be empty.")?; + let solc = Solc::find_or_install(&MIN_SOLC)?; + project.compiler = SolcCompiler::Specific(solc); + if let Ok(output) = ProjectCompiler::new().quiet(true).compile(&project) { out = output; let (_, new_artifact) = out .artifacts() @@ -170,79 +185,154 @@ impl StorageArgs { // Clear temp directory root.close()?; - fetch_and_print_storage(provider, address, artifact, true).await + fetch_and_print_storage(provider, address, block, artifact, !shell::is_json()).await + } +} + +/// Represents the value of a storage slot `eth_getStorageAt` call. +#[derive(Clone, Debug, PartialEq, Eq)] +struct StorageValue { + /// The slot number. + slot: B256, + /// The value as returned by `eth_getStorageAt`. + raw_slot_value: B256, +} + +impl StorageValue { + /// Returns the value of the storage slot, applying the offset if necessary. + fn value(&self, offset: i64, number_of_bytes: Option) -> B256 { + let offset = offset as usize; + let mut end = 32; + if let Some(number_of_bytes) = number_of_bytes { + end = offset + number_of_bytes; + if end > 32 { + end = 32; + } + } + + // reverse range, because the value is stored in big endian + let raw_sliced_value = &self.raw_slot_value.as_slice()[32 - end..32 - offset]; + + // copy the raw sliced value as tail + let mut value = [0u8; 32]; + value[32 - raw_sliced_value.len()..32].copy_from_slice(raw_sliced_value); + B256::from(value) } } -async fn fetch_and_print_storage( - provider: RetryProvider, +/// Represents the storage layout of a contract and its values. +#[derive(Clone, Debug, Serialize, Deserialize)] +struct StorageReport { + #[serde(flatten)] + layout: StorageLayout, + values: Vec, +} + +async fn fetch_and_print_storage>( + provider: P, address: Address, + block: Option, artifact: &ConfigurableContractArtifact, pretty: bool, ) -> Result<()> { if is_storage_layout_empty(&artifact.storage_layout) { - eprintln!("Storage layout is empty."); + sh_warn!("Storage layout is empty.")?; Ok(()) } else { let layout = artifact.storage_layout.as_ref().unwrap().clone(); - let values = fetch_storage_values(provider, address, &layout).await?; + let values = fetch_storage_slots(provider, address, block, &layout).await?; print_storage(layout, values, pretty) } } -/// Overrides the `value` field in [StorageLayout] with the slot's value to avoid creating new data -/// structures. -async fn fetch_storage_values( - provider: RetryProvider, +async fn fetch_storage_slots>( + provider: P, address: Address, + block: Option, layout: &StorageLayout, -) -> Result> { - // TODO: Batch request; handle array values - let futures: Vec<_> = layout - .storage - .iter() - .map(|slot| { - let slot_h256 = H256::from_uint(&U256::from_dec_str(&slot.slot)?); - Ok(provider.get_storage_at(address, slot_h256, None)) - }) - .collect::>()?; - - // TODO: Better format values according to their Solidity type - join_all(futures).await.into_iter().map(|value| Ok(format!("{}", value?.into_uint()))).collect() +) -> Result> { + let requests = layout.storage.iter().map(|storage_slot| async { + let slot = B256::from(U256::from_str(&storage_slot.slot)?); + let raw_slot_value = provider + .get_storage_at(address, slot.into()) + .block_id(block.unwrap_or_default()) + .await?; + + let value = StorageValue { slot, raw_slot_value: raw_slot_value.into() }; + + Ok(value) + }); + + futures::future::try_join_all(requests).await } -fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { +fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) -> Result<()> { if !pretty { - println!("{}", serde_json::to_string_pretty(&serde_json::to_value(layout)?)?); - return Ok(()) + let values: Vec<_> = layout + .storage + .iter() + .zip(&values) + .map(|(slot, storage_value)| { + let storage_type = layout.types.get(&slot.storage_type); + storage_value.value( + slot.offset, + storage_type.and_then(|t| t.number_of_bytes.parse::().ok()), + ) + }) + .collect(); + sh_println!( + "{}", + serde_json::to_string_pretty(&serde_json::to_value(StorageReport { layout, values })?)? + )?; + return Ok(()); } let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(vec!["Name", "Type", "Slot", "Offset", "Bytes", "Value", "Contract"]); - - for (slot, value) in layout.storage.into_iter().zip(values) { + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Name"), + Cell::new("Type"), + Cell::new("Slot"), + Cell::new("Offset"), + Cell::new("Bytes"), + Cell::new("Value"), + Cell::new("Hex Value"), + Cell::new("Contract"), + ]); + + for (slot, storage_value) in layout.storage.into_iter().zip(values) { let storage_type = layout.types.get(&slot.storage_type); - table.add_row(vec![ - slot.label, - storage_type.as_ref().map_or("?".to_string(), |t| t.label.clone()), - slot.slot, - slot.offset.to_string(), - storage_type.as_ref().map_or("?".to_string(), |t| t.number_of_bytes.clone()), - value, - slot.contract, + let value = storage_value + .value(slot.offset, storage_type.and_then(|t| t.number_of_bytes.parse::().ok())); + let converted_value = U256::from_be_bytes(value.0); + + table.add_row([ + slot.label.as_str(), + storage_type.map_or("?", |t| &t.label), + &slot.slot, + &slot.offset.to_string(), + storage_type.map_or("?", |t| &t.number_of_bytes), + &converted_value.to_string(), + &value.to_string(), + &slot.contract, ]); } - println!("{table}"); + sh_println!("\n{table}\n")?; Ok(()) } -fn add_storage_layout_output(project: &mut Project) { +fn add_storage_layout_output>(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; - let output_selection = project.artifacts.output_selection(); - project.solc_config.settings.push_all(output_selection); + project.update_output_selection(|selection| { + selection.0.values_mut().for_each(|contract_selection| { + contract_selection + .values_mut() + .for_each(|selection| selection.push("storageLayout".to_string())) + }); + }) } fn is_storage_layout_empty(storage_layout: &Option) -> bool { @@ -252,3 +342,23 @@ fn is_storage_layout_empty(storage_layout: &Option) -> bool { true } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_storage_etherscan_api_key() { + let args = + StorageArgs::parse_from(["foundry-cli", "addr.eth", "--etherscan-api-key", "dummykey"]); + assert_eq!(args.etherscan.key(), Some("dummykey".to_string())); + + std::env::set_var("ETHERSCAN_API_KEY", "FXY"); + let config = args.load_config().unwrap(); + std::env::remove_var("ETHERSCAN_API_KEY"); + assert_eq!(config.etherscan_api_key, Some("dummykey".to_string())); + + let key = config.get_etherscan_api_key(None).unwrap(); + assert_eq!(key, "dummykey".to_string()); + } +} diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs new file mode 100644 index 0000000000000..be29cb22bde98 --- /dev/null +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -0,0 +1,110 @@ +use clap::Parser; +use eyre::Result; + +use foundry_common::{fs, sh_err, sh_println}; +use foundry_config::Config; +use foundry_wallets::multi_wallet::MultiWalletOptsBuilder; + +/// CLI arguments for `cast wallet list`. +#[derive(Clone, Debug, Parser)] +pub struct ListArgs { + /// List all the accounts in the keystore directory. + /// Default keystore directory is used if no path provided. + #[arg(long, default_missing_value = "", num_args(0..=1))] + dir: Option, + + /// List accounts from a Ledger hardware wallet. + #[arg(long, short, group = "hw-wallets")] + ledger: bool, + + /// List accounts from a Trezor hardware wallet. + #[arg(long, short, group = "hw-wallets")] + trezor: bool, + + /// List accounts from AWS KMS. + #[arg(long, hide = !cfg!(feature = "aws-kms"))] + aws: bool, + + /// List all configured accounts. + #[arg(long, group = "hw-wallets")] + all: bool, + + /// Max number of addresses to display from hardware wallets. + #[arg(long, short, default_value = "3", requires = "hw-wallets")] + max_senders: Option, +} + +impl ListArgs { + pub async fn run(self) -> Result<()> { + // list local accounts as files in keystore dir, no need to unlock / provide password + if self.dir.is_some() || self.all || (!self.ledger && !self.trezor && !self.aws) { + let _ = self.list_local_senders(); + } + + // Create options for multi wallet - ledger, trezor and AWS + let list_opts = MultiWalletOptsBuilder::default() + .ledger(self.ledger || self.all) + .mnemonic_indexes(Some(vec![0])) + .trezor(self.trezor || self.all) + .aws(self.aws || self.all) + .interactives(0) + .build() + .expect("build multi wallet"); + + // macro to print senders for a list of signers + macro_rules! list_senders { + ($signers:expr, $label:literal) => { + match $signers.await { + Ok(signers) => { + for signer in signers.unwrap_or_default().iter() { + signer + .available_senders(self.max_senders.unwrap()) + .await? + .iter() + .for_each(|sender| { + let _ = sh_println!("{} ({})", sender, $label); + }) + } + } + Err(e) => { + if !self.all { + sh_err!("{}", e)?; + } + } + } + }; + } + + list_senders!(list_opts.ledgers(), "Ledger"); + list_senders!(list_opts.trezors(), "Trezor"); + list_senders!(list_opts.aws_signers(), "AWS"); + + Ok(()) + } + + fn list_local_senders(&self) -> Result<()> { + let keystore_path = self.dir.clone().unwrap_or_default(); + let keystore_dir = if keystore_path.is_empty() { + // Create the keystore default directory if it doesn't exist + let default_dir = Config::foundry_keystores_dir().unwrap(); + fs::create_dir_all(&default_dir)?; + default_dir + } else { + dunce::canonicalize(keystore_path)? + }; + + // List all files within the keystore directory. + for entry in std::fs::read_dir(keystore_dir)? { + let path = entry?.path(); + if path.is_file() { + if let Some(file_name) = path.file_name() { + if let Some(name) = file_name.to_str() { + sh_println!("{name} (Local)")?; + } + } + } + } + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index 1fdaa923e25ea..9d83c7e21a837 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -1,25 +1,35 @@ -use cast::SimpleCast; -use clap::Parser; -use ethers::{ - core::rand::thread_rng, - signers::{LocalWallet, Signer}, - types::{transaction::eip712::TypedData, Address, Signature}, +use alloy_chains::Chain; +use alloy_dyn_abi::TypedData; +use alloy_primitives::{hex, Address, PrimitiveSignature as Signature, B256, U256}; +use alloy_provider::Provider; +use alloy_signer::Signer; +use alloy_signer_local::{ + coins_bip39::{English, Entropy, Mnemonic}, + MnemonicBuilder, PrivateKeySigner, }; +use cast::revm::primitives::Authorization; +use clap::Parser; use eyre::{Context, Result}; -use foundry_cli::opts::{RawWallet, Wallet}; -use foundry_common::fs; +use foundry_cli::{opts::RpcOpts, utils, utils::LoadConfig}; +use foundry_common::{fs, sh_println, shell}; use foundry_config::Config; +use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; +use rand::thread_rng; +use serde_json::json; use std::path::Path; use yansi::Paint; pub mod vanity; use vanity::VanityArgs; +pub mod list; +use list::ListArgs; + /// CLI arguments for `cast wallet`. #[derive(Debug, Parser)] pub enum WalletSubcommands { /// Create a new random keypair. - #[clap(visible_alias = "n")] + #[command(visible_alias = "n")] New { /// If provided, then keypair will be written to an encrypted JSON keystore. path: Option, @@ -27,43 +37,61 @@ pub enum WalletSubcommands { /// Triggers a hidden password prompt for the JSON keystore. /// /// Deprecated: prompting for a hidden password is now the default. - #[clap(long, short, requires = "path", conflicts_with = "unsafe_password")] + #[arg(long, short, requires = "path", conflicts_with = "unsafe_password")] password: bool, /// Password for the JSON keystore in cleartext. /// /// This is UNSAFE to use and we recommend using the --password. - #[clap(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] + #[arg(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, + + /// Number of wallets to generate. + #[arg(long, short, default_value = "1")] + number: u32, + }, + + /// Generates a random BIP39 mnemonic phrase + #[command(visible_alias = "nm")] + NewMnemonic { + /// Number of words for the mnemonic + #[arg(long, short, default_value = "12")] + words: usize, + + /// Number of accounts to display + #[arg(long, short, default_value = "1")] + accounts: u8, + + /// Entropy to use for the mnemonic + #[arg(long, short, conflicts_with = "words")] + entropy: Option, }, /// Generate a vanity address. - #[clap(visible_alias = "va")] + #[command(visible_alias = "va")] Vanity(VanityArgs), /// Convert a private key to an address. - #[clap(visible_aliases = &["a", "addr"])] + #[command(visible_aliases = &["a", "addr"])] Address { /// If provided, the address will be derived from the specified private key. - #[clap( - value_name = "PRIVATE_KEY", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - )] + #[arg(value_name = "PRIVATE_KEY")] private_key_override: Option, - #[clap(flatten)] - wallet: Wallet, + #[command(flatten)] + wallet: WalletOpts, }, /// Sign a message or typed data. - #[clap(visible_alias = "s")] + #[command(visible_alias = "s")] Sign { - /// The message or typed data to sign. + /// The message, typed data, or hash to sign. + /// + /// Messages starting with 0x are expected to be hex encoded, which get decoded before + /// being signed. /// - /// Messages starting with 0x are expected to be hex encoded, - /// which get decoded before being signed. /// The message will be prefixed with the Ethereum Signed Message header and hashed before - /// signing. + /// signing, unless `--no-hash` is provided. /// /// Typed data can be provided as a json string or a file name. /// Use --data flag to denote the message is a string of typed data. @@ -72,21 +100,43 @@ pub enum WalletSubcommands { /// The data should be formatted as JSON. message: String, - /// If provided, the message will be treated as typed data. - #[clap(long)] + /// Treat the message as JSON typed data. + #[arg(long)] data: bool, - /// If provided, the message will be treated as a file name containing typed data. Requires - /// --data. - #[clap(long, requires = "data")] + /// Treat the message as a file containing JSON typed data. Requires `--data`. + #[arg(long, requires = "data")] from_file: bool, - #[clap(flatten)] - wallet: Wallet, + /// Treat the message as a raw 32-byte hash and sign it directly without hashing it again. + #[arg(long, conflicts_with = "data")] + no_hash: bool, + + #[command(flatten)] + wallet: WalletOpts, + }, + + /// EIP-7702 sign authorization. + #[command(visible_alias = "sa")] + SignAuth { + /// Address to sign authorization for. + address: Address, + + #[command(flatten)] + rpc: RpcOpts, + + #[arg(long)] + nonce: Option, + + #[arg(long)] + chain: Option, + + #[command(flatten)] + wallet: WalletOpts, }, /// Verify the signature of a message. - #[clap(visible_alias = "v")] + #[command(visible_alias = "v")] Verify { /// The original message. message: String, @@ -95,35 +145,81 @@ pub enum WalletSubcommands { signature: Signature, /// The address of the message signer. - #[clap(long, short)] + #[arg(long, short)] address: Address, }, + /// Import a private key into an encrypted keystore. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Import { /// The name for the account in the keystore. - #[clap(value_name = "ACCOUNT_NAME")] + #[arg(value_name = "ACCOUNT_NAME")] account_name: String, /// If provided, keystore will be saved here instead of the default keystores directory /// (~/.foundry/keystores) - #[clap(long, short)] + #[arg(long, short)] keystore_dir: Option, - #[clap(flatten)] - raw_wallet_options: RawWallet, + /// Password for the JSON keystore in cleartext + /// This is unsafe, we recommend using the default hidden password prompt + #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] + unsafe_password: Option, + #[command(flatten)] + raw_wallet_options: RawWalletOpts, }, + /// List all the accounts in the keystore default directory - #[clap(visible_alias = "ls")] - List, + #[command(visible_alias = "ls")] + List(ListArgs), + + /// Derives private key from mnemonic + #[command(name = "private-key", visible_alias = "pk", aliases = &["derive-private-key", "--derive-private-key"])] + PrivateKey { + /// If provided, the private key will be derived from the specified menomonic phrase. + #[arg(value_name = "MNEMONIC")] + mnemonic_override: Option, + + /// If provided, the private key will be derived using the + /// specified mnemonic index (if integer) or derivation path. + #[arg(value_name = "MNEMONIC_INDEX_OR_DERIVATION_PATH")] + mnemonic_index_or_derivation_path_override: Option, + + #[command(flatten)] + wallet: WalletOpts, + }, + + /// Decrypt a keystore file to get the private key + #[command(name = "decrypt-keystore", visible_alias = "dk")] + DecryptKeystore { + /// The name for the account in the keystore. + #[arg(value_name = "ACCOUNT_NAME")] + account_name: String, + /// If not provided, keystore will try to be located at the default keystores directory + /// (~/.foundry/keystores) + #[arg(long, short)] + keystore_dir: Option, + /// Password for the JSON keystore in cleartext + /// This is unsafe, we recommend using the default hidden password prompt + #[arg(long, env = "CAST_UNSAFE_PASSWORD", value_name = "PASSWORD")] + unsafe_password: Option, + }, } impl WalletSubcommands { pub async fn run(self) -> Result<()> { match self { - WalletSubcommands::New { path, unsafe_password, .. } => { + Self::New { path, unsafe_password, number, .. } => { let mut rng = thread_rng(); + let mut json_values = if shell::is_json() { Some(vec![]) } else { None }; if let Some(path) = path { - let path = dunce::canonicalize(path)?; + let path = match dunce::canonicalize(path.clone()) { + Ok(path) => path, + // If the path doesn't exist, it will fail to be canonicalized, + // so we attach more context to the error message. + Err(e) => { + eyre::bail!("If you specified a directory, please make sure it exists, or create it before running `cast wallet new `.\n{path} is not a directory.\nError: {}", e); + } + }; if !path.is_dir() { // we require path to be an existing directory eyre::bail!("`{}` is not a directory", path.display()); @@ -136,35 +232,125 @@ impl WalletSubcommands { rpassword::prompt_password("Enter secret: ")? }; - let (wallet, uuid) = - LocalWallet::new_keystore(&path, &mut rng, password, None)?; + for _ in 0..number { + let (wallet, uuid) = PrivateKeySigner::new_keystore( + &path, + &mut rng, + password.clone(), + None, + )?; + + if let Some(json) = json_values.as_mut() { + json.push(json!({ + "address": wallet.address().to_checksum(None), + "path": format!("{}", path.join(uuid).display()), + } + )); + } else { + sh_println!( + "Created new encrypted keystore file: {}", + path.join(uuid).display() + )?; + sh_println!("Address: {}", wallet.address().to_checksum(None))?; + } + } - println!("Created new encrypted keystore file: {}", path.join(uuid).display()); - println!("Address: {}", SimpleCast::to_checksum_address(&wallet.address())); + if let Some(json) = json_values.as_ref() { + sh_println!("{}", serde_json::to_string_pretty(json)?)?; + } } else { - let wallet = LocalWallet::new(&mut rng); - println!("Successfully created new keypair."); - println!("Address: {}", SimpleCast::to_checksum_address(&wallet.address())); - println!("Private key: 0x{}", hex::encode(wallet.signer().to_bytes())); + for _ in 0..number { + let wallet = PrivateKeySigner::random_with(&mut rng); + + if let Some(json) = json_values.as_mut() { + json.push(json!({ + "address": wallet.address().to_checksum(None), + "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), + })) + } else { + sh_println!("Successfully created new keypair.")?; + sh_println!("Address: {}", wallet.address().to_checksum(None))?; + sh_println!( + "Private key: 0x{}", + hex::encode(wallet.credential().to_bytes()) + )?; + } + } + + if let Some(json) = json_values.as_ref() { + sh_println!("{}", serde_json::to_string_pretty(json)?)?; + } } } - WalletSubcommands::Vanity(cmd) => { + Self::NewMnemonic { words, accounts, entropy } => { + let phrase = if let Some(entropy) = entropy { + let entropy = Entropy::from_slice(hex::decode(entropy)?)?; + Mnemonic::::new_from_entropy(entropy).to_phrase() + } else { + let mut rng = thread_rng(); + Mnemonic::::new_with_count(&mut rng, words)?.to_phrase() + }; + + let format_json = shell::is_json(); + + if !format_json { + sh_println!("{}", "Generating mnemonic from provided entropy...".yellow())?; + } + + let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); + let derivation_path = "m/44'/60'/0'/0/"; + let wallets = (0..accounts) + .map(|i| builder.clone().derivation_path(format!("{derivation_path}{i}"))) + .collect::, _>>()?; + let wallets = + wallets.into_iter().map(|b| b.build()).collect::, _>>()?; + + if !format_json { + sh_println!("{}", "Successfully generated a new mnemonic.".green())?; + sh_println!("Phrase:\n{phrase}")?; + sh_println!("\nAccounts:")?; + } + + let mut accounts = json!([]); + for (i, wallet) in wallets.iter().enumerate() { + let private_key = hex::encode(wallet.credential().to_bytes()); + if format_json { + accounts.as_array_mut().unwrap().push(json!({ + "address": format!("{}", wallet.address()), + "private_key": format!("0x{}", private_key), + })); + } else { + sh_println!("- Account {i}:")?; + sh_println!("Address: {}", wallet.address())?; + sh_println!("Private key: 0x{private_key}\n")?; + } + } + + if format_json { + let obj = json!({ + "mnemonic": phrase, + "accounts": accounts, + }); + sh_println!("{}", serde_json::to_string_pretty(&obj)?)?; + } + } + Self::Vanity(cmd) => { cmd.run()?; } - WalletSubcommands::Address { wallet, private_key_override } => { + Self::Address { wallet, private_key_override } => { let wallet = private_key_override - .map(|pk| Wallet { - raw: RawWallet { private_key: Some(pk), ..Default::default() }, + .map(|pk| WalletOpts { + raw: RawWalletOpts { private_key: Some(pk), ..Default::default() }, ..Default::default() }) .unwrap_or(wallet) - .signer(0) + .signer() .await?; let addr = wallet.address(); - println!("{}", SimpleCast::to_checksum_address(&addr)); + sh_println!("{}", addr.to_checksum(None))?; } - WalletSubcommands::Sign { message, data, from_file, wallet } => { - let wallet = wallet.signer(0).await?; + Self::Sign { message, data, from_file, no_hash, wallet } => { + let wallet = wallet.signer().await?; let sig = if data { let typed_data: TypedData = if from_file { // data is a file name, read json from file @@ -173,23 +359,41 @@ impl WalletSubcommands { // data is a json string serde_json::from_str(&message)? }; - wallet.sign_typed_data(&typed_data).await? + wallet.sign_dynamic_typed_data(&typed_data).await? + } else if no_hash { + wallet.sign_hash(&hex::decode(&message)?[..].try_into()?).await? } else { - wallet.sign_message(Self::hex_str_to_bytes(&message)?).await? + wallet.sign_message(&Self::hex_str_to_bytes(&message)?).await? }; - println!("0x{sig}"); + sh_println!("0x{}", hex::encode(sig.as_bytes()))?; } - WalletSubcommands::Verify { message, signature, address } => { - match signature.verify(Self::hex_str_to_bytes(&message)?, address) { - Ok(_) => { - println!("Validation succeeded. Address {address} signed this message.") - } - Err(_) => { - println!("Validation failed. Address {address} did not sign this message.") - } + Self::SignAuth { rpc, nonce, chain, wallet, address } => { + let wallet = wallet.signer().await?; + let provider = utils::get_provider(&rpc.load_config()?)?; + let nonce = if let Some(nonce) = nonce { + nonce + } else { + provider.get_transaction_count(wallet.address()).await? + }; + let chain_id = if let Some(chain) = chain { + chain.id() + } else { + provider.get_chain_id().await? + }; + let auth = Authorization { chain_id: U256::from(chain_id), address, nonce }; + let signature = wallet.sign_hash(&auth.signature_hash()).await?; + let auth = auth.into_signed(signature); + sh_println!("{}", hex::encode_prefixed(alloy_rlp::encode(&auth)))?; + } + Self::Verify { message, signature, address } => { + let recovered_address = Self::recover_address_from_message(&message, &signature)?; + if address == recovered_address { + sh_println!("Validation succeeded. Address {address} signed this message.")?; + } else { + eyre::bail!("Validation failed. Address {address} did not sign this message."); } } - WalletSubcommands::Import { account_name, keystore_dir, raw_wallet_options } => { + Self::Import { account_name, keystore_dir, unsafe_password, raw_wallet_options } => { // Set up keystore directory let dir = if let Some(path) = keystore_dir { Path::new(&path).to_path_buf() @@ -208,76 +412,131 @@ impl WalletSubcommands { } // get wallet - let wallet: Wallet = raw_wallet_options.into(); - let wallet = wallet.try_resolve_local_wallet()?.ok_or_else(|| { - eyre::eyre!( - "\ + let wallet = raw_wallet_options + .signer()? + .and_then(|s| match s { + WalletSigner::Local(s) => Some(s), + _ => None, + }) + .ok_or_else(|| { + eyre::eyre!( + "\ Did you set a private key or mnemonic? Run `cast wallet import --help` and use the corresponding CLI flag to set your key via: --private-key, --mnemonic-path or --interactive." - ) - })?; + ) + })?; - let private_key = wallet.signer().to_bytes(); - let password = rpassword::prompt_password("Enter password: ")?; + let private_key = wallet.credential().to_bytes(); + let password = if let Some(password) = unsafe_password { + password + } else { + // if no --unsafe-password was provided read via stdin + rpassword::prompt_password("Enter password: ")? + }; let mut rng = thread_rng(); - eth_keystore::encrypt_key( - &dir, + let (wallet, _) = PrivateKeySigner::encrypt_keystore( + dir, &mut rng, private_key, - &password, + password, Some(&account_name), )?; let address = wallet.address(); let success_message = format!( - "`{}` keystore was saved successfully. Address: {}", + "`{}` keystore was saved successfully. Address: {:?}", &account_name, address, ); - println!("{}", Paint::green(success_message)); + sh_println!("{}", success_message.green())?; } - WalletSubcommands::List => { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // Create the keystore directory if it doesn't exist - fs::create_dir_all(&default_keystore_dir)?; - // List all files in keystore directory - let keystore_files: Result, eyre::Report> = - std::fs::read_dir(&default_keystore_dir) - .wrap_err("Failed to read the directory")? - .filter_map(|entry| match entry { - Ok(entry) => { - let path = entry.path(); - if path.is_file() && path.extension().is_none() { - Some(Ok(path)) - } else { - None - } - } - Err(e) => Some(Err(e.into())), - }) - .collect::, eyre::Report>>(); - // Print the names of the keystore files - match keystore_files { - Ok(files) => { - // Print the names of the keystore files - for file in files { - if let Some(file_name) = file.file_name() { - if let Some(name) = file_name.to_str() { - println!("{}", name); - } - } + Self::List(cmd) => { + cmd.run().await?; + } + Self::PrivateKey { + wallet, + mnemonic_override, + mnemonic_index_or_derivation_path_override, + } => { + let (index_override, derivation_path_override) = + match mnemonic_index_or_derivation_path_override { + Some(value) => match value.parse::() { + Ok(index) => (Some(index), None), + Err(_) => (None, Some(value)), + }, + None => (None, None), + }; + let wallet = WalletOpts { + raw: RawWalletOpts { + mnemonic: mnemonic_override.or(wallet.raw.mnemonic), + mnemonic_index: index_override.unwrap_or(wallet.raw.mnemonic_index), + hd_path: derivation_path_override.or(wallet.raw.hd_path), + ..wallet.raw + }, + ..wallet + } + .signer() + .await?; + match wallet { + WalletSigner::Local(wallet) => { + if shell::verbosity() > 0 { + sh_println!("Address: {}", wallet.address())?; + sh_println!( + "Private key: 0x{}", + hex::encode(wallet.credential().to_bytes()) + )?; + } else { + sh_println!("0x{}", hex::encode(wallet.credential().to_bytes()))?; } } - Err(e) => return Err(e), + _ => { + eyre::bail!("Only local wallets are supported by this command."); + } } } + Self::DecryptKeystore { account_name, keystore_dir, unsafe_password } => { + // Set up keystore directory + let dir = if let Some(path) = keystore_dir { + Path::new(&path).to_path_buf() + } else { + Config::foundry_keystores_dir().ok_or_else(|| { + eyre::eyre!("Could not find the default keystore directory.") + })? + }; + + let keypath = dir.join(&account_name); + + if !keypath.exists() { + eyre::bail!("Keystore file does not exist at {}", keypath.display()); + } + + let password = if let Some(password) = unsafe_password { + password + } else { + // if no --unsafe-password was provided read via stdin + rpassword::prompt_password("Enter password: ")? + }; + + let wallet = PrivateKeySigner::decrypt_keystore(keypath, password)?; + + let private_key = B256::from_slice(&wallet.credential().to_bytes()); + + let success_message = + format!("{}'s private key is: {}", &account_name, private_key); + + sh_println!("{}", success_message.green())?; + } }; Ok(()) } + /// Recovers an address from the specified message and signature + fn recover_address_from_message(message: &str, signature: &Signature) -> Result
{ + Ok(signature.recover_address_from_msg(message)?) + } + fn hex_str_to_bytes(s: &str) -> Result> { Ok(match s.strip_prefix("0x") { Some(data) => hex::decode(data).wrap_err("Could not decode 0x-prefixed string.")?, @@ -289,6 +548,8 @@ flag to set your key via: #[cfg(test)] mod tests { use super::*; + use alloy_primitives::address; + use std::str::FromStr; #[test] fn can_parse_wallet_sign_message() { @@ -316,6 +577,17 @@ mod tests { } } + #[test] + fn can_verify_signed_hex_message() { + let message = "hello"; + let signature = Signature::from_str("f2dd00eac33840c04b6fc8a5ec8c4a47eff63575c2bc7312ecb269383de0c668045309c423484c8d097df306e690c653f8e1ec92f7f6f45d1f517027771c3e801c").unwrap(); + let address = address!("28A4F420a619974a2393365BCe5a7b560078Cc13"); + let recovered_address = + WalletSubcommands::recover_address_from_message(message, &signature); + assert!(recovered_address.is_ok()); + assert_eq!(address, recovered_address.unwrap()); + } + #[test] fn can_parse_wallet_sign_data() { let args = WalletSubcommands::parse_from(["foundry-cli", "sign", "--data", "{ ... }"]); diff --git a/crates/cast/bin/cmd/wallet/vanity.rs b/crates/cast/bin/cmd/wallet/vanity.rs index 9252c9b9c8959..2137feb42b31d 100644 --- a/crates/cast/bin/cmd/wallet/vanity.rs +++ b/crates/cast/bin/cmd/wallet/vanity.rs @@ -1,64 +1,92 @@ -use cast::SimpleCast; -use clap::{builder::TypedValueParser, Parser}; -use ethers::{ - core::{k256::ecdsa::SigningKey, rand::thread_rng}, - prelude::{LocalWallet, Signer}, - types::{H160, U256}, - utils::{get_contract_address, secret_key_to_address}, -}; +use alloy_primitives::{hex, Address}; +use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address}; +use alloy_signer_local::PrivateKeySigner; +use clap::Parser; use eyre::Result; - +use foundry_common::sh_println; +use itertools::Either; use rayon::iter::{self, ParallelIterator}; use regex::Regex; -use std::time::Instant; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + path::{Path, PathBuf}, + time::Instant, +}; /// Type alias for the result of [generate_wallet]. -pub type GeneratedWallet = (SigningKey, H160); +pub type GeneratedWallet = (SigningKey, Address); /// CLI arguments for `cast wallet vanity`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct VanityArgs { - /// Prefix for the vanity address. - #[clap( - 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. - #[clap(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). /// Generate a vanity contract address created by the generated keypair with the specified /// nonce. - #[clap(long)] + #[arg(long)] pub nonce: Option, + + /// Path to save the generated vanity contract address to. + /// + /// If provided, the generated vanity addresses will appended to a JSON array in the specified + /// file. + #[arg( + long, + value_hint = clap::ValueHint::FilePath, + value_name = "PATH", + )] + pub save_path: Option, +} + +/// WalletData contains address and private_key information for a wallet. +#[derive(Serialize, Deserialize)] +struct WalletData { + address: String, + private_key: String, +} + +/// Wallets is a collection of WalletData. +#[derive(Default, Serialize, Deserialize)] +struct Wallets { + wallets: Vec, +} + +impl WalletData { + pub fn new(wallet: &PrivateKeySigner) -> Self { + Self { + address: wallet.address().to_checksum(None), + private_key: format!("0x{}", hex::encode(wallet.credential().to_bytes())), + } + } } impl VanityArgs { - pub fn run(self) -> Result { - let Self { starts_with, ends_with, nonce } = self; + 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.as_bytes()) { - 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.as_bytes()) { - 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), } } @@ -72,7 +100,7 @@ impl VanityArgs { }; } - println!("Starting to generate vanity address..."); + sh_println!("Starting to generate vanity address...")?; let timer = Instant::now(); let wallet = match (left_exact_hex, left_regex, right_exact_hex, right_regex) { @@ -112,28 +140,47 @@ impl VanityArgs { } .expect("failed to generate vanity wallet"); - println!( - "Successfully found vanity address in {} seconds.{}{}\nAddress: {}\nPrivate Key: 0x{}", - timer.elapsed().as_secs(), + // If a save path is provided, save the generated vanity wallet to the specified path. + if let Some(save_path) = save_path { + save_wallet_to_file(&wallet, &save_path)?; + } + + sh_println!( + "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() { - SimpleCast::to_checksum_address(&get_contract_address( - wallet.address(), - nonce.unwrap(), - )) + wallet.address().create(nonce.unwrap()).to_checksum(None) } else { - "".to_string() + String::new() }, - SimpleCast::to_checksum_address(&wallet.address()), - hex::encode(wallet.signer().to_bytes()), - ); + wallet.address().to_checksum(None), + hex::encode(wallet.credential().to_bytes()), + )?; Ok(wallet) } } +/// Saves the specified `wallet` to a 'vanity_addresses.json' file at the given `save_path`. +/// If the file exists, the wallet data is appended to the existing content; +/// otherwise, a new file is created. +fn save_wallet_to_file(wallet: &PrivateKeySigner, path: &Path) -> Result<()> { + let mut wallets = if path.exists() { + let data = fs::read_to_string(path)?; + serde_json::from_str::(&data).unwrap_or_default() + } else { + Wallets::default() + }; + + wallets.wallets.push(WalletData::new(wallet)); + + fs::write(path, serde_json::to_string_pretty(&wallets)?)?; + Ok(()) +} + /// Generates random wallets until `matcher` matches the wallet address, returning the wallet. -pub fn find_vanity_address(matcher: T) -> Option { +pub fn find_vanity_address(matcher: T) -> Option { wallet_generator().find_any(create_matcher(matcher)).map(|(key, _)| key.into()) } @@ -142,8 +189,7 @@ pub fn find_vanity_address(matcher: T) -> Option pub fn find_vanity_address_with_nonce( matcher: T, nonce: u64, -) -> Option { - let nonce: U256 = nonce.into(); +) -> Option { wallet_generator().find_any(create_nonce_matcher(matcher, nonce)).map(|(key, _)| key.into()) } @@ -159,30 +205,30 @@ pub fn create_matcher(matcher: T) -> impl Fn(&GeneratedWallet) #[inline] pub fn create_nonce_matcher( matcher: T, - nonce: U256, + nonce: u64, ) -> impl Fn(&GeneratedWallet) -> bool { move |(_, addr)| { - let contract_addr = get_contract_address(*addr, nonce); + let contract_addr = addr.create(nonce); matcher.is_match(&contract_addr) } } /// Returns an infinite parallel iterator which yields a [GeneratedWallet]. #[inline] -pub fn wallet_generator() -> iter::Map, fn(()) -> GeneratedWallet> { - iter::repeat(()).map(|_| generate_wallet()) +pub fn wallet_generator() -> iter::Map, impl Fn(()) -> GeneratedWallet> { + iter::repeat(()).map(|()| generate_wallet()) } /// Generates a random K-256 signing key and derives its Ethereum address. pub fn generate_wallet() -> GeneratedWallet { - let key = SigningKey::random(&mut thread_rng()); + let key = SigningKey::random(&mut rand::thread_rng()); let address = secret_key_to_address(&key); (key, address) } /// A trait to match vanity addresses. pub trait VanityMatcher: Send + Sync { - fn is_match(&self, addr: &H160) -> bool; + fn is_match(&self, addr: &Address) -> bool; } /// Matches start and end hex. @@ -193,8 +239,8 @@ pub struct HexMatcher { impl VanityMatcher for HexMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let bytes = addr.as_bytes(); + fn is_match(&self, addr: &Address) -> bool { + let bytes = addr.as_slice(); bytes.starts_with(&self.left) && bytes.ends_with(&self.right) } } @@ -206,8 +252,8 @@ pub struct LeftHexMatcher { impl VanityMatcher for LeftHexMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let bytes = addr.as_bytes(); + fn is_match(&self, addr: &Address) -> bool { + let bytes = addr.as_slice(); bytes.starts_with(&self.left) } } @@ -219,8 +265,8 @@ pub struct RightHexMatcher { impl VanityMatcher for RightHexMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let bytes = addr.as_bytes(); + fn is_match(&self, addr: &Address) -> bool { + let bytes = addr.as_slice(); bytes.ends_with(&self.right) } } @@ -233,8 +279,8 @@ pub struct LeftExactRightRegexMatcher { impl VanityMatcher for LeftExactRightRegexMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let bytes = addr.as_bytes(); + fn is_match(&self, addr: &Address) -> bool { + let bytes = addr.as_slice(); bytes.starts_with(&self.left) && self.right.is_match(&hex::encode(bytes)) } } @@ -247,8 +293,8 @@ pub struct LeftRegexRightExactMatcher { impl VanityMatcher for LeftRegexRightExactMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let bytes = addr.as_bytes(); + fn is_match(&self, addr: &Address) -> bool { + let bytes = addr.as_slice(); bytes.ends_with(&self.right) && self.left.is_match(&hex::encode(bytes)) } } @@ -260,8 +306,8 @@ pub struct SingleRegexMatcher { impl VanityMatcher for SingleRegexMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let addr = hex::encode(addr.as_ref()); + fn is_match(&self, addr: &Address) -> bool { + let addr = hex::encode(addr); self.re.is_match(&addr) } } @@ -274,35 +320,21 @@ pub struct RegexMatcher { impl VanityMatcher for RegexMatcher { #[inline] - fn is_match(&self, addr: &H160) -> bool { - let addr = hex::encode(addr.as_ref()); + fn is_match(&self, addr: &Address) -> bool { + let addr = hex::encode(addr); self.left.is_match(&addr) && self.right.is_match(&addr) } } -/// Parse 40 byte addresses -#[derive(Copy, Clone, 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}"))?)) } } @@ -336,4 +368,21 @@ mod tests { let addr = format!("{addr:x}"); assert!(addr.ends_with("00")); } + + #[test] + fn save_path() { + let tmp = tempfile::NamedTempFile::new().unwrap(); + let args: VanityArgs = VanityArgs::parse_from([ + "foundry-cli", + "--starts-with", + "00", + "--save-path", + tmp.path().to_str().unwrap(), + ]); + args.run().unwrap(); + assert!(tmp.path().exists()); + let s = fs::read_to_string(tmp.path()).unwrap(); + let wallets: Wallets = serde_json::from_str(&s).unwrap(); + assert!(!wallets.wallets.is_empty()); + } } diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index a431b9213b9f7..7febaae97fb5b 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -1,364 +1,533 @@ +#[macro_use] +extern crate tracing; + +use alloy_dyn_abi::{DynSolValue, ErrorExt, EventExt}; +use alloy_primitives::{eip191_hash_message, hex, keccak256, Address, B256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; use cast::{Cast, SimpleCast}; use clap::{CommandFactory, Parser}; use clap_complete::generate; -use ethers::{ - core::types::{BlockId, BlockNumber::Latest, H256}, - providers::Middleware, - types::Address, - utils::keccak256, -}; use eyre::Result; -use foundry_cli::{handler, prompt, stdin, utils}; +use foundry_cli::{handler, utils, utils::LoadConfig}; use foundry_common::{ - abi::{format_tokens, get_event}, + abi::{get_error, get_event}, + ens::{namehash, ProviderEnsExt}, + fmt::{format_tokens, format_tokens_raw, format_uint_exp}, fs, selectors::{ - decode_calldata, decode_event_topic, decode_function_selector, import_selectors, - parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData, + decode_calldata, decode_event_topic, decode_function_selector, decode_selectors, + import_selectors, parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData, + SelectorType, }, + shell, stdin, }; use foundry_config::Config; use std::time::Instant; +pub mod args; pub mod cmd; -pub mod opts; +pub mod tx; -use opts::{Opts, Subcommands, ToBaseArgs}; +use args::{Cast as CastArgs, CastSubcommand, ToBaseArgs}; +use cast::traces::identifier::SignaturesIdentifier; -#[tokio::main] -async fn main() -> Result<()> { +#[macro_use] +extern crate foundry_common; + +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +fn main() { + if let Err(err) = run() { + let _ = foundry_common::sh_err!("{err:?}"); + std::process::exit(1); + } +} + +fn run() -> Result<()> { + handler::install(); utils::load_dotenv(); - handler::install()?; utils::subscriber(); utils::enable_paint(); - let opts = Opts::parse(); - match opts.sub { + let args = CastArgs::parse(); + args.global.init()?; + main_args(args) +} + +#[tokio::main] +async fn main_args(args: CastArgs) -> Result<()> { + match args.cmd { // Constants - Subcommands::MaxInt { r#type } => { - println!("{}", SimpleCast::max_int(&r#type)?); + CastSubcommand::MaxInt { r#type } => { + sh_println!("{}", SimpleCast::max_int(&r#type)?)?; } - Subcommands::MinInt { r#type } => { - println!("{}", SimpleCast::min_int(&r#type)?); + CastSubcommand::MinInt { r#type } => { + sh_println!("{}", SimpleCast::min_int(&r#type)?)?; } - Subcommands::MaxUint { r#type } => { - println!("{}", SimpleCast::max_int(&r#type)?); + CastSubcommand::MaxUint { r#type } => { + sh_println!("{}", SimpleCast::max_int(&r#type)?)?; } - Subcommands::AddressZero => { - println!("{:?}", Address::zero()); + CastSubcommand::AddressZero => { + sh_println!("{:?}", Address::ZERO)?; } - Subcommands::HashZero => { - println!("{:?}", H256::zero()); + CastSubcommand::HashZero => { + sh_println!("{:?}", B256::ZERO)?; } // Conversions & transformations - Subcommands::FromUtf8 { text } => { + CastSubcommand::FromUtf8 { text } => { let value = stdin::unwrap(text, false)?; - println!("{}", SimpleCast::from_utf8(&value)); + sh_println!("{}", SimpleCast::from_utf8(&value))? + } + CastSubcommand::ToAscii { hexdata } => { + let value = stdin::unwrap(hexdata, false)?; + sh_println!("{}", SimpleCast::to_ascii(value.trim())?)? } - Subcommands::ToAscii { hexdata } => { + CastSubcommand::ToUtf8 { hexdata } => { let value = stdin::unwrap(hexdata, false)?; - println!("{}", SimpleCast::to_ascii(&value)?); + sh_println!("{}", SimpleCast::to_utf8(&value)?)? } - Subcommands::FromFixedPoint { value, decimals } => { + CastSubcommand::FromFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; - println!("{}", SimpleCast::from_fixed_point(&value, &decimals)?); + sh_println!("{}", SimpleCast::from_fixed_point(&value, &decimals)?)? } - Subcommands::ToFixedPoint { value, decimals } => { + CastSubcommand::ToFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; - println!("{}", SimpleCast::to_fixed_point(&value, &decimals)?); + sh_println!("{}", SimpleCast::to_fixed_point(&value, &decimals)?)? } - Subcommands::ConcatHex { data } => { + CastSubcommand::ConcatHex { data } => { if data.is_empty() { let s = stdin::read(true)?; - println!("{}", SimpleCast::concat_hex(s.split_whitespace())) + sh_println!("{}", SimpleCast::concat_hex(s.split_whitespace()))? } else { - println!("{}", SimpleCast::concat_hex(data)) + sh_println!("{}", SimpleCast::concat_hex(data))? } } - Subcommands::FromBin => { + CastSubcommand::FromBin => { let hex = stdin::read_bytes(false)?; - println!("0x{}", hex::encode(hex)); + sh_println!("{}", hex::encode_prefixed(hex))? } - Subcommands::ToHexdata { input } => { + CastSubcommand::ToHexdata { input } => { let value = stdin::unwrap_line(input)?; let output = match value { s if s.starts_with('@') => hex::encode(std::env::var(&s[1..])?), s if s.starts_with('/') => hex::encode(fs::read(s)?), s => s.split(':').map(|s| s.trim_start_matches("0x").to_lowercase()).collect(), }; - println!("0x{output}"); + sh_println!("0x{output}")? } - Subcommands::ToCheckSumAddress { address } => { + CastSubcommand::ToCheckSumAddress { address } => { let value = stdin::unwrap_line(address)?; - println!("{}", SimpleCast::to_checksum_address(&value)); + sh_println!("{}", value.to_checksum(None))? + } + CastSubcommand::ToUint256 { value } => { + let value = stdin::unwrap_line(value)?; + sh_println!("{}", SimpleCast::to_uint256(&value)?)? + } + CastSubcommand::ToInt256 { value } => { + let value = stdin::unwrap_line(value)?; + sh_println!("{}", SimpleCast::to_int256(&value)?)? } - Subcommands::ToUint256 { value } => { + CastSubcommand::ToUnit { value, unit } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_uint256(&value)?); + sh_println!("{}", SimpleCast::to_unit(&value, &unit)?)? } - Subcommands::ToInt256 { value } => { + CastSubcommand::ParseUnits { value, unit } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_int256(&value)?); + sh_println!("{}", SimpleCast::parse_units(&value, unit)?)?; } - Subcommands::ToUnit { value, unit } => { + CastSubcommand::FormatUnits { value, unit } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_unit(&value, &unit)?); + sh_println!("{}", SimpleCast::format_units(&value, unit)?)?; } - Subcommands::FromWei { value, unit } => { + CastSubcommand::FromWei { value, unit } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::from_wei(&value, &unit)?); + sh_println!("{}", SimpleCast::from_wei(&value, &unit)?)? } - Subcommands::ToWei { value, unit } => { + CastSubcommand::ToWei { value, unit } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_wei(&value, &unit)?); + sh_println!("{}", SimpleCast::to_wei(&value, &unit)?)? } - Subcommands::FromRlp { value } => { + CastSubcommand::FromRlp { value, as_int } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::from_rlp(value)?); + sh_println!("{}", SimpleCast::from_rlp(value, as_int)?)? } - Subcommands::ToRlp { value } => { + CastSubcommand::ToRlp { value } => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_rlp(&value)?); + sh_println!("{}", SimpleCast::to_rlp(&value)?)? } - Subcommands::ToHex(ToBaseArgs { value, base_in }) => { + CastSubcommand::ToHex(ToBaseArgs { value, base_in }) => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_base(&value, base_in, "hex")?); + sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "hex")?)? } - Subcommands::ToDec(ToBaseArgs { value, base_in }) => { + CastSubcommand::ToDec(ToBaseArgs { value, base_in }) => { let value = stdin::unwrap_line(value)?; - println!("{}", SimpleCast::to_base(&value, base_in, "dec")?); + sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), "dec")?)? } - Subcommands::ToBase { base: ToBaseArgs { value, base_in }, base_out } => { + CastSubcommand::ToBase { base: ToBaseArgs { value, base_in }, base_out } => { let (value, base_out) = stdin::unwrap2(value, base_out)?; - println!("{}", SimpleCast::to_base(&value, base_in, &base_out)?); + sh_println!("{}", SimpleCast::to_base(&value, base_in.as_deref(), &base_out)?)? } - Subcommands::ToBytes32 { bytes } => { + CastSubcommand::ToBytes32 { bytes } => { let value = stdin::unwrap_line(bytes)?; - println!("{}", SimpleCast::to_bytes32(&value)?); + sh_println!("{}", SimpleCast::to_bytes32(&value)?)? } - Subcommands::FormatBytes32String { string } => { + CastSubcommand::FormatBytes32String { string } => { let value = stdin::unwrap_line(string)?; - println!("{}", SimpleCast::format_bytes32_string(&value)?); + sh_println!("{}", SimpleCast::format_bytes32_string(&value)?)? } - Subcommands::ParseBytes32String { bytes } => { + CastSubcommand::ParseBytes32String { bytes } => { let value = stdin::unwrap_line(bytes)?; - println!("{}", SimpleCast::parse_bytes32_string(&value)?); + sh_println!("{}", SimpleCast::parse_bytes32_string(&value)?)? } - Subcommands::ParseBytes32Address { bytes } => { + CastSubcommand::ParseBytes32Address { bytes } => { let value = stdin::unwrap_line(bytes)?; - println!("{}", SimpleCast::parse_bytes32_address(&value)?); + sh_println!("{}", SimpleCast::parse_bytes32_address(&value)?)? } // ABI encoding & decoding - Subcommands::AbiDecode { sig, calldata, input } => { + CastSubcommand::DecodeAbi { sig, calldata, input } => { let tokens = SimpleCast::abi_decode(&sig, &calldata, input)?; - let tokens = format_tokens(&tokens); - tokens.for_each(|t| println!("{t}")); + print_tokens(&tokens); } - Subcommands::AbiEncode { sig, args } => { - println!("{}", SimpleCast::abi_encode(&sig, &args)?); + CastSubcommand::AbiEncode { sig, packed, args } => { + if !packed { + sh_println!("{}", SimpleCast::abi_encode(&sig, &args)?)? + } else { + sh_println!("{}", SimpleCast::abi_encode_packed(&sig, &args)?)? + } } - Subcommands::CalldataDecode { sig, calldata } => { + CastSubcommand::DecodeCalldata { sig, calldata } => { let tokens = SimpleCast::calldata_decode(&sig, &calldata, true)?; - let tokens = format_tokens(&tokens); - tokens.for_each(|t| println!("{t}")); + print_tokens(&tokens); + } + CastSubcommand::CalldataEncode { sig, args } => { + sh_println!("{}", SimpleCast::calldata_encode(sig, &args)?)?; } - Subcommands::CalldataEncode { sig, args } => { - println!("{}", SimpleCast::calldata_encode(sig, &args)?); + CastSubcommand::DecodeString { data } => { + let tokens = SimpleCast::calldata_decode("Any(string)", &data, true)?; + print_tokens(&tokens); + } + CastSubcommand::DecodeEvent { sig, data } => { + let decoded_event = if let Some(event_sig) = sig { + get_event(event_sig.as_str())?.decode_log_parts(None, &hex::decode(data)?, false)? + } else { + let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let selector = data.get(..64).unwrap_or_default(); + let identified_event = + SignaturesIdentifier::new(Config::foundry_cache_dir(), false)? + .write() + .await + .identify_event(&hex::decode(selector)?) + .await; + if let Some(event) = identified_event { + let _ = sh_println!("{}", event.signature()); + let data = data.get(64..).unwrap_or_default(); + get_event(event.signature().as_str())?.decode_log_parts( + None, + &hex::decode(data)?, + false, + )? + } else { + eyre::bail!("No matching event signature found for selector `{selector}`") + } + }; + print_tokens(&decoded_event.body); } - Subcommands::Interface(cmd) => cmd.run().await?, - Subcommands::Bind(cmd) => cmd.run().await?, - Subcommands::PrettyCalldata { calldata, offline } => { + CastSubcommand::DecodeError { sig, data } => { + let error = if let Some(err_sig) = sig { + get_error(err_sig.as_str())? + } else { + let data = data.strip_prefix("0x").unwrap_or(data.as_str()); + let selector = data.get(..8).unwrap_or_default(); + let identified_error = + SignaturesIdentifier::new(Config::foundry_cache_dir(), false)? + .write() + .await + .identify_error(&hex::decode(selector)?) + .await; + if let Some(error) = identified_error { + let _ = sh_println!("{}", error.signature()); + error + } else { + eyre::bail!("No matching error signature found for selector `{selector}`") + } + }; + let decoded_error = error.decode_error(&hex::decode(data)?)?; + print_tokens(&decoded_error.body); + } + CastSubcommand::Interface(cmd) => cmd.run().await?, + CastSubcommand::CreationCode(cmd) => cmd.run().await?, + CastSubcommand::ConstructorArgs(cmd) => cmd.run().await?, + CastSubcommand::Artifact(cmd) => cmd.run().await?, + CastSubcommand::Bind(cmd) => cmd.run().await?, + CastSubcommand::PrettyCalldata { calldata, offline } => { let calldata = stdin::unwrap_line(calldata)?; - println!("{}", pretty_calldata(&calldata, offline).await?); + sh_println!("{}", pretty_calldata(&calldata, offline).await?)?; } - Subcommands::Sig { sig, optimize } => { + CastSubcommand::Sig { sig, optimize } => { let sig = stdin::unwrap_line(sig)?; - if optimize.is_none() { - println!("{}", SimpleCast::get_selector(&sig, None)?.0); - } else { - println!("Starting to optimize signature..."); - let start_time = Instant::now(); - let (selector, signature) = SimpleCast::get_selector(&sig, optimize)?; - let elapsed_time = start_time.elapsed(); - println!("Successfully generated in {} seconds", elapsed_time.as_secs()); - println!("Selector: {}", selector); - println!("Optimized signature: {}", signature); + match optimize { + Some(opt) => { + sh_println!("Starting to optimize signature...")?; + let start_time = Instant::now(); + let (selector, signature) = SimpleCast::get_selector(&sig, opt)?; + sh_println!("Successfully generated in {:?}", start_time.elapsed())?; + sh_println!("Selector: {selector}")?; + sh_println!("Optimized signature: {signature}")?; + } + None => sh_println!("{}", SimpleCast::get_selector(&sig, 0)?.0)?, } } // Blockchain & RPC queries - Subcommands::AccessList(cmd) => cmd.run().await?, - Subcommands::Age { block, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::AccessList(cmd) => cmd.run().await?, + CastSubcommand::Age { block, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!( - "{}", + sh_println!( + "{} UTC", Cast::new(provider).age(block.unwrap_or(BlockId::Number(Latest))).await? - ); + )? } - Subcommands::Balance { block, who, ether, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Balance { block, who, ether, rpc, erc20 } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let value = Cast::new(provider).balance(who, block).await?; - if ether { - println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?); - } else { - println!("{value}"); + let account_addr = who.resolve(&provider).await?; + + match erc20 { + Some(token) => { + let balance = + Cast::new(&provider).erc20_balance(token, account_addr, block).await?; + sh_println!("{}", format_uint_exp(balance))? + } + None => { + let value = Cast::new(&provider).balance(account_addr, block).await?; + if ether { + sh_println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?)? + } else { + sh_println!("{value}")? + } + } } } - Subcommands::BaseFee { block, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::BaseFee { block, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!( + sh_println!( "{}", Cast::new(provider).base_fee(block.unwrap_or(BlockId::Number(Latest))).await? - ); + )? } - Subcommands::Block { block, full, field, json, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Block { block, full, field, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!( + sh_println!( "{}", Cast::new(provider) - .block(block.unwrap_or(BlockId::Number(Latest)), full, field, json) + .block(block.unwrap_or(BlockId::Number(Latest)), full, field) .await? - ); + )? } - Subcommands::BlockNumber { rpc } => { - let config = Config::from(&rpc); + CastSubcommand::BlockNumber { rpc, block } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).block_number().await?); + let number = match block { + Some(id) => { + provider + .get_block(id, false.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {id:?} not found"))? + .header + .number + } + None => Cast::new(provider).block_number().await?, + }; + sh_println!("{number}")? } - Subcommands::Chain { rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Chain { rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).chain().await?); + sh_println!("{}", Cast::new(provider).chain().await?)? } - Subcommands::ChainId { rpc } => { - let config = Config::from(&rpc); + CastSubcommand::ChainId { rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).chain_id().await?); + sh_println!("{}", Cast::new(provider).chain_id().await?)? } - Subcommands::Client { rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Client { rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", provider.client_version().await?); + sh_println!("{}", provider.get_client_version().await?)? } - Subcommands::Code { block, who, disassemble, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Code { block, who, disassemble, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).code(who, block, disassemble).await?); + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).code(who, block, disassemble).await?)? } - Subcommands::Codesize { block, who, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Codesize { block, who, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).codesize(who, block).await?); + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).codesize(who, block).await?)? } - Subcommands::ComputeAddress { address, nonce, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::ComputeAddress { address, nonce, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let address: Address = stdin::unwrap_line(address)?.parse()?; - let computed = Cast::new(&provider).compute_address(address, nonce).await?; - println!("Computed Address: {}", SimpleCast::to_checksum_address(&computed)); - } - Subcommands::Disassemble { bytecode } => { - println!("{}", SimpleCast::disassemble(&bytecode)?); + let address = stdin::unwrap_line(address)?; + let computed = Cast::new(provider).compute_address(address, nonce).await?; + sh_println!("Computed Address: {}", computed.to_checksum(None))? + } + CastSubcommand::Disassemble { bytecode } => { + let bytecode = stdin::unwrap_line(bytecode)?; + sh_println!("{}", SimpleCast::disassemble(&hex::decode(bytecode)?)?)? + } + CastSubcommand::Selectors { bytecode, resolve } => { + let bytecode = stdin::unwrap_line(bytecode)?; + let functions = SimpleCast::extract_functions(&bytecode)?; + let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0); + let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0); + + let resolve_results = if resolve { + let selectors_it = functions.iter().map(|r| &r.0); + let ds = decode_selectors(SelectorType::Function, selectors_it).await?; + ds.into_iter().map(|v| v.unwrap_or_default().join("|")).collect() + } else { + vec![] + }; + for (pos, (selector, arguments, state_mutability)) in functions.into_iter().enumerate() + { + if resolve { + let resolved = &resolve_results[pos]; + sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}")? + } else { + sh_println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}")? + } + } } - Subcommands::FindBlock(cmd) => cmd.run().await?, - Subcommands::GasPrice { rpc } => { - let config = Config::from(&rpc); + CastSubcommand::FindBlock(cmd) => cmd.run().await?, + CastSubcommand::GasPrice { rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).gas_price().await?); + sh_println!("{}", Cast::new(provider).gas_price().await?)?; + } + CastSubcommand::Index { key_type, key, slot_number } => { + sh_println!("{}", SimpleCast::index(&key_type, &key, &slot_number)?)?; } - Subcommands::Index { key_type, key, slot_number } => { - println!("{}", SimpleCast::index(&key_type, &key, &slot_number)?); + CastSubcommand::IndexErc7201 { id, formula_id } => { + eyre::ensure!(formula_id == "erc7201", "unsupported formula ID: {formula_id}"); + let id = stdin::unwrap_line(id)?; + sh_println!("{}", foundry_common::erc7201(&id))?; } - Subcommands::Implementation { block, who, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Implementation { block, beacon, who, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).implementation(who, block).await?); + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).implementation(who, beacon, block).await?)?; } - Subcommands::Admin { block, who, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Admin { block, who, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).admin(who, block).await?); + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).admin(who, block).await?)?; } - Subcommands::Nonce { block, who, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Nonce { block, who, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).nonce(who, block).await?); + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).nonce(who, block).await?)?; } - Subcommands::Proof { address, slots, rpc, block } => { - let config = Config::from(&rpc); + CastSubcommand::Codehash { block, who, slots, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - let value = provider.get_proof(address, slots, block).await?; - println!("{}", serde_json::to_string(&value)?); + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).codehash(who, slots, block).await?)?; } - Subcommands::Rpc(cmd) => cmd.run().await?, - Subcommands::Storage(cmd) => cmd.run().await?, + CastSubcommand::StorageRoot { block, who, slots, rpc } => { + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; + sh_println!("{}", Cast::new(provider).storage_root(who, slots, block).await?)?; + } + CastSubcommand::Proof { address, slots, rpc, block } => { + let config = rpc.load_config()?; + let provider = utils::get_provider(&config)?; + let address = address.resolve(&provider).await?; + let value = provider + .get_proof(address, slots.into_iter().collect()) + .block_id(block.unwrap_or_default()) + .await?; + sh_println!("{}", serde_json::to_string(&value)?)?; + } + CastSubcommand::Rpc(cmd) => cmd.run().await?, + CastSubcommand::Storage(cmd) => cmd.run().await?, // Calls & transactions - Subcommands::Call(cmd) => cmd.run().await?, - Subcommands::Estimate(cmd) => cmd.run().await?, - Subcommands::PublishTx { raw_tx, cast_async, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Call(cmd) => cmd.run().await?, + CastSubcommand::Estimate(cmd) => cmd.run().await?, + CastSubcommand::MakeTx(cmd) => cmd.run().await?, + CastSubcommand::PublishTx { raw_tx, cast_async, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); let pending_tx = cast.publish(raw_tx).await?; - let tx_hash = *pending_tx; + let tx_hash = pending_tx.inner().tx_hash(); if cast_async { - println!("{tx_hash:#x}"); + sh_println!("{tx_hash:#x}")?; } else { - let receipt = - pending_tx.await?.ok_or_else(|| eyre::eyre!("tx {tx_hash} not found"))?; - println!("{}", serde_json::json!(receipt)); + let receipt = pending_tx.get_receipt().await?; + sh_println!("{}", serde_json::json!(receipt))?; } } - Subcommands::Receipt { tx_hash, field, json, cast_async, confirmations, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Receipt { tx_hash, field, cast_async, confirmations, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; - println!( + sh_println!( "{}", Cast::new(provider) - .receipt(tx_hash, field, confirmations, cast_async, json) + .receipt(tx_hash, field, confirmations, None, cast_async) .await? - ); + )? } - Subcommands::Run(cmd) => cmd.run().await?, - Subcommands::SendTx(cmd) => cmd.run().await?, - Subcommands::Tx { tx_hash, field, raw, json, rpc } => { - let config = Config::from(&rpc); + CastSubcommand::Run(cmd) => cmd.run().await?, + CastSubcommand::SendTx(cmd) => cmd.run().await?, + CastSubcommand::Tx { tx_hash, field, raw, rpc } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; // Can use either --raw or specify raw as a field let raw = raw || field.as_ref().is_some_and(|f| f == "raw"); - println!("{}", Cast::new(&provider).transaction(tx_hash, field, raw, json).await?) + sh_println!("{}", Cast::new(&provider).transaction(tx_hash, field, raw).await?)? } // 4Byte - Subcommands::FourByte { selector } => { + CastSubcommand::FourByte { selector } => { let selector = stdin::unwrap_line(selector)?; let sigs = decode_function_selector(&selector).await?; if sigs.is_empty() { eyre::bail!("No matching function signatures found for selector `{selector}`"); } for sig in sigs { - println!("{sig}"); + sh_println!("{sig}")? } } - Subcommands::FourByteDecode { calldata } => { + CastSubcommand::FourByteDecode { calldata } => { let calldata = stdin::unwrap_line(calldata)?; let sigs = decode_calldata(&calldata).await?; - sigs.iter().enumerate().for_each(|(i, sig)| println!("{}) \"{sig}\"", i + 1)); + sigs.iter().enumerate().for_each(|(i, sig)| { + let _ = sh_println!("{}) \"{sig}\"", i + 1); + }); let sig = match sigs.len() { 0 => eyre::bail!("No signatures found"), - 1 => sigs.get(0).unwrap(), + 1 => sigs.first().unwrap(), _ => { let i: usize = prompt!("Select a function signature by number: ")?; sigs.get(i - 1).ok_or_else(|| eyre::eyre!("Invalid signature index"))? @@ -366,21 +535,19 @@ async fn main() -> Result<()> { }; let tokens = SimpleCast::calldata_decode(sig, &calldata, true)?; - for token in format_tokens(&tokens) { - println!("{token}"); - } + print_tokens(&tokens); } - Subcommands::FourByteEvent { topic } => { + CastSubcommand::FourByteEvent { topic } => { let topic = stdin::unwrap_line(topic)?; let sigs = decode_event_topic(&topic).await?; if sigs.is_empty() { eyre::bail!("No matching event signatures found for topic `{topic}`"); } for sig in sigs { - println!("{sig}"); + sh_println!("{sig}")? } } - Subcommands::UploadSignature { signatures } => { + CastSubcommand::UploadSignature { signatures } => { let signatures = stdin::unwrap_vec(signatures)?; let ParsedSignatures { signatures, abis } = parse_signatures(signatures); if !abis.is_empty() { @@ -392,43 +559,43 @@ async fn main() -> Result<()> { } // ENS - Subcommands::Namehash { name } => { + CastSubcommand::Namehash { name } => { let name = stdin::unwrap_line(name)?; - println!("{}", SimpleCast::namehash(&name)?); + sh_println!("{}", namehash(&name))? } - Subcommands::LookupAddress { who, rpc, verify } => { - let config = Config::from(&rpc); + CastSubcommand::LookupAddress { who, rpc, verify } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; - let name = provider.lookup_address(who).await?; + let name = provider.lookup_address(&who).await?; if verify { let address = provider.resolve_name(&name).await?; eyre::ensure!( address == who, - "Forward lookup verification failed: got `{name:?}`, expected `{who:?}`" + "Reverse lookup verification failed: got `{address}`, expected `{who}`" ); } - println!("{name}"); + sh_println!("{name}")? } - Subcommands::ResolveName { who, rpc, verify } => { - let config = Config::from(&rpc); + CastSubcommand::ResolveName { who, rpc, verify } => { + let config = rpc.load_config()?; let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; let address = provider.resolve_name(&who).await?; if verify { - let name = provider.lookup_address(address).await?; - assert_eq!( - name, who, - "forward lookup verification failed. got {name}, expected {who}" + let name = provider.lookup_address(&address).await?; + eyre::ensure!( + name == who, + "Forward lookup verification failed: got `{name}`, expected `{who}`" ); } - println!("{}", SimpleCast::to_checksum_address(&address)); + sh_println!("{address}")? } // Misc - Subcommands::Keccak { data } => { + CastSubcommand::Keccak { data } => { let bytes = match data { Some(data) => data.into_bytes(), None => stdin::read_bytes(false)?, @@ -436,55 +603,120 @@ async fn main() -> Result<()> { match String::from_utf8(bytes) { Ok(s) => { let s = SimpleCast::keccak(&s)?; - println!("{s}"); + sh_println!("{s}")? } Err(e) => { let hash = keccak256(e.as_bytes()); let s = hex::encode(hash); - println!("0x{s}"); + sh_println!("0x{s}")? } }; } - Subcommands::SigEvent { event_string } => { + CastSubcommand::HashMessage { message } => { + let message = stdin::unwrap_line(message)?; + sh_println!("{}", eip191_hash_message(message))? + } + CastSubcommand::SigEvent { event_string } => { let event_string = stdin::unwrap_line(event_string)?; let parsed_event = get_event(&event_string)?; - println!("{:?}", parsed_event.signature()); - } - Subcommands::LeftShift { value, bits, base_in, base_out } => { - println!("{}", SimpleCast::left_shift(&value, &bits, base_in, &base_out)?); - } - Subcommands::RightShift { value, bits, base_in, base_out } => { - println!("{}", SimpleCast::right_shift(&value, &bits, base_in, &base_out)?); - } - Subcommands::EtherscanSource { address, directory, etherscan } => { - let config = Config::from(ðerscan); - let chain = config.chain_id.unwrap_or_default(); - let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - let chain = chain.named()?; - match directory { - Some(dir) => { - SimpleCast::expand_etherscan_source_to_directory(chain, address, api_key, dir) - .await? + sh_println!("{:?}", parsed_event.selector())? + } + CastSubcommand::LeftShift { value, bits, base_in, base_out } => sh_println!( + "{}", + SimpleCast::left_shift(&value, &bits, base_in.as_deref(), &base_out)? + )?, + CastSubcommand::RightShift { value, bits, base_in, base_out } => sh_println!( + "{}", + SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)? + )?, + CastSubcommand::Source { + address, + directory, + explorer_api_url, + explorer_url, + etherscan, + flatten, + } => { + let config = etherscan.load_config()?; + let chain = config.chain.unwrap_or_default(); + let api_key = config.get_etherscan_api_key(Some(chain)); + match (directory, flatten) { + (Some(dir), false) => { + SimpleCast::expand_etherscan_source_to_directory( + chain, + address, + api_key, + dir, + explorer_api_url, + explorer_url, + ) + .await? } - None => { - println!("{}", SimpleCast::etherscan_source(chain, address, api_key).await?); + (None, false) => sh_println!( + "{}", + SimpleCast::etherscan_source( + chain, + address, + api_key, + explorer_api_url, + explorer_url + ) + .await? + )?, + (dir, true) => { + SimpleCast::etherscan_source_flatten( + chain, + address, + api_key, + dir, + explorer_api_url, + explorer_url, + ) + .await?; } } } - Subcommands::Create2(cmd) => { + CastSubcommand::Create2(cmd) => { cmd.run()?; } - Subcommands::Wallet { command } => command.run().await?, - Subcommands::Completions { shell } => { - generate(shell, &mut Opts::command(), "cast", &mut std::io::stdout()) + CastSubcommand::Wallet { command } => command.run().await?, + CastSubcommand::Completions { shell } => { + generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout()) } - Subcommands::GenerateFigSpec => clap_complete::generate( + CastSubcommand::GenerateFigSpec => clap_complete::generate( clap_complete_fig::Fig, - &mut Opts::command(), + &mut CastArgs::command(), "cast", &mut std::io::stdout(), ), - Subcommands::Logs(cmd) => cmd.run().await?, + CastSubcommand::Logs(cmd) => cmd.run().await?, + CastSubcommand::DecodeTransaction { tx } => { + let tx = stdin::unwrap_line(tx)?; + let tx = SimpleCast::decode_raw_transaction(&tx)?; + + sh_println!("{}", serde_json::to_string_pretty(&tx)?)? + } + CastSubcommand::DecodeEof { eof } => { + let eof = stdin::unwrap_line(eof)?; + sh_println!("{}", SimpleCast::decode_eof(&eof)?)? + } }; + + /// Prints slice of tokens using [`format_tokens`] or [`format_tokens_raw`] depending whether + /// the shell is in JSON mode. + /// + /// This is included here to avoid a cyclic dependency between `fmt` and `common`. + fn print_tokens(tokens: &[DynSolValue]) { + if shell::is_json() { + let tokens: Vec = format_tokens_raw(tokens).collect(); + let _ = sh_println!("{}", serde_json::to_string_pretty(&tokens).unwrap()); + } else { + let tokens = format_tokens(tokens); + tokens.for_each(|t| { + let _ = sh_println!("{t}"); + }); + } + } + Ok(()) } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/opts.rs deleted file mode 100644 index e5c7b89a264cd..0000000000000 --- a/crates/cast/bin/opts.rs +++ /dev/null @@ -1,948 +0,0 @@ -use crate::cmd::{ - access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args, - estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, - rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, -}; -use clap::{Parser, Subcommand, ValueHint}; -use ethers::{ - abi::ethabi::ethereum_types::BigEndianHash, - types::{serde_helpers::Numeric, Address, BlockId, NameOrAddress, H256, U256}, -}; -use eyre::Result; -use foundry_cli::{ - opts::{EtherscanOpts, RpcOpts}, - utils::parse_u256, -}; -use std::{path::PathBuf, str::FromStr}; - -const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); - -#[derive(Debug, Parser)] -#[clap(name = "cast", version = VERSION_MESSAGE)] -pub struct Opts { - #[clap(subcommand)] - pub sub: Subcommands, -} - -/// Perform Ethereum RPC calls from the comfort of your command line. -#[derive(Debug, Subcommand)] -#[clap( - after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html", - next_display_order = None -)] -pub enum Subcommands { - /// Prints the maximum value of the given integer type. - #[clap(visible_aliases = &["--max-int", "maxi"])] - MaxInt { - /// The integer type to get the maximum value of. - #[clap(default_value = "int256")] - r#type: String, - }, - - /// Prints the minimum value of the given integer type. - #[clap(visible_aliases = &["--min-int", "mini"])] - MinInt { - /// The integer type to get the minimum value of. - #[clap(default_value = "int256")] - r#type: String, - }, - - /// Prints the maximum value of the given integer type. - #[clap(visible_aliases = &["--max-uint", "maxu"])] - MaxUint { - /// The unsigned integer type to get the maximum value of. - #[clap(default_value = "uint256")] - r#type: String, - }, - - /// Prints the zero address. - #[clap(visible_aliases = &["--address-zero", "az"])] - AddressZero, - - /// Prints the zero hash. - #[clap(visible_aliases = &["--hash-zero", "hz"])] - HashZero, - - /// Convert UTF8 text to hex. - #[clap( - visible_aliases = &[ - "--from-ascii", - "from-ascii", - "fu", - "fa"] - )] - FromUtf8 { - /// The text to convert. - text: Option, - }, - - /// Concatenate hex strings. - #[clap(visible_aliases = &["--concat-hex", "ch"])] - ConcatHex { - /// The data to concatenate. - data: Vec, - }, - - /// "Convert binary data into hex data." - #[clap(visible_aliases = &["--from-bin", "from-binx", "fb"])] - FromBin, - - /// Normalize the input to lowercase, 0x-prefixed hex. - /// - /// The input can be: - /// - mixed case hex with or without 0x prefix - /// - 0x prefixed hex, concatenated with a ':' - /// - an absolute path to file - /// - @tag, where the tag is defined in an environment variable - #[clap(visible_aliases = &["--to-hexdata", "thd", "2hd"])] - ToHexdata { - /// The input to normalize. - input: Option, - }, - - /// Convert an address to a checksummed format (EIP-55). - #[clap( - visible_aliases = &["--to-checksum-address", - "--to-checksum", - "to-checksum", - "ta", - "2a"] - )] - ToCheckSumAddress { - /// The address to convert. - address: Option
, - }, - - /// Convert hex data to an ASCII string. - #[clap(visible_aliases = &["--to-ascii", "tas", "2as"])] - ToAscii { - /// The hex data to convert. - hexdata: Option, - }, - - /// Convert a fixed point number into an integer. - #[clap(visible_aliases = &["--from-fix", "ff"])] - FromFixedPoint { - /// The number of decimals to use. - decimals: Option, - - /// The value to convert. - #[clap(allow_hyphen_values = true)] - value: Option, - }, - - /// Right-pads hex data to 32 bytes. - #[clap(visible_aliases = &["--to-bytes32", "tb", "2b"])] - ToBytes32 { - /// The hex data to convert. - bytes: Option, - }, - - /// Convert an integer into a fixed point number. - #[clap(visible_aliases = &["--to-fix", "tf", "2f"])] - ToFixedPoint { - /// The number of decimals to use. - decimals: Option, - - /// The value to convert. - #[clap(allow_hyphen_values = true)] - value: Option, - }, - - /// Convert a number to a hex-encoded uint256. - #[clap(name = "to-uint256", visible_aliases = &["--to-uint256", "tu", "2u"])] - ToUint256 { - /// The value to convert. - value: Option, - }, - - /// Convert a number to a hex-encoded int256. - #[clap(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] - ToInt256 { - /// The value to convert. - value: Option, - }, - - /// Perform a left shifting operation - #[clap(name = "shl")] - LeftShift { - /// The value to shift. - value: String, - - /// The number of bits to shift. - bits: String, - - /// The input base. - #[clap(long)] - base_in: Option, - - /// The output base. - #[clap(long, default_value = "16")] - base_out: String, - }, - - /// Perform a right shifting operation - #[clap(name = "shr")] - RightShift { - /// The value to shift. - value: String, - - /// The number of bits to shift. - bits: String, - - /// The input base, - #[clap(long)] - base_in: Option, - - /// The output base, - #[clap(long, default_value = "16")] - base_out: String, - }, - - /// Convert an ETH amount into another unit (ether, gwei or wei). - /// - /// Examples: - /// - 1ether wei - /// - "1 ether" wei - /// - 1ether - /// - 1 gwei - /// - 1gwei ether - #[clap(visible_aliases = &["--to-unit", "tun", "2un"])] - ToUnit { - /// The value to convert. - value: Option, - - /// The unit to convert to (ether, gwei, wei). - #[clap(default_value = "wei")] - unit: String, - }, - - /// Convert an ETH amount to wei. - /// - /// Consider using --to-unit. - #[clap(visible_aliases = &["--to-wei", "tw", "2w"])] - ToWei { - /// The value to convert. - #[clap(allow_hyphen_values = true)] - value: Option, - - /// The unit to convert from (ether, gwei, wei). - #[clap(default_value = "eth")] - unit: String, - }, - - /// Convert wei into an ETH amount. - /// - /// Consider using --to-unit. - #[clap(visible_aliases = &["--from-wei", "fw"])] - FromWei { - /// The value to convert. - #[clap(allow_hyphen_values = true)] - value: Option, - - /// The unit to convert from (ether, gwei, wei). - #[clap(default_value = "eth")] - unit: String, - }, - - /// RLP encodes hex data, or an array of hex data - #[clap(visible_aliases = &["--to-rlp"])] - ToRlp { - /// The value to convert. - value: Option, - }, - - /// Decodes RLP encoded data. - /// - /// Input must be hexadecimal. - #[clap(visible_aliases = &["--from-rlp"])] - FromRlp { - /// The value to convert. - value: Option, - }, - - /// Converts a number of one base to another - #[clap(visible_aliases = &["--to-hex", "th", "2h"])] - ToHex(ToBaseArgs), - - /// Converts a number of one base to decimal - #[clap(visible_aliases = &["--to-dec", "td", "2d"])] - ToDec(ToBaseArgs), - - /// Converts a number of one base to another - #[clap( - visible_aliases = &["--to-base", - "--to-radix", - "to-radix", - "tr", - "2r"] - )] - ToBase { - #[clap(flatten)] - base: ToBaseArgs, - - /// The output base. - #[clap(value_name = "BASE")] - base_out: Option, - }, - /// Create an access list for a transaction. - #[clap(visible_aliases = &["ac", "acl"])] - AccessList(AccessListArgs), - /// Get logs by signature or topic. - #[clap(visible_alias = "l")] - Logs(LogsArgs), - /// Get information about a block. - #[clap(visible_alias = "bl")] - Block { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - block: Option, - - /// If specified, only get the given field of the block. - #[clap(long, short)] - field: Option, - - #[clap(long, env = "CAST_FULL_BLOCK")] - full: bool, - - /// Print the block as JSON. - #[clap(long, short, help_heading = "Display options")] - json: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the latest block number. - #[clap(visible_alias = "bn")] - BlockNumber { - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Perform a call on an account without publishing a transaction. - #[clap(visible_alias = "c")] - Call(CallArgs), - - /// ABI-encode a function with arguments. - #[clap(name = "calldata", visible_alias = "cd")] - CalldataEncode { - /// The function signature in the form () - sig: String, - - /// The arguments to encode. - #[clap(allow_hyphen_values = true)] - args: Vec, - }, - - /// Get the symbolic name of the current chain. - Chain { - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the Ethereum chain ID. - #[clap(visible_aliases = &["ci", "cid"])] - ChainId { - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the current client version. - #[clap(visible_alias = "cl")] - Client { - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Compute the contract address from a given nonce and deployer address. - #[clap(visible_alias = "ca")] - ComputeAddress { - /// The deployer address. - address: Option, - - /// The nonce of the deployer address. - #[clap(long, value_parser = parse_u256)] - nonce: Option, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Disassembles hex encoded bytecode into individual / human readable opcodes - #[clap(visible_alias = "da")] - Disassemble { - /// The hex encoded bytecode. - bytecode: String, - }, - - /// Calculate the ENS namehash of a name. - #[clap(visible_aliases = &["na", "nh"])] - Namehash { name: Option }, - - /// Get information about a transaction. - #[clap(visible_alias = "t")] - Tx { - /// The transaction hash. - tx_hash: String, - - /// If specified, only get the given field of the transaction. If "raw", the RLP encoded - /// transaction will be printed. - field: Option, - - /// Print the raw RLP encoded transaction. - #[clap(long, conflicts_with = "field")] - raw: bool, - - /// Print as JSON. - #[clap(long, short, help_heading = "Display options")] - json: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the transaction receipt for a transaction. - #[clap(visible_alias = "re")] - Receipt { - /// The transaction hash. - tx_hash: String, - - /// If specified, only get the given field of the transaction. - field: Option, - - /// The number of confirmations until the receipt is fetched - #[clap(long, default_value = "1")] - confirmations: usize, - - /// Exit immediately if the transaction was not found. - #[clap(long = "async", env = "CAST_ASYNC", name = "async", alias = "cast-async")] - cast_async: bool, - - /// Print as JSON. - #[clap(long, short, help_heading = "Display options")] - json: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Sign and publish a transaction. - #[clap(name = "send", visible_alias = "s")] - SendTx(SendTxArgs), - - /// Publish a raw transaction to the network. - #[clap(name = "publish", visible_alias = "p")] - PublishTx { - /// The raw transaction - raw_tx: String, - - /// Only print the transaction hash and exit immediately. - #[clap(long = "async", env = "CAST_ASYNC", name = "async", alias = "cast-async")] - cast_async: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Estimate the gas cost of a transaction. - #[clap(visible_alias = "e")] - Estimate(EstimateArgs), - - /// Decode ABI-encoded input data. - /// - /// Similar to `abi-decode --input`, but function selector MUST be prefixed in `calldata` - /// string - #[clap(visible_aliases = &["--calldata-decode","cdd"])] - CalldataDecode { - /// The function signature in the format `()()`. - sig: String, - - /// The ABI-encoded calldata. - calldata: String, - }, - - /// Decode ABI-encoded input or output data. - /// - /// Defaults to decoding output data. To decode input data pass --input. - /// - /// When passing `--input`, function selector must NOT be prefixed in `calldata` string - #[clap(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] - AbiDecode { - /// The function signature in the format `()()`. - sig: String, - - /// The ABI-encoded calldata. - calldata: String, - - /// Whether to decode the input or output data. - #[clap(long, short, help_heading = "Decode input data instead of output data")] - input: bool, - }, - - /// ABI encode the given function argument, excluding the selector. - #[clap(visible_alias = "ae")] - AbiEncode { - /// The function signature. - sig: String, - - /// The arguments of the function. - #[clap(allow_hyphen_values = true)] - args: Vec, - }, - - /// Compute the storage slot for an entry in a mapping. - #[clap(visible_alias = "in")] - Index { - /// The mapping key type. - key_type: String, - - /// The mapping key. - key: String, - - /// The storage slot of the mapping. - slot_number: String, - }, - - /// Fetch the EIP-1967 implementation account - #[clap(visible_alias = "impl")] - Implementation { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] - who: NameOrAddress, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Fetch the EIP-1967 admin account - #[clap(visible_alias = "adm")] - Admin { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] - who: NameOrAddress, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the function signatures for the given selector from https://openchain.xyz. - #[clap(name = "4byte", visible_aliases = &["4", "4b"])] - FourByte { - /// The function selector. - selector: Option, - }, - - /// Decode ABI-encoded calldata using https://openchain.xyz. - #[clap(name = "4byte-decode", visible_aliases = &["4d", "4bd"])] - FourByteDecode { - /// The ABI-encoded calldata. - calldata: Option, - }, - - /// Get the event signature for a given topic 0 from https://openchain.xyz. - #[clap(name = "4byte-event", visible_aliases = &["4e", "4be"])] - FourByteEvent { - /// Topic 0 - #[clap(value_name = "TOPIC_0")] - topic: Option, - }, - - /// Upload the given signatures to https://openchain.xyz. - /// - /// Example inputs: - /// - "transfer(address,uint256)" - /// - "function transfer(address,uint256)" - /// - "function transfer(address,uint256)" "event Transfer(address,address,uint256)" - /// - "./out/Contract.sol/Contract.json" - #[clap(visible_aliases = &["ups"])] - UploadSignature { - /// The signatures to upload. - /// - /// Prefix with 'function', 'event', or 'error'. Defaults to function if no prefix given. - /// Can also take paths to contract artifact JSON. - signatures: Vec, - }, - - /// Pretty print calldata. - /// - /// Tries to decode the calldata using https://openchain.xyz unless --offline is passed. - #[clap(visible_alias = "pc")] - PrettyCalldata { - /// The calldata. - calldata: Option, - - /// Skip the https://openchain.xyz lookup. - #[clap(long, short)] - offline: bool, - }, - - /// Get the timestamp of a block. - #[clap(visible_alias = "a")] - Age { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - block: Option, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the balance of an account in wei. - #[clap(visible_alias = "b")] - Balance { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - /// The account to query. - #[clap(value_parser = NameOrAddress::from_str)] - who: NameOrAddress, - - /// Format the balance in ether. - #[clap(long, short)] - ether: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the basefee of a block. - #[clap(visible_aliases = &["ba", "fee", "basefee"])] - BaseFee { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - block: Option, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the runtime bytecode of a contract. - #[clap(visible_alias = "co")] - Code { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] - who: NameOrAddress, - - /// Disassemble bytecodes into individual opcodes. - #[clap(long, short)] - disassemble: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the runtime bytecode size of a contract. - #[clap(visible_alias = "cs")] - Codesize { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] - who: NameOrAddress, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the current gas price. - #[clap(visible_alias = "g")] - GasPrice { - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Generate event signatures from event string. - #[clap(visible_alias = "se")] - SigEvent { - /// The event string. - event_string: Option, - }, - - /// Hash arbitrary data using Keccak-256. - #[clap(visible_alias = "k")] - Keccak { - /// The data to hash. - data: Option, - }, - - /// Perform an ENS lookup. - #[clap(visible_alias = "rn")] - ResolveName { - /// The name to lookup. - who: Option, - - /// Perform a reverse lookup to verify that the name is correct. - #[clap(long, short)] - verify: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Perform an ENS reverse lookup. - #[clap(visible_alias = "la")] - LookupAddress { - /// The account to perform the lookup for. - who: Option
, - - /// Perform a normal lookup to verify that the address is correct. - #[clap(long, short)] - verify: bool, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the raw value of a contract's storage slot. - #[clap(visible_alias = "st")] - Storage(StorageArgs), - - /// Generate a storage proof for a given storage slot. - #[clap(visible_alias = "pr")] - Proof { - /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] - address: NameOrAddress, - - /// The storage slot numbers (hex or decimal). - #[clap(value_parser = parse_slot)] - slots: Vec, - - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the nonce for an account. - #[clap(visible_alias = "n")] - Nonce { - /// The block height to query at. - /// - /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] - block: Option, - - /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] - who: NameOrAddress, - - #[clap(flatten)] - rpc: RpcOpts, - }, - - /// Get the source code of a contract from Etherscan. - #[clap(visible_aliases = &["et", "src"])] - EtherscanSource { - /// The contract's address. - address: String, - - /// The output directory to expand source tree into. - #[clap(short, value_hint = ValueHint::DirPath)] - directory: Option, - - #[clap(flatten)] - etherscan: EtherscanOpts, - }, - - /// Wallet management utilities. - #[clap(visible_alias = "w")] - Wallet { - #[clap(subcommand)] - command: WalletSubcommands, - }, - - /// Generate a Solidity interface from a given ABI. - /// - /// Currently does not support ABI encoder v2. - #[clap(visible_alias = "i")] - Interface(InterfaceArgs), - - /// Generate a rust binding from a given ABI. - #[clap(visible_alias = "bi")] - Bind(BindArgs), - - /// Get the selector for a function. - #[clap(visible_alias = "si")] - Sig { - /// The function signature, e.g. transfer(address,uint256). - sig: Option, - - /// Optimize signature to contain provided amount of leading zeroes in selector. - optimize: Option, - }, - - /// Generate a deterministic contract address using CREATE2. - #[clap(visible_alias = "c2")] - Create2(Create2Args), - - /// Get the block number closest to the provided timestamp. - #[clap(visible_alias = "f")] - FindBlock(FindBlockArgs), - - /// Generate shell completions script. - #[clap(visible_alias = "com")] - Completions { - #[clap(value_enum)] - shell: clap_complete::Shell, - }, - - /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] - GenerateFigSpec, - - /// Runs a published transaction in a local environment and prints the trace. - #[clap(visible_alias = "r")] - Run(RunArgs), - - /// Perform a raw JSON-RPC request. - #[clap(visible_alias = "rp")] - Rpc(RpcArgs), - - /// Formats a string into bytes32 encoding. - #[clap(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] - FormatBytes32String { - /// The string to format. - string: Option, - }, - - /// Parses a string from bytes32 encoding. - #[clap(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] - ParseBytes32String { - /// The string to parse. - bytes: Option, - }, - #[clap(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] - #[clap(about = "Parses a checksummed address from bytes32 encoding.")] - ParseBytes32Address { - #[clap(value_name = "BYTES")] - bytes: Option, - }, -} - -/// CLI arguments for `cast --to-base`. -#[derive(Debug, Parser)] -pub struct ToBaseArgs { - /// The value to convert. - #[clap(allow_hyphen_values = true)] - pub value: Option, - - /// The input base. - #[clap(long, short = 'i')] - pub base_in: Option, -} - -pub fn parse_slot(s: &str) -> Result { - Numeric::from_str(s) - .map_err(|e| eyre::eyre!("Could not parse slot number: {e}")) - .map(|n| H256::from_uint(&n.into())) -} - -#[cfg(test)] -mod tests { - use super::*; - use ethers::types::BlockNumber; - - #[test] - fn parse_call_data() { - let args: Opts = Opts::parse_from([ - "foundry-cli", - "calldata", - "f()", - "5c9d55b78febcc2061715ba4f57ecf8ea2711f2c", - "2", - ]); - match args.sub { - Subcommands::CalldataEncode { args, .. } => { - assert_eq!( - args, - vec!["5c9d55b78febcc2061715ba4f57ecf8ea2711f2c".to_string(), "2".to_string()] - ) - } - _ => unreachable!(), - }; - } - - #[test] - fn parse_block_ids() { - struct TestCase { - input: String, - expect: BlockId, - } - - let test_cases = [ - TestCase { - input: "0".to_string(), - expect: BlockId::Number(BlockNumber::Number(0u64.into())), - }, - TestCase { - input: "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" - .to_string(), - expect: BlockId::Hash( - "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" - .parse() - .unwrap(), - ), - }, - TestCase { input: "latest".to_string(), expect: BlockId::Number(BlockNumber::Latest) }, - TestCase { - input: "earliest".to_string(), - expect: BlockId::Number(BlockNumber::Earliest), - }, - TestCase { - input: "pending".to_string(), - expect: BlockId::Number(BlockNumber::Pending), - }, - TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumber::Safe) }, - TestCase { - input: "finalized".to_string(), - expect: BlockId::Number(BlockNumber::Finalized), - }, - ]; - - for test in test_cases { - let result: BlockId = test.input.parse().unwrap(); - assert_eq!(result, test.expect); - } - } -} diff --git a/crates/cast/bin/tx.rs b/crates/cast/bin/tx.rs new file mode 100644 index 0000000000000..945cd56d3d939 --- /dev/null +++ b/crates/cast/bin/tx.rs @@ -0,0 +1,455 @@ +use alloy_consensus::{SidecarBuilder, SimpleCoder}; +use alloy_dyn_abi::ErrorExt; +use alloy_json_abi::Function; +use alloy_network::{ + AnyNetwork, TransactionBuilder, TransactionBuilder4844, TransactionBuilder7702, +}; +use alloy_primitives::{hex, Address, Bytes, TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{AccessList, Authorization, TransactionInput, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; +use alloy_transport::TransportError; +use cast::traces::identifier::SignaturesIdentifier; +use eyre::Result; +use foundry_cli::{ + opts::{CliAuthorizationList, TransactionOpts}, + utils::{self, parse_function_args}, +}; +use foundry_common::{ens::NameOrAddress, fmt::format_tokens}; +use foundry_config::{Chain, Config}; +use foundry_wallets::{WalletOpts, WalletSigner}; +use itertools::Itertools; +use serde_json::value::RawValue; +use std::fmt::Write; + +/// Different sender kinds used by [`CastTxBuilder`]. +pub enum SenderKind<'a> { + /// An address without signer. Used for read-only calls and transactions sent through unlocked + /// accounts. + Address(Address), + /// A reference to a signer. + Signer(&'a WalletSigner), + /// An owned signer. + OwnedSigner(WalletSigner), +} + +impl SenderKind<'_> { + /// Resolves the name to an Ethereum Address. + pub fn address(&self) -> Address { + match self { + Self::Address(addr) => *addr, + Self::Signer(signer) => signer.address(), + Self::OwnedSigner(signer) => signer.address(), + } + } + + /// Resolves the sender from the wallet options. + /// + /// This function prefers the `from` field and may return a different address from the + /// configured signer + /// If from is specified, returns it + /// If from is not specified, but there is a signer configured, returns the signer's address + /// If from is not specified and there is no signer configured, returns zero address + pub async fn from_wallet_opts(opts: WalletOpts) -> Result { + if let Some(from) = opts.from { + Ok(from.into()) + } else if let Ok(signer) = opts.signer().await { + Ok(Self::OwnedSigner(signer)) + } else { + Ok(Address::ZERO.into()) + } + } + + /// Returns the signer if available. + pub fn as_signer(&self) -> Option<&WalletSigner> { + match self { + Self::Signer(signer) => Some(signer), + Self::OwnedSigner(signer) => Some(signer), + _ => None, + } + } +} + +impl From
for SenderKind<'_> { + fn from(addr: Address) -> Self { + Self::Address(addr) + } +} + +impl<'a> From<&'a WalletSigner> for SenderKind<'a> { + fn from(signer: &'a WalletSigner) -> Self { + Self::Signer(signer) + } +} + +impl From for SenderKind<'_> { + fn from(signer: WalletSigner) -> Self { + Self::OwnedSigner(signer) + } +} + +/// Prevents a misconfigured hwlib from sending a transaction that defies user-specified --from +pub fn validate_from_address( + specified_from: Option
, + signer_address: Address, +) -> Result<()> { + if let Some(specified_from) = specified_from { + if specified_from != signer_address { + eyre::bail!( + "\ +The specified sender via CLI/env vars does not match the sender configured via +the hardware wallet's HD Path. +Please use the `--hd-path ` parameter to specify the BIP32 Path which +corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." + ) + } + } + Ok(()) +} + +/// Initial state. +#[derive(Debug)] +pub struct InitState; + +/// State with known [TxKind]. +#[derive(Debug)] +pub struct ToState { + to: Option
, +} + +/// State with known input for the transaction. +#[derive(Debug)] +pub struct InputState { + kind: TxKind, + input: Vec, + func: Option, +} + +/// Builder type constructing [TransactionRequest] from cast send/mktx inputs. +/// +/// It is implemented as a stateful builder with expected state transition of [InitState] -> +/// [TxKindState] -> [InputState]. +#[derive(Debug)] +pub struct CastTxBuilder { + provider: P, + tx: WithOtherFields, + legacy: bool, + blob: bool, + auth: Option, + chain: Chain, + etherscan_api_key: Option, + access_list: Option>, + state: S, +} + +impl> CastTxBuilder { + /// Creates a new instance of [CastTxBuilder] filling transaction with fields present in + /// provided [TransactionOpts]. + pub async fn new(provider: P, tx_opts: TransactionOpts, config: &Config) -> Result { + let mut tx = WithOtherFields::::default(); + + let 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()); + } + + if let Some(value) = tx_opts.value { + tx.set_value(value); + } + + if let Some(gas_price) = tx_opts.gas_price { + if legacy { + tx.set_gas_price(gas_price.to()); + } else { + tx.set_max_fee_per_gas(gas_price.to()); + } + } + + if !legacy { + if let Some(priority_fee) = tx_opts.priority_gas_price { + tx.set_max_priority_fee_per_gas(priority_fee.to()); + } + } + + if let Some(max_blob_fee) = tx_opts.blob_gas_price { + tx.set_max_fee_per_blob_gas(max_blob_fee.to()) + } + + if let Some(nonce) = tx_opts.nonce { + tx.set_nonce(nonce.to()); + } + + Ok(Self { + provider, + tx, + legacy, + blob: tx_opts.blob, + chain, + etherscan_api_key, + auth: tx_opts.auth, + access_list: tx_opts.access_list, + state: InitState, + }) + } + + /// Sets [TxKind] for this builder and changes state to [TxKindState]. + pub async fn with_to(self, to: Option) -> Result> { + let to = if let Some(to) = to { Some(to.resolve(&self.provider).await?) } else { None }; + Ok(CastTxBuilder { + provider: self.provider, + tx: self.tx, + legacy: self.legacy, + blob: self.blob, + chain: self.chain, + etherscan_api_key: self.etherscan_api_key, + auth: self.auth, + access_list: self.access_list, + state: ToState { to }, + }) + } +} + +impl> CastTxBuilder { + /// Accepts user-provided code, sig and args params and constructs calldata for the transaction. + /// If code is present, input will be set to code + encoded constructor arguments. If no code is + /// present, input is set to just provided arguments. + pub async fn with_code_sig_and_args( + self, + code: Option, + sig: Option, + args: Vec, + ) -> Result> { + let (mut args, func) = if let Some(sig) = sig { + parse_function_args( + &sig, + args, + self.state.to, + self.chain, + &self.provider, + self.etherscan_api_key.as_deref(), + ) + .await? + } else { + (Vec::new(), None) + }; + + let input = if let Some(code) = &code { + let mut code = hex::decode(code)?; + code.append(&mut args); + code + } else { + args + }; + + if self.state.to.is_none() && code.is_none() { + let has_value = self.tx.value.is_some_and(|v| !v.is_zero()); + let has_auth = self.auth.is_some(); + // We only allow user to omit the recipient address if transaction is an EIP-7702 tx + // without a value. + if !has_auth || has_value { + eyre::bail!("Must specify a recipient address or contract code to deploy"); + } + } + + Ok(CastTxBuilder { + provider: self.provider, + tx: self.tx, + legacy: self.legacy, + blob: self.blob, + chain: self.chain, + etherscan_api_key: self.etherscan_api_key, + auth: self.auth, + access_list: self.access_list, + state: InputState { kind: self.state.to.into(), input, func }, + }) + } +} + +impl> CastTxBuilder { + /// Builds [TransactionRequest] and fiils missing fields. Returns a transaction which is ready + /// to be broadcasted. + pub async fn build( + self, + sender: impl Into>, + ) -> Result<(WithOtherFields, Option)> { + self._build(sender, true).await + } + + /// Builds [TransactionRequest] without filling missing fields. Used for read-only calls such as + /// eth_call, eth_estimateGas, etc + pub async fn build_raw( + self, + sender: impl Into>, + ) -> Result<(WithOtherFields, Option)> { + self._build(sender, false).await + } + + async fn _build( + mut self, + sender: impl Into>, + fill: bool, + ) -> Result<(WithOtherFields, Option)> { + let sender = sender.into(); + let from = sender.address(); + + self.tx.set_kind(self.state.kind); + + // we set both fields to the same value because some nodes only accept the legacy `data` field: + let input = Bytes::copy_from_slice(&self.state.input); + self.tx.input = TransactionInput { input: Some(input.clone()), data: Some(input) }; + + self.tx.set_from(from); + self.tx.set_chain_id(self.chain.id()); + + let tx_nonce = if let Some(nonce) = self.tx.nonce { + nonce + } else { + let nonce = self.provider.get_transaction_count(from).await?; + if fill { + self.tx.nonce = Some(nonce); + } + nonce + }; + + self.resolve_auth(sender, tx_nonce).await?; + + if let Some(access_list) = match self.access_list.take() { + None => None, + // --access-list provided with no value, call the provider to create it + Some(None) => Some(self.provider.create_access_list(&self.tx).await?.access_list), + // Access list provided as a string, attempt to parse it + Some(Some(access_list)) => Some(access_list), + } { + self.tx.set_access_list(access_list); + } + + if !fill { + return Ok((self.tx, self.state.func)); + } + + if self.legacy && self.tx.gas_price.is_none() { + self.tx.gas_price = Some(self.provider.get_gas_price().await?); + } + + if self.blob && self.tx.max_fee_per_blob_gas.is_none() { + self.tx.max_fee_per_blob_gas = Some(self.provider.get_blob_base_fee().await?) + } + + if !self.legacy && + (self.tx.max_fee_per_gas.is_none() || self.tx.max_priority_fee_per_gas.is_none()) + { + let estimate = self.provider.estimate_eip1559_fees(None).await?; + + if !self.legacy { + if self.tx.max_fee_per_gas.is_none() { + self.tx.max_fee_per_gas = Some(estimate.max_fee_per_gas); + } + + if self.tx.max_priority_fee_per_gas.is_none() { + self.tx.max_priority_fee_per_gas = Some(estimate.max_priority_fee_per_gas); + } + } + } + + if self.tx.gas.is_none() { + self.estimate_gas().await?; + } + + Ok((self.tx, self.state.func)) + } + + /// Estimate tx gas from provider call. Tries to decode custom error if execution reverted. + async fn estimate_gas(&mut self) -> Result<()> { + match self.provider.estimate_gas(&self.tx).await { + Ok(estimated) => { + self.tx.gas = Some(estimated); + Ok(()) + } + Err(err) => { + if let TransportError::ErrorResp(payload) = &err { + // If execution reverted with code 3 during provider gas estimation then try + // to decode custom errors and append it to the error message. + if payload.code == 3 { + if let Some(data) = &payload.data { + if let Ok(Some(decoded_error)) = decode_execution_revert(data).await { + eyre::bail!("Failed to estimate gas: {}: {}", err, decoded_error) + } + } + } + } + eyre::bail!("Failed to estimate gas: {}", err) + } + } + } + + /// Parses the passed --auth value and sets the authorization list on the transaction. + async fn resolve_auth(&mut self, sender: SenderKind<'_>, tx_nonce: u64) -> Result<()> { + let Some(auth) = self.auth.take() else { return Ok(()) }; + + let auth = match auth { + CliAuthorizationList::Address(address) => { + let auth = Authorization { + chain_id: U256::from(self.chain.id()), + nonce: tx_nonce + 1, + address, + }; + + let Some(signer) = sender.as_signer() else { + eyre::bail!("No signer available to sign authorization"); + }; + let signature = signer.sign_hash(&auth.signature_hash()).await?; + + auth.into_signed(signature) + } + CliAuthorizationList::Signed(auth) => auth, + }; + + self.tx.set_authorization_list(vec![auth]); + + Ok(()) + } +} + +impl CastTxBuilder +where + P: Provider, +{ + pub fn with_blob_data(mut self, blob_data: Option>) -> Result { + let Some(blob_data) = blob_data else { return Ok(self) }; + + let mut coder = SidecarBuilder::::default(); + coder.ingest(&blob_data); + let sidecar = coder.build()?; + + self.tx.set_blob_sidecar(sidecar); + self.tx.populate_blob_hashes(); + + Ok(self) + } +} + +/// Helper function that tries to decode custom error name and inputs from error payload data. +async fn decode_execution_revert(data: &RawValue) -> Result> { + if let Some(err_data) = serde_json::from_str::(data.get())?.strip_prefix("0x") { + let selector = err_data.get(..8).unwrap(); + if let Some(known_error) = SignaturesIdentifier::new(Config::foundry_cache_dir(), false)? + .write() + .await + .identify_error(&hex::decode(selector)?) + .await + { + let mut decoded_error = known_error.name.clone(); + if !known_error.inputs.is_empty() { + if let Ok(error) = known_error.decode_error(&hex::decode(err_data)?) { + write!(decoded_error, "({})", format_tokens(&error.body).format(", "))?; + } + } + return Ok(Some(decoded_error)) + } + } + Ok(None) +} diff --git a/crates/cast/build.rs b/crates/cast/build.rs deleted file mode 100644 index c2f550fb6f829..0000000000000 --- a/crates/cast/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/cast/src/base.rs b/crates/cast/src/base.rs index 016d7402aea37..63ae59c1cbffb 100644 --- a/crates/cast/src/base.rs +++ b/crates/cast/src/base.rs @@ -1,13 +1,8 @@ -use ethers_core::{ - abi::ethereum_types::FromStrRadixErrKind, - types::{Sign, I256, U256}, - utils::ParseUnits, -}; +use alloy_primitives::{utils::ParseUnits, Sign, I256, U256}; use eyre::Result; use std::{ - convert::{Infallible, TryFrom, TryInto}, + convert::Infallible, fmt::{Binary, Debug, Display, Formatter, LowerHex, Octal, Result as FmtResult, UpperHex}, - iter::FromIterator, num::IntErrorKind, str::FromStr, }; @@ -44,12 +39,12 @@ impl FromStr for Base { "10" | "d" | "dec" | "decimal" => Ok(Self::Decimal), "16" | "h" | "hex" | "hexadecimal" => Ok(Self::Hexadecimal), s => Err(eyre::eyre!( - r#"Invalid base "{}". Possible values: -2, b, bin, binary -8, o, oct, octal + "\ +Invalid base \"{s}\". Possible values: + 2, b, bin, binary + 8, o, oct, octal 10, d, dec, decimal -16, h, hex, hexadecimal"#, - s +16, h, hex, hexadecimal" )), } } @@ -89,25 +84,13 @@ impl TryFrom for Base { type Error = eyre::Report; fn try_from(n: U256) -> Result { - Self::try_from(n.low_u32()) + Self::try_from(n.saturating_to::()) } } impl From for u32 { fn from(b: Base) -> Self { - b as u32 - } -} - -impl From for I256 { - fn from(b: Base) -> Self { - Self::from(b as u32) - } -} - -impl From for U256 { - fn from(b: Base) -> Self { - Self::from(b as u32) + b as Self } } @@ -118,9 +101,9 @@ impl From for String { } impl Base { - pub fn unwrap_or_detect(base: Option, s: impl AsRef) -> Result { + pub fn unwrap_or_detect(base: Option<&str>, s: impl AsRef) -> Result { match base { - Some(base) => base.try_into(), + Some(base) => base.parse(), None => Self::detect(s), } } @@ -137,50 +120,36 @@ impl Base { // anyway; // strip prefix when using u128::from_str_radix because it does not recognize it as // valid. - _ if s.starts_with("0b") => match u128::from_str_radix(&s[2..], 2) { + _ if s.starts_with("0b") => match u64::from_str_radix(&s[2..], 2) { Ok(_) => Ok(Self::Binary), Err(e) => match e.kind() { IntErrorKind::PosOverflow => Ok(Self::Binary), _ => Err(eyre::eyre!("could not parse binary value: {}", e)), }, }, - _ if s.starts_with("0o") => match u128::from_str_radix(&s[2..], 8) { + _ if s.starts_with("0o") => match u64::from_str_radix(&s[2..], 8) { Ok(_) => Ok(Self::Octal), Err(e) => match e.kind() { IntErrorKind::PosOverflow => Ok(Self::Octal), - _ => Err(eyre::eyre!("could not parse octal value: {}", e)), + _ => Err(eyre::eyre!("could not parse octal value: {e}")), }, }, - _ if s.starts_with("0x") => match U256::from_str_radix(s, 16) { + _ if s.starts_with("0x") => match u64::from_str_radix(&s[2..], 16) { Ok(_) => Ok(Self::Hexadecimal), Err(e) => match e.kind() { - FromStrRadixErrKind::InvalidLength => { - Err(eyre::eyre!("number must be less than U256::MAX ({})", U256::MAX)) - } - _ => Err(eyre::eyre!("could not parse hexadecimal value: {}", e)), + IntErrorKind::PosOverflow => Ok(Self::Hexadecimal), + _ => Err(eyre::eyre!("could not parse hexadecimal value: {e}")), }, }, // No prefix => first try parsing as decimal _ => match U256::from_str_radix(s, 10) { - Ok(_) => { - // Can be both, ambiguous but default to Decimal - - // Err(eyre::eyre!("Could not autodetect base: input could be decimal or - // hexadecimal. Please prepend with 0x if the input is hexadecimal, or specify a - // --base-in parameter.")) - Ok(Self::Decimal) - } + // Can be both, ambiguous but default to Decimal + Ok(_) => Ok(Self::Decimal), Err(_) => match U256::from_str_radix(s, 16) { Ok(_) => Ok(Self::Hexadecimal), - Err(e) => match e.kind() { - FromStrRadixErrKind::InvalidLength => { - Err(eyre::eyre!("number must be less than U256::MAX ({})", U256::MAX)) - } - _ => Err(eyre::eyre!( - "could not autodetect base as neither decimal or hexadecimal: {}", - e - )), - }, + Err(e) => Err(eyre::eyre!( + "could not autodetect base as neither decimal or hexadecimal: {e}" + )), }, }, } @@ -189,10 +158,10 @@ impl Base { /// Returns the Rust standard prefix for a base pub const fn prefix(&self) -> &str { match self { - Base::Binary => "0b", - Base::Octal => "0o", - Base::Hexadecimal => "0x", - _ => "", + Self::Binary => "0b", + Self::Octal => "0o", + Self::Decimal => "", + Self::Hexadecimal => "0x", } } } @@ -205,7 +174,7 @@ impl Base { /// /// ``` /// use cast::base::NumberWithBase; -/// use ethers_core::types::U256; +/// use alloy_primitives::U256; /// /// let number: NumberWithBase = U256::from(12345).into(); /// assert_eq!(number.format(), "12345"); @@ -287,7 +256,7 @@ impl LowerHex for NumberWithBase { impl UpperHex for NumberWithBase { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - let n = format!("{:X}", self.number); + let n = format!("{self:x}").to_uppercase(); f.pad_integral(true, Base::Hexadecimal.prefix(), &n) } } @@ -324,7 +293,7 @@ impl From for NumberWithBase { impl From for I256 { fn from(n: NumberWithBase) -> Self { - I256::from_raw(n.number) + Self::from_raw(n.number) } } @@ -355,7 +324,7 @@ impl NumberWithBase { /// Parses a string slice into a signed integer. If base is None then it tries to determine base /// from the prefix, otherwise defaults to Decimal. - pub fn parse_int(s: &str, base: Option) -> Result { + pub fn parse_int(s: &str, base: Option<&str>) -> Result { let base = Base::unwrap_or_detect(base, s)?; let (number, is_nonnegative) = Self::_parse_int(s, base)?; Ok(Self { number, is_nonnegative, base }) @@ -363,7 +332,7 @@ impl NumberWithBase { /// Parses a string slice into an unsigned integer. If base is None then it tries to determine /// base from the prefix, otherwise defaults to Decimal. - pub fn parse_uint(s: &str, base: Option) -> Result { + pub fn parse_uint(s: &str, base: Option<&str>) -> Result { let base = Base::unwrap_or_detect(base, s)?; let number = Self::_parse_uint(s, base)?; Ok(Self { number, is_nonnegative: true, base }) @@ -402,10 +371,9 @@ impl NumberWithBase { /// signs or padding. Refer to the [std::fmt] module documentation on how to format this /// number with the aforementioned properties. pub fn format(&self) -> String { - match self.base { - // Binary and Octal traits are not implemented for primitive-types types, so we're using - // a custom formatter - Base::Binary | Base::Octal => self.format_radix(), + let s = match self.base { + Base::Binary => format!("{:b}", self.number), + Base::Octal => format!("{:o}", self.number), Base::Decimal => { if self.is_nonnegative { self.number.to_string() @@ -415,53 +383,32 @@ impl NumberWithBase { } } Base::Hexadecimal => format!("{:x}", self.number), + }; + if s.starts_with('0') { + s.trim_start_matches('0').to_string() + } else { + s } } - /// Constructs a String from every digit of the number using [std::char::from_digit]. - /// - /// Modified from: https://stackoverflow.com/a/50278316 - fn format_radix(&self) -> String { - let mut x = self.number; - let radix = self.base as u32; - let r = U256::from(radix); - - let mut buf = ['\0'; 256]; - let mut i = 255; - loop { - let m = (x % r).low_u64() as u32; - // radix is always less than 37 so from_digit cannot panic - // m is always in the radix's range so unwrap cannot panic - buf[i] = char::from_digit(m, radix).unwrap(); - x /= r; - if x.is_zero() { - break - } - i -= 1; - } - String::from_iter(&buf[i..]) - } - fn _parse_int(s: &str, base: Base) -> Result<(U256, bool)> { let (s, sign) = get_sign(s); let mut n = Self::_parse_uint(s, base)?; let is_neg = matches!(sign, Sign::Negative); if is_neg { - n = (!n).overflowing_add(U256::one()).0; + n = (!n).overflowing_add(U256::from(1)).0; } Ok((n, !is_neg)) } fn _parse_uint(s: &str, base: Base) -> Result { - // TODO: Parse from binary or octal str into U256, requires a parser - U256::from_str_radix(s, base as u32).map_err(|e| match e.kind() { - FromStrRadixErrKind::UnsupportedRadix => { - eyre::eyre!("numbers in base {} are currently not supported as input", base) - } - _ => eyre::eyre!(e), - }) + let s = match s.get(0..2) { + Some("0x" | "0X" | "0o" | "0O" | "0b" | "0B") => &s[2..], + _ => s, + }; + U256::from_str_radix(s, base as u64).map_err(Into::into) } } @@ -479,8 +426,8 @@ pub trait ToBase { /// # Example /// /// ``` + /// use alloy_primitives::U256; /// use cast::base::{Base, ToBase}; - /// use ethers_core::types::U256; /// /// // Any type that implements ToBase /// let number = U256::from(12345); @@ -665,9 +612,9 @@ mod tests { let def: Base = Default::default(); assert!(matches!(def, Decimal)); - let n: NumberWithBase = U256::zero().into(); + let n: NumberWithBase = U256::ZERO.into(); assert!(matches!(n.base, Decimal)); - let n: NumberWithBase = I256::zero().into(); + let n: NumberWithBase = I256::ZERO.into(); assert!(matches!(n.base, Decimal)); } @@ -719,7 +666,7 @@ mod tests { let expected_u16: Vec<_> = POS_NUM.iter().map(|n| format!("{n:X}")).collect(); for (i, n) in POS_NUM.into_iter().enumerate() { - let mut num: NumberWithBase = I256::from(n).into(); + let mut num: NumberWithBase = I256::try_from(n).unwrap().into(); assert_eq!(num.set_base(Binary).format(), expected_2[i]); assert_eq!(num.set_base(Octal).format(), expected_8[i]); @@ -743,7 +690,7 @@ mod tests { let expected_u16: Vec<_> = NEG_NUM.iter().map(|n| format!("{n:F>64X}")).collect(); for (i, n) in NEG_NUM.into_iter().enumerate() { - let mut num: NumberWithBase = I256::from(n).into(); + let mut num: NumberWithBase = I256::try_from(n).unwrap().into(); assert_eq!(num.set_base(Binary).format(), expected_2[i]); // assert_eq!(num.set_base(Octal).format(), expected_8[i]); @@ -756,7 +703,7 @@ mod tests { #[test] fn test_fmt_macro() { let nums: Vec<_> = - POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::from(n))).collect(); + POS_NUM.into_iter().map(|n| NumberWithBase::from(I256::try_from(n).unwrap())).collect(); let actual_2: Vec<_> = nums.iter().map(|n| format!("{n:b}")).collect(); let actual_2_alt: Vec<_> = nums.iter().map(|n| format!("{n:#b}")).collect(); diff --git a/crates/cast/src/errors.rs b/crates/cast/src/errors.rs index 235ec4d7ca44f..3e6c5977bfa4c 100644 --- a/crates/cast/src/errors.rs +++ b/crates/cast/src/errors.rs @@ -4,7 +4,7 @@ use foundry_config::Chain; use std::fmt; /// An error thrown when resolving a function via signature failed -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum FunctionSignatureError { MissingSignature, MissingEtherscan { sig: String }, @@ -15,18 +15,18 @@ pub enum FunctionSignatureError { impl fmt::Display for FunctionSignatureError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FunctionSignatureError::MissingSignature => { + Self::MissingSignature => { writeln!(f, "Function signature must be set") } - FunctionSignatureError::MissingEtherscan { sig } => { + Self::MissingEtherscan { sig } => { writeln!(f, "Failed to determine function signature for `{sig}`")?; writeln!(f, "To lookup a function signature of a deployed contract by name, a valid ETHERSCAN_API_KEY must be set.")?; write!(f, "\tOr did you mean:\t {sig}()") } - FunctionSignatureError::UnknownChain(chain) => { + Self::UnknownChain(chain) => { write!(f, "Resolving via etherscan requires a known chain. Unknown chain: {chain}") } - FunctionSignatureError::MissingToAddress => f.write_str("Target address must be set"), + Self::MissingToAddress => f.write_str("Target address must be set"), } } } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 7df722fe87639..40b1e6dae93f3 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1,67 +1,94 @@ -use crate::rlp_converter::Item; +//! Cast is a Swiss Army knife for interacting with Ethereum applications from the command line. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_consensus::TxEnvelope; +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, +}; +use alloy_provider::{ + network::eip2718::{Decodable2718, Encodable2718}, + PendingTransactionBuilder, Provider, +}; +use alloy_rlp::Decodable; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; use base::{Base, NumberWithBase, ToBase}; -use chrono::NaiveDateTime; -use ethers_core::{ - abi::{ - token::{LenientTokenizer, Tokenizer}, - Function, HumanReadableParser, ParamType, RawAbi, Token, - }, - types::{Chain, *}, - utils::{ - format_bytes32_string, format_units, get_contract_address, keccak256, parse_bytes32_string, - parse_units, rlp, Units, - }, +use chrono::DateTime; +use eyre::{Context, ContextCompat, OptionExt, Result}; +use foundry_block_explorers::Client; +use foundry_common::{ + abi::{encode_function_args, get_func}, + compile::etherscan_project, + fmt::*, + fs, get_pretty_tx_receipt_attr, shell, TransactionReceiptWithRevertReason, }; -use ethers_etherscan::{errors::EtherscanError, Client}; -use ethers_providers::{Middleware, PendingTransaction}; -use evm_disassembler::{disassemble_bytes, disassemble_str, format_operations}; -use eyre::{Context, Result}; -use foundry_common::{abi::encode_args, fmt::*, TransactionReceiptWithRevertReason}; -pub use foundry_evm::*; +use foundry_compilers::flatten::Flattener; +use foundry_config::Chain; +use futures::{future::Either, FutureExt, StreamExt}; use rayon::prelude::*; -pub use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -pub use rusoto_kms::KmsClient; +use revm::primitives::Eof; use std::{ + borrow::Cow, + fmt::Write, + io, path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, Ordering}, + time::Duration, }; -pub use tx::TxBuilder; -use tx::{TxBuilderOutput, TxBuilderPeekOutput}; +use tokio::signal::ctrl_c; +use utils::decode_instructions; + +use foundry_common::abi::encode_function_args_packed; +pub use foundry_evm::*; pub mod base; pub mod errors; mod rlp_converter; -mod tx; + +use rlp_converter::Item; + +#[macro_use] +extern crate foundry_common; // TODO: CastContract with common contract initializers? Same for CastProviders? -pub struct Cast { - provider: M, +sol! { + #[sol(rpc)] + interface IERC20 { + #[derive(Debug)] + function balanceOf(address owner) external view returns (uint256); + } +} + +pub struct Cast

{ + provider: P, } -impl Cast -where - M::Error: 'static, -{ +impl> Cast

{ /// Creates a new Cast instance from the provided client /// /// # Example /// /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// # Ok(()) /// # } /// ``` - pub fn new(provider: M) -> Self { + pub fn new(provider: P) -> Self { Self { provider } } @@ -69,69 +96,83 @@ where /// /// # Example /// - /// ```no_run - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::{Address, Chain}; - /// use ethers_providers::{Provider, Http}; - /// use std::{str::FromStr, convert::TryFrom}; + /// ``` + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_rpc_types::{TransactionRequest}; + /// use alloy_serde::WithOtherFields; + /// use cast::Cast; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; + /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; + /// + /// sol!( + /// function greeting(uint256 i) public returns (string); + /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let alloy_provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "function greeting(uint256 i) public returns (string)"; - /// let args = vec!["5".to_owned()]; - /// let mut builder = TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder - /// .set_args(sig, args).await?; - /// let builder_output = builder.build(); - /// let cast = Cast::new(provider); - /// let data = cast.call(builder_output, None).await?; + /// let greeting = greetingCall { i: U256::from(5) }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()); + /// let tx = WithOtherFields::new(tx); + /// let cast = Cast::new(alloy_provider); + /// let data = cast.call(&tx, None, None).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` - pub async fn call<'a>( + pub async fn call( &self, - builder_output: TxBuilderOutput, + req: &WithOtherFields, + func: Option<&Function>, block: Option, ) -> Result { - let (tx, func) = builder_output; - let res = self.provider.call(&tx, block).await?; + let res = self.provider.call(req).block(block.unwrap_or_default()).await?; let mut decoded = vec![]; if let Some(func) = func { // decode args into tokens - decoded = match func.decode_output(res.as_ref()) { + decoded = match func.abi_decode_output(res.as_ref(), false) { Ok(decoded) => decoded, Err(err) => { // ensure the address is a contract if res.is_empty() { // check that the recipient is a contract that can be called - if let Some(NameOrAddress::Address(addr)) = tx.to() { - let code = self.provider.get_code(*addr, block).await?; - if code.is_empty() { - eyre::bail!("Contract {:?} does not exist", addr) + if let Some(TxKind::Call(addr)) = req.to { + if let Ok(code) = self + .provider + .get_code_at(addr) + .block_id(block.unwrap_or_default()) + .await + { + if code.is_empty() { + eyre::bail!("contract {addr:?} does not have any code") + } } + } else if Some(TxKind::Create) == req.to { + eyre::bail!("tx req is a contract deployment"); + } else { + eyre::bail!("recipient is None"); } } return Err(err).wrap_err( - "could not decode output. did you specify the wrong function return data type perhaps?" + "could not decode output; did you specify the wrong function return data type?" ); } }; } + // handle case when return type is not specified Ok(if decoded.is_empty() { - format!("{res}\n") + res.to_string() + } else if shell::is_json() { + let tokens = decoded.iter().map(format_token_raw).collect::>(); + serde_json::to_string_pretty(&tokens).unwrap() } else { // seth compatible user-friendly return type conversions - decoded - .iter() - .map(TokenDisplay) - .map(|token| token.to_string()) - .collect::>() - .join("\n") + decoded.iter().map(format_token).collect::>().join("\n") }) } @@ -139,42 +180,46 @@ where /// /// # Example /// - /// ```no_run - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::{Address, Chain}; - /// use ethers_providers::{Provider, Http}; - /// use std::{str::FromStr, convert::TryFrom}; + /// ``` + /// use cast::{Cast}; + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_rpc_types::{TransactionRequest}; + /// use alloy_serde::WithOtherFields; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; + /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; + /// + /// sol!( + /// function greeting(uint256 i) public returns (string); + /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greeting(uint256)(string)"; - /// let args = vec!["5".to_owned()]; - /// let mut builder = TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder - /// .set_args(sig, args).await?; - /// let builder_output = builder.peek(); + /// let greeting = greetingCall { i: U256::from(5) }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()); + /// let tx = WithOtherFields::new(tx); /// let cast = Cast::new(&provider); - /// let access_list = cast.access_list(builder_output, None, false).await?; + /// let access_list = cast.access_list(&tx, None).await?; /// println!("{}", access_list); /// # Ok(()) /// # } /// ``` pub async fn access_list( &self, - builder_output: TxBuilderPeekOutput<'_>, + req: &WithOtherFields, block: Option, - to_json: bool, ) -> Result { - let (tx, _) = builder_output; - let access_list = self.provider.create_access_list(tx, block).await?; - let res = if to_json { + let access_list = + self.provider.create_access_list(req).block_id(block.unwrap_or_default()).await?; + let res = if shell::is_json() { serde_json::to_string(&access_list)? } else { let mut s = vec![format!("gas used: {}", access_list.gas_used), "access list:".to_string()]; for al in access_list.access_list.0 { - s.push(format!("- address: {}", SimpleCast::to_checksum_address(&al.address))); + s.push(format!("- address: {}", &al.address.to_checksum(None))); if !al.storage_keys.is_empty() { s.push(" keys:".to_string()); for key in al.storage_keys { @@ -188,188 +233,138 @@ where Ok(res) } - pub async fn balance + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - Ok(self.provider.get_balance(who, block).await?) + pub async fn balance(&self, who: Address, block: Option) -> Result { + Ok(self.provider.get_balance(who).block_id(block.unwrap_or_default()).await?) } /// Sends a transaction to the specified address /// /// # Example /// - /// ```no_run - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::{Address, Chain, U256}; - /// use ethers_providers::{Provider, Http}; - /// use std::{str::FromStr, convert::TryFrom}; + /// ``` + /// use cast::{Cast}; + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_serde::WithOtherFields; + /// use alloy_rpc_types::{TransactionRequest}; + /// use alloy_provider::{RootProvider, ProviderBuilder, network::AnyNetwork}; + /// use std::str::FromStr; + /// use alloy_sol_types::{sol, SolCall}; + /// + /// sol!( + /// function greet(string greeting) public; + /// ); /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let from = "vitalik.eth"; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; + /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greet(string)()"; - /// let args = vec!["hello".to_owned()]; + /// let greeting = greetCall { greeting: "hello".to_string() }.abi_encode(); + /// let bytes = Bytes::from_iter(greeting.iter()); /// let gas = U256::from_str("200000").unwrap(); /// let value = U256::from_str("1").unwrap(); /// let nonce = U256::from_str("1").unwrap(); - /// let mut builder = TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder - /// .set_args(sig, args).await? - /// .set_gas(gas) - /// .set_value(value) - /// .set_nonce(nonce); - /// let builder_output = builder.build(); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from); + /// let tx = WithOtherFields::new(tx); /// let cast = Cast::new(provider); - /// let data = cast.send(builder_output).await?; - /// println!("{}", *data); + /// let data = cast.send(tx).await?; + /// println!("{:#?}", data); /// # Ok(()) /// # } /// ``` - pub async fn send<'a>( + pub async fn send( &self, - builder_output: TxBuilderOutput, - ) -> Result> { - let (tx, _) = builder_output; - let res = self.provider.send_transaction(tx, None).await?; + tx: WithOtherFields, + ) -> Result> { + let res = self.provider.send_transaction(tx).await?; - Ok::<_, eyre::Error>(res) + Ok(res) } /// Publishes a raw transaction to the network /// /// # Example /// - /// ```no_run + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let res = cast.publish("0x1234".to_string()).await?; /// println!("{:?}", res); /// # Ok(()) /// # } /// ``` - pub async fn publish(&self, mut raw_tx: String) -> Result> { + pub async fn publish( + &self, + mut raw_tx: String, + ) -> Result> { raw_tx = match raw_tx.strip_prefix("0x") { Some(s) => s.to_string(), None => raw_tx, }; - let tx = Bytes::from(hex::decode(raw_tx)?); - let res = self.provider.send_raw_transaction(tx).await?; + let tx = hex::decode(raw_tx)?; + let res = self.provider.send_raw_transaction(&tx).await?; - Ok::<_, eyre::Error>(res) + Ok(res) } - /// Estimates the gas cost of a transaction - /// /// # Example /// - /// ```no_run - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::{Address, Chain, U256}; - /// use ethers_providers::{Provider, Http}; - /// use std::{str::FromStr, convert::TryFrom}; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let from = "vitalik.eth"; - /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greet(string)()"; - /// let args = vec!["5".to_owned()]; - /// let value = U256::from_str("1").unwrap(); - /// let mut builder = TxBuilder::new(&provider, from, Some(to), Chain::Mainnet, false).await?; - /// builder - /// .set_value(value) - /// .set_args(sig, args).await?; - /// let builder_output = builder.peek(); - /// let cast = Cast::new(&provider); - /// let data = cast.estimate(builder_output).await?; - /// println!("{}", data); - /// # Ok(()) - /// # } /// ``` - pub async fn estimate(&self, builder_output: TxBuilderPeekOutput<'_>) -> Result { - let (tx, _) = builder_output; - - let res = self.provider.estimate_gas(tx, None).await?; - - Ok::<_, eyre::Error>(res) - } - - /// # Example - /// - /// ```no_run + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let block = cast.block(5, true, None, false).await?; + /// let block = cast.block(5, true, None).await?; /// println!("{}", block); /// # Ok(()) /// # } /// ``` - pub async fn block>( + pub async fn block>( &self, - block: T, + block: B, full: bool, field: Option, - to_json: bool, ) -> Result { let block = block.into(); - let block = if full { - let block = self - .provider - .get_block_with_txs(block) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - if let Some(ref field) = field { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } else if to_json { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() + if let Some(ref field) = field { + if field == "transactions" && !full { + eyre::bail!("use --full to view transactions") } + } + + let block = self + .provider + .get_block(block, full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; + + let block = if let Some(ref field) = field { + get_pretty_block_attr(&block, field) + .unwrap_or_else(|| format!("{field} is not a valid block field")) + } else if shell::is_json() { + serde_json::to_value(&block).unwrap().to_string() } else { - let block = self - .provider - .get_block(block) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - - if let Some(ref field) = field { - if field == "transactions" { - "use --full to view transactions".to_string() - } else { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } - } else if to_json { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() - } + block.pretty() }; Ok(block) } - async fn block_field_as_num>(&self, block: T, field: String) -> Result { + async fn block_field_as_num>(&self, block: B, field: String) -> Result { let block = block.into(); - let block_field = Cast::block( + let block_field = Self::block( self, block, false, // Select only select field Some(field), - false, ) .await?; @@ -381,37 +376,34 @@ where Ok(ret) } - pub async fn base_fee>(&self, block: T) -> Result { - Cast::block_field_as_num(self, block, String::from("baseFeePerGas")).await + pub async fn base_fee>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, String::from("baseFeePerGas")).await } - pub async fn age>(&self, block: T) -> Result { + pub async fn age>(&self, block: B) -> Result { let timestamp_str = - Cast::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); - let datetime = - NaiveDateTime::from_timestamp_opt(timestamp_str.parse::().unwrap(), 0).unwrap(); + Self::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); + let datetime = DateTime::from_timestamp(timestamp_str.parse::().unwrap(), 0).unwrap(); Ok(datetime.format("%a %b %e %H:%M:%S %Y").to_string()) } - pub async fn timestamp>(&self, block: T) -> Result { - Cast::block_field_as_num(self, block, "timestamp".to_string()).await + pub async fn timestamp>(&self, block: B) -> Result { + Self::block_field_as_num(self, block, "timestamp".to_string()).await } pub async fn chain(&self) -> Result<&str> { - let genesis_hash = Cast::block( + let genesis_hash = Self::block( self, 0, false, // Select only block hash Some(String::from("hash")), - false, ) .await?; Ok(match &genesis_hash[..] { "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { - match &(Cast::block(self, 1920000, false, Some("hash".to_string()), false).await?)[..] - { + match &(Self::block(self, 1920000, false, Some("hash".to_string())).await?)[..] { "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { "etclive" } @@ -429,6 +421,10 @@ where "0x02adc9b449ff5f2467b8c674ece7ff9b21319d76c4ad62a67a70d552655927e5" => { "optimism-kovan" } + "0x521982bd54239dc71269eefb58601762cc15cfb2978e0becb46af7962ed6bfaa" => "fraxtal", + "0x910f5c4084b63fd860d0c2f9a04615115a5a991254700b39ba072290dbd77489" => { + "fraxtal-testnet" + } "0x7ee576b35482195fc49205cec9af72ce14f003b9ae69f6ba0faef4514be8b442" => { "arbitrum-mainnet" } @@ -436,48 +432,55 @@ where "0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177" => "rinkeby", "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a" => "goerli", "0x14c2283285a88fe5fce9bf5c573ab03d6616695d717b12a127188bcacfc743c4" => "kotti", - "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon", - "0x7b66506a9ebdbf30d32b43c5f15a3b1216269a1ec3a75aa3182b86176a2b1ca7" => { - "polygon-mumbai" + "0xa9c28ce2141b56c474f1dc504bee9b01eb1bd7d1a507580d5519d4437a97de1b" => "polygon-pos", + "0x7202b2b53c5a0836e773e319d18922cc756dd67432f9a1f65352b61f4406c697" => { + "polygon-pos-amoy-testnet" + } + "0x81005434635456a16f74ff7023fbe0bf423abbc8a8deb093ffff455c0ad3b741" => "polygon-zkevm", + "0x676c1a76a6c5855a32bdf7c61977a0d1510088a4eeac1330466453b3d08b60b9" => { + "polygon-zkevm-cardona-testnet" } "0x4f1dd23188aab3a76b463e4af801b52b1248ef073c648cbdc4c9333d3da79756" => "gnosis", "0xada44fd8d2ecab8b08f256af07ad3e777f17fb434f8f8e678b312f576212ba9a" => "chiado", "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { - match &(Cast::block(self, 1, false, Some(String::from("hash")), false).await?)[..] { + match &(Self::block(self, 1, false, Some(String::from("hash"))).await?)[..] { "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { "avalanche-fuji" } _ => "avalanche", } } + "0x23a2658170ba70d014ba0d0d2709f8fbfe2fa660cd868c5f282f991eecbe38ee" => "ink", + "0xe5fd5cf0be56af58ad5751b401410d6b7a09d830fa459789746a3d0dd1c79834" => "ink-sepolia", _ => "unknown", }) } - pub async fn chain_id(&self) -> Result { - Ok(self.provider.get_chainid().await?) + pub async fn chain_id(&self) -> Result { + Ok(self.provider.get_chain_id().await?) } - pub async fn block_number(&self) -> Result { + pub async fn block_number(&self) -> Result { Ok(self.provider.get_block_number().await?) } - pub async fn gas_price(&self) -> Result { + pub async fn gas_price(&self) -> Result { Ok(self.provider.get_gas_price().await?) } /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::Address; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let nonce = cast.nonce(addr, None).await?; @@ -485,53 +488,136 @@ where /// # Ok(()) /// # } /// ``` - pub async fn nonce + Send + Sync>( + pub async fn nonce(&self, who: Address, block: Option) -> Result { + Ok(self.provider.get_transaction_count(who).block_id(block.unwrap_or_default()).await?) + } + + /// #Example + /// + /// ``` + /// use alloy_primitives::{Address, FixedBytes}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use cast::Cast; + /// use std::str::FromStr; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; + /// let slots = vec![FixedBytes::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")?]; + /// let codehash = cast.codehash(addr, slots, None).await?; + /// println!("{}", codehash); + /// # Ok(()) + /// # } + pub async fn codehash( &self, - who: T, + who: Address, + slots: Vec, block: Option, - ) -> Result { - Ok(self.provider.get_transaction_count(who, block).await?) + ) -> Result { + Ok(self + .provider + .get_proof(who, slots) + .block_id(block.unwrap_or_default()) + .await? + .code_hash + .to_string()) + } + + /// #Example + /// + /// ``` + /// use alloy_primitives::{Address, FixedBytes}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use cast::Cast; + /// use std::str::FromStr; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; + /// let slots = vec![FixedBytes::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")?]; + /// let storage_root = cast.storage_root(addr, slots, None).await?; + /// println!("{}", storage_root); + /// # Ok(()) + /// # } + pub async fn storage_root( + &self, + who: Address, + slots: Vec, + block: Option, + ) -> Result { + Ok(self + .provider + .get_proof(who, slots) + .block_id(block.unwrap_or_default()) + .await? + .storage_hash + .to_string()) } /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::Address; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; - /// let implementation = cast.implementation(addr, None).await?; + /// let implementation = cast.implementation(addr, false, None).await?; /// println!("{}", implementation); /// # Ok(()) /// # } /// ``` - pub async fn implementation + Send + Sync>( + pub async fn implementation( &self, - who: T, + who: Address, + is_beacon: bool, block: Option, ) -> Result { - let slot = - H256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")?; - let value = self.provider.get_storage_at(who, slot, block).await?; - let addr: H160 = value.into(); + let slot = match is_beacon { + true => { + // Use the beacon slot : bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) + B256::from_str( + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + )? + } + false => { + // Use the implementation slot : + // bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) + B256::from_str( + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + )? + } + }; + + let value = self + .provider + .get_storage_at(who, slot.into()) + .block_id(block.unwrap_or_default()) + .await?; + let addr = Address::from_word(value.into()); Ok(format!("{addr:?}")) } /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::Address; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let admin = cast.admin(addr, None).await?; @@ -539,62 +625,52 @@ where /// # Ok(()) /// # } /// ``` - pub async fn admin + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { + pub async fn admin(&self, who: Address, block: Option) -> Result { let slot = - H256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")?; - let value = self.provider.get_storage_at(who, slot, block).await?; - let addr: H160 = value.into(); + B256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")?; + let value = self + .provider + .get_storage_at(who, slot.into()) + .block_id(block.unwrap_or_default()) + .await?; + let addr = Address::from_word(value.into()); Ok(format!("{addr:?}")) } /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::{Address, U256}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::Address; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; - /// let nonce = cast.nonce(addr, None).await? + 5; - /// let computed_address = cast.compute_address(addr, Some(nonce)).await?; - /// println!("Computed address for address {} with nonce {}: {}", addr, nonce, computed_address); - /// let computed_address_no_nonce = cast.compute_address(addr, None).await?; - /// println!("Computed address for address {} with nonce {}: {}", addr, nonce, computed_address_no_nonce); + /// let addr = Address::from_str("7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; + /// let computed_address = cast.compute_address(addr, None).await?; + /// println!("Computed address for address {addr}: {computed_address}"); /// # Ok(()) /// # } /// ``` - pub async fn compute_address + Copy + Send + Sync>( - &self, - address: T, - nonce: Option, - ) -> Result

{ - let unpacked = if let Some(n) = nonce { - n - } else { - self.provider.get_transaction_count(address.into(), None).await? - }; - - Ok(get_contract_address(address, unpacked)) + pub async fn compute_address(&self, address: Address, nonce: Option) -> Result
{ + let unpacked = if let Some(n) = nonce { n } else { self.nonce(address, None).await? }; + Ok(address.create(unpacked)) } /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::Address; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let code = cast.code(addr, None, false).await?; @@ -602,30 +678,35 @@ where /// # Ok(()) /// # } /// ``` - pub async fn code + Send + Sync>( + pub async fn code( &self, - who: T, + who: Address, block: Option, disassemble: bool, ) -> Result { if disassemble { - let code = self.provider.get_code(who, block).await?.to_vec(); - Ok(format_operations(disassemble_bytes(code)?)?) + let code = + self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await?.to_vec(); + SimpleCast::disassemble(&code) } else { - Ok(format!("{}", self.provider.get_code(who, block).await?)) + Ok(format!( + "{}", + self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await? + )) } } /// Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::Address; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let codesize = cast.codesize(addr, None).await?; @@ -633,26 +714,24 @@ where /// # Ok(()) /// # } /// ``` - pub async fn codesize + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - let code = self.provider.get_code(who, block).await?.to_vec(); + pub async fn codesize(&self, who: Address, block: Option) -> Result { + let code = + self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await?.to_vec(); Ok(format!("{}", code.len())) } /// # Example /// - /// ```no_run + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let tx = cast.transaction(tx_hash.to_string(), None, false, false).await?; + /// let tx = cast.transaction(tx_hash.to_string(), None, false).await?; /// println!("{}", tx); /// # Ok(()) /// # } @@ -662,21 +741,20 @@ where tx_hash: String, field: Option, raw: bool, - to_json: bool, ) -> Result { - let tx_hash = H256::from_str(&tx_hash).wrap_err("invalid tx hash")?; + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; let tx = self .provider - .get_transaction(tx_hash) + .get_transaction_by_hash(tx_hash) .await? .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; Ok(if raw { - format!("0x{}", hex::encode(tx.rlp())) + format!("0x{}", hex::encode(tx.inner.inner.encoded_2718())) } else if let Some(field) = field { - get_pretty_tx_attr(&tx, field.as_str()) + get_pretty_tx_attr(&tx.inner, field.as_str()) .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? - } else if to_json { + } else if shell::is_json() { // to_value first to sort json object keys serde_json::to_value(&tx)?.to_string() } else { @@ -686,15 +764,16 @@ where /// # Example /// - /// ```no_run + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; - /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, false, false).await?; + /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, None, false).await?; /// println!("{}", receipt); /// # Ok(()) /// # } @@ -703,11 +782,11 @@ where &self, tx_hash: String, field: Option, - confs: usize, + confs: u64, + timeout: Option, cast_async: bool, - to_json: bool, ) -> Result { - let tx_hash = H256::from_str(&tx_hash).wrap_err("invalid tx hash")?; + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; let mut receipt: TransactionReceiptWithRevertReason = match self.provider.get_transaction_receipt(tx_hash).await? { @@ -718,13 +797,11 @@ where if cast_async { eyre::bail!("tx not found: {:?}", tx_hash) } else { - let tx = PendingTransaction::new(tx_hash, self.provider.provider()); - tx.confirmations(confs).await?.ok_or_else(|| { - eyre::eyre!( - "tx not found, might have been dropped from mempool: {:?}", - tx_hash - ) - })? + PendingTransactionBuilder::new(self.provider.root().clone(), tx_hash) + .with_required_confirmations(confs) + .with_timeout(timeout.map(Duration::from_secs)) + .get_receipt() + .await? } } } @@ -736,7 +813,7 @@ where Ok(if let Some(ref field) = field { get_pretty_tx_receipt_attr(&receipt, field) .ok_or_else(|| eyre::eyre!("invalid receipt field: {}", field))? - } else if to_json { + } else if shell::is_json() { // to_value first to sort json object keys serde_json::to_value(&receipt)?.to_string() } else { @@ -748,24 +825,29 @@ where /// /// # Example /// - /// ```no_run + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); - /// let result = cast.rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) + /// let result = cast + /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) /// .await?; /// println!("{}", result); /// # Ok(()) /// # } /// ``` - pub async fn rpc(&self, method: &str, params: T) -> Result + pub async fn rpc(&self, method: &str, params: V) -> Result where - T: std::fmt::Debug + serde::Serialize + Send + Sync, + V: alloy_json_rpc::RpcSend, { - let res = self.provider.provider().request::(method, params).await?; + let res = self + .provider + .raw_request::(Cow::Owned(method.to_string()), params) + .await?; Ok(serde_json::to_string(&res)?) } @@ -773,35 +855,44 @@ where /// /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::{Address, B256}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Provider, Http}; - /// use ethers_core::types::{Address, H256}; - /// use std::{str::FromStr, convert::TryFrom}; + /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; - /// let slot = H256::zero(); + /// let slot = B256::ZERO; /// let storage = cast.storage(addr, slot, None).await?; /// println!("{}", storage); /// # Ok(()) /// # } /// ``` - pub async fn storage + Send + Sync>( + pub async fn storage( &self, - from: T, - slot: H256, + from: Address, + slot: B256, block: Option, ) -> Result { - Ok(format!("{:?}", self.provider.get_storage_at(from, slot, block).await?)) - } - - pub async fn filter_logs(&self, filter: Filter, to_json: bool) -> Result { + Ok(format!( + "{:?}", + B256::from( + self.provider + .get_storage_at(from, slot.into()) + .block_id(block.unwrap_or_default()) + .await? + ) + )) + } + + pub async fn filter_logs(&self, filter: Filter) -> Result { let logs = self.provider.get_logs(&filter).await?; - let res = if to_json { + let res = if shell::is_json() { serde_json::to_string(&logs)? } else { let mut s = vec![]; @@ -816,18 +907,163 @@ where }; Ok(res) } -} -pub struct InterfaceSource { - pub name: String, - pub source: String, -} + /// Converts a block identifier into a block number. + /// + /// If the block identifier is a block number, then this function returns the block number. If + /// the block identifier is a block hash, then this function returns the block number of + /// that block hash. If the block identifier is `None`, then this function returns `None`. + /// + /// # Example + /// + /// ``` + /// use alloy_primitives::fixed_bytes; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_rpc_types::{BlockId, BlockNumberOrTag}; + /// use cast::Cast; + /// use std::{convert::TryFrom, str::FromStr}; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// + /// let block_number = cast.convert_block_number(Some(BlockId::number(5))).await?; + /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(5))); + /// + /// let block_number = cast + /// .convert_block_number(Some(BlockId::hash(fixed_bytes!( + /// "0000000000000000000000000000000000000000000000000000000000001234" + /// )))) + /// .await?; + /// assert_eq!(block_number, Some(BlockNumberOrTag::Number(4660))); + /// + /// let block_number = cast.convert_block_number(None).await?; + /// assert_eq!(block_number, None); + /// # Ok(()) + /// # } + /// ``` + pub async fn convert_block_number( + &self, + block: Option, + ) -> Result, eyre::Error> { + match block { + Some(block) => match block { + BlockId::Number(block_number) => Ok(Some(block_number)), + BlockId::Hash(hash) => { + let block = + self.provider.get_block_by_hash(hash.block_hash, false.into()).await?; + Ok(block.map(|block| block.header.number).map(BlockNumberOrTag::from)) + } + }, + None => Ok(None), + } + } + + /// Sets up a subscription to the given filter and writes the logs to the given output. + /// + /// # Example + /// + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_rpc_types::Filter; + /// use alloy_transport::BoxTransport; + /// use cast::Cast; + /// use std::{io, str::FromStr}; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("wss://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// + /// let filter = + /// Filter::new().address(Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?); + /// let mut output = io::stdout(); + /// cast.subscribe(filter, &mut output).await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn subscribe(&self, filter: Filter, output: &mut dyn io::Write) -> Result<()> { + // Initialize the subscription stream for logs + let mut subscription = self.provider.subscribe_logs(&filter).await?.into_stream(); -// Local is a path to the directory containing the ABI files -// In case of etherscan, ABI is fetched from the address on the chain -pub enum AbiPath { - Local { path: String, name: Option }, - Etherscan { address: Address, chain: Chain, api_key: String }, + // Check if a to_block is specified, if so, subscribe to blocks + let mut block_subscription = if filter.get_to_block().is_some() { + Some(self.provider.subscribe_blocks().await?.into_stream()) + } else { + None + }; + + let format_json = shell::is_json(); + let to_block_number = filter.get_to_block(); + + // If output should be JSON, start with an opening bracket + if format_json { + write!(output, "[")?; + } + + let mut first = true; + + loop { + tokio::select! { + // If block subscription is present, listen to it to avoid blocking indefinitely past the desired to_block + block = if let Some(bs) = &mut block_subscription { + Either::Left(bs.next().fuse()) + } else { + Either::Right(futures::future::pending()) + } => { + if let (Some(block), Some(to_block)) = (block, to_block_number) { + if block.number > to_block { + break; + } + } + }, + // Process incoming log + log = subscription.next() => { + if format_json { + if !first { + write!(output, ",")?; + } + first = false; + let log_str = serde_json::to_string(&log).unwrap(); + write!(output, "{log_str}")?; + } else { + let log_str = log.pretty() + .replacen('\n', "- ", 1) // Remove empty first line + .replace('\n', "\n "); // Indent + writeln!(output, "{log_str}")?; + } + }, + // Break on cancel signal, to allow for closing JSON bracket + _ = ctrl_c() => { + break; + }, + else => break, + } + } + + // If output was JSON, end with a closing bracket + if format_json { + write!(output, "]")?; + } + + Ok(()) + } + + pub async fn erc20_balance( + &self, + token: Address, + owner: Address, + block: Option, + ) -> Result { + Ok(IERC20::new(token, &self.provider) + .balanceOf(owner) + .block(block.unwrap_or_default()) + .call() + .await? + ._0) + } } pub struct SimpleCast; @@ -838,11 +1074,12 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast; - /// # use ethers_core::types::{I256, U256}; - /// assert_eq!(SimpleCast::max_int("uint256")?, format!("{}", U256::MAX)); - /// assert_eq!(SimpleCast::max_int("int256")?, format!("{}", I256::MAX)); - /// assert_eq!(SimpleCast::max_int("int32")?, format!("{}", i32::MAX)); + /// use alloy_primitives::{I256, U256}; + /// use cast::SimpleCast; + /// + /// assert_eq!(SimpleCast::max_int("uint256")?, U256::MAX.to_string()); + /// assert_eq!(SimpleCast::max_int("int256")?, I256::MAX.to_string()); + /// assert_eq!(SimpleCast::max_int("int32")?, i32::MAX.to_string()); /// # Ok::<(), eyre::Report>(()) /// ``` pub fn max_int(s: &str) -> Result { @@ -854,11 +1091,12 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast; - /// # use ethers_core::types::{I256, U256}; + /// use alloy_primitives::{I256, U256}; + /// use cast::SimpleCast; + /// /// assert_eq!(SimpleCast::min_int("uint256")?, "0"); - /// assert_eq!(SimpleCast::min_int("int256")?, format!("{}", I256::MIN)); - /// assert_eq!(SimpleCast::min_int("int32")?, format!("{}", i32::MIN)); + /// assert_eq!(SimpleCast::min_int("int256")?, I256::MIN.to_string()); + /// assert_eq!(SimpleCast::min_int("int32")?, i32::MIN.to_string()); /// # Ok::<(), eyre::Report>(()) /// ``` pub fn min_int(s: &str) -> Result { @@ -866,24 +1104,23 @@ impl SimpleCast { } fn max_min_int(s: &str) -> Result { - let ty = HumanReadableParser::parse_type(s) - .wrap_err("Invalid type, expected `(u)int`")?; + let ty = DynSolType::parse(s).wrap_err("Invalid type, expected `(u)int`")?; match ty { - ParamType::Int(n) => { - let mask = U256::one() << U256::from(n - 1); - let max = (U256::MAX & mask) - 1; + DynSolType::Int(n) => { + let mask = U256::from(1).wrapping_shl(n - 1); + let max = (U256::MAX & mask).saturating_sub(U256::from(1)); if MAX { Ok(max.to_string()) } else { - let min = I256::from_raw(max).wrapping_neg() + I256::minus_one(); + let min = I256::from_raw(max).wrapping_neg() + I256::MINUS_ONE; Ok(min.to_string()) } } - ParamType::Uint(n) => { + DynSolType::Uint(n) => { if MAX { let mut max = U256::MAX; if n < 255 { - max &= U256::one() << U256::from(n); + max &= U256::from(1).wrapping_shl(n).wrapping_sub(U256::from(1)); } Ok(max.to_string()) } else { @@ -901,63 +1138,73 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::from_utf8("yo"), "0x796f"); - /// assert_eq!(Cast::from_utf8("Hello, World!"), "0x48656c6c6f2c20576f726c6421"); - /// assert_eq!(Cast::from_utf8("TurboDappTools"), "0x547572626f44617070546f6f6c73"); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::from_utf8("yo"), "0x796f"); + /// assert_eq!(Cast::from_utf8("Hello, World!"), "0x48656c6c6f2c20576f726c6421"); + /// assert_eq!(Cast::from_utf8("TurboDappTools"), "0x547572626f44617070546f6f6c73"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_utf8(s: &str) -> String { hex::encode_prefixed(s) } - /// Converts hex data into text data + /// Converts hex input to UTF-8 text /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_ascii("0x796f")?, "yo"); - /// assert_eq!(Cast::to_ascii("48656c6c6f2c20576f726c6421")?, "Hello, World!"); - /// assert_eq!(Cast::to_ascii("0x547572626f44617070546f6f6c73")?, "TurboDappTools"); + /// assert_eq!(Cast::to_utf8("0x796f")?, "yo"); + /// assert_eq!(Cast::to_utf8("0x48656c6c6f2c20576f726c6421")?, "Hello, World!"); + /// assert_eq!(Cast::to_utf8("0x547572626f44617070546f6f6c73")?, "TurboDappTools"); + /// assert_eq!(Cast::to_utf8("0xe4bda0e5a5bd")?, "你好"); + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn to_utf8(s: &str) -> Result { + let bytes = hex::decode(s)?; + Ok(String::from_utf8_lossy(bytes.as_ref()).to_string()) + } + + /// Converts hex data into text data + /// + /// # Example /// - /// Ok(()) - /// } + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// assert_eq!(Cast::to_ascii("0x796f")?, "yo"); + /// assert_eq!(Cast::to_ascii("48656c6c6f2c20576f726c6421")?, "Hello, World!"); + /// assert_eq!(Cast::to_ascii("0x547572626f44617070546f6f6c73")?, "TurboDappTools"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_ascii(hex: &str) -> Result { let bytes = hex::decode(hex)?; if !bytes.iter().all(u8::is_ascii) { - return Err(eyre::eyre!("Invalid ASCII bytes")) + return Err(eyre::eyre!("Invalid ASCII bytes")); } Ok(String::from_utf8(bytes).unwrap()) } /// Converts fixed point number into specified number of decimals /// ``` + /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::U256; - /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::from_fixed_point("10", "0")?, "10"); - /// assert_eq!(Cast::from_fixed_point("1.0", "1")?, "10"); - /// assert_eq!(Cast::from_fixed_point("0.10", "2")?, "10"); - /// assert_eq!(Cast::from_fixed_point("0.010", "3")?, "10"); /// - /// Ok(()) - /// } + /// assert_eq!(Cast::from_fixed_point("10", "0")?, "10"); + /// assert_eq!(Cast::from_fixed_point("1.0", "1")?, "10"); + /// assert_eq!(Cast::from_fixed_point("0.10", "2")?, "10"); + /// assert_eq!(Cast::from_fixed_point("0.010", "3")?, "10"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_fixed_point(value: &str, decimals: &str) -> Result { - // first try u32 as Units assumes a string can only be "ether", "gwei"... and not a number - let units = match decimals.parse::() { - Ok(d) => Units::Other(d), - Err(_) => Units::try_from(decimals)?, + // TODO: https://github.com/alloy-rs/core/pull/461 + let units: Unit = if let Ok(x) = decimals.parse() { + Unit::new(x).ok_or_else(|| eyre::eyre!("invalid unit"))? + } else { + decimals.parse()? }; - let n: NumberWithBase = parse_units(value, units.as_num())?.into(); - Ok(format!("{n}")) + let n = ParseUnits::parse_units(value, units)?; + Ok(n.to_string()) } /// Converts integers with specified decimals into fixed point numbers @@ -965,22 +1212,19 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::U256; - /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_fixed_point("10", "0")?, "10."); - /// assert_eq!(Cast::to_fixed_point("10", "1")?, "1.0"); - /// assert_eq!(Cast::to_fixed_point("10", "2")?, "0.10"); - /// assert_eq!(Cast::to_fixed_point("10", "3")?, "0.010"); /// - /// assert_eq!(Cast::to_fixed_point("-10", "0")?, "-10."); - /// assert_eq!(Cast::to_fixed_point("-10", "1")?, "-1.0"); - /// assert_eq!(Cast::to_fixed_point("-10", "2")?, "-0.10"); - /// assert_eq!(Cast::to_fixed_point("-10", "3")?, "-0.010"); + /// assert_eq!(Cast::to_fixed_point("10", "0")?, "10."); + /// assert_eq!(Cast::to_fixed_point("10", "1")?, "1.0"); + /// assert_eq!(Cast::to_fixed_point("10", "2")?, "0.10"); + /// assert_eq!(Cast::to_fixed_point("10", "3")?, "0.010"); /// - /// Ok(()) - /// } + /// assert_eq!(Cast::to_fixed_point("-10", "0")?, "-10."); + /// assert_eq!(Cast::to_fixed_point("-10", "1")?, "-1.0"); + /// assert_eq!(Cast::to_fixed_point("-10", "2")?, "-0.10"); + /// assert_eq!(Cast::to_fixed_point("-10", "3")?, "-0.010"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_fixed_point(value: &str, decimals: &str) -> Result { let (sign, mut value, value_len) = { @@ -991,7 +1235,7 @@ impl SimpleCast { let value_len = value_stripped.len(); (sign, value_stripped, value_len) }; - let decimals = NumberWithBase::parse_uint(decimals, None)?.number().low_u64() as usize; + let decimals = NumberWithBase::parse_uint(decimals, None)?.number().to::(); let value = if decimals >= value_len { // Add "0." and pad with 0s @@ -1012,12 +1256,9 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001"); - /// assert_eq!(Cast::concat_hex(["1", "2"]), "0x12"); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::concat_hex(["0x00", "0x01"]), "0x0001"); + /// assert_eq!(Cast::concat_hex(["1", "2"]), "0x12"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn concat_hex>(values: impl IntoIterator) -> String { let mut out = String::new(); @@ -1028,110 +1269,154 @@ impl SimpleCast { format!("0x{out}") } - /// Converts an Ethereum address to its checksum format - /// according to [EIP-55](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md) + /// Converts a number into uint256 hex string with 0x prefix /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; - /// use ethers_core::types::Address; - /// use std::str::FromStr; - /// - /// # fn main() -> eyre::Result<()> { - /// let addr = Address::from_str("0xb7e390864a90b7b923c9f9310c6f98aafe43f707")?; - /// let addr = Cast::to_checksum_address(&addr); - /// assert_eq!(addr, "0xB7e390864a90b7b923C9f9310C6F98aafE43F707"); /// - /// # Ok(()) - /// # } + /// assert_eq!( + /// Cast::to_uint256("100")?, + /// "0x0000000000000000000000000000000000000000000000000000000000000064" + /// ); + /// assert_eq!( + /// Cast::to_uint256("192038293923")?, + /// "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3" + /// ); + /// assert_eq!( + /// Cast::to_uint256( + /// "115792089237316195423570985008687907853269984665640564039457584007913129639935" + /// )?, + /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + /// ); + /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn to_checksum_address(address: &Address) -> String { - ethers_core::utils::to_checksum(address, None) + pub fn to_uint256(value: &str) -> Result { + let n = NumberWithBase::parse_uint(value, None)?; + Ok(format!("{n:#066x}")) } - /// Converts a number into uint256 hex string with 0x prefix + /// Converts a number into int256 hex string with 0x prefix /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_uint256("100")?, "0x0000000000000000000000000000000000000000000000000000000000000064"); - /// assert_eq!(Cast::to_uint256("192038293923")?, "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3"); - /// assert_eq!( - /// Cast::to_uint256("115792089237316195423570985008687907853269984665640564039457584007913129639935")?, - /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - /// ); - /// - /// Ok(()) - /// } + /// assert_eq!( + /// Cast::to_int256("0")?, + /// "0x0000000000000000000000000000000000000000000000000000000000000000" + /// ); + /// assert_eq!( + /// Cast::to_int256("100")?, + /// "0x0000000000000000000000000000000000000000000000000000000000000064" + /// ); + /// assert_eq!( + /// Cast::to_int256("-100")?, + /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c" + /// ); + /// assert_eq!( + /// Cast::to_int256("192038293923")?, + /// "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3" + /// ); + /// assert_eq!( + /// Cast::to_int256("-192038293923")?, + /// "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffd349a02e5d" + /// ); + /// assert_eq!( + /// Cast::to_int256( + /// "57896044618658097711785492504343953926634992332820282019728792003956564819967" + /// )?, + /// "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + /// ); + /// assert_eq!( + /// Cast::to_int256( + /// "-57896044618658097711785492504343953926634992332820282019728792003956564819968" + /// )?, + /// "0x8000000000000000000000000000000000000000000000000000000000000000" + /// ); + /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn to_uint256(value: &str) -> Result { - let n = NumberWithBase::parse_uint(value, None)?; + pub fn to_int256(value: &str) -> Result { + let n = NumberWithBase::parse_int(value, None)?; Ok(format!("{n:#066x}")) } - /// Converts a number into int256 hex string with 0x prefix + /// Converts an eth amount into a specified unit /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_int256("0")?, "0x0000000000000000000000000000000000000000000000000000000000000000"); - /// assert_eq!(Cast::to_int256("100")?, "0x0000000000000000000000000000000000000000000000000000000000000064"); - /// assert_eq!(Cast::to_int256("-100")?, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c"); - /// assert_eq!(Cast::to_int256("192038293923")?, "0x0000000000000000000000000000000000000000000000000000002cb65fd1a3"); - /// assert_eq!(Cast::to_int256("-192038293923")?, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffd349a02e5d"); - /// assert_eq!( - /// Cast::to_int256("57896044618658097711785492504343953926634992332820282019728792003956564819967")?, - /// "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" - /// ); - /// assert_eq!( - /// Cast::to_int256("-57896044618658097711785492504343953926634992332820282019728792003956564819968")?, - /// "0x8000000000000000000000000000000000000000000000000000000000000000" - /// ); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::to_unit("1 wei", "wei")?, "1"); + /// assert_eq!(Cast::to_unit("1", "wei")?, "1"); + /// assert_eq!(Cast::to_unit("1ether", "wei")?, "1000000000000000000"); + /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn to_int256(value: &str) -> Result { - let n = NumberWithBase::parse_int(value, None)?; - Ok(format!("{n:#066x}")) + pub fn to_unit(value: &str, unit: &str) -> Result { + let value = DynSolType::coerce_str(&DynSolType::Uint(256), value)? + .as_uint() + .wrap_err("Could not convert to uint")? + .0; + let unit = unit.parse().wrap_err("could not parse units")?; + Ok(Self::format_unit_as_string(value, unit)) } - /// Converts an eth amount into a specified unit + /// Convert a number into a uint with arbitrary decimals. /// /// # Example /// /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_unit("1 wei", "wei")?, "1"); - /// assert_eq!(Cast::to_unit("1", "wei")?, "1"); - /// assert_eq!(Cast::to_unit("1ether", "wei")?, "1000000000000000000"); - /// assert_eq!(Cast::to_unit("100 gwei", "gwei")?, "100"); + /// # fn main() -> eyre::Result<()> { + /// assert_eq!(Cast::parse_units("1.0", 6)?, "1000000"); // USDC (6 decimals) + /// assert_eq!(Cast::parse_units("2.5", 6)?, "2500000"); + /// assert_eq!(Cast::parse_units("1.0", 12)?, "1000000000000"); // 12 decimals + /// assert_eq!(Cast::parse_units("1.23", 3)?, "1230"); // 3 decimals + /// # Ok(()) + /// # } + /// ``` + pub fn parse_units(value: &str, unit: u8) -> Result { + let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?; + + Ok(ParseUnits::parse_units(value, unit)?.to_string()) + } + + /// Format a number from smallest unit to decimal with arbitrary decimals. + /// + /// # Example /// - /// Ok(()) - /// } /// ``` - pub fn to_unit(value: &str, unit: &str) -> Result { - let value = U256::from(LenientTokenizer::tokenize_uint(value)?); - - Ok(match unit { - "eth" | "ether" => ethers_core::utils::format_units(value, 18)? - .trim_end_matches(".000000000000000000") - .to_string(), - "gwei" | "nano" | "nanoether" => ethers_core::utils::format_units(value, 9)? - .trim_end_matches(".000000000") - .to_string(), - "wei" => ethers_core::utils::format_units(value, 0)?.trim_end_matches(".0").to_string(), - _ => eyre::bail!("invalid unit: \"{}\"", unit), - }) + /// use cast::SimpleCast as Cast; + /// + /// # fn main() -> eyre::Result<()> { + /// assert_eq!(Cast::format_units("1000000", 6)?, "1"); // USDC (6 decimals) + /// assert_eq!(Cast::format_units("2500000", 6)?, "2.500000"); + /// assert_eq!(Cast::format_units("1000000000000", 12)?, "1"); // 12 decimals + /// assert_eq!(Cast::format_units("1230", 3)?, "1.230"); // 3 decimals + /// # Ok(()) + /// # } + /// ``` + pub fn format_units(value: &str, unit: u8) -> Result { + let value = NumberWithBase::parse_int(value, None)?.number(); + let unit = Unit::new(unit).ok_or_else(|| eyre::eyre!("invalid unit"))?; + Ok(Self::format_unit_as_string(value, unit)) + } + + // Helper function to format units as a string + fn format_unit_as_string(value: U256, unit: Unit) -> String { + let mut formatted = ParseUnits::U256(value).format_units(unit); + // Trim empty fractional part. + if let Some(dot) = formatted.find('.') { + let fractional = &formatted[dot + 1..]; + if fractional.chars().all(|c: char| c == '0') { + formatted = formatted[..dot].to_string(); + } + } + formatted } /// Converts wei into an eth amount @@ -1141,23 +1426,16 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::from_wei("1", "gwei")?, "0.000000001"); - /// assert_eq!(Cast::from_wei("12340000005", "gwei")?, "12.340000005"); - /// assert_eq!(Cast::from_wei("10", "ether")?, "0.000000000000000010"); - /// assert_eq!(Cast::from_wei("100", "eth")?, "0.000000000000000100"); - /// assert_eq!(Cast::from_wei("17", "")?, "0.000000000000000017"); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::from_wei("1", "gwei")?, "0.000000001"); + /// assert_eq!(Cast::from_wei("12340000005", "gwei")?, "12.340000005"); + /// assert_eq!(Cast::from_wei("10", "ether")?, "0.000000000000000010"); + /// assert_eq!(Cast::from_wei("100", "eth")?, "0.000000000000000100"); + /// assert_eq!(Cast::from_wei("17", "ether")?, "0.000000000000000017"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn from_wei(value: &str, unit: &str) -> Result { let value = NumberWithBase::parse_int(value, None)?.number(); - - Ok(match unit { - "gwei" => format_units(value, 9), - _ => format_units(value, 18), - }?) + Ok(ParseUnits::U256(value).format_units(unit.parse()?)) } /// Converts an eth amount into wei @@ -1167,43 +1445,41 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_wei("1", "")?, "1000000000000000000"); - /// assert_eq!(Cast::to_wei("100", "gwei")?, "100000000000"); - /// assert_eq!(Cast::to_wei("100", "eth")?, "100000000000000000000"); - /// assert_eq!(Cast::to_wei("1000", "ether")?, "1000000000000000000000"); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::to_wei("100", "gwei")?, "100000000000"); + /// assert_eq!(Cast::to_wei("100", "eth")?, "100000000000000000000"); + /// assert_eq!(Cast::to_wei("1000", "ether")?, "1000000000000000000000"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_wei(value: &str, unit: &str) -> Result { - let wei = match unit { - "gwei" => parse_units(value, 9), - _ => parse_units(value, 18), - }?; - Ok(wei.to_string()) + let unit = unit.parse().wrap_err("could not parse units")?; + Ok(ParseUnits::parse_units(value, unit)?.to_string()) } - /// Decodes rlp encoded list with hex data - /// - /// # Example + // Decodes RLP encoded data with validation for canonical integer representation /// + /// # Examples /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::from_rlp("0xc0".to_string()).unwrap(), "[]"); - /// assert_eq!(Cast::from_rlp("0x0f".to_string()).unwrap(), "\"0x0f\""); - /// assert_eq!(Cast::from_rlp("0x33".to_string()).unwrap(), "\"0x33\""); - /// assert_eq!(Cast::from_rlp("0xc161".to_string()).unwrap(), "[\"0x61\"]"); - /// assert_eq!(Cast::from_rlp("0xc26162".to_string()).unwrap(), "[\"0x61\",\"0x62\"]"); - /// Ok(()) - /// } - /// ``` - pub fn from_rlp(value: impl AsRef) -> Result { - let data = strip_0x(value.as_ref()); - let bytes = hex::decode(data).wrap_err("Could not decode hex")?; - let item = rlp::decode::(&bytes).wrap_err("Could not decode rlp")?; + /// assert_eq!(Cast::from_rlp("0xc0", false).unwrap(), "[]"); + /// assert_eq!(Cast::from_rlp("0x0f", false).unwrap(), "\"0x0f\""); + /// assert_eq!(Cast::from_rlp("0x33", false).unwrap(), "\"0x33\""); + /// assert_eq!(Cast::from_rlp("0xc161", false).unwrap(), "[\"0x61\"]"); + /// assert_eq!(Cast::from_rlp("820002", true).is_err(), true); + /// assert_eq!(Cast::from_rlp("820002", false).unwrap(), "\"0x0002\""); + /// assert_eq!(Cast::from_rlp("00", true).is_err(), true); + /// assert_eq!(Cast::from_rlp("00", false).unwrap(), "\"0x00\""); + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn from_rlp(value: impl AsRef, as_int: bool) -> Result { + let bytes = hex::decode(value.as_ref()).wrap_err("Could not decode hex")?; + + if as_int { + return Ok(U256::decode(&mut &bytes[..])?.to_string()); + } + + let item = Item::decode(&mut &bytes[..]).wrap_err("Could not decode rlp")?; + Ok(item.to_string()) } @@ -1214,19 +1490,17 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// 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()); - /// Ok(()) - /// } + /// 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()); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn to_rlp(value: &str) -> Result { let val = serde_json::from_str(value) .unwrap_or_else(|_| serde_json::Value::String(value.to_string())); let item = Item::value_to_item(&val)?; - Ok(format!("0x{}", hex::encode(rlp::encode(&item)))) + Ok(format!("0x{}", hex::encode(alloy_rlp::encode(item)))) } /// Converts a number of one base to another @@ -1234,30 +1508,37 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::I256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::{I256, U256}; - /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::to_base("100", Some("10".to_string()), "16")?, "0x64"); - /// assert_eq!(Cast::to_base("100", Some("10".to_string()), "oct")?, "0o144"); - /// assert_eq!(Cast::to_base("100", Some("10".to_string()), "binary")?, "0b1100100"); /// - /// assert_eq!(Cast::to_base("0xffffffffffffffff", None, "10")?, u64::MAX.to_string()); - /// assert_eq!(Cast::to_base("0xffffffffffffffffffffffffffffffff", None, "dec")?, u128::MAX.to_string()); - /// // U256::MAX overflows as internally it is being parsed as I256 - /// assert_eq!(Cast::to_base("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", None, "decimal")?, I256::MAX.to_string()); - /// - /// Ok(()) - /// } - /// ``` - pub fn to_base(value: &str, base_in: Option, base_out: &str) -> Result { + /// assert_eq!(Cast::to_base("100", Some("10"), "16")?, "0x64"); + /// assert_eq!(Cast::to_base("100", Some("10"), "oct")?, "0o144"); + /// assert_eq!(Cast::to_base("100", Some("10"), "binary")?, "0b1100100"); + /// + /// assert_eq!(Cast::to_base("0xffffffffffffffff", None, "10")?, u64::MAX.to_string()); + /// assert_eq!( + /// Cast::to_base("0xffffffffffffffffffffffffffffffff", None, "dec")?, + /// u128::MAX.to_string() + /// ); + /// // U256::MAX overflows as internally it is being parsed as I256 + /// assert_eq!( + /// Cast::to_base( + /// "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + /// None, + /// "decimal" + /// )?, + /// I256::MAX.to_string() + /// ); + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn to_base(value: &str, base_in: Option<&str>, base_out: &str) -> Result { let base_in = Base::unwrap_or_detect(base_in, value)?; let base_out: Base = base_out.parse()?; if base_in == base_out { - return Ok(value.to_string()) + return Ok(value.to_string()); } - let mut n = NumberWithBase::parse_int(value, Some(base_in.to_string()))?; + let mut n = NumberWithBase::parse_int(value, Some(&base_in.to_string()))?; n.set_base(base_out); // Use Debug fmt @@ -1271,7 +1552,6 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// # fn main() -> eyre::Result<()> { /// let bytes = Cast::to_bytes32("1234")?; /// assert_eq!(bytes, "0x1234000000000000000000000000000000000000000000000000000000000000"); /// @@ -1280,9 +1560,7 @@ impl SimpleCast { /// /// let err = Cast::to_bytes32("0x123400000000000000000000000000000000000000000000000000000000000011").unwrap_err(); /// assert_eq!(err.to_string(), "string >32 bytes"); - /// - /// # Ok(()) - /// # } + /// # Ok::<_, eyre::Report>(()) pub fn to_bytes32(s: &str) -> Result { let s = strip_0x(s); if s.len() > 64 { @@ -1290,28 +1568,25 @@ impl SimpleCast { } let padded = format!("{s:0<64}"); - // need to use the Debug implementation - Ok(format!("{:?}", H256::from_str(&padded)?)) + Ok(padded.parse::()?.to_string()) } /// Encodes string into bytes32 value pub fn format_bytes32_string(s: &str) -> Result { - let formatted = format_bytes32_string(s)?; - Ok(format!("0x{}", hex::encode(formatted))) + let str_bytes: &[u8] = s.as_bytes(); + eyre::ensure!(str_bytes.len() <= 32, "bytes32 strings must not exceed 32 bytes in length"); + + let mut bytes32: [u8; 32] = [0u8; 32]; + bytes32[..str_bytes.len()].copy_from_slice(str_bytes); + Ok(hex::encode_prefixed(bytes32)) } /// Decodes string from bytes32 value pub fn parse_bytes32_string(s: &str) -> Result { - let s = strip_0x(s); - if s.len() != 64 { - eyre::bail!("expected 64 byte hex-string, got {s}"); - } - let bytes = hex::decode(s)?; - let mut buffer = [0u8; 32]; - buffer.copy_from_slice(&bytes); - - Ok(parse_bytes32_string(&buffer)?.to_owned()) + eyre::ensure!(bytes.len() == 32, "expected 32 byte hex-string"); + let len = bytes.iter().take_while(|x| **x != 0).count(); + Ok(std::str::from_utf8(&bytes[..len])?.into()) } /// Decodes checksummed address from bytes32 value @@ -1324,13 +1599,13 @@ impl SimpleCast { let s = if let Some(stripped) = s.strip_prefix("000000000000000000000000") { stripped } else { - return Err(eyre::eyre!("Not convertible to address, there are non-zero bytes")) + return Err(eyre::eyre!("Not convertible to address, there are non-zero bytes")); }; let lowercase_address_string = format!("0x{s}"); let lowercase_address = Address::from_str(&lowercase_address_string)?; - Ok(ethers_core::utils::to_checksum(&lowercase_address, None)) + Ok(lowercase_address.to_checksum(None)) } /// Decodes abi-encoded hex input or output @@ -1341,14 +1616,14 @@ impl SimpleCast { /// /// ``` /// use cast::SimpleCast as Cast; + /// use alloy_primitives::hex; /// - /// fn main() -> eyre::Result<()> { /// // Passing `input = false` will decode the data as the output type. /// // The input data types and the full function sig are ignored, i.e. /// // you could also pass `balanceOf()(uint256)` and it'd still work. /// let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; /// let sig = "balanceOf(address, uint256)(uint256)"; - /// let decoded = Cast::abi_decode(sig, data, false)?[0].to_string(); + /// let decoded = Cast::abi_decode(sig, data, false)?[0].as_uint().unwrap().0.to_string(); /// assert_eq!(decoded, "1"); /// /// // Passing `input = true` will decode the data with the input function signature. @@ -1356,18 +1631,24 @@ impl SimpleCast { /// let data = "0x0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; /// let sig = "safeTransferFrom(address, address, uint256, uint256, bytes)"; /// let decoded = Cast::abi_decode(sig, data, true)?; - /// let decoded = decoded.iter().map(ToString::to_string).collect::>(); + /// let decoded = [ + /// decoded[0].as_address().unwrap().to_string().to_lowercase(), + /// decoded[1].as_address().unwrap().to_string().to_lowercase(), + /// decoded[2].as_uint().unwrap().0.to_string(), + /// decoded[3].as_uint().unwrap().0.to_string(), + /// hex::encode(decoded[4].as_bytes().unwrap()) + /// ] + /// .into_iter() + /// .collect::>(); + /// /// assert_eq!( /// decoded, - /// vec!["8dbd1b711dc621e1404633da156fcc779e1c6f3e", "d9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "2a", "1", ""] + /// vec!["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "0xd9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "42", "1", ""] /// ); - /// - /// - /// # Ok(()) - /// } + /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn abi_decode(sig: &str, calldata: &str, input: bool) -> Result> { - foundry_common::abi::abi_decode(sig, calldata, input, false) + pub fn abi_decode(sig: &str, calldata: &str, input: bool) -> Result> { + foundry_common::abi::abi_decode_calldata(sig, calldata, input, false) } /// Decodes calldata-encoded hex input or output @@ -1378,32 +1659,37 @@ impl SimpleCast { /// /// ``` /// use cast::SimpleCast as Cast; + /// use alloy_primitives::hex; /// - /// fn main() -> eyre::Result<()> { - /// // Passing `input = false` will decode the data as the output type. - /// // The input data types and the full function sig are ignored, i.e. - /// // you could also pass `balanceOf()(uint256)` and it'd still work. - /// let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; - /// let sig = "balanceOf(address, uint256)(uint256)"; - /// let decoded = Cast::calldata_decode(sig, data, false)?[0].to_string(); - /// assert_eq!(decoded, "1"); + /// // Passing `input = false` will decode the data as the output type. + /// // The input data types and the full function sig are ignored, i.e. + /// // you could also pass `balanceOf()(uint256)` and it'd still work. + /// let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; + /// let sig = "balanceOf(address, uint256)(uint256)"; + /// let decoded = Cast::calldata_decode(sig, data, false)?[0].as_uint().unwrap().0.to_string(); + /// assert_eq!(decoded, "1"); /// /// // Passing `input = true` will decode the data with the input function signature. /// let data = "0xf242432a0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; /// let sig = "safeTransferFrom(address, address, uint256, uint256, bytes)"; /// let decoded = Cast::calldata_decode(sig, data, true)?; - /// let decoded = decoded.iter().map(ToString::to_string).collect::>(); + /// let decoded = [ + /// decoded[0].as_address().unwrap().to_string().to_lowercase(), + /// decoded[1].as_address().unwrap().to_string().to_lowercase(), + /// decoded[2].as_uint().unwrap().0.to_string(), + /// decoded[3].as_uint().unwrap().0.to_string(), + /// hex::encode(decoded[4].as_bytes().unwrap()), + /// ] + /// .into_iter() + /// .collect::>(); /// assert_eq!( /// decoded, - /// vec!["8dbd1b711dc621e1404633da156fcc779e1c6f3e", "d9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "2a", "1", ""] + /// vec!["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "0xd9f3c9cc99548bf3b44a43e0a2d07399eb918adc", "42", "1", ""] /// ); - /// - /// - /// # Ok(()) - /// } + /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn calldata_decode(sig: &str, calldata: &str, input: bool) -> Result> { - foundry_common::abi::abi_decode(sig, calldata, input, true) + pub fn calldata_decode(sig: &str, calldata: &str, input: bool) -> Result> { + foundry_common::abi::abi_decode_calldata(sig, calldata, input, true) } /// Performs ABI encoding based off of the function signature. Does not include @@ -1412,196 +1698,141 @@ impl SimpleCast { /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; + /// use cast::SimpleCast as Cast; /// - /// # fn main() -> eyre::Result<()> { - /// assert_eq!( - /// "0x0000000000000000000000000000000000000000000000000000000000000001", - /// Cast::abi_encode("f(uint a)", &["1"]).unwrap().as_str() - /// ); - /// assert_eq!( - /// "0x0000000000000000000000000000000000000000000000000000000000000001", - /// Cast::abi_encode("constructor(uint a)", &["1"]).unwrap().as_str() - /// ); - /// # Ok(()) - /// # } + /// assert_eq!( + /// "0x0000000000000000000000000000000000000000000000000000000000000001", + /// Cast::abi_encode("f(uint a)", &["1"]).unwrap().as_str() + /// ); + /// assert_eq!( + /// "0x0000000000000000000000000000000000000000000000000000000000000001", + /// Cast::abi_encode("constructor(uint a)", &["1"]).unwrap().as_str() + /// ); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn abi_encode(sig: &str, args: &[impl AsRef]) -> Result { - let func = match HumanReadableParser::parse_function(sig) { - Ok(func) => func, - Err(err) => { - if let Ok(constructor) = HumanReadableParser::parse_constructor(sig) { - #[allow(deprecated)] - Function { - name: "constructor".to_string(), - inputs: constructor.inputs, - outputs: vec![], - constant: None, - state_mutability: Default::default(), - } - } else { - // we return the `Function` parse error as this case is more likely - eyre::bail!("Could not process human-readable ABI. Please check if you've left the parenthesis unclosed or if some type is incomplete.\nError:\n{}", err) - // return Err(err.into()).wrap_err("Could not process human-readable ABI. Please - // check if you've left the parenthesis unclosed or if some type is - // incomplete.") - } - } - }; - let calldata = match encode_args(&func, args) { - Ok(res) => hex::encode(res), + let func = get_func(sig)?; + match encode_function_args(&func, args) { + Ok(res) => Ok(hex::encode_prefixed(&res[4..])), Err(e) => eyre::bail!("Could not ABI encode the function and arguments. Did you pass in the right types?\nError\n{}", e), - }; - let encoded = &calldata[8..]; - Ok(format!("0x{encoded}")) + } } - /// Performs ABI encoding to produce the hexadecimal calldata with the given arguments. + /// Performs packed ABI encoding based off of the function signature or tuple. /// - /// # Example - /// - /// ``` - /// # use cast::SimpleCast as Cast; + /// # Examplez /// - /// # fn main() -> eyre::Result<()> { - /// assert_eq!( - /// "0xb3de648b0000000000000000000000000000000000000000000000000000000000000001", - /// Cast::calldata_encode("f(uint a)", &["1"]).unwrap().as_str() - /// ); - /// # Ok(()) - /// # } /// ``` - pub fn calldata_encode(sig: impl AsRef, args: &[impl AsRef]) -> Result { - let func = HumanReadableParser::parse_function(sig.as_ref())?; - let calldata = encode_args(&func, args)?; - Ok(hex::encode_prefixed(calldata)) - } - - /// Generates an interface in solidity from either a local file ABI or a verified contract on - /// Etherscan. It returns a vector of [`InterfaceSource`] structs that contain the source of the - /// interface and their name. - /// ```no_run /// use cast::SimpleCast as Cast; - /// use cast::AbiPath; - /// # async fn foo() -> eyre::Result<()> { - /// let path = AbiPath::Local { - /// path: "utils/testdata/interfaceTestABI.json".to_owned(), - /// name: None, - /// }; - /// let interfaces= Cast::generate_interface(path).await?; - /// println!("interface {} {{\n {}\n}}", interfaces[0].name, interfaces[0].source); - /// # Ok(()) - /// # } + /// + /// assert_eq!( + /// "0x0000000000000000000000000000000000000000000000000000000000000064000000000000000000000000000000000000000000000000000000000000012c00000000000000c8", + /// Cast::abi_encode_packed("(uint128[] a, uint64 b)", &["[100, 300]", "200"]).unwrap().as_str() + /// ); + /// + /// assert_eq!( + /// "0x8dbd1b711dc621e1404633da156fcc779e1c6f3e68656c6c6f20776f726c64", + /// Cast::abi_encode_packed("foo(address a, string b)", &["0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", "hello world"]).unwrap().as_str() + /// ); + /// # Ok::<_, eyre::Report>(()) /// ``` - pub async fn generate_interface(address_or_path: AbiPath) -> Result> { - let (contract_abis, contract_names): (Vec, Vec) = match address_or_path { - AbiPath::Local { path, name } => { - let file = std::fs::read_to_string(path).wrap_err("unable to read abi file")?; - let mut json: serde_json::Value = serde_json::from_str(&file)?; - let json = if !json["abi"].is_null() { json["abi"].take() } else { json }; - let abi: RawAbi = - serde_json::from_value(json).wrap_err("unable to parse json ABI from file")?; - - (vec![abi], vec![name.unwrap_or_else(|| "Interface".to_owned())]) - } - AbiPath::Etherscan { address, chain, api_key } => { - let client = Client::new(chain, api_key)?; - - // get the source - let source = match client.contract_source_code(address).await { - Ok(source) => source, - Err(EtherscanError::InvalidApiKey) => { - eyre::bail!("Invalid Etherscan API key. Did you set it correctly? You may be using an API key for another Etherscan API chain (e.g. Etherscan API key for Polygonscan).") - } - Err(EtherscanError::ContractCodeNotVerified(address)) => { - eyre::bail!("Contract source code at {:?} on {} not verified. Maybe you have selected the wrong chain?", address, chain) - } - Err(err) => { - eyre::bail!(err) - } - }; - - let names = source - .items - .iter() - .map(|item| item.contract_name.clone()) - .collect::>(); + pub fn abi_encode_packed(sig: &str, args: &[impl AsRef]) -> Result { + // If the signature is a tuple, we need to prefix it to make it a function + let sig = + if sig.trim_start().starts_with('(') { format!("foo{sig}") } else { sig.to_string() }; - let abis = source.raw_abis()?; - - (abis, names) - } + let func = get_func(sig.as_str())?; + let encoded = match encode_function_args_packed(&func, args) { + Ok(res) => hex::encode(res), + Err(e) => eyre::bail!("Could not ABI encode the function and arguments. Did you pass in the right types?\nError\n{}", e), }; - contract_abis - .iter() - .zip(contract_names) - .map(|(contract_abi, name)| { - let source = foundry_utils::abi::abi_to_solidity(contract_abi, &name)?; - Ok(InterfaceSource { name, source }) - }) - .collect::>>() + Ok(format!("0x{encoded}")) } - /// Prints the slot number for the specified mapping type and input data - /// Uses abi_encode to pad the data to 32 bytes. - /// For value types v, slot number of v is keccak256(concat(h(v) , p)) where h is the padding - /// function and p is slot number of the mapping. + /// Performs ABI encoding to produce the hexadecimal calldata with the given arguments. /// /// # Example /// /// ``` - /// # use cast::SimpleCast as Cast; - /// - /// # fn main() -> eyre::Result<()> { + /// use cast::SimpleCast as Cast; /// - /// assert_eq!(Cast::index("address", "0xD0074F4E6490ae3f888d1d4f7E3E43326bD3f0f5" ,"2").unwrap().as_str(),"0x9525a448a9000053a4d151336329d6563b7e80b24f8e628e95527f218e8ab5fb"); - /// assert_eq!(Cast::index("uint256","42" ,"6").unwrap().as_str(),"0xfc808b0f31a1e6b9cf25ff6289feae9b51017b392cc8e25620a94a38dcdafcc1"); - /// # Ok(()) - /// # } + /// assert_eq!( + /// "0xb3de648b0000000000000000000000000000000000000000000000000000000000000001", + /// Cast::calldata_encode("f(uint256 a)", &["1"]).unwrap().as_str() + /// ); + /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn index(from_type: &str, from_value: &str, slot_number: &str) -> Result { - let sig = format!("x({from_type},uint256)"); - let encoded = Self::abi_encode(&sig, &[from_value, slot_number])?; - let location: String = Self::keccak(&encoded)?; - Ok(location) + pub fn calldata_encode(sig: impl AsRef, args: &[impl AsRef]) -> Result { + let func = get_func(sig.as_ref())?; + let calldata = encode_function_args(&func, args)?; + Ok(hex::encode_prefixed(calldata)) } - /// Converts ENS names to their namehash representation - /// [Namehash reference](https://docs.ens.domains/contract-api-reference/name-processing#hashing-names) - /// [namehash-rust reference](https://github.com/InstateDev/namehash-rust/blob/master/src/lib.rs) + /// Returns the slot number for a given mapping key and slot. /// - /// # Example + /// Given `mapping(k => v) m`, for a key `k` the slot number of its associated `v` is + /// `keccak256(concat(h(k), p))`, where `h` is the padding function for `k`'s type, and `p` + /// is slot number of the mapping `m`. /// - /// ``` - /// use cast::SimpleCast as Cast; + /// See [the Solidity documentation](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#mappings-and-dynamic-arrays) + /// for more details. /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::namehash("")?, "0x0000000000000000000000000000000000000000000000000000000000000000"); - /// assert_eq!(Cast::namehash("eth")?, "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"); - /// assert_eq!(Cast::namehash("foo.eth")?, "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"); - /// assert_eq!(Cast::namehash("sub.foo.eth")?, "0x500d86f9e663479e5aaa6e99276e55fc139c597211ee47d17e1e92da16a83402"); + /// # Example /// - /// Ok(()) - /// } /// ``` - pub fn namehash(ens: &str) -> Result { - let mut node = vec![0u8; 32]; - - if !ens.is_empty() { - let ens_lower = ens.to_lowercase(); - let mut labels: Vec<&str> = ens_lower.split('.').collect(); - labels.reverse(); - - for label in labels { - let mut label_hash = keccak256(label.as_bytes()); - node.append(&mut label_hash.to_vec()); - - label_hash = keccak256(node.as_slice()); - node = label_hash.to_vec(); + /// # use cast::SimpleCast as Cast; + /// + /// // Value types. + /// assert_eq!( + /// Cast::index("address", "0xD0074F4E6490ae3f888d1d4f7E3E43326bD3f0f5", "2").unwrap().as_str(), + /// "0x9525a448a9000053a4d151336329d6563b7e80b24f8e628e95527f218e8ab5fb" + /// ); + /// assert_eq!( + /// Cast::index("uint256", "42", "6").unwrap().as_str(), + /// "0xfc808b0f31a1e6b9cf25ff6289feae9b51017b392cc8e25620a94a38dcdafcc1" + /// ); + /// + /// // Strings and byte arrays. + /// assert_eq!( + /// Cast::index("string", "hello", "1").unwrap().as_str(), + /// "0x8404bb4d805e9ca2bd5dd5c43a107e935c8ec393caa7851b353b3192cd5379ae" + /// ); + /// # Ok::<_, eyre::Report>(()) + /// ``` + pub fn index(key_type: &str, key: &str, slot_number: &str) -> Result { + let mut hasher = Keccak256::new(); + + let k_ty = DynSolType::parse(key_type).wrap_err("Could not parse type")?; + let k = k_ty.coerce_str(key).wrap_err("Could not parse value")?; + match k_ty { + // For value types, `h` pads the value to 32 bytes in the same way as when storing the + // value in memory. + DynSolType::Bool | + DynSolType::Int(_) | + DynSolType::Uint(_) | + DynSolType::FixedBytes(_) | + DynSolType::Address | + DynSolType::Function => hasher.update(k.as_word().unwrap()), + + // For strings and byte arrays, `h(k)` is just the unpadded data. + DynSolType::String | DynSolType::Bytes => hasher.update(k.as_packed_seq().unwrap()), + + DynSolType::Array(..) | + DynSolType::FixedArray(..) | + DynSolType::Tuple(..) | + DynSolType::CustomStruct { .. } => { + eyre::bail!("Type `{k_ty}` is not supported as a mapping key") } } - Ok(hex::encode_prefixed(node)) + let p = DynSolType::Uint(256) + .coerce_str(slot_number) + .wrap_err("Could not parse slot number")?; + let p = p.as_word().unwrap(); + hasher.update(p); + + let location = hasher.finalize(); + Ok(location.to_string()) } /// Keccak-256 hashes arbitrary data @@ -1611,24 +1842,29 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::keccak("foo")?, "0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d"); - /// assert_eq!(Cast::keccak("123abc")?, "0xb1f1c74a1ba56f07a892ea1110a39349d40f66ca01d245e704621033cb7046a4"); - /// assert_eq!(Cast::keccak("0x12")?, "0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa"); - /// assert_eq!(Cast::keccak("12")?, "0x7f8b6b088b6d74c2852fc86c796dca07b44eed6fb3daf5e6b59f7c364db14528"); - /// - /// Ok(()) - /// } + /// assert_eq!( + /// Cast::keccak("foo")?, + /// "0x41b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d" + /// ); + /// assert_eq!( + /// Cast::keccak("123abc")?, + /// "0xb1f1c74a1ba56f07a892ea1110a39349d40f66ca01d245e704621033cb7046a4" + /// ); + /// assert_eq!( + /// Cast::keccak("0x12")?, + /// "0x5fa2358263196dbbf23d1ca7a509451f7a2f64c15837bfbb81298b1e3e24e4fa" + /// ); + /// assert_eq!( + /// Cast::keccak("12")?, + /// "0x7f8b6b088b6d74c2852fc86c796dca07b44eed6fb3daf5e6b59f7c364db14528" + /// ); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn keccak(data: &str) -> Result { - let hash = match data.as_bytes() { - // 0x prefix => read as hex data - [b'0', b'x', rest @ ..] => keccak256(hex::decode(rest)?), - // No 0x prefix => read as text - _ => keccak256(data), - }; - - Ok(format!("{:?}", H256(hash))) + // Hex-decode if data starts with 0x. + let hash = + if data.starts_with("0x") { keccak256(hex::decode(data)?) } else { keccak256(data) }; + Ok(hash.to_string()) } /// Performs the left shift operation (<<) on a number @@ -1638,18 +1874,15 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::left_shift("16", "10", Some("10".to_string()), "hex")?, "0x4000"); - /// assert_eq!(Cast::left_shift("255", "16", Some("dec".to_string()), "hex")?, "0xff0000"); - /// assert_eq!(Cast::left_shift("0xff", "16", None, "hex")?, "0xff0000"); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::left_shift("16", "10", Some("10"), "hex")?, "0x4000"); + /// assert_eq!(Cast::left_shift("255", "16", Some("dec"), "hex")?, "0xff0000"); + /// assert_eq!(Cast::left_shift("0xff", "16", None, "hex")?, "0xff0000"); + /// # Ok::<_, eyre::Report>(()) /// ``` pub fn left_shift( value: &str, bits: &str, - base_in: Option, + base_in: Option<&str>, base_out: &str, ) -> Result { let base_out: Base = base_out.parse()?; @@ -1668,25 +1901,22 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::right_shift("0x4000", "10", None, "dec")?, "16"); - /// assert_eq!(Cast::right_shift("16711680", "16", Some("10".to_string()), "hex")?, "0xff"); - /// assert_eq!(Cast::right_shift("0xff0000", "16", None, "hex")?, "0xff"); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::right_shift("0x4000", "10", None, "dec")?, "16"); + /// assert_eq!(Cast::right_shift("16711680", "16", Some("10"), "hex")?, "0xff"); + /// assert_eq!(Cast::right_shift("0xff0000", "16", None, "hex")?, "0xff"); + /// # Ok::<(), eyre::Report>(()) /// ``` pub fn right_shift( value: &str, bits: &str, - base_in: Option, + base_in: Option<&str>, base_out: &str, ) -> Result { let base_out: Base = base_out.parse()?; let value = NumberWithBase::parse_uint(value, base_in)?; let bits = NumberWithBase::parse_uint(bits, None)?; - let res = value.number() >> bits.number(); + let res = value.number().wrapping_shr(bits.number().saturating_to()); Ok(res.to_base(base_out, true)?) } @@ -1697,24 +1927,34 @@ impl SimpleCast { /// /// ``` /// # use cast::SimpleCast as Cast; - /// # use ethers_core::types::Chain; - /// + /// # use foundry_config::NamedChain; /// # async fn foo() -> eyre::Result<()> { - /// assert_eq!( - /// "/* + /// assert_eq!( + /// "/* /// - Bytecode Verification performed was compared on second iteration - /// This file is part of the DAO.....", - /// Cast::etherscan_source(Chain::Mainnet, "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), "".to_string()).await.unwrap().as_str() - /// ); - /// # Ok(()) + /// Cast::etherscan_source( + /// NamedChain::Mainnet.into(), + /// "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), + /// Some("".to_string()), + /// None, + /// None + /// ) + /// .await + /// .unwrap() + /// .as_str() + /// ); + /// # Ok(()) /// # } /// ``` pub async fn etherscan_source( chain: Chain, contract_address: String, - etherscan_api_key: String, + etherscan_api_key: Option, + explorer_api_url: Option, + explorer_url: Option, ) -> Result { - let client = Client::new(chain, etherscan_api_key)?; + let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let metadata = client.contract_source_code(contract_address.parse()?).await?; Ok(metadata.source_code()) } @@ -1726,43 +1966,104 @@ impl SimpleCast { /// /// ``` /// # use cast::SimpleCast as Cast; - /// # use ethers_core::types::Chain; + /// # use foundry_config::NamedChain; /// # use std::path::PathBuf; - /// /// # async fn expand() -> eyre::Result<()> { - /// Cast::expand_etherscan_source_to_directory(Chain::Mainnet, "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), "".to_string(), PathBuf::from("output_dir")).await?; - /// # Ok(()) + /// Cast::expand_etherscan_source_to_directory( + /// NamedChain::Mainnet.into(), + /// "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413".to_string(), + /// Some("".to_string()), + /// PathBuf::from("output_dir"), + /// None, + /// None, + /// ) + /// .await?; + /// # Ok(()) /// # } /// ``` pub async fn expand_etherscan_source_to_directory( chain: Chain, contract_address: String, - etherscan_api_key: String, + etherscan_api_key: Option, output_directory: PathBuf, + explorer_api_url: Option, + explorer_url: Option, ) -> eyre::Result<()> { - let client = Client::new(chain, etherscan_api_key)?; + let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; let meta = client.contract_source_code(contract_address.parse()?).await?; let source_tree = meta.source_tree(); source_tree.write_to(&output_directory)?; Ok(()) } + /// Fetches the source code of verified contracts from etherscan, flattens it and writes it to + /// the given path or stdout. + pub async fn etherscan_source_flatten( + chain: Chain, + contract_address: String, + etherscan_api_key: Option, + output_path: Option, + explorer_api_url: Option, + explorer_url: Option, + ) -> Result<()> { + let client = explorer_client(chain, etherscan_api_key, explorer_api_url, explorer_url)?; + let metadata = client.contract_source_code(contract_address.parse()?).await?; + let Some(metadata) = metadata.items.first() else { + eyre::bail!("Empty contract source code") + }; + + let tmp = tempfile::tempdir()?; + let project = etherscan_project(metadata, tmp.path())?; + let target_path = project.find_contract_path(&metadata.contract_name)?; + + let flattened = Flattener::new(project, &target_path)?.flatten(); + + if let Some(path) = output_path { + fs::create_dir_all(path.parent().unwrap())?; + fs::write(&path, flattened)?; + sh_println!("Flattened file written at {}", path.display())? + } else { + sh_println!("{flattened}")? + } + + Ok(()) + } + /// Disassembles hex encoded bytecode into individual / human readable opcodes /// /// # Example /// - /// ```no_run + /// ``` + /// use alloy_primitives::hex; /// use cast::SimpleCast as Cast; /// /// # async fn foo() -> eyre::Result<()> { /// let bytecode = "0x608060405260043610603f57600035"; - /// let opcodes = Cast::disassemble(bytecode)?; + /// let opcodes = Cast::disassemble(&hex::decode(bytecode)?)?; /// println!("{}", opcodes); /// # Ok(()) /// # } /// ``` - pub fn disassemble(bytecode: &str) -> Result { - format_operations(disassemble_str(bytecode)?) + pub fn disassemble(code: &[u8]) -> Result { + let mut output = String::new(); + + for step in decode_instructions(code)? { + write!(output, "{:08x}: ", step.pc)?; + + if let Some(op) = step.op { + write!(output, "{op}")?; + } else { + write!(output, "INVALID")?; + } + + if !step.immediate.is_empty() { + write!(output, " {}", hex::encode_prefixed(step.immediate))?; + } + + writeln!(output)?; + } + + Ok(output) } /// Gets the selector for a given function signature @@ -1773,23 +2074,19 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// fn main() -> eyre::Result<()> { - /// assert_eq!(Cast::get_selector("foo(address,uint256)", None)?.0, String::from("0xbd0d639f")); - /// - /// Ok(()) - /// } + /// assert_eq!(Cast::get_selector("foo(address,uint256)", 0)?.0, String::from("0xbd0d639f")); + /// # Ok::<(), eyre::Error>(()) /// ``` - pub fn get_selector(signature: &str, optimize: Option) -> Result<(String, String)> { - let optimize = optimize.unwrap_or(0); + pub fn get_selector(signature: &str, optimize: usize) -> Result<(String, String)> { if optimize > 4 { - eyre::bail!("Number of leading zeroes must not be greater than 4"); + eyre::bail!("number of leading zeroes must not be greater than 4"); } if optimize == 0 { - let selector = HumanReadableParser::parse_function(signature)?.short_signature(); - return Ok((format!("0x{}", hex::encode(selector)), String::from(signature))) + let selector = get_func(signature)?.selector(); + return Ok((selector.to_string(), String::from(signature))); } let Some((name, params)) = signature.split_once('(') else { - eyre::bail!("Invalid signature"); + eyre::bail!("invalid function signature"); }; let num_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); @@ -1802,13 +2099,13 @@ impl SimpleCast { let mut nonce = nonce_start; while nonce < u32::MAX && !found.load(Ordering::Relaxed) { - let input = format!("{}{}({}", name, nonce, params); + let input = format!("{name}{nonce}({params}"); let hash = keccak256(input.as_bytes()); let selector = &hash[..4]; if selector.iter().take_while(|&&byte| byte == 0).count() == optimize { found.store(true, Ordering::Relaxed); - return Some((nonce, format!("0x{}", hex::encode(selector)), input)) + return Some((nonce, hex::encode_prefixed(selector), input)); } nonce += nonce_step; @@ -1821,21 +2118,135 @@ impl SimpleCast { None => eyre::bail!("No selector found"), } } + + /// Extracts function selectors, arguments and state mutability from bytecode + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// let bytecode = "6080604052348015600e575f80fd5b50600436106026575f3560e01c80632125b65b14602a575b5f80fd5b603a6035366004603c565b505050565b005b5f805f60608486031215604d575f80fd5b833563ffffffff81168114605f575f80fd5b925060208401356001600160a01b03811681146079575f80fd5b915060408401356001600160e01b03811681146093575f80fd5b80915050925092509256"; + /// let functions = Cast::extract_functions(bytecode)?; + /// assert_eq!(functions, vec![("0x2125b65b".to_string(), "uint32,address,uint224".to_string(), "pure")]); + /// # Ok::<(), eyre::Report>(()) + /// ``` + pub fn extract_functions(bytecode: &str) -> Result> { + let code = hex::decode(strip_0x(bytecode))?; + let info = evmole::contract_info( + evmole::ContractInfoArgs::new(&code) + .with_selectors() + .with_arguments() + .with_state_mutability(), + ); + Ok(info + .functions + .expect("functions extraction was requested") + .into_iter() + .map(|f| { + ( + hex::encode_prefixed(f.selector), + f.arguments + .expect("arguments extraction was requested") + .into_iter() + .map(|t| t.sol_type_name().to_string()) + .collect::>() + .join(","), + f.state_mutability + .expect("state_mutability extraction was requested") + .as_json_str(), + ) + }) + .collect()) + } + + /// Decodes a raw EIP2718 transaction payload + /// Returns details about the typed transaction and ECSDA signature components + /// + /// # Example + /// + /// ``` + /// use cast::SimpleCast as Cast; + /// + /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; + /// let tx_envelope = Cast::decode_raw_transaction(&tx)?; + /// # Ok::<(), eyre::Report>(()) + pub fn decode_raw_transaction(tx: &str) -> Result { + let tx_hex = hex::decode(strip_0x(tx))?; + 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 { s.strip_prefix("0x").unwrap_or(s) } +fn explorer_client( + chain: Chain, + api_key: Option, + api_url: Option, + explorer_url: Option, +) -> Result { + let mut builder = Client::builder().with_chain_id(chain); + + let deduced = chain.etherscan_urls(); + + let explorer_url = explorer_url + .or(deduced.map(|d| d.1.to_string())) + .ok_or_eyre("Please provide the explorer browser URL using `--explorer-url`")?; + builder = builder.with_url(explorer_url)?; + + let api_url = api_url + .or(deduced.map(|d| d.0.to_string())) + .ok_or_eyre("Please provide the explorer API URL using `--explorer-api-url`")?; + builder = builder.with_api_url(api_url)?; + + if let Some(api_key) = api_key { + builder = builder.with_api_key(api_key); + } + + builder.build().map_err(Into::into) +} + #[cfg(test)] mod tests { use super::SimpleCast as Cast; + use alloy_primitives::hex; + + #[test] + fn simple_selector() { + assert_eq!("0xc2985578", Cast::get_selector("foo()", 0).unwrap().0.as_str()) + } + + #[test] + fn selector_with_arg() { + assert_eq!("0xbd0d639f", Cast::get_selector("foo(address,uint256)", 0).unwrap().0.as_str()) + } #[test] fn calldata_uint() { assert_eq!( "0xb3de648b0000000000000000000000000000000000000000000000000000000000000001", - Cast::calldata_encode("f(uint a)", &["1"]).unwrap().as_str() + Cast::calldata_encode("f(uint256 a)", &["1"]).unwrap().as_str() ); } @@ -1860,18 +2271,42 @@ mod tests { fn abi_decode() { let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; let sig = "balanceOf(address, uint256)(uint256)"; - assert_eq!("1", Cast::abi_decode(sig, data, false).unwrap()[0].to_string()); + assert_eq!( + "1", + Cast::abi_decode(sig, data, false).unwrap()[0].as_uint().unwrap().0.to_string() + ); let data = "0x0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; let sig = "safeTransferFrom(address,address,uint256,uint256,bytes)"; let decoded = Cast::abi_decode(sig, data, true).unwrap(); - let decoded = decoded.iter().map(ToString::to_string).collect::>(); + let decoded = [ + decoded[0] + .as_address() + .unwrap() + .to_string() + .strip_prefix("0x") + .unwrap() + .to_owned() + .to_lowercase(), + decoded[1] + .as_address() + .unwrap() + .to_string() + .strip_prefix("0x") + .unwrap() + .to_owned() + .to_lowercase(), + decoded[2].as_uint().unwrap().0.to_string(), + decoded[3].as_uint().unwrap().0.to_string(), + hex::encode(decoded[4].as_bytes().unwrap()), + ] + .to_vec(); assert_eq!( decoded, vec![ "8dbd1b711dc621e1404633da156fcc779e1c6f3e", "d9f3c9cc99548bf3b44a43e0a2d07399eb918adc", - "2a", + "42", "1", "" ] @@ -1882,7 +2317,8 @@ mod tests { fn calldata_decode() { let data = "0x0000000000000000000000000000000000000000000000000000000000000001"; let sig = "balanceOf(address, uint256)(uint256)"; - let decoded = Cast::calldata_decode(sig, data, false).unwrap()[0].to_string(); + let decoded = + Cast::calldata_decode(sig, data, false).unwrap()[0].as_uint().unwrap().0.to_string(); assert_eq!(decoded, "1"); // Passing `input = true` will decode the data with the input function signature. @@ -1890,13 +2326,21 @@ mod tests { let data = "0xf242432a0000000000000000000000008dbd1b711dc621e1404633da156fcc779e1c6f3e000000000000000000000000d9f3c9cc99548bf3b44a43e0a2d07399eb918adc000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000"; let sig = "safeTransferFrom(address, address, uint256, uint256, bytes)"; let decoded = Cast::calldata_decode(sig, data, true).unwrap(); - let decoded = decoded.iter().map(ToString::to_string).collect::>(); + let decoded = [ + decoded[0].as_address().unwrap().to_string().to_lowercase(), + decoded[1].as_address().unwrap().to_string().to_lowercase(), + decoded[2].as_uint().unwrap().0.to_string(), + decoded[3].as_uint().unwrap().0.to_string(), + hex::encode(decoded[4].as_bytes().unwrap()), + ] + .into_iter() + .collect::>(); assert_eq!( decoded, vec![ - "8dbd1b711dc621e1404633da156fcc779e1c6f3e", - "d9f3c9cc99548bf3b44a43e0a2d07399eb918adc", - "2a", + "0x8dbd1b711dc621e1404633da156fcc779e1c6f3e", + "0xd9f3c9cc99548bf3b44a43e0a2d07399eb918adc", + "42", "1", "" ] @@ -1912,10 +2356,29 @@ mod tests { #[test] fn from_rlp() { let rlp = "0xf8b1a02b5df5f0757397573e8ff34a8b987b21680357de1f6c8d10273aa528a851eaca8080a02838ac1d2d2721ba883169179b48480b2ba4f43d70fcf806956746bd9e83f90380a0e46fff283b0ab96a32a7cc375cecc3ed7b6303a43d64e0a12eceb0bc6bd8754980a01d818c1c414c665a9c9a0e0c0ef1ef87cacb380b8c1f6223cb2a68a4b2d023f5808080a0236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff08080808080"; - let item = Cast::from_rlp(rlp).unwrap(); + let item = Cast::from_rlp(rlp, false).unwrap(); assert_eq!( item, r#"["0x2b5df5f0757397573e8ff34a8b987b21680357de1f6c8d10273aa528a851eaca","0x","0x","0x2838ac1d2d2721ba883169179b48480b2ba4f43d70fcf806956746bd9e83f903","0x","0xe46fff283b0ab96a32a7cc375cecc3ed7b6303a43d64e0a12eceb0bc6bd87549","0x","0x1d818c1c414c665a9c9a0e0c0ef1ef87cacb380b8c1f6223cb2a68a4b2d023f5","0x","0x","0x","0x236e8f61ecde6abfebc6c529441f782f62469d8a2cc47b7aace2c136bd3b1ff0","0x","0x","0x","0x","0x"]"# ) } + + #[test] + fn disassemble_incomplete_sequence() { + let incomplete = &hex!("60"); // PUSH1 + let disassembled = Cast::disassemble(incomplete); + assert!(disassembled.is_err()); + + let complete = &hex!("6000"); // PUSH1 0x00 + let disassembled = Cast::disassemble(complete); + assert!(disassembled.is_ok()); + + let incomplete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 31 bytes + let disassembled = Cast::disassemble(incomplete); + assert!(disassembled.is_err()); + + let complete = &hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // PUSH32 with 32 bytes + let disassembled = Cast::disassemble(complete); + assert!(disassembled.is_ok()); + } } diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index 5c64556daa8cf..3f24aa563c1e9 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -1,56 +1,62 @@ -use ethers_core::utils::rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; +use alloy_primitives::{hex, U256}; +use alloy_rlp::{Buf, Decodable, Encodable, Header}; +use eyre::Context; use serde_json::Value; -use std::fmt::{Debug, Display, Formatter, Write}; +use std::fmt; -/// Arbitrary nested data -/// Item::Array(vec![]); is equivalent to [] -/// Item::Array(vec![Item::Data(vec![])]); is equivalent to [""] or [null] -#[derive(Debug, Clone, Eq, PartialEq)] +/// Arbitrary nested data. +/// +/// - `Item::Array(vec![])` is equivalent to `[]`. +/// - `Item::Array(vec![Item::Data(vec![])])` is equivalent to `[""]` or `[null]`. +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Item { Data(Vec), Array(Vec), } impl Encodable for Item { - fn rlp_append(&self, s: &mut RlpStream) { + fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { match self { - Item::Array(arr) => { - s.begin_unbounded_list(); - for item in arr { - s.append(item); - } - s.finalize_unbounded_list(); - } - Item::Data(data) => { - s.append(data); - } + Self::Array(arr) => arr.encode(out), + Self::Data(data) => <[u8]>::encode(data, out), } } } impl Decodable for Item { - fn decode(rlp: &Rlp) -> Result { - if rlp.is_data() { - return Ok(Item::Data(Vec::from(rlp.data()?))) + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let h = Header::decode(buf)?; + if buf.len() < h.payload_length { + return Err(alloy_rlp::Error::InputTooShort); } - Ok(Item::Array(rlp.as_list()?)) + let mut d = &buf[..h.payload_length]; + let r = if h.list { + let view = &mut d; + let mut v = Vec::new(); + while !view.is_empty() { + v.push(Self::decode(view)?); + } + Ok(Self::Array(v)) + } else { + Ok(Self::Data(d.to_vec())) + }; + buf.advance(h.payload_length); + r } } impl Item { - pub(crate) fn value_to_item(value: &Value) -> eyre::Result { - return match value { - Value::Null => Ok(Item::Data(vec![])), + pub(crate) fn value_to_item(value: &Value) -> eyre::Result { + 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(Item::value_to_item(&Value::String(n.to_string()))?), - Value::String(s) => { - let hex_string = s.strip_prefix("0x").unwrap_or(s); - Ok(Item::Data(hex::decode(hex_string).expect("Could not decode hex"))) + Value::Number(n) => { + Ok(Self::Data(n.to_string().parse::()?.to_be_bytes_trimmed_vec())) } - Value::Array(values) => values.iter().map(Item::value_to_item).collect(), + 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") } @@ -58,29 +64,28 @@ impl Item { } } -impl FromIterator for Item { - fn from_iter>(iter: T) -> Self { - Item::Array(iter.into_iter().collect()) +impl FromIterator for Item { + fn from_iter>(iter: T) -> Self { + Self::Array(Vec::from_iter(iter)) } } // Display as hex values -impl Display for Item { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Item { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { match self { - Item::Data(dat) => { + Self::Data(dat) => { write!(f, "\"0x{}\"", hex::encode(dat))?; } - Item::Array(arr) => { - write!(f, "[")?; - let mut iter = arr.iter().peekable(); - while let Some(item) = iter.next() { - write!(f, "{item}")?; - if iter.peek().is_some() { - f.write_char(',')?; + Self::Array(items) => { + f.write_str("[")?; + for (i, item) in items.iter().enumerate() { + if i > 0 { + f.write_str(",")?; } + fmt::Display::fmt(item, f)?; } - write!(f, "]")?; + f.write_str("]")?; } }; Ok(()) @@ -90,7 +95,8 @@ impl Display for Item { #[cfg(test)] mod test { use crate::rlp_converter::Item; - use ethers_core::utils::{rlp, rlp::DecoderError}; + use alloy_primitives::hex; + use alloy_rlp::{Bytes, Decodable}; use serde_json::Result as JsonResult; // https://en.wikipedia.org/wiki/Set-theoretic_definition_of_natural_numbers @@ -103,7 +109,8 @@ mod test { } #[test] - fn encode_decode_test() -> Result<(), DecoderError> { + #[allow(clippy::disallowed_macros)] + fn encode_decode_test() -> alloy_rlp::Result<()> { let parameters = vec![ (1, b"\xc0".to_vec(), Item::Array(vec![])), (2, b"\xc1\x80".to_vec(), Item::Array(vec![Item::Data(vec![])])), @@ -133,10 +140,10 @@ mod test { ), ]; for params in parameters { - let encoded = rlp::encode::(¶ms.2); - assert_eq!(rlp::decode::(&encoded)?, params.2); - let decoded = rlp::decode::(¶ms.1); - assert_eq!(rlp::encode::(&decoded?), params.1); + let encoded = alloy_rlp::encode(¶ms.2); + assert_eq!(Item::decode(&mut &encoded[..])?, params.2); + let decoded = Item::decode(&mut ¶ms.1[..])?; + assert_eq!(alloy_rlp::encode(&decoded), params.1); println!("case {} validated", params.0) } @@ -144,6 +151,7 @@ mod test { } #[test] + #[allow(clippy::disallowed_macros)] fn deserialize_from_str_test_hex() -> JsonResult<()> { let parameters = vec![ (1, "[\"\"]", Item::Array(vec![Item::Data(vec![])])), @@ -172,4 +180,24 @@ mod test { Ok(()) } + + #[test] + fn rlp_data() { + // + let hex_val_rlp = hex!("820002"); + let item = Item::decode(&mut &hex_val_rlp[..]).unwrap(); + + let data = hex!("0002"); + let encoded = alloy_rlp::encode(&data[..]); + let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap(); + assert_eq!(Item::Data(decoded.to_vec()), item); + + let hex_val_rlp = hex!("00"); + let item = Item::decode(&mut &hex_val_rlp[..]).unwrap(); + + let data = hex!("00"); + let encoded = alloy_rlp::encode(&data[..]); + let decoded: Bytes = alloy_rlp::decode_exact(&encoded[..]).unwrap(); + assert_eq!(Item::Data(decoded.to_vec()), item); + } } diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs deleted file mode 100644 index fae149129bce0..0000000000000 --- a/crates/cast/src/tx.rs +++ /dev/null @@ -1,393 +0,0 @@ -use crate::errors::FunctionSignatureError; -use ethers_core::{ - abi::Function, - types::{ - transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, NameOrAddress, - TransactionRequest, H160, U256, - }, -}; -use ethers_providers::Middleware; -use eyre::{eyre, Result}; -use foundry_common::abi::{encode_args, get_func, get_func_etherscan}; -use foundry_config::Chain; -use futures::future::join_all; - -use crate::strip_0x; - -pub struct TxBuilder<'a, M: Middleware> { - to: Option, - chain: Chain, - tx: TypedTransaction, - func: Option, - etherscan_api_key: Option, - provider: &'a M, -} - -pub type TxBuilderOutput = (TypedTransaction, Option); -pub type TxBuilderPeekOutput<'a> = (&'a TypedTransaction, &'a Option); - -/// Transaction builder -/// ``` -/// async fn foo() -> eyre::Result<()> { -/// use ethers_core::types::{Chain, U256}; -/// use cast::TxBuilder; -/// let provider = ethers_providers::test_provider::MAINNET.provider(); -/// let mut builder = TxBuilder::new(&provider, "a.eth", Some("b.eth"), Chain::Mainnet, false).await?; -/// builder -/// .gas(Some(U256::from(1))); -/// let (tx, _) = builder.build(); -/// Ok(()) -/// } -/// ``` -impl<'a, M: Middleware> TxBuilder<'a, M> { - /// Create a new TxBuilder - /// `provider` - provider to use - /// `from` - 'from' field. Could be an ENS name - /// `to` - `to`. Could be a ENS - /// `chain` - chain to construct the tx for - /// `legacy` - use type 1 transaction - pub async fn new, T: Into>( - provider: &'a M, - from: F, - to: Option, - chain: impl Into, - legacy: bool, - ) -> Result> { - let chain = chain.into(); - let from_addr = resolve_ens(provider, from).await?; - - let mut tx: TypedTransaction = if chain.is_legacy() || legacy { - TransactionRequest::new().from(from_addr).chain_id(chain.id()).into() - } else { - Eip1559TransactionRequest::new().from(from_addr).chain_id(chain.id()).into() - }; - - let to_addr = if let Some(to) = to { - let addr = - resolve_ens(provider, foundry_utils::resolve_addr(to, chain.try_into().ok())?) - .await?; - tx.set_to(addr); - Some(addr) - } else { - None - }; - Ok(Self { to: to_addr, chain, tx, func: None, etherscan_api_key: None, provider }) - } - - /// Set gas for tx - pub fn set_gas(&mut self, v: U256) -> &mut Self { - self.tx.set_gas(v); - self - } - - /// Set gas for tx, if `v` is not None - pub fn gas(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_gas(value); - } - self - } - - /// Set gas price - pub fn set_gas_price(&mut self, v: U256) -> &mut Self { - self.tx.set_gas_price(v); - self - } - - /// Set gas price, if `v` is not None - pub fn gas_price(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_gas_price(value); - } - self - } - - /// Set priority gas price - pub fn set_priority_gas_price(&mut self, v: U256) -> &mut Self { - if let TypedTransaction::Eip1559(tx) = &mut self.tx { - tx.max_priority_fee_per_gas = Some(v) - } - self - } - - /// Set priority gas price, if `v` is not None - pub fn priority_gas_price(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_priority_gas_price(value); - } - self - } - - /// Set value - pub fn set_value(&mut self, v: U256) -> &mut Self { - self.tx.set_value(v); - self - } - - /// Set value, if `v` is not None - pub fn value(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_value(value); - } - self - } - - /// Set nonce - pub fn set_nonce(&mut self, v: U256) -> &mut Self { - self.tx.set_nonce(v); - self - } - - /// Set nonce, if `v` is not None - pub fn nonce(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_nonce(value); - } - self - } - - /// Set etherscan API key. Used to look up function signature buy name - pub fn set_etherscan_api_key(&mut self, v: String) -> &mut Self { - self.etherscan_api_key = Some(v); - self - } - - /// Set etherscan API key, if `v` is not None - pub fn etherscan_api_key(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_etherscan_api_key(value); - } - self - } - - pub fn set_data(&mut self, v: Vec) -> &mut Self { - self.tx.set_data(v.into()); - self - } - - pub async fn create_args( - &mut self, - sig: &str, - args: Vec, - ) -> Result<(Vec, Function)> { - if sig.trim().is_empty() { - return Err(FunctionSignatureError::MissingSignature.into()) - } - - let args = resolve_name_args(&args, self.provider).await; - - let func = if sig.contains('(') { - // a regular function signature with parentheses - get_func(sig)? - } else if sig.starts_with("0x") { - // if only calldata is provided, returning a dummy function - get_func("x()")? - } else { - let chain = self - .chain - .try_into() - .map_err(|_| FunctionSignatureError::UnknownChain(self.chain))?; - get_func_etherscan( - sig, - self.to.ok_or(FunctionSignatureError::MissingToAddress)?, - &args, - chain, - self.etherscan_api_key.as_ref().ok_or_else(|| { - FunctionSignatureError::MissingEtherscan { sig: sig.to_string() } - })?, - ) - .await? - }; - - if sig.starts_with("0x") { - Ok((hex::decode(strip_0x(sig))?, func)) - } else { - Ok((encode_args(&func, &args)?, func)) - } - } - - /// Set function arguments - /// `sig` can be: - /// * a fragment (`do(uint32,string)`) - /// * selector + abi-encoded calldata - /// (`0xcdba2fd40000000000000000000000000000000000000000000000000000000000007a69`) - /// * only function name (`do`) - in this case, etherscan lookup is performed on `tx.to`'s - /// contract - pub async fn set_args( - &mut self, - sig: &str, - args: Vec, - ) -> Result<&mut TxBuilder<'a, M>> { - let (data, func) = self.create_args(sig, args).await?; - self.tx.set_data(data.into()); - self.func = Some(func); - Ok(self) - } - - /// Set function arguments, if `value` is not None - pub async fn args( - &mut self, - value: Option<(&str, Vec)>, - ) -> Result<&mut TxBuilder<'a, M>> { - if let Some((sig, args)) = value { - return self.set_args(sig, args).await - } - Ok(self) - } - - /// Consuming build: returns typed transaction and optional function call - pub fn build(self) -> TxBuilderOutput { - (self.tx, self.func) - } - - /// Non-consuming build: peek into the tx content - pub fn peek(&self) -> TxBuilderPeekOutput { - (&self.tx, &self.func) - } -} - -async fn resolve_ens>(provider: &M, addr: T) -> Result { - let from_addr = match addr.into() { - NameOrAddress::Name(ref ens_name) => provider.resolve_name(ens_name).await, - NameOrAddress::Address(addr) => Ok(addr), - } - .map_err(|x| eyre!("Failed to resolve ENS name: {x}"))?; - Ok(from_addr) -} - -async fn resolve_name_args(args: &[String], provider: &M) -> Vec { - join_all(args.iter().map(|arg| async { - if arg.contains('.') { - let addr = provider.resolve_name(arg).await; - match addr { - Ok(addr) => format!("0x{}", hex::encode(addr.as_bytes())), - Err(_) => arg.to_string(), - } - } else { - arg.to_string() - } - })) - .await -} - -#[cfg(test)] -mod tests { - use crate::TxBuilder; - use async_trait::async_trait; - use ethers_core::types::{ - transaction::eip2718::TypedTransaction, Address, Chain, NameOrAddress, H160, U256, - }; - use ethers_providers::{JsonRpcClient, Middleware, ProviderError}; - use serde::{de::DeserializeOwned, Serialize}; - use std::str::FromStr; - - const ADDR_1: &str = "0000000000000000000000000000000000000001"; - const ADDR_2: &str = "0000000000000000000000000000000000000002"; - - #[derive(Debug)] - struct MyProvider {} - - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] - impl JsonRpcClient for MyProvider { - type Error = ProviderError; - - async fn request( - &self, - _method: &str, - _params: T, - ) -> Result { - Err(ProviderError::CustomError("There is no request".to_string())) - } - } - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] - impl Middleware for MyProvider { - type Error = ProviderError; - type Provider = MyProvider; - type Inner = MyProvider; - - fn inner(&self) -> &Self::Inner { - self - } - - async fn resolve_name(&self, ens_name: &str) -> Result { - match ens_name { - "a.eth" => Ok(H160::from_str(ADDR_1).unwrap()), - "b.eth" => Ok(H160::from_str(ADDR_2).unwrap()), - _ => unreachable!("don't know how to resolve {ens_name}"), - } - } - } - #[tokio::test(flavor = "multi_thread")] - async fn builder_new_non_legacy() -> eyre::Result<()> { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), Chain::Mainnet, false).await?; - let (tx, args) = builder.build(); - assert_eq!(*tx.from().unwrap(), H160::from_str(ADDR_1).unwrap()); - assert_eq!(*tx.to().unwrap(), NameOrAddress::Address(H160::from_str(ADDR_2).unwrap())); - assert_eq!(args, None); - - match tx { - TypedTransaction::Eip1559(_) => {} - _ => { - panic!("Wrong tx type"); - } - } - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_new_legacy() -> eyre::Result<()> { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), Chain::Mainnet, true).await?; - // don't check anything other than the tx type - the rest is covered in the non-legacy case - let (tx, _) = builder.build(); - match tx { - TypedTransaction::Legacy(_) => {} - _ => { - panic!("Wrong tx type"); - } - } - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_fields() -> eyre::Result<()> { - let provider = MyProvider {}; - let mut builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), Chain::Mainnet, false).await.unwrap(); - builder - .gas(Some(U256::from(12u32))) - .gas_price(Some(U256::from(34u32))) - .value(Some(U256::from(56u32))) - .nonce(Some(U256::from(78u32))); - - builder.etherscan_api_key(Some(String::from("what a lovely day"))); // not testing for this :-/ - let (tx, _) = builder.build(); - - assert_eq!(tx.gas().unwrap().as_u32(), 12); - assert_eq!(tx.gas_price().unwrap().as_u32(), 34); - assert_eq!(tx.value().unwrap().as_u32(), 56); - assert_eq!(tx.nonce().unwrap().as_u32(), 78); - assert_eq!(tx.chain_id().unwrap().as_u32(), 1); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_args() -> eyre::Result<()> { - let provider = MyProvider {}; - let mut builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), Chain::Mainnet, false).await.unwrap(); - builder.args(Some(("what_a_day(int)", vec![String::from("31337")]))).await?; - let (_, function_maybe) = builder.build(); - - assert_ne!(function_maybe, None); - let function = function_maybe.unwrap(); - assert_eq!(function.name, String::from("what_a_day")); - // could test function.inputs() but that should be covered by utils's unit test - Ok(()) - } -} diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 9eff595b69695..a28cf27f3c66a 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,62 +1,174 @@ //! Contains various tests for checking cast commands +use alloy_chains::NamedChain; +use alloy_network::{TransactionBuilder, TransactionResponse}; +use alloy_primitives::{address, b256, Bytes, B256}; +use alloy_provider::{Provider, ProviderBuilder}; +use alloy_rpc_types::{BlockNumberOrTag, Index, TransactionRequest}; +use anvil::{EthereumHardfork, NodeConfig}; use foundry_test_utils::{ - casttest, - util::{OutputExt, TestCommand, TestProject}, + rpc::{ + next_etherscan_api_key, next_http_rpc_endpoint, next_mainnet_etherscan_api_key, + next_rpc_endpoint, next_ws_rpc_endpoint, + }, + str, + util::OutputExt, }; -use foundry_utils::rpc::next_http_rpc_endpoint; -use std::{io::Write, path::Path}; +use std::{fs, io::Write, path::Path, str::FromStr}; + +#[macro_use] +extern crate foundry_test_utils; + +mod selectors; + +casttest!(print_short_version, |_prj, cmd| { + cmd.arg("-V").assert_success().stdout_eq(str![[r#" +cast [..]-[..] ([..] [..]) + +"#]]); +}); + +casttest!(print_long_version, |_prj, cmd| { + cmd.arg("--version").assert_success().stdout_eq(str![[r#" +cast Version: [..] +Commit SHA: [..] +Build Timestamp: [..] +Build Profile: [..] + +"#]]); +}); // tests `--help` is printed to std out -casttest!(print_help, |_: TestProject, mut cmd: TestCommand| { - cmd.arg("--help"); - cmd.assert_non_empty_stdout(); +casttest!(print_help, |_prj, cmd| { + cmd.arg("--help").assert_success().stdout_eq(str![[r#" +A Swiss Army knife for interacting with Ethereum applications from the command line + +Usage: cast[..] + +Commands: +... + +Options: + -h, --help + Print help (see a summary with '-h') + + -j, --threads + Number of threads to use. Specifying 0 defaults to the number of logical cores + + [aliases: jobs] + + -V, --version + Print version + +Display options: + --color + The color of the log messages + + Possible values: + - auto: Intelligently guess whether to use color output (default) + - always: Force color output + - never: Force disable color output + + --json + Format log messages as JSON + + -q, --quiet + Do not print log messages + + -v, --verbosity... + Verbosity level of the log messages. + + Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). + + Depending on the context the verbosity levels have different meanings. + + For example, the verbosity levels of the EVM are: + - 2 (-vv): Print logs for all tests. + - 3 (-vvv): Print execution traces for failing tests. + - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. + - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. + +Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html + +"#]]); }); // tests that the `cast block` command works correctly -casttest!(latest_block, |_: TestProject, mut cmd: TestCommand| { +casttest!(latest_block, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast find-block` cmd.args(["block", "latest", "--rpc-url", eth_rpc_url.as_str()]); - let output = cmd.stdout_lossy(); - assert!(output.contains("transactions:")); - assert!(output.contains("gasUsed")); + cmd.assert_success().stdout_eq(str![[r#" + + +baseFeePerGas [..] +difficulty [..] +extraData [..] +gasLimit [..] +gasUsed [..] +hash [..] +logsBloom [..] +miner [..] +mixHash [..] +nonce [..] +number [..] +parentHash [..] +parentBeaconRoot [..] +transactionsRoot [..] +receiptsRoot [..] +sha3Uncles [..] +size [..] +stateRoot [..] +timestamp [..] +withdrawalsRoot [..] +totalDifficulty [..] +blobGasUsed [..] +excessBlobGas [..] +requestsHash [..] +transactions: [ +... +] + +"#]]); // cmd.cast_fuse().args(["block", "15007840", "-f", "hash", "--rpc-url", eth_rpc_url.as_str()]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0x950091817a57e22b6c1f3b951a15f52d41ac89b299cc8f9c89bb6d185f80c415") + cmd.assert_success().stdout_eq(str![[r#" +0x950091817a57e22b6c1f3b951a15f52d41ac89b299cc8f9c89bb6d185f80c415 + +"#]]); }); // tests that the `cast find-block` command works correctly -casttest!(finds_block, |_: TestProject, mut cmd: TestCommand| { +casttest!(finds_block, |_prj, cmd| { // Construct args let timestamp = "1647843609".to_string(); let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast find-block` - cmd.args(["find-block", "--rpc-url", eth_rpc_url.as_str(), ×tamp]); - let output = cmd.stdout_lossy(); - println!("{output}"); + // + cmd.args(["find-block", "--rpc-url", eth_rpc_url.as_str(), ×tamp]) + .assert_success() + .stdout_eq(str![[r#" +14428082 - // Expect successful block query - // Query: 1647843609, Mar 21 2022 06:20:09 UTC - // Output block: https://etherscan.io/block/14428082 - // Output block time: Mar 21 2022 06:20:09 UTC - assert!(output.contains("14428082"), "{}", output); +"#]]); }); // tests that we can create a new wallet with keystore -casttest!(new_wallet_keystore_with_password, |_: TestProject, mut cmd: TestCommand| { - cmd.args(["wallet", "new", ".", "--unsafe-password", "test"]); - let out = cmd.stdout_lossy(); - assert!(out.contains("Created new encrypted keystore file")); - assert!(out.contains("Address")); +casttest!(new_wallet_keystore_with_password, |_prj, cmd| { + cmd.args(["wallet", "new", ".", "--unsafe-password", "test"]).assert_success().stdout_eq(str![ + [r#" +Created new encrypted keystore file: [..] +[ADDRESS] + +"#] + ]); }); // tests that we can get the address of a keystore file -casttest!(wallet_address_keystore_with_password_file, |_: TestProject, mut cmd: TestCommand| { +casttest!(wallet_address_keystore_with_password_file, |_prj, cmd| { let keystore_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/keystore"); cmd.args([ @@ -69,39 +181,61 @@ casttest!(wallet_address_keystore_with_password_file, |_: TestProject, mut cmd: .unwrap(), "--password-file", keystore_dir.join("password-ec554").to_str().unwrap(), - ]); - let out = cmd.stdout_lossy(); - assert!(out.contains("0xeC554aeAFE75601AaAb43Bd4621A22284dB566C2")); + ]) + .assert_success() + .stdout_eq(str![[r#" +0xeC554aeAFE75601AaAb43Bd4621A22284dB566C2 + +"#]]); }); // tests that `cast wallet sign message` outputs the expected signature -casttest!(cast_wallet_sign_message_utf8_data, |_: TestProject, mut cmd: TestCommand| { - cmd.args([ - "wallet", - "sign", - "--private-key", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "test", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c"); +casttest!(wallet_sign_message_utf8_data, |_prj, cmd| { + let pk = "0x0000000000000000000000000000000000000000000000000000000000000001"; + let address = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"; + let msg = "test"; + let expected = "0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c"; + + cmd.args(["wallet", "sign", "--private-key", pk, msg]).assert_success().stdout_eq(str![[r#" +0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c + +"#]]); + + // Success. + cmd.cast_fuse() + .args(["wallet", "verify", "-a", address, msg, expected]) + .assert_success() + .stdout_eq(str![[r#" +Validation succeeded. Address 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf signed this message. + +"#]]); + + // Fail. + cmd.cast_fuse() + .args(["wallet", "verify", "-a", address, "other msg", expected]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Validation failed. Address 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf did not sign this message. + +"#]]); }); // tests that `cast wallet sign message` outputs the expected signature, given a 0x-prefixed data -casttest!(cast_wallet_sign_message_hex_data, |_: TestProject, mut cmd: TestCommand| { +casttest!(wallet_sign_message_hex_data, |_prj, cmd| { cmd.args([ "wallet", "sign", "--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 -casttest!(cast_wallet_sign_typed_data_string, |_: TestProject, mut cmd: TestCommand| { +casttest!(wallet_sign_typed_data_string, |_prj, cmd| { cmd.args([ "wallet", "sign", @@ -109,13 +243,14 @@ casttest!(cast_wallet_sign_typed_data_string, |_: TestProject, mut cmd: TestComm "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 -casttest!(cast_wallet_sign_typed_data_file, |_: TestProject, mut cmd: TestCommand| { +casttest!(wallet_sign_typed_data_file, |_prj, cmd| { cmd.args([ "wallet", "sign", @@ -129,132 +264,407 @@ casttest!(cast_wallet_sign_typed_data_file, |_: TestProject, mut cmd: TestComman .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 estimate` is working correctly. -casttest!(estimate_function_gas, |_: TestProject, mut cmd: TestCommand| { - let eth_rpc_url = next_http_rpc_endpoint(); +// tests that `cast wallet sign-auth message` outputs the expected signature +casttest!(wallet_sign_auth, |_prj, cmd| { cmd.args([ - "estimate", - "vitalik.eth", - "--value", + "wallet", + "sign-auth", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--nonce", "100", - "deposit()", - "--rpc-url", - eth_rpc_url.as_str(), + "--chain", + "1", + "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"]).assert_success().stdout_eq(str![[r#" +0xf85a01947e5f4552091a69125d5dfcb7b8c2659029395bdf6401a0ad489ee0314497c3f06567f3080a46a63908edc1c7cdf2ac2d609ca911212086a065a6ba951c8748dd8634740fe498efb61770097d99ff5fdcb9a863b62ea899f6 + +"#]]); +}); + +// tests that `cast wallet list` outputs the local accounts +casttest!(wallet_list_local_accounts, |prj, cmd| { + let keystore_path = prj.root().join("keystore"); + fs::create_dir_all(keystore_path).unwrap(); + cmd.set_current_dir(prj.root()); + + // empty results + cmd.cast_fuse() + .args(["wallet", "list", "--dir", "keystore"]) + .assert_success() + .stdout_eq(str![""]); + + // create 10 wallets + cmd.cast_fuse() + .args(["wallet", "new", "keystore", "-n", "10", "--unsafe-password", "test"]) + .assert_success() + .stdout_eq(str![[r#" +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] +Created new encrypted keystore file: [..] +[ADDRESS] + +"#]]); + + // test list new wallet + cmd.cast_fuse().args(["wallet", "list", "--dir", "keystore"]).assert_success().stdout_eq(str![ + [r#" +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) +[..] (Local) + +"#] ]); - let out: u32 = cmd.stdout_lossy().trim().parse().unwrap(); - // ensure we get a positive non-error value for gas estimate - assert!(out.ge(&0)); }); -// tests that `cast estimate --create` is working correctly. -casttest!(estimate_contract_deploy_gas, |_: TestProject, mut cmd: TestCommand| { - let eth_rpc_url = next_http_rpc_endpoint(); - // sample contract code bytecode. Wouldn't run but is valid bytecode that the estimate method - // accepts and could be deployed. +// tests that `cast wallet new-mnemonic --entropy` outputs the expected mnemonic +casttest!(wallet_mnemonic_from_entropy, |_prj, cmd| { cmd.args([ - "estimate", - "--rpc-url", - eth_rpc_url.as_str(), - "--create", - "0000", - "ERC20(uint256,string,string)", - "100", - "Test", - "TST", - ]); + "wallet", + "new-mnemonic", + "--accounts", + "3", + "--entropy", + "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", + ]) + .assert_success() + .stdout_eq(str![[r#" +Generating mnemonic from provided entropy... +Successfully generated a new mnemonic. +Phrase: +test test test test test test test test test test test junk - let gas: u32 = cmd.stdout_lossy().trim().parse().unwrap(); - // ensure we get a positive non-error value for gas estimate - assert!(gas > 0); +Accounts: +- Account 0: +Address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Private key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +- Account 1: +Address: 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 +Private key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +- Account 2: +Address: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC +Private key: 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a + + +"#]]); }); -// tests that the `cast upload-signatures` command works correctly -casttest!(upload_signatures, |_: TestProject, mut cmd: TestCommand| { - // test no prefix is accepted as function - cmd.args(["upload-signature", "transfer(address,uint256)"]); - let output = cmd.stdout_lossy(); +// tests that `cast wallet new-mnemonic --json` outputs the expected mnemonic +casttest!(wallet_mnemonic_from_entropy_json, |_prj, cmd| { + cmd.args([ + "wallet", + "new-mnemonic", + "--accounts", + "3", + "--entropy", + "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c", + "--json", + ]) + .assert_success() + .stdout_eq(str![[r#" +{ + "mnemonic": "test test test test test test test test test test test junk", + "accounts": [ + { + "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "private_key": "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + }, + { + "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "private_key": "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + }, + { + "address": "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", + "private_key": "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" + } + ] +} - assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); +"#]]); +}); - // test event prefix - cmd.args(["upload-signature", "event Transfer(address,uint256)"]); - let output = cmd.stdout_lossy(); +// tests that `cast wallet private-key` with arguments outputs the private key +casttest!(wallet_private_key_from_mnemonic_arg, |_prj, cmd| { + cmd.args([ + "wallet", + "private-key", + "test test test test test test test test test test test junk", + "1", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d - assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); +"#]]); +}); - // test multiple sigs +// tests that `cast wallet private-key` with options outputs the private key +casttest!(wallet_private_key_from_mnemonic_option, |_prj, cmd| { cmd.args([ - "upload-signature", - "event Transfer(address,uint256)", - "transfer(address,uint256)", - "approve(address,uint256)", - ]); - let output = cmd.stdout_lossy(); + "wallet", + "private-key", + "--mnemonic", + "test test test test test test test test test test test junk", + "--mnemonic-index", + "1", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d - assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); - assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); - assert!(output.contains("Function approve(address,uint256): 0x095ea7b3"), "{}", output); +"#]]); +}); - // test abi +// tests that `cast wallet private-key` with derivation path outputs the private key +casttest!(wallet_private_key_with_derivation_path, |_prj, cmd| { cmd.args([ - "upload-signature", - "event Transfer(address,uint256)", - "transfer(address,uint256)", - Path::new(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/ERC20Artifact.json") - .into_os_string() - .into_string() - .unwrap() - .as_str(), + "wallet", + "private-key", + "--mnemonic", + "test test test test test test test test test test test junk", + "--mnemonic-derivation-path", + "m/44'/60'/0'/0/1", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d + +"#]]); +}); + +// tests that `cast wallet import` creates a keystore for a private key and that `cast wallet +// decrypt-keystore` can access it +casttest!(wallet_import_and_decrypt, |prj, cmd| { + let keystore_path = prj.root().join("keystore"); + + cmd.set_current_dir(prj.root()); + + let account_name = "testAccount"; + + // Default Anvil private key + let test_private_key = + b256!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + + // import private key + cmd.cast_fuse() + .args([ + "wallet", + "import", + account_name, + "--private-key", + &test_private_key.to_string(), + "-k", + "keystore", + "--unsafe-password", + "test", + ]) + .assert_success() + .stdout_eq(str![[r#" +`testAccount` keystore was saved successfully. [ADDRESS] + +"#]]); + + // check that the keystore file was created + let keystore_file = keystore_path.join(account_name); + + assert!(keystore_file.exists()); + + // decrypt the keystore file + let decrypt_output = cmd.cast_fuse().args([ + "wallet", + "decrypt-keystore", + account_name, + "-k", + "keystore", + "--unsafe-password", + "test", ]); - let output = cmd.stdout_lossy(); - assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); - assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); - assert!(output.contains("Function approve(address,uint256): 0x095ea7b3"), "{}", output); - assert!(output.contains("Function decimals(): 0x313ce567"), "{}", output); - assert!(output.contains("Function allowance(address,address): 0xdd62ed3e"), "{}", output); + // get the PK out of the output (last word in the output) + let decrypt_output = decrypt_output.assert_success().get_output().stdout_lossy(); + let private_key_string = decrypt_output.split_whitespace().last().unwrap(); + // check that the decrypted private key matches the imported private key + let decrypted_private_key = B256::from_str(private_key_string).unwrap(); + // the form + assert_eq!(decrypted_private_key, test_private_key); +}); + +// tests that `cast estimate` is working correctly. +casttest!(estimate_function_gas, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // ensure we get a positive non-error value for gas estimate + let output: u32 = cmd + .args([ + "estimate", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // vitalik.eth + "--value", + "100", + "deposit()", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .parse() + .unwrap(); + assert!(output.ge(&0)); +}); + +// tests that `cast estimate --create` is working correctly. +casttest!(estimate_contract_deploy_gas, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + // sample contract code bytecode. Wouldn't run but is valid bytecode that the estimate method + // accepts and could be deployed. + let output = cmd + .args([ + "estimate", + "--rpc-url", + eth_rpc_url.as_str(), + "--create", + "0000", + "ERC20(uint256,string,string)", + "100", + "Test", + "TST", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + // ensure we get a positive non-error value for gas estimate + let output: u32 = output.trim().parse().unwrap(); + assert!(output > 0); }); // tests that the `cast to-rlp` and `cast from-rlp` commands work correctly -casttest!(cast_rlp, |_: TestProject, mut cmd: TestCommand| { - cmd.args(["--to-rlp", "[\"0xaa\", [[\"bb\"]], \"0xcc\"]"]); - let out = cmd.stdout_lossy(); - assert!(out.contains("0xc881aac3c281bb81cc"), "{}", out); +casttest!(rlp, |_prj, cmd| { + cmd.args(["--to-rlp", "[\"0xaa\", [[\"bb\"]], \"0xcc\"]"]).assert_success().stdout_eq(str![[ + r#" +0xc881aac3c281bb81cc + +"# + ]]); cmd.cast_fuse(); - cmd.args(["--from-rlp", "0xcbc58455556666c0c0c2c1c0"]); - let out = cmd.stdout_lossy(); - assert!(out.contains("[[\"0x55556666\"],[],[],[[[]]]]"), "{}", out); + cmd.args(["--from-rlp", "0xcbc58455556666c0c0c2c1c0"]).assert_success().stdout_eq(str![[r#" +[["0x55556666"],[],[],[[[]]]] + +"#]]); +}); + +// test that `cast impl` works correctly for both the implementation slot and the beacon slot +casttest!(impl_slot, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // Call `cast impl` for the implementation slot (AAVE Proxy) + cmd.args([ + "impl", + "0x4965f6FA20fE9728deCf5165016fc338a5a85aBF", + "--rpc-url", + eth_rpc_url.as_str(), + "--block", + "21422087", + ]) + .assert_success() + .stdout_eq(str![[r#" +0xb61306c8eb34a2104d9eb8d84f1bb1001067fa4b + +"#]]); +}); + +casttest!(impl_slot_beacon, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + + // Call `cast impl` for the beacon slot + cmd.args([ + "impl", + "0xc63d9f0040d35f328274312fc8771a986fc4ba86", + "--beacon", + "--rpc-url", + eth_rpc_url.as_str(), + "--block", + "21422087", + ]) + .assert_success() + .stdout_eq(str![[r#" +0xa748ae65ba11606492a9c57effa0d4b7be551ec2 + +"#]]); }); // test for cast_rpc without arguments -casttest!(cast_rpc_no_args, |_: TestProject, mut cmd: TestCommand| { +casttest!(rpc_no_args, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_chainId` - cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_chainId"]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim_end(), r#""0x1""#); + cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_chainId"]).assert_success().stdout_eq( + str![[r#" +"0x1" + +"#]], + ); +}); + +// test for cast_rpc without arguments using websocket +casttest!(ws_rpc_no_args, |_prj, cmd| { + let eth_rpc_url = next_ws_rpc_endpoint(); + + // Call `cast rpc eth_chainId` + cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_chainId"]).assert_success().stdout_eq( + str![[r#" +"0x1" + +"#]], + ); }); // test for cast_rpc with arguments -casttest!(cast_rpc_with_args, |_: TestProject, mut cmd: TestCommand| { +casttest!(rpc_with_args, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_getBlockByNumber 0x123 false` - cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "0x123", "false"]); - let output = cmd.stdout_lossy(); - assert!(output.contains(r#""number":"0x123""#), "{}", output); + cmd.args(["rpc", "--rpc-url", eth_rpc_url.as_str(), "eth_getBlockByNumber", "0x123", "false"]) + .assert_json_stdout(str![[r#" +{"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"totalDifficulty":"[..]","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} + +"#]]); }); // test for cast_rpc with raw params -casttest!(cast_rpc_raw_params, |_: TestProject, mut cmd: TestCommand| { +casttest!(rpc_raw_params, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `cast rpc eth_getBlockByNumber --raw '["0x123", false]'` @@ -265,13 +675,15 @@ casttest!(cast_rpc_raw_params, |_: TestProject, mut cmd: TestCommand| { "eth_getBlockByNumber", "--raw", r#"["0x123", false]"#, - ]); - let output = cmd.stdout_lossy(); - assert!(output.contains(r#""number":"0x123""#), "{}", output); + ]) + .assert_json_stdout(str![[r#" +{"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"totalDifficulty":"[..]","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} + +"#]]); }); // test for cast_rpc with direct params -casttest!(cast_rpc_raw_params_stdin, |_: TestProject, mut cmd: TestCommand| { +casttest!(rpc_raw_params_stdin, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); // Call `echo "\n[\n\"0x123\",\nfalse\n]\n" | cast rpc eth_getBlockByNumber --raw @@ -279,21 +691,23 @@ casttest!(cast_rpc_raw_params_stdin, |_: TestProject, mut cmd: TestCommand| { |mut stdin| { stdin.write_all(b"\n[\n\"0x123\",\nfalse\n]\n").unwrap(); }, - ); - let output = cmd.stdout_lossy(); - assert!(output.contains(r#""number":"0x123""#), "{}", output); + ) + .assert_json_stdout(str![[r#" +{"number":"0x123","hash":"0xc5dab4e189004a1312e9db43a40abb2de91ad7dd25e75880bf36016d8e9df524","transactions":[],"totalDifficulty":"[..]","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","extraData":"0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32","nonce":"0x29d6547c196e00e0","miner":"0xbb7b8287f3f0a933474a79eae42cbca977791171","difficulty":"0x494433b31","gasLimit":"0x1388","gasUsed":"0x0","uncles":[],"sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x220","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","stateRoot":"0x3fe6bd17aa85376c7d566df97d9f2e536f37f7a87abb3a6f9e2891cf9442f2e4","mixHash":"0x943056aa305aa6d22a3c06110942980342d1f4d4b11c17711961436a0f963ea0","parentHash":"0x7abfd11e862ccde76d6ea8ee20978aac26f4bcb55de1188cc0335be13e817017","timestamp":"0x55ba4564"} + +"#]]); }); // checks `cast calldata` can handle arrays -casttest!(calldata_array, |_: TestProject, mut cmd: TestCommand| { - cmd.args(["calldata", "propose(string[])", "[\"\"]"]); - let out = cmd.stdout_lossy(); - assert_eq!(out.trim(),"0xcde2baba0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" - ); +casttest!(calldata_array, |_prj, cmd| { + cmd.args(["calldata", "propose(string[])", "[\"\"]"]).assert_success().stdout_eq(str![[r#" +0xcde2baba0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000 + +"#]]); }); // -casttest!(cast_run_succeeds, |_: TestProject, mut cmd: TestCommand| { +casttest!(run_succeeds, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "run", @@ -302,14 +716,18 @@ casttest!(cast_run_succeeds, |_: TestProject, mut cmd: TestCommand| { "--quick", "--rpc-url", rpc.as_str(), - ]); - let output = cmd.stdout_lossy(); - assert!(output.contains("Transaction successfully executed")); - assert!(!output.contains("Revert")); + ]) + .assert_success() + .stdout_eq(str![[r#" +... +Transaction successfully executed. +[GAS] + +"#]]); }); // tests that `cast --to-base` commands are working correctly. -casttest!(cast_to_base, |_: TestProject, mut cmd: TestCommand| { +casttest!(to_base, |_prj, cmd| { let values = [ "1", "100", @@ -326,53 +744,99 @@ casttest!(cast_to_base, |_: TestProject, mut cmd: TestCommand| { if subcmd == "--to-base" { for base in ["bin", "oct", "dec", "hex"] { cmd.cast_fuse().args([subcmd, value, base]); - assert!(!cmd.stdout_lossy().trim().is_empty()); + assert!(!cmd.assert_success().get_output().stdout_lossy().trim().is_empty()); } } else { cmd.cast_fuse().args([subcmd, value]); - assert!(!cmd.stdout_lossy().trim().is_empty()); + assert!(!cmd.assert_success().get_output().stdout_lossy().trim().is_empty()); } } } }); // tests that revert reason is only present if transaction has reverted. -casttest!(cast_receipt_revert_reason, |_: TestProject, mut cmd: TestCommand| { +casttest!(receipt_revert_reason, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); // - cmd.cast_fuse().args([ + cmd.args([ "receipt", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "--rpc-url", rpc.as_str(), - ]); - let output = cmd.stdout_lossy(); - assert!(!output.contains("revertReason")); + ]) + .assert_success() + .stdout_eq(str![[r#" + +blockHash 0x2cfe65be49863676b6dbc04d58176a14f39b123f1e2f4fea0383a2d82c2c50d0 +blockNumber 16239315 +contractAddress +cumulativeGasUsed 10743428 +effectiveGasPrice 10539984136 +from 0x199D5ED7F45F4eE35960cF22EAde2076e95B253F +gasUsed 21000 +logs [] +logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +root +status 1 (success) +transactionHash 0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e +transactionIndex 116 +type 0 +blobGasPrice +blobGasUsed +to 0x91da5bf3F8Eb72724E6f50Ec6C3D199C6355c59c + +"#]]); + + let rpc = next_http_rpc_endpoint(); // - cmd.cast_fuse().args([ - "receipt", - "0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c", - "--rpc-url", - rpc.as_str(), - ]); - let output = cmd.stdout_lossy(); - assert!(output.contains("revertReason")); - assert!(output.contains("Transaction too old")); + cmd.cast_fuse() + .args([ + "receipt", + "0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c", + "--rpc-url", + rpc.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" + +blockHash 0x883f974b17ca7b28cb970798d1c80f4d4bb427473dc6d39b2a7fe24edc02902d +blockNumber 14839405 +contractAddress +cumulativeGasUsed 20273649 +effectiveGasPrice 21491736378 +from 0x3cF412d970474804623bb4e3a42dE13F9bCa5436 +gasUsed 24952 +logs [] +logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +root +status 0 (failed) +transactionHash 0x0e07d8b53ed3d91314c80e53cf25bcde02084939395845cbb625b029d568135c +transactionIndex 173 +type 2 +blobGasPrice +blobGasUsed +to 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 +revertReason [..]Transaction too old, data: "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000135472616e73616374696f6e20746f6f206f6c6400000000000000000000000000" + +"#]]); }); // tests that `cast --parse-bytes32-address` command is working correctly. -casttest!(parse_bytes32_address, |_: TestProject, mut cmd: TestCommand| { +casttest!(parse_bytes32_address, |_prj, cmd| { cmd.args([ "--parse-bytes32-address", "0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045", - ]); - let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045") + ]) + .assert_success() + .stdout_eq(str![[r#" +0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 + +"#]]); }); -casttest!(cast_access_list, |_: TestProject, mut cmd: TestCommand| { +casttest!(access_list, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "access-list", @@ -383,15 +847,25 @@ casttest!(cast_access_list, |_: TestProject, mut cmd: TestCommand| { rpc.as_str(), "--gas-limit", // need to set this for alchemy.io to avoid "intrinsic gas too low" error "100000", - ]); + ]) + .assert_success() + .stdout_eq(str![[r#" +[GAS] +access list: +- address: [..] + keys: +... +- address: [..] + keys: +... +- address: [..] + keys: +... - let output = cmd.stdout_lossy(); - assert!(output.contains("address: 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599")); - assert!(output.contains("0x0d2a19d3ac39dc6cc6fd07423195495e18679bd8c7dd610aa1db7cd784a683a8")); - assert!(output.contains("0x7fba2702a7d6e85ac783a88eacdc48e51310443458071f6db9ac66f8ca7068b8")); +"#]]); }); -casttest!(cast_logs_topics, |_: TestProject, mut cmd: TestCommand| { +casttest!(logs_topics, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "logs", @@ -403,14 +877,12 @@ casttest!(cast_logs_topics, |_: TestProject, mut cmd: TestCommand| { "12421182", "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000ab5801a7d398351b8be11c439e05c5b3259aec9b", - ]); - - cmd.unchecked_output().stdout_matches_path( - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cast_logs.stdout"), - ); + ]) + .assert_success() + .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); -casttest!(cast_logs_topic_2, |_: TestProject, mut cmd: TestCommand| { +casttest!(logs_topic_2, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "logs", @@ -424,14 +896,12 @@ casttest!(cast_logs_topic_2, |_: TestProject, mut cmd: TestCommand| { "", "0x00000000000000000000000068a99f89e475a078645f4bac491360afe255dff1", /* Filter on the * `to` address */ - ]); - - cmd.unchecked_output().stdout_matches_path( - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cast_logs.stdout"), - ); + ]) + .assert_success() + .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); -casttest!(cast_logs_sig, |_: TestProject, mut cmd: TestCommand| { +casttest!(logs_sig, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "logs", @@ -443,14 +913,12 @@ casttest!(cast_logs_sig, |_: TestProject, mut cmd: TestCommand| { "12421182", "Transfer(address indexed from, address indexed to, uint256 value)", "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B", - ]); - - cmd.unchecked_output().stdout_matches_path( - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cast_logs.stdout"), - ); + ]) + .assert_success() + .stdout_eq(file!["../fixtures/cast_logs.stdout"]); }); -casttest!(cast_logs_sig_2, |_: TestProject, mut cmd: TestCommand| { +casttest!(logs_sig_2, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); cmd.args([ "logs", @@ -463,40 +931,1114 @@ casttest!(cast_logs_sig_2, |_: TestProject, mut cmd: TestCommand| { "Transfer(address indexed from, address indexed to, uint256 value)", "", "0x68A99f89E475a078645f4BAC491360aFe255Dff1", + ]) + .assert_success() + .stdout_eq(file!["../fixtures/cast_logs.stdout"]); +}); + +casttest!(mktx, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--chain", + "1", + "--nonce", + "0", + "--value", + "100", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]).assert_success().stdout_eq(str![[r#" +0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000016480c001a070d55e79ed3ac9fc8f51e78eb91fd054720d943d66633f2eb1bc960f0126b0eca052eda05a792680de3181e49bab4093541f75b49d1ecbe443077b3660c836016a + +"#]]); +}); + +// ensure recipient or code is required +casttest!(mktx_requires_to, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--chain", + "1", ]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: Must specify a recipient address or contract code to deploy - cmd.unchecked_output().stdout_matches_path( - Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/cast_logs.stdout"), - ); +"#]]); +}); + +casttest!(mktx_signer_from_mismatch, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--from", + "0x0000000000000000000000000000000000000001", + "--chain", + "1", + "0x0000000000000000000000000000000000000001", + ]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: The specified sender via CLI/env vars does not match the sender configured via +the hardware wallet's HD Path. +Please use the `--hd-path ` parameter to specify the BIP32 Path which +corresponds to the sender, or let foundry automatically detect it by not specifying any sender address. + +"#]]); +}); + +casttest!(mktx_signer_from_match, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--from", + "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf", + "--chain", + "1", + "--nonce", + "0", + "--gas-limit", + "21000", + "--gas-price", + "10000000000", + "--priority-gas-price", + "1000000000", + "0x0000000000000000000000000000000000000001", + ]).assert_success().stdout_eq(str![[r#" +0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c001a0cce9a61187b5d18a89ecd27ec675e3b3f10d37f165627ef89a15a7fe76395ce8a07537f5bffb358ffbef22cda84b1c92f7211723f9e09ae037e81686805d3e5505 + +"#]]); }); // tests that the raw encoded transaction is returned -casttest!(cast_tx_raw, |_: TestProject, mut cmd: TestCommand| { +casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); - // - cmd.cast_fuse().args([ + // + cmd.args([ "tx", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "raw", "--rpc-url", rpc.as_str(), - ]); - let output = cmd.stdout_lossy(); + ]).assert_success().stdout_eq(str![[r#" +0xf86d824c548502743b65088275309491da5bf3f8eb72724e6f50ec6c3d199c6355c59c87a0a73f33e9e4cc8025a0428518b1748a08bbeb2392ea055b418538944d30adfc2accbbfa8362a401d3a4a07d6093ab2580efd17c11b277de7664fce56e6953cae8e925bec3313399860470 - // - assert_eq!( - output.trim(), - "0xf86d824c548502743b65088275309491da5bf3f8eb72724e6f50ec6c3d199c6355c59c87a0a73f33e9e4cc8025a0428518b1748a08bbeb2392ea055b418538944d30adfc2accbbfa8362a401d3a4a07d6093ab2580efd17c11b277de7664fce56e6953cae8e925bec3313399860470" - ); +"#]]); + // cmd.cast_fuse().args([ "tx", "0x44f2aaa351460c074f2cb1e5a9e28cbc7d83f33e425101d2de14331c7b7ec31e", "--raw", "--rpc-url", rpc.as_str(), + ]).assert_success().stdout_eq(str![[r#" +0xf86d824c548502743b65088275309491da5bf3f8eb72724e6f50ec6c3d199c6355c59c87a0a73f33e9e4cc8025a0428518b1748a08bbeb2392ea055b418538944d30adfc2accbbfa8362a401d3a4a07d6093ab2580efd17c11b277de7664fce56e6953cae8e925bec3313399860470 + +"#]]); +}); + +// ensure receipt or code is required +casttest!(send_requires_to, |_prj, cmd| { + cmd.args([ + "send", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + "--chain", + "1", + ]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: Must specify a recipient address or contract code to deploy + +"#]]); +}); + +casttest!(storage, |_prj, cmd| { + let rpc = next_http_rpc_endpoint(); + cmd.args(["storage", "vitalik.eth", "1", "--rpc-url", &rpc]).assert_success().stdout_eq(str![ + [r#" +0x0000000000000000000000000000000000000000000000000000000000000000 + +"#] ]); - let output2 = cmd.stdout_lossy(); - assert_eq!(output, output2); + + let rpc = next_http_rpc_endpoint(); + cmd.cast_fuse() + .args(["storage", "vitalik.eth", "0x01", "--rpc-url", &rpc]) + .assert_success() + .stdout_eq(str![[r#" +0x0000000000000000000000000000000000000000000000000000000000000000 + +"#]]); + + let rpc = next_http_rpc_endpoint(); + let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + let decimals_slot = "0x09"; + cmd.cast_fuse() + .args(["storage", usdt, decimals_slot, "--rpc-url", &rpc]) + .assert_success() + .stdout_eq(str![[r#" +0x0000000000000000000000000000000000000000000000000000000000000006 + +"#]]); + + let rpc = next_http_rpc_endpoint(); + let total_supply_slot = "0x01"; + let block_before = "4634747"; + let block_after = "4634748"; + cmd.cast_fuse() + .args(["storage", usdt, total_supply_slot, "--rpc-url", &rpc, "--block", block_before]) + .assert_success() + .stdout_eq(str![[r#" +0x0000000000000000000000000000000000000000000000000000000000000000 + +"#]]); + + let rpc = next_http_rpc_endpoint(); + cmd.cast_fuse() + .args(["storage", usdt, total_supply_slot, "--rpc-url", &rpc, "--block", block_after]) + .assert_success() + .stdout_eq(str![[r#" +0x000000000000000000000000000000000000000000000000000000174876e800 + +"#]]); +}); + +// +casttest!(storage_layout_simple, |_prj, cmd| { + cmd.args([ + "storage", + "--rpc-url", + next_rpc_endpoint(NamedChain::Mainnet).as_str(), + "--block", + "21034138", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + ]) + .assert_success() + .stdout_eq(str![[r#" + +╭---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╮ +| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | ++========================================================================================================================================================================+ +| _owner | address | 0 | 0 | 20 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer | +|---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------| +| _paused | bool | 0 | 20 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/Create2Deployer.sol:Create2Deployer | +╰---------+---------+------+--------+-------+-------+--------------------------------------------------------------------+-----------------------------------------------╯ + + +"#]]); +}); + +// +casttest!(storage_layout_simple_json, |_prj, cmd| { + cmd.args([ + "storage", + "--rpc-url", + next_rpc_endpoint(NamedChain::Mainnet).as_str(), + "--block", + "21034138", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2", + "--json", + ]) + .assert_success() + .stdout_eq(file!["../fixtures/storage_layout_simple.json": Json]); +}); + +// +casttest!(storage_layout_complex, |_prj, cmd| { + cmd.args([ + "storage", + "--rpc-url", + next_rpc_endpoint(NamedChain::Mainnet).as_str(), + "--block", + "21034138", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + ]) + .assert_success() + .stdout_eq(str![[r#" + +╭-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╮ +| Name | Type | Slot | Offset | Bytes | Value | Hex Value | Contract | ++======================================================================================================================================================================================================================================================================================+ +| _status | uint256 | 0 | 0 | 32 | 1 | 0x0000000000000000000000000000000000000000000000000000000000000001 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _generalPoolsBalances | mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map) | 1 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _nextNonce | mapping(address => uint256) | 2 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _paused | bool | 3 | 0 | 1 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _authorizer | contract IAuthorizer | 3 | 1 | 20 | 549683469959765988649777481110995959958745616871 | 0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _approvedRelayers | mapping(address => mapping(address => bool)) | 4 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _isPoolRegistered | mapping(bytes32 => bool) | 5 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _nextPoolNonce | uint256 | 6 | 0 | 32 | 1760 | 0x00000000000000000000000000000000000000000000000000000000000006e0 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _minimalSwapInfoPoolsBalances | mapping(bytes32 => mapping(contract IERC20 => bytes32)) | 7 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _minimalSwapInfoPoolsTokens | mapping(bytes32 => struct EnumerableSet.AddressSet) | 8 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _twoTokenPoolTokens | mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens) | 9 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _poolAssetManagers | mapping(bytes32 => mapping(contract IERC20 => address)) | 10 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +|-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------| +| _internalTokenBalance | mapping(address => mapping(contract IERC20 => uint256)) | 11 | 0 | 32 | 0 | 0x0000000000000000000000000000000000000000000000000000000000000000 | contracts/vault/Vault.sol:Vault | +╰-------------------------------+--------------------------------------------------------------------+------+--------+-------+--------------------------------------------------+--------------------------------------------------------------------+---------------------------------╯ + + +"#]]); +}); + +casttest!(storage_layout_complex_json, |_prj, cmd| { + cmd.args([ + "storage", + "--rpc-url", + next_rpc_endpoint(NamedChain::Mainnet).as_str(), + "--block", + "21034138", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0xBA12222222228d8Ba445958a75a0704d566BF2C8", + "--json", + ]) + .assert_success() + .stdout_eq(file!["../fixtures/storage_layout_complex.json": Json]); +}); + +casttest!(balance, |_prj, cmd| { + let rpc = next_http_rpc_endpoint(); + let usdt = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + + let usdt_result = cmd + .args([ + "balance", + "0x0000000000000000000000000000000000000000", + "--erc20", + usdt, + "--rpc-url", + &rpc, + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + let alias_result = cmd + .cast_fuse() + .args([ + "balance", + "0x0000000000000000000000000000000000000000", + "--erc721", + usdt, + "--rpc-url", + &rpc, + ]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + + assert_ne!(usdt_result, "0"); + assert_eq!(alias_result, usdt_result); +}); + +// tests that `cast interface` excludes the constructor +// +casttest!(interface_no_constructor, |prj, cmd| { + let interface = include_str!("../fixtures/interface.json"); + + let path = prj.root().join("interface.json"); + fs::write(&path, interface).unwrap(); + // Call `cast find-block` + cmd.arg("interface").arg(&path).assert_success().stdout_eq(str![[ + r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +library IIntegrationManager { + type SpendAssetsHandleType is uint8; +} + +interface Interface { + function getIntegrationManager() external view returns (address integrationManager_); + function lend(address _vaultProxy, bytes memory, bytes memory _assetData) external; + function parseAssetsForAction(address, bytes4 _selector, bytes memory _actionData) + external + view + returns ( + IIntegrationManager.SpendAssetsHandleType spendAssetsHandleType_, + address[] memory spendAssets_, + uint256[] memory spendAssetAmounts_, + address[] memory incomingAssets_, + uint256[] memory minIncomingAssetAmounts_ + ); + function redeem(address _vaultProxy, bytes memory, bytes memory _assetData) external; +} + +"# + ]]); +}); + +// tests that fetches WETH interface from etherscan +// +casttest!(fetch_weth_interface_from_etherscan, |_prj, cmd| { + cmd.args([ + "interface", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + ]) + .assert_success() + .stdout_eq(str![[r#" +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface WETH9 { + event Approval(address indexed src, address indexed guy, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + fallback() external payable; + + function allowance(address, address) external view returns (uint256); + function approve(address guy, uint256 wad) external returns (bool); + function balanceOf(address) external view returns (uint256); + function decimals() external view returns (uint8); + function deposit() external payable; + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function totalSupply() external view returns (uint256); + function transfer(address dst, uint256 wad) external returns (bool); + function transferFrom(address src, address dst, uint256 wad) external returns (bool); + function withdraw(uint256 wad) external; +} + +"#]]); +}); + +casttest!(ens_namehash, |_prj, cmd| { + cmd.args(["namehash", "emo.eth"]).assert_success().stdout_eq(str![[r#" +0x0a21aaf2f6414aa664deb341d1114351fdb023cad07bf53b28e57c26db681910 + +"#]]); +}); + +casttest!(ens_lookup, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "lookup-address", + "0x28679A1a632125fbBf7A68d850E50623194A709E", + "--rpc-url", + ð_rpc_url, + "--verify", + ]) + .assert_success() + .stdout_eq(str![[r#" +emo.eth + +"#]]); +}); + +casttest!(ens_resolve, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args(["resolve-name", "emo.eth", "--rpc-url", ð_rpc_url, "--verify"]) + .assert_success() + .stdout_eq(str![[r#" +0x28679A1a632125fbBf7A68d850E50623194A709E + +"#]]); +}); + +casttest!(ens_resolve_no_dot_eth, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args(["resolve-name", "emo", "--rpc-url", ð_rpc_url, "--verify"]) + .assert_failure() + .stderr_eq(str![[r#" +Error: ENS resolver not found for name "emo" + +"#]]); +}); + +casttest!(index7201, |_prj, cmd| { + cmd.args(["index-erc7201", "example.main"]).assert_success().stdout_eq(str![[r#" +0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500 + +"#]]); +}); + +casttest!(index7201_unknown_formula_id, |_prj, cmd| { + cmd.args(["index-erc7201", "test", "--formula-id", "unknown"]).assert_failure().stderr_eq( + str![[r#" +Error: unsupported formula ID: unknown + +"#]], + ); +}); + +casttest!(block_number, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + let s = cmd + .args(["block-number", "--rpc-url", eth_rpc_url.as_str()]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!(s.trim().parse::().unwrap() > 0, "{s}") +}); + +casttest!(block_number_latest, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + let s = cmd + .args(["block-number", "--rpc-url", eth_rpc_url.as_str(), "latest"]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!(s.trim().parse::().unwrap() > 0, "{s}") +}); + +casttest!(block_number_hash, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + let s = cmd + .args([ + "block-number", + "--rpc-url", + eth_rpc_url.as_str(), + "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + assert_eq!(s.trim().parse::().unwrap(), 1, "{s}") +}); + +casttest!(send_eip7702, async |_prj, cmd| { + let (_api, handle) = + anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::PragueEOF.into()))) + .await; + let endpoint = handle.http_endpoint(); + + cmd.args([ + "send", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--auth", + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &endpoint, + ]) + .assert_success(); + + cmd.cast_fuse() + .args(["code", "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "--rpc-url", &endpoint]) + .assert_success() + .stdout_eq(str![[r#" +0xef010070997970c51812dc3a010c7d01b50e0d17dc79c8 + +"#]]); +}); + +casttest!(hash_message, |_prj, cmd| { + cmd.args(["hash-message", "hello"]).assert_success().stdout_eq(str![[r#" +0x50b2c43fd39106bafbba0da34fc430e1f91e3c96ea2acee2bc34119f92b37750 + +"#]]); + + cmd.cast_fuse().args(["hash-message", "0x68656c6c6f"]).assert_success().stdout_eq(str![[r#" +0x83a0870b6c63a71efdd3b2749ef700653d97454152c4b53fa9b102dc430c7c32 + +"#]]); +}); + +casttest!(parse_units, |_prj, cmd| { + cmd.args(["parse-units", "1.5", "6"]).assert_success().stdout_eq(str![[r#" +1500000 + +"#]]); + + cmd.cast_fuse().args(["pun", "1.23", "18"]).assert_success().stdout_eq(str![[r#" +1230000000000000000 + +"#]]); + + cmd.cast_fuse().args(["--parse-units", "1.23", "3"]).assert_success().stdout_eq(str![[r#" +1230 + +"#]]); +}); + +casttest!(string_decode, |_prj, cmd| { + cmd.args(["string-decode", "0x88c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000054753303235000000000000000000000000000000000000000000000000000000"]).assert_success().stdout_eq(str![[r#" +"GS025" + +"#]]); +}); + +casttest!(format_units, |_prj, cmd| { + cmd.args(["format-units", "1000000", "6"]).assert_success().stdout_eq(str![[r#" +1 + +"#]]); + + cmd.cast_fuse().args(["--format-units", "2500000", "6"]).assert_success().stdout_eq(str![[ + r#" +2.500000 + +"# + ]]); + + cmd.cast_fuse().args(["fun", "1230", "3"]).assert_success().stdout_eq(str![[r#" +1.230 + +"#]]); +}); + +// tests that fetches a sample contract creation code +// +casttest!(fetch_creation_code_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "creation-code", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033 + +"#]]); +}); + +// tests that fetches a sample contract creation args bytes +// +casttest!(fetch_creation_code_only_args_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "creation-code", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x6982508145454ce325ddbe47a25d4ec3d2311933", + "--rpc-url", + eth_rpc_url.as_str(), + "--only-args", + ]) + .assert_success() + .stdout_eq(str![[r#" +0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 + +"#]]); +}); + +// tests that displays a sample contract creation args +// +casttest!(fetch_constructor_args_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "constructor-args", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x6982508145454ce325ddbe47a25d4ec3d2311933", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +0x00000000000000000000000000000000000014bddab3e51a57cff87a50000000 → Uint(420690000000000000000000000000000, 256) + +"#]]); +}); + +// +casttest!(test_non_mainnet_traces, |prj, cmd| { + prj.clear(); + cmd.args([ + "run", + "0xa003e419e2d7502269eb5eda56947b580120e00abfd5b5460d08f8af44a0c24f", + "--rpc-url", + next_rpc_endpoint(NamedChain::Optimism).as_str(), + "--etherscan-api-key", + next_etherscan_api_key(NamedChain::Optimism).as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [33841] FiatTokenProxy::fallback(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) + ├─ [26673] FiatTokenV2_2::approve(0x111111125421cA6dc452d289314280a0f8842A65, 164054805 [1.64e8]) [delegatecall] + │ ├─ emit Approval(owner: 0x9a95Af47C51562acfb2107F44d7967DF253197df, spender: 0x111111125421cA6dc452d289314280a0f8842A65, value: 164054805 [1.64e8]) + │ └─ ← [Return] true + └─ ← [Return] true +... + +"#]]); +}); + +// tests that displays a sample contract artifact +// +casttest!(fetch_artifact_from_etherscan, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args([ + "artifact", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0x0923cad07f06b2d0e5e49e63b8b35738d4156b95", + "--rpc-url", + eth_rpc_url.as_str(), + ]) + .assert_success() + .stdout_eq(str![[r#"{ + "abi": [], + "bytecode": { + "object": "0x60566050600b82828239805160001a6073146043577f4e487b7100000000000000000000000000000000000000000000000000000000600052600060045260246000fd5b30600052607381538281f3fe73000000000000000000000000000000000000000030146080604052600080fdfea264697066735822122074c61e8e4eefd410ca92eec26e8112ec6e831d0a4bf35718fdd78b45d68220d064736f6c63430008070033" + } +} + +"#]]); +}); + +// tests cast can decode traces when using project artifacts +forgetest_async!(decode_traces_with_project_artifacts, |prj, cmd| { + let (api, handle) = + anvil::spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "LocalProjectContract", + r#" +contract LocalProjectContract { + event LocalProjectContractCreated(address owner); + + constructor() { + emit LocalProjectContractCreated(msg.sender); + } +} + "#, + ) + .unwrap(); + prj.add_script( + "LocalProjectScript", + r#" +import "forge-std/Script.sol"; +import {LocalProjectContract} from "../src/LocalProjectContract.sol"; + +contract LocalProjectScript is Script { + function run() public { + vm.startBroadcast(); + new LocalProjectContract(); + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "LocalProjectScript", + ]); + + cmd.assert_success(); + + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + // Assert cast with local artifacts from outside the project. + cmd.cast_fuse() + .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Compiling project to generate artifacts +Nothing to compile + +"#]]); + + // Run cast from project dir. + cmd.cast_fuse().set_current_dir(prj.root()); + + // Assert cast without local artifacts cannot decode traces. + cmd.cast_fuse() + .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [..] → new @0x5FbDB2315678afecb367f032d93F642f64180aa3 + ├─ emit topic 0: 0xa7263295d3a687d750d1fd377b5df47de69d7db8decc745aaa4bbee44dc1688d + │ data: 0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266 + └─ ← [Return] 62 bytes of code + + +Transaction successfully executed. +[GAS] + +"#]]); + + // Assert cast with local artifacts can decode traces. + cmd.cast_fuse() + .args(["run", "--la", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Compiling project to generate artifacts +No files changed, compilation skipped +Traces: + [..] → new LocalProjectContract@0x5FbDB2315678afecb367f032d93F642f64180aa3 + ├─ emit LocalProjectContractCreated(owner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266) + └─ ← [Return] 62 bytes of code + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + +// tests cast can decode traces when running with verbosity level > 4 +forgetest_async!(show_state_changes_in_traces, |prj, cmd| { + let (api, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + // Deploy counter contract. + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "CounterScript", + ]) + .assert_success(); + + // Send tx to change counter storage value. + cmd.cast_fuse() + .args([ + "send", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "setNumber(uint256)", + "111", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success(); + + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + // Assert cast with verbosity displays storage changes. + cmd.cast_fuse() + .args([ + "run", + format!("{tx_hash}").as_str(), + "-vvvvv", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success() + .stdout_eq(str![[r#" +Executing previous transactions from the block. +Traces: + [..] 0x5FbDB2315678afecb367f032d93F642f64180aa3::setNumber(111) + ├─ storage changes: + │ @ 0: 0 → 111 + └─ ← [Stop] + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + +// tests cast can decode external libraries traces with project cached selectors +forgetest_async!(decode_external_libraries_with_cached_selectors, |prj, cmd| { + let (api, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "ExternalLib", + r#" +import "./CounterInExternalLib.sol"; +library ExternalLib { + function updateCounterInExternalLib(CounterInExternalLib.Info storage counterInfo, uint256 counter) public { + counterInfo.counter = counter + 1; + } +} + "#, + ) + .unwrap(); + prj.add_source( + "CounterInExternalLib", + r#" +import "./ExternalLib.sol"; +contract CounterInExternalLib { + struct Info { + uint256 counter; + } + Info info; + constructor() { + ExternalLib.updateCounterInExternalLib(info, 100); + } +} + "#, + ) + .unwrap(); + prj.add_script( + "CounterInExternalLibScript", + r#" +import "forge-std/Script.sol"; +import {CounterInExternalLib} from "../src/CounterInExternalLib.sol"; +contract CounterInExternalLibScript is Script { + function run() public { + vm.startBroadcast(); + new CounterInExternalLib(); + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "CounterInExternalLibScript", + ]) + .assert_success(); + + let tx_hash = api + .transaction_by_block_number_and_index(BlockNumberOrTag::Latest, Index::from(0)) + .await + .unwrap() + .unwrap() + .tx_hash(); + + // Cache project selectors. + cmd.forge_fuse().set_current_dir(prj.root()); + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); + + // Assert cast with local artifacts can decode external lib signature. + cmd.cast_fuse().set_current_dir(prj.root()); + cmd.cast_fuse() + .args(["run", format!("{tx_hash}").as_str(), "--rpc-url", &handle.http_endpoint()]) + .assert_success() + .stdout_eq(str![[r#" +... +Traces: + [..] → new @0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ├─ [..] 0x52F3e85EC3F0f9D0a2200D646482fcD134D5adc9::updateCounterInExternalLib(0, 100) [delegatecall] + │ └─ ← [Stop] + └─ ← [Return] 62 bytes of code + + +Transaction successfully executed. +[GAS] + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/9476 +forgetest_async!(cast_call_custom_chain_id, |_prj, cmd| { + let chain_id = 55555u64; + let (_api, handle) = anvil::spawn(NodeConfig::test().with_chain_id(Some(chain_id))).await; + + let http_endpoint = handle.http_endpoint(); + + cmd.cast_fuse() + .args([ + "call", + "5FbDB2315678afecb367f032d93F642f64180aa3", + "--rpc-url", + &http_endpoint, + "--chain", + &chain_id.to_string(), + ]) + .assert_success(); +}); + +// https://github.com/foundry-rs/foundry/issues/9541 +forgetest_async!(cast_run_impersonated_tx, |_prj, cmd| { + let (_api, handle) = anvil::spawn( + NodeConfig::test() + .with_auto_impersonate(true) + .with_eth_rpc_url(Some("https://sepolia.base.org")), + ) + .await; + + let http_endpoint = handle.http_endpoint(); + + let provider = ProviderBuilder::new().on_http(http_endpoint.parse().unwrap()); + + // send impersonated tx + let tx = TransactionRequest::default() + .with_from(address!("041563c07028Fc89106788185763Fc73028e8511")) + .with_to(address!("F38aA5909D89F5d98fCeA857e708F6a6033f6CF8")) + .with_input( + Bytes::from_str( + "0x60fe47b1000000000000000000000000000000000000000000000000000000000000000c", + ) + .unwrap(), + ); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert!(receipt.status()); + + // run impersonated tx + cmd.cast_fuse() + .args(["run", &receipt.transaction_hash.to_string(), "--rpc-url", &http_endpoint]) + .assert_success(); +}); + +// +casttest!(fetch_src_blockscout, |_prj, cmd| { + let url = "https://eth.blockscout.com/api"; + + let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + + cmd.args([ + "source", + &weth.to_string(), + "--chain-id", + "1", + "--explorer-api-url", + url, + "--flatten", + ]) + .assert_success() + .stdout_eq(str![[r#" +... +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; +..."#]]); +}); + +casttest!(fetch_src_default, |_prj, cmd| { + let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + let etherscan_api_key = next_mainnet_etherscan_api_key(); + + cmd.args(["source", &weth.to_string(), "--flatten", "--etherscan-api-key", ðerscan_api_key]) + .assert_success() + .stdout_eq(str![[r#" +... +contract WETH9 { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; +..."#]]); +}); + +// tests cast send gas estimate execution failure message contains decoded custom error +// +forgetest_async!(cast_send_estimate_gas_error, |prj, cmd| { + let (_, handle) = anvil::spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "SimpleStorage", + r#" +contract SimpleStorage { + uint256 private storedValue; + error AddressInsufficientBalance(address account, uint256 newValue); + function setValue(uint256 _newValue) public { + if (_newValue > 100) { + revert AddressInsufficientBalance(msg.sender, _newValue); + } + storedValue = _newValue; + } +} + "#, + ) + .unwrap(); + prj.add_script( + "SimpleStorageScript", + r#" +import "forge-std/Script.sol"; +import {SimpleStorage} from "../src/SimpleStorage.sol"; +contract SimpleStorageScript is Script { + function run() public { + vm.startBroadcast(); + new SimpleStorage(); + vm.stopBroadcast(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "SimpleStorageScript", + ]) + .assert_success(); + + // Cache project selectors. + cmd.forge_fuse().set_current_dir(prj.root()); + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); + + // Assert cast send can decode custom error on estimate gas execution failure. + cmd.cast_fuse() + .args([ + "send", + "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "setValue(uint256)", + "1000", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_failure().stderr_eq(str![[r#" +Error: Failed to estimate gas: server returned an error response: error code 3: execution reverted: custom error 0x6786ad34: 000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8, data: "0x6786ad34000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000003e8": AddressInsufficientBalance(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 1000) + +"#]]); }); diff --git a/crates/cast/tests/cli/selectors.rs b/crates/cast/tests/cli/selectors.rs new file mode 100644 index 0000000000000..c427f24f566d3 --- /dev/null +++ b/crates/cast/tests/cli/selectors.rs @@ -0,0 +1,195 @@ +use foundry_test_utils::util::OutputExt; +use std::path::Path; + +casttest!(error_decode_with_openchain, |prj, cmd| { + prj.clear_cache(); + cmd.args(["decode-error", "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064"]).assert_success().stdout_eq(str![[r#" +ValueTooHigh(uint256,uint256) +101 +100 + +"#]]); +}); + +casttest!(fourbyte, |_prj, cmd| { + cmd.args(["4byte", "0xa9059cbb"]).assert_success().stdout_eq(str![[r#" +transfer(address,uint256) + +"#]]); +}); + +casttest!(fourbyte_invalid, |_prj, cmd| { + cmd.args(["4byte", "0xa9059c"]).assert_failure().stderr_eq(str![[r#" +Error: Invalid selector 0xa9059c: expected 10 characters (including 0x prefix). + +"#]]); +}); + +casttest!(fourbyte_decode, |_prj, cmd| { + cmd.args(["4byte-decode", "0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79"]).assert_success().stdout_eq(str![[r#" +1) "transfer(address,uint256)" +0x0A2AC0c368Dc8eC680a0c98C907656BD97067595 +31802608249 [3.18e10] + +"#]]); +}); + +casttest!(fourbyte_event, |_prj, cmd| { + cmd.args(["4byte-event", "0x7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6"]) + .assert_success() + .stdout_eq(str![[r#" +updateAuthority(address,uint8) + +"#]]); +}); + +casttest!(fourbyte_event_2, |_prj, cmd| { + cmd.args(["4byte-event", "0xb7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd"]) + .assert_success() + .stdout_eq(str![[r#" +canCall(address,address,bytes4) + +"#]]); +}); + +casttest!(upload_signatures, |_prj, cmd| { + // test no prefix is accepted as function + let output = cmd + .args(["upload-signature", "transfer(address,uint256)"]) + .assert_success() + .get_output() + .stdout_lossy(); + assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); + + // test event prefix + cmd.args(["upload-signature", "event Transfer(address,uint256)"]); + let output = cmd.assert_success().get_output().stdout_lossy(); + assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); + + // test error prefix + cmd.args(["upload-signature", "error ERC20InsufficientBalance(address,uint256,uint256)"]); + let output = cmd.assert_success().get_output().stdout_lossy(); + assert!( + output.contains("Function ERC20InsufficientBalance(address,uint256,uint256): 0xe450d38c"), + "{}", + output + ); // Custom error is interpreted as function + + // test multiple sigs + cmd.args([ + "upload-signature", + "event Transfer(address,uint256)", + "transfer(address,uint256)", + "approve(address,uint256)", + ]); + let output = cmd.assert_success().get_output().stdout_lossy(); + assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); + assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); + assert!(output.contains("Function approve(address,uint256): 0x095ea7b3"), "{}", output); + + // test abi + cmd.args([ + "upload-signature", + "event Transfer(address,uint256)", + "transfer(address,uint256)", + "error ERC20InsufficientBalance(address,uint256,uint256)", + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/ERC20Artifact.json") + .as_os_str() + .to_str() + .unwrap(), + ]); + let output = cmd.assert_success().get_output().stdout_lossy(); + assert!(output.contains("Event Transfer(address,uint256): 0x69ca02dd4edd7bf0a4abb9ed3b7af3f14778db5d61921c7dc7cd545266326de2"), "{}", output); + assert!(output.contains("Function transfer(address,uint256): 0xa9059cbb"), "{}", output); + assert!(output.contains("Function approve(address,uint256): 0x095ea7b3"), "{}", output); + assert!(output.contains("Function decimals(): 0x313ce567"), "{}", output); + assert!(output.contains("Function allowance(address,address): 0xdd62ed3e"), "{}", output); + assert!( + output.contains("Function ERC20InsufficientBalance(address,uint256,uint256): 0xe450d38c"), + "{}", + output + ); +}); + +// tests cast can decode event with provided signature +casttest!(event_decode_with_sig, |_prj, cmd| { + cmd.args(["decode-event", "--sig", "MyEvent(uint256,address)", "0x000000000000000000000000000000000000000000000000000000000000004e0000000000000000000000000000000000000000000000000000000000d0004f"]).assert_success().stdout_eq(str![[r#" +78 +0x0000000000000000000000000000000000D0004F + +"#]]); + + cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" +[ + "78", + "0x0000000000000000000000000000000000D0004F" +] + +"#]]); +}); + +// tests cast can decode event with Openchain API +casttest!(event_decode_with_openchain, |prj, cmd| { + prj.clear_cache(); + cmd.args(["decode-event", "0xe27c4c1372396a3d15a9922f74f9dfc7c72b1ad6d63868470787249c356454c1000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]).assert_success().stdout_eq(str![[r#" +BaseCurrencySet(address,uint256) +0x000000000000000000000000000000000000004e +15187004358734 [1.518e13] + +"#]]); +}); + +// tests cast can decode error with provided signature +casttest!(error_decode_with_sig, |_prj, cmd| { + cmd.args(["decode-error", "--sig", "AnotherValueTooHigh(uint256,address)", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]).assert_success().stdout_eq(str![[r#" +101 +0x0000000000000000000000000000000000D0004F + +"#]]); + + cmd.args(["--json"]).assert_success().stdout_eq(str![[r#" +[ + "101", + "0x0000000000000000000000000000000000D0004F" +] + +"#]]); +}); + +// tests cast can decode error and event when using local sig identifiers cache +forgetest_init!(error_event_decode_with_cache, |prj, cmd| { + prj.add_source( + "LocalProjectContract", + r#" +contract ContractWithCustomError { + error AnotherValueTooHigh(uint256, address); + event MyUniqueEventWithinLocalProject(uint256 a, address b); +} + "#, + ) + .unwrap(); + // Store selectors in local cache. + cmd.forge_fuse().args(["selectors", "cache"]).assert_success(); + + // Assert cast can decode custom error with local cache. + cmd.cast_fuse() + .args(["decode-error", "0x7191bc6200000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000D0004F"]) + .assert_success() + .stdout_eq(str![[r#" +AnotherValueTooHigh(uint256,address) +101 +0x0000000000000000000000000000000000D0004F + +"#]]); + // Assert cast can decode event with local cache. + cmd.cast_fuse() + .args(["decode-event", "0xbd3699995dcc867b64dbb607be2c33be38df9134bef1178df13bfb9446e73104000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000dd00000004e"]) + .assert_success() + .stdout_eq(str![[r#" +MyUniqueEventWithinLocalProject(uint256,address) +78 +0x00000000000000000000000000000DD00000004e + +"#]]); +}); diff --git a/crates/cast/tests/fixtures/ERC20Artifact.json b/crates/cast/tests/fixtures/ERC20Artifact.json index 4f4fd6e61fa33..508464c246ada 100644 --- a/crates/cast/tests/fixtures/ERC20Artifact.json +++ b/crates/cast/tests/fixtures/ERC20Artifact.json @@ -1,385 +1 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "string", - "name": "name", - "type": "string" - }, - { - "internalType": "string", - "name": "symbol", - "type": "string" - }, - { - "internalType": "uint8", - "name": "decimals", - "type": "uint8" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": { - "object": "0x60e06040523480156200001157600080fd5b5060405162000f7738038062000f7783398101604081905262000034916200029a565b82828282600090805190602001906200004f92919062000127565b5081516200006590600190602085019062000127565b5060ff81166080524660a0526200007b6200008b565b60c0525062000400945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051620000bf91906200035c565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b82805462000135906200031f565b90600052602060002090601f016020900481019282620001595760008555620001a4565b82601f106200017457805160ff1916838001178555620001a4565b82800160010185558215620001a4579182015b82811115620001a457825182559160200191906001019062000187565b50620001b2929150620001b6565b5090565b5b80821115620001b25760008155600101620001b7565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001f557600080fd5b81516001600160401b0380821115620002125762000212620001cd565b604051601f8301601f19908116603f011681019082821181831017156200023d576200023d620001cd565b816040528381526020925086838588010111156200025a57600080fd5b600091505b838210156200027e57858201830151818301840152908201906200025f565b83821115620002905760008385830101525b9695505050505050565b600080600060608486031215620002b057600080fd5b83516001600160401b0380821115620002c857600080fd5b620002d687838801620001e3565b94506020860151915080821115620002ed57600080fd5b50620002fc86828701620001e3565b925050604084015160ff811681146200031457600080fd5b809150509250925092565b600181811c908216806200033457607f821691505b602082108114156200035657634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200037957607f831692505b60208084108214156200039a57634e487b7160e01b86526022600452602486fd5b818015620003b15760018114620003c357620003f2565b60ff19861689528489019650620003f2565b60008a81526020902060005b86811015620003ea5781548b820152908501908301620003cf565b505084890196505b509498975050505050505050565b60805160a05160c051610b476200043060003960006104530152600061041e015260006101440152610b476000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033", - "sourceMap": "113:230:22:-:0;;;148:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;224:4;230:6;238:8;2098:5:10;2091:4;:12;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2113:16:10;;;;:6;;:16;;;;;:::i;:::-;-1:-1:-1;2139:20:10;;;;;2189:13;2170:32;;2239:24;:22;:24::i;:::-;2212:51;;-1:-1:-1;113:230:22;;-1:-1:-1;;;;;113:230:22;5507:446:10;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;3635:25:23;;;;3676:18;;3669:34;;;;5830:14:10;3719:18:23;;;3712:34;5866:13:10;3762:18:23;;;3755:34;5909:4:10;3805:19:23;;;3798:61;3607:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;113:230:22:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;113:230:22;;;-1:-1:-1;113:230:22;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;14:127:23;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:885;200:5;253:3;246:4;238:6;234:17;230:27;220:55;;271:1;268;261:12;220:55;294:13;;-1:-1:-1;;;;;356:10:23;;;353:36;;;369:18;;:::i;:::-;444:2;438:9;412:2;498:13;;-1:-1:-1;;494:22:23;;;518:2;490:31;486:40;474:53;;;542:18;;;562:22;;;539:46;536:72;;;588:18;;:::i;:::-;628:10;624:2;617:22;663:2;655:6;648:18;685:4;675:14;;730:3;725:2;720;712:6;708:15;704:24;701:33;698:53;;;747:1;744;737:12;698:53;769:1;760:10;;779:133;793:2;790:1;787:9;779:133;;;881:14;;;877:23;;871:30;850:14;;;846:23;;839:63;804:10;;;;779:133;;;930:2;927:1;924:9;921:80;;;989:1;984:2;979;971:6;967:15;963:24;956:35;921:80;1019:6;146:885;-1:-1:-1;;;;;;146:885:23:o;1036:712::-;1142:6;1150;1158;1211:2;1199:9;1190:7;1186:23;1182:32;1179:52;;;1227:1;1224;1217:12;1179:52;1254:16;;-1:-1:-1;;;;;1319:14:23;;;1316:34;;;1346:1;1343;1336:12;1316:34;1369:61;1422:7;1413:6;1402:9;1398:22;1369:61;:::i;:::-;1359:71;;1476:2;1465:9;1461:18;1455:25;1439:41;;1505:2;1495:8;1492:16;1489:36;;;1521:1;1518;1511:12;1489:36;;1544:63;1599:7;1588:8;1577:9;1573:24;1544:63;:::i;:::-;1534:73;;;1650:2;1639:9;1635:18;1629:25;1694:4;1687:5;1683:16;1676:5;1673:27;1663:55;;1714:1;1711;1704:12;1663:55;1737:5;1727:15;;;1036:712;;;;;:::o;1753:380::-;1832:1;1828:12;;;;1875;;;1896:61;;1950:4;1942:6;1938:17;1928:27;;1896:61;2003:2;1995:6;1992:14;1972:18;1969:38;1966:161;;;2049:10;2044:3;2040:20;2037:1;2030:31;2084:4;2081:1;2074:15;2112:4;2109:1;2102:15;1966:161;;1753:380;;;:::o;2267:1104::-;2397:3;2426:1;2459:6;2453:13;2489:3;2511:1;2539:9;2535:2;2531:18;2521:28;;2599:2;2588:9;2584:18;2621;2611:61;;2665:4;2657:6;2653:17;2643:27;;2611:61;2691:2;2739;2731:6;2728:14;2708:18;2705:38;2702:165;;;-1:-1:-1;;;2766:33:23;;2822:4;2819:1;2812:15;2852:4;2773:3;2840:17;2702:165;2883:18;2910:104;;;;3028:1;3023:323;;;;2876:470;;2910:104;-1:-1:-1;;2943:24:23;;2931:37;;2988:16;;;;-1:-1:-1;2910:104:23;;3023:323;2214:1;2207:14;;;2251:4;2238:18;;3121:1;3135:165;3149:6;3146:1;3143:13;3135:165;;;3227:14;;3214:11;;;3207:35;3270:16;;;;3164:10;;3135:165;;;3139:3;;3329:6;3324:3;3320:16;3313:23;;2876:470;-1:-1:-1;3362:3:23;;2267:1104;-1:-1:-1;;;;;;;;2267:1104:23:o;3376:489::-;113:230:22;;;;;;;;;;;;;;;;;;;;;;;;", - "linkReferences": {} - }, - "deployedBytecode": { - "object": "0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033", - "sourceMap": "113:230:22:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1028:18:10;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2458:211;;;;;;:::i;:::-;;:::i;:::-;;;1218:14:23;;1211:22;1193:41;;1181:2;1166:18;2458:211:10;1053:187:23;1301:26:10;;;;;;;;;1391:25:23;;;1379:2;1364:18;1301:26:10;1245:177:23;3054:592:10;;;;;;:::i;:::-;;:::i;1080:31::-;;;;;;;;1932:4:23;1920:17;;;1902:36;;1890:2;1875:18;1080:31:10;1760:184:23;5324:177:10;;;:::i;256:85:22:-;;;;;;:::i;:::-;;:::i;:::-;;1334:44:10;;;;;;:::i;:::-;;;;;;;;;;;;;;1748:41;;;;;;:::i;:::-;;;;;;;;;;;;;;1053:20;;;:::i;2675:373::-;;;;;;:::i;:::-;;:::i;3835:1483::-;;;;;;:::i;:::-;;:::i;1385:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1028:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;2458:211::-;2558:10;2532:4;2548:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;2548:30:10;;;;;;;;;;:39;;;2603:37;2532:4;;2548:30;;2603:37;;;;2581:6;1391:25:23;;1379:2;1364:18;;1245:177;2603:37:10;;;;;;;;-1:-1:-1;2658:4:10;2458:211;;;;:::o;3054:592::-;-1:-1:-1;;;;;3206:15:10;;3172:4;3206:15;;;:9;:15;;;;;;;;3222:10;3206:27;;;;;;;;-1:-1:-1;;3284:28:10;;3280:80;;3344:16;3354:6;3344:7;:16;:::i;:::-;-1:-1:-1;;;;;3314:15:10;;;;;;:9;:15;;;;;;;;3330:10;3314:27;;;;;;;:46;3280:80;-1:-1:-1;;;;;3371:15:10;;;;;;:9;:15;;;;;:25;;3390:6;;3371:15;:25;;3390:6;;3371:25;:::i;:::-;;;;-1:-1:-1;;;;;;;3542:13:10;;;;;;;:9;:13;;;;;;;:23;;;;;;3591:26;3542:13;;3591:26;;;;;;;3559:6;1391:25:23;;1379:2;1364:18;;1245:177;3591:26:10;;;;;;;;-1:-1:-1;3635:4:10;;3054:592;-1:-1:-1;;;;3054:592:10:o;5324:177::-;5381:7;5424:16;5407:13;:33;:87;;5470:24;:22;:24::i;:::-;5400:94;;5324:177;:::o;5407:87::-;-1:-1:-1;5443:24:10;;5324:177::o;256:85:22:-;317:17;323:2;327:6;317:5;:17::i;:::-;256:85;;:::o;1053:20:10:-;;;;;;;:::i;2675:373::-;2771:10;2745:4;2761:21;;;:9;:21;;;;;:31;;2786:6;;2761:21;2745:4;;2761:31;;2786:6;;2761:31;:::i;:::-;;;;-1:-1:-1;;;;;;;2938:13:10;;;;;;:9;:13;;;;;;;:23;;;;;;2987:32;2996:10;;2987:32;;;;2955:6;1391:25:23;;1379:2;1364:18;;1245:177;3835:1483:10;4054:15;4042:8;:27;;4034:63;;;;-1:-1:-1;;;4034:63:10;;4134:2:23;4034:63:10;;;4116:21:23;4173:2;4153:18;;;4146:30;4212:25;4192:18;;;4185:53;4255:18;;4034:63:10;;;;;;;;;4262:24;4289:805;4425:18;:16;:18::i;:::-;-1:-1:-1;;;;;4870:13:10;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;4508:449;;4552:165;4508:449;;;4571:25:23;4650:18;;;4643:43;;;;4722:15;;;4702:18;;;4695:43;4754:18;;;4747:34;;;4797:19;;;4790:35;;;;4841:19;;;;4834:35;;;4508:449:10;;;;;;;;;;4543:19:23;;;4508:449:10;;;4469:514;;;;;;;;-1:-1:-1;;;4347:658:10;;;5138:27:23;5181:11;;;5174:27;;;;5217:12;;;5210:28;;;;5254:12;;4347:658:10;;;-1:-1:-1;;4347:658:10;;;;;;;;;4316:707;;4347:658;4316:707;;;;4289:805;;;;;;;;;5504:25:23;5577:4;5565:17;;5545:18;;;5538:45;5599:18;;;5592:34;;;5642:18;;;5635:34;;;5476:19;;4289:805:10;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4289:805:10;;-1:-1:-1;;4289:805:10;;;-1:-1:-1;;;;;;;5117:30:10;;;;;;:59;;;5171:5;-1:-1:-1;;;;;5151:25:10;:16;-1:-1:-1;;;;;5151:25:10;;5117:59;5109:86;;;;-1:-1:-1;;;5109:86:10;;5882:2:23;5109:86:10;;;5864:21:23;5921:2;5901:18;;;5894:30;-1:-1:-1;;;5940:18:23;;;5933:44;5994:18;;5109:86:10;5680:338:23;5109:86:10;-1:-1:-1;;;;;5210:27:10;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;5280:31;1391:25:23;;;5210:36:10;;5280:31;;;;;1364:18:23;5280:31:10;;;;;;;3835:1483;;;;;;;:::o;5507:446::-;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;7520:25:23;;;;7561:18;;7554:34;;;;5830:14:10;7604:18:23;;;7597:34;5866:13:10;7647:18:23;;;7640:34;5909:4:10;7690:19:23;;;7683:61;7492:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;6147:325::-;6232:6;6217:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;;;6384:13:10;;;;;;:9;:13;;;;;;;;:23;;;;;;6433:32;1391:25:23;;;6433:32:10;;1364:18:23;6433:32:10;;;;;;;6147:325;;:::o;14:597:23:-;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;452:6;449:1;446:13;443:91;;;522:1;517:2;508:6;497:9;493:22;489:31;482:42;443:91;-1:-1:-1;595:2:23;574:15;-1:-1:-1;;570:29:23;555:45;;;;602:2;551:54;;14:597;-1:-1:-1;;;14:597:23:o;616:173::-;684:20;;-1:-1:-1;;;;;733:31:23;;723:42;;713:70;;779:1;776;769:12;713:70;616:173;;;:::o;794:254::-;862:6;870;923:2;911:9;902:7;898:23;894:32;891:52;;;939:1;936;929:12;891:52;962:29;981:9;962:29;:::i;:::-;952:39;1038:2;1023:18;;;;1010:32;;-1:-1:-1;;;794:254:23:o;1427:328::-;1504:6;1512;1520;1573:2;1561:9;1552:7;1548:23;1544:32;1541:52;;;1589:1;1586;1579:12;1541:52;1612:29;1631:9;1612:29;:::i;:::-;1602:39;;1660:38;1694:2;1683:9;1679:18;1660:38;:::i;:::-;1650:48;;1745:2;1734:9;1730:18;1717:32;1707:42;;1427:328;;;;;:::o;2131:186::-;2190:6;2243:2;2231:9;2222:7;2218:23;2214:32;2211:52;;;2259:1;2256;2249:12;2211:52;2282:29;2301:9;2282:29;:::i;:::-;2272:39;2131:186;-1:-1:-1;;;2131:186:23:o;2322:693::-;2433:6;2441;2449;2457;2465;2473;2481;2534:3;2522:9;2513:7;2509:23;2505:33;2502:53;;;2551:1;2548;2541:12;2502:53;2574:29;2593:9;2574:29;:::i;:::-;2564:39;;2622:38;2656:2;2645:9;2641:18;2622:38;:::i;:::-;2612:48;;2707:2;2696:9;2692:18;2679:32;2669:42;;2758:2;2747:9;2743:18;2730:32;2720:42;;2812:3;2801:9;2797:19;2784:33;2857:4;2850:5;2846:16;2839:5;2836:27;2826:55;;2877:1;2874;2867:12;2826:55;2322:693;;;;-1:-1:-1;2322:693:23;;;;2900:5;2952:3;2937:19;;2924:33;;-1:-1:-1;3004:3:23;2989:19;;;2976:33;;2322:693;-1:-1:-1;;2322:693:23:o;3020:260::-;3088:6;3096;3149:2;3137:9;3128:7;3124:23;3120:32;3117:52;;;3165:1;3162;3155:12;3117:52;3188:29;3207:9;3188:29;:::i;:::-;3178:39;;3236:38;3270:2;3259:9;3255:18;3236:38;:::i;:::-;3226:48;;3020:260;;;;;:::o;3285:380::-;3364:1;3360:12;;;;3407;;;3428:61;;3482:4;3474:6;3470:17;3460:27;;3428:61;3535:2;3527:6;3524:14;3504:18;3501:38;3498:161;;;3581:10;3576:3;3572:20;3569:1;3562:31;3616:4;3613:1;3606:15;3644:4;3641:1;3634:15;3498:161;;3285:380;;;:::o;3670:127::-;3731:10;3726:3;3722:20;3719:1;3712:31;3762:4;3759:1;3752:15;3786:4;3783:1;3776:15;3802:125;3842:4;3870:1;3867;3864:8;3861:34;;;3875:18;;:::i;:::-;-1:-1:-1;3912:9:23;;3802:125::o;6152:1104::-;6282:3;6311:1;6344:6;6338:13;6374:3;6396:1;6424:9;6420:2;6416:18;6406:28;;6484:2;6473:9;6469:18;6506;6496:61;;6550:4;6542:6;6538:17;6528:27;;6496:61;6576:2;6624;6616:6;6613:14;6593:18;6590:38;6587:165;;;-1:-1:-1;;;6651:33:23;;6707:4;6704:1;6697:15;6737:4;6658:3;6725:17;6587:165;6768:18;6795:104;;;;6913:1;6908:323;;;;6761:470;;6795:104;-1:-1:-1;;6828:24:23;;6816:37;;6873:16;;;;-1:-1:-1;6795:104:23;;6908:323;6099:1;6092:14;;;6136:4;6123:18;;7006:1;7020:165;7034:6;7031:1;7028:13;7020:165;;;7112:14;;7099:11;;;7092:35;7155:16;;;;7049:10;;7020:165;;;7024:3;;7214:6;7209:3;7205:16;7198:23;;6761:470;-1:-1:-1;7247:3:23;;6152:1104;-1:-1:-1;;;;;;;;6152:1104:23:o;7755:128::-;7795:3;7826:1;7822:6;7819:1;7816:13;7813:39;;;7832:18;;:::i;:::-;-1:-1:-1;7868:9:23;;7755:128::o", - "linkReferences": {}, - "immutableReferences": { - "3499": [ - { - "start": 324, - "length": 32 - } - ], - "3513": [ - { - "start": 1054, - "length": 32 - } - ], - "3515": [ - { - "start": 1107, - "length": 32 - } - ] - } - }, - "methodIdentifiers": { - "DOMAIN_SEPARATOR()": "3644e515", - "allowance(address,address)": "dd62ed3e", - "approve(address,uint256)": "095ea7b3", - "balanceOf(address)": "70a08231", - "decimals()": "313ce567", - "mint(address,uint256)": "40c10f19", - "name()": "06fdde03", - "nonces(address)": "7ecebe00", - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": "d505accf", - "symbol()": "95d89b41", - "totalSupply()": "18160ddd", - "transfer(address,uint256)": "a9059cbb", - "transferFrom(address,address,uint256)": "23b872dd" - } -} +{"abi":[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"bytecode":{"object":"0x60e06040523480156200001157600080fd5b5060405162000f7738038062000f7783398101604081905262000034916200029a565b82828282600090805190602001906200004f92919062000127565b5081516200006590600190602085019062000127565b5060ff81166080524660a0526200007b6200008b565b60c0525062000400945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6000604051620000bf91906200035c565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b82805462000135906200031f565b90600052602060002090601f016020900481019282620001595760008555620001a4565b82601f106200017457805160ff1916838001178555620001a4565b82800160010185558215620001a4579182015b82811115620001a457825182559160200191906001019062000187565b50620001b2929150620001b6565b5090565b5b80821115620001b25760008155600101620001b7565b634e487b7160e01b600052604160045260246000fd5b600082601f830112620001f557600080fd5b81516001600160401b0380821115620002125762000212620001cd565b604051601f8301601f19908116603f011681019082821181831017156200023d576200023d620001cd565b816040528381526020925086838588010111156200025a57600080fd5b600091505b838210156200027e57858201830151818301840152908201906200025f565b83821115620002905760008385830101525b9695505050505050565b600080600060608486031215620002b057600080fd5b83516001600160401b0380821115620002c857600080fd5b620002d687838801620001e3565b94506020860151915080821115620002ed57600080fd5b50620002fc86828701620001e3565b925050604084015160ff811681146200031457600080fd5b809150509250925092565b600181811c908216806200033457607f821691505b602082108114156200035657634e487b7160e01b600052602260045260246000fd5b50919050565b600080835481600182811c9150808316806200037957607f831692505b60208084108214156200039a57634e487b7160e01b86526022600452602486fd5b818015620003b15760018114620003c357620003f2565b60ff19861689528489019650620003f2565b60008a81526020902060005b86811015620003ea5781548b820152908501908301620003cf565b505084890196505b509498975050505050505050565b60805160a05160c051610b476200043060003960006104530152600061041e015260006101440152610b476000f3fe608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033","sourceMap":"113:230:22:-:0;;;148:102;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;224:4;230:6;238:8;2098:5:10;2091:4;:12;;;;;;;;;;;;:::i;:::-;-1:-1:-1;2113:16:10;;;;:6;;:16;;;;;:::i;:::-;-1:-1:-1;2139:20:10;;;;;2189:13;2170:32;;2239:24;:22;:24::i;:::-;2212:51;;-1:-1:-1;113:230:22;;-1:-1:-1;;;;;113:230:22;5507:446:10;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;3635:25:23;;;;3676:18;;3669:34;;;;5830:14:10;3719:18:23;;;3712:34;5866:13:10;3762:18:23;;;3755:34;5909:4:10;3805:19:23;;;3798:61;3607:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;113:230:22:-;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;113:230:22;;;-1:-1:-1;113:230:22;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;14:127:23;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:885;200:5;253:3;246:4;238:6;234:17;230:27;220:55;;271:1;268;261:12;220:55;294:13;;-1:-1:-1;;;;;356:10:23;;;353:36;;;369:18;;:::i;:::-;444:2;438:9;412:2;498:13;;-1:-1:-1;;494:22:23;;;518:2;490:31;486:40;474:53;;;542:18;;;562:22;;;539:46;536:72;;;588:18;;:::i;:::-;628:10;624:2;617:22;663:2;655:6;648:18;685:4;675:14;;730:3;725:2;720;712:6;708:15;704:24;701:33;698:53;;;747:1;744;737:12;698:53;769:1;760:10;;779:133;793:2;790:1;787:9;779:133;;;881:14;;;877:23;;871:30;850:14;;;846:23;;839:63;804:10;;;;779:133;;;930:2;927:1;924:9;921:80;;;989:1;984:2;979;971:6;967:15;963:24;956:35;921:80;1019:6;146:885;-1:-1:-1;;;;;;146:885:23:o;1036:712::-;1142:6;1150;1158;1211:2;1199:9;1190:7;1186:23;1182:32;1179:52;;;1227:1;1224;1217:12;1179:52;1254:16;;-1:-1:-1;;;;;1319:14:23;;;1316:34;;;1346:1;1343;1336:12;1316:34;1369:61;1422:7;1413:6;1402:9;1398:22;1369:61;:::i;:::-;1359:71;;1476:2;1465:9;1461:18;1455:25;1439:41;;1505:2;1495:8;1492:16;1489:36;;;1521:1;1518;1511:12;1489:36;;1544:63;1599:7;1588:8;1577:9;1573:24;1544:63;:::i;:::-;1534:73;;;1650:2;1639:9;1635:18;1629:25;1694:4;1687:5;1683:16;1676:5;1673:27;1663:55;;1714:1;1711;1704:12;1663:55;1737:5;1727:15;;;1036:712;;;;;:::o;1753:380::-;1832:1;1828:12;;;;1875;;;1896:61;;1950:4;1942:6;1938:17;1928:27;;1896:61;2003:2;1995:6;1992:14;1972:18;1969:38;1966:161;;;2049:10;2044:3;2040:20;2037:1;2030:31;2084:4;2081:1;2074:15;2112:4;2109:1;2102:15;1966:161;;1753:380;;;:::o;2267:1104::-;2397:3;2426:1;2459:6;2453:13;2489:3;2511:1;2539:9;2535:2;2531:18;2521:28;;2599:2;2588:9;2584:18;2621;2611:61;;2665:4;2657:6;2653:17;2643:27;;2611:61;2691:2;2739;2731:6;2728:14;2708:18;2705:38;2702:165;;;-1:-1:-1;;;2766:33:23;;2822:4;2819:1;2812:15;2852:4;2773:3;2840:17;2702:165;2883:18;2910:104;;;;3028:1;3023:323;;;;2876:470;;2910:104;-1:-1:-1;;2943:24:23;;2931:37;;2988:16;;;;-1:-1:-1;2910:104:23;;3023:323;2214:1;2207:14;;;2251:4;2238:18;;3121:1;3135:165;3149:6;3146:1;3143:13;3135:165;;;3227:14;;3214:11;;;3207:35;3270:16;;;;3164:10;;3135:165;;;3139:3;;3329:6;3324:3;3320:16;3313:23;;2876:470;-1:-1:-1;3362:3:23;;2267:1104;-1:-1:-1;;;;;;;;2267:1104:23:o;3376:489::-;113:230:22;;;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b50600436106100cf5760003560e01c806340c10f191161008c57806395d89b411161006657806395d89b41146101d5578063a9059cbb146101dd578063d505accf146101f0578063dd62ed3e1461020357600080fd5b806340c10f191461018057806370a08231146101955780637ecebe00146101b557600080fd5b806306fdde03146100d4578063095ea7b3146100f257806318160ddd1461011557806323b872dd1461012c578063313ce5671461013f5780633644e51514610178575b600080fd5b6100dc61022e565b6040516100e99190610856565b60405180910390f35b6101056101003660046108c7565b6102bc565b60405190151581526020016100e9565b61011e60025481565b6040519081526020016100e9565b61010561013a3660046108f1565b610328565b6101667f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016100e9565b61011e61041a565b61019361018e3660046108c7565b610475565b005b61011e6101a336600461092d565b60036020526000908152604090205481565b61011e6101c336600461092d565b60056020526000908152604090205481565b6100dc610483565b6101056101eb3660046108c7565b610490565b6101936101fe36600461094f565b610508565b61011e6102113660046109c2565b600460209081526000928352604080842090915290825290205481565b6000805461023b906109f5565b80601f0160208091040260200160405190810160405280929190818152602001828054610267906109f5565b80156102b45780601f10610289576101008083540402835291602001916102b4565b820191906000526020600020905b81548152906001019060200180831161029757829003601f168201915b505050505081565b3360008181526004602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103179086815260200190565b60405180910390a350600192915050565b6001600160a01b038316600090815260046020908152604080832033845290915281205460001981146103845761035f8382610a46565b6001600160a01b03861660009081526004602090815260408083203384529091529020555b6001600160a01b038516600090815260036020526040812080548592906103ac908490610a46565b90915550506001600160a01b03808516600081815260036020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906104079087815260200190565b60405180910390a3506001949350505050565b60007f000000000000000000000000000000000000000000000000000000000000000046146104505761044b610751565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b61047f82826107eb565b5050565b6001805461023b906109f5565b336000908152600360205260408120805483919083906104b1908490610a46565b90915550506001600160a01b038316600081815260036020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906103179086815260200190565b4284101561055d5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b6000600161056961041a565b6001600160a01b038a811660008181526005602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015610675573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906106ab5750876001600160a01b0316816001600160a01b0316145b6106e85760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b6044820152606401610554565b6001600160a01b0390811660009081526004602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60006040516107839190610a5d565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80600260008282546107fd9190610af9565b90915550506001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef910160405180910390a35050565b600060208083528351808285015260005b8181101561088357858101830151858201604001528201610867565b81811115610895576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108c257600080fd5b919050565b600080604083850312156108da57600080fd5b6108e3836108ab565b946020939093013593505050565b60008060006060848603121561090657600080fd5b61090f846108ab565b925061091d602085016108ab565b9150604084013590509250925092565b60006020828403121561093f57600080fd5b610948826108ab565b9392505050565b600080600080600080600060e0888a03121561096a57600080fd5b610973886108ab565b9650610981602089016108ab565b95506040880135945060608801359350608088013560ff811681146109a557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b600080604083850312156109d557600080fd5b6109de836108ab565b91506109ec602084016108ab565b90509250929050565b600181811c90821680610a0957607f821691505b60208210811415610a2a57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a5857610a58610a30565b500390565b600080835481600182811c915080831680610a7957607f831692505b6020808410821415610a9957634e487b7160e01b86526022600452602486fd5b818015610aad5760018114610abe57610aeb565b60ff19861689528489019650610aeb565b60008a81526020902060005b86811015610ae35781548b820152908501908301610aca565b505084890196505b509498975050505050505050565b60008219821115610b0c57610b0c610a30565b50019056fea26469706673582212209ca55739676d094f8ef4b3b5cfcc08f600b805843c4c3027beb2b4159a02f63664736f6c634300080a0033","sourceMap":"113:230:22:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1028:18:10;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;2458:211;;;;;;:::i;:::-;;:::i;:::-;;;1218:14:23;;1211:22;1193:41;;1181:2;1166:18;2458:211:10;1053:187:23;1301:26:10;;;;;;;;;1391:25:23;;;1379:2;1364:18;1301:26:10;1245:177:23;3054:592:10;;;;;;:::i;:::-;;:::i;1080:31::-;;;;;;;;1932:4:23;1920:17;;;1902:36;;1890:2;1875:18;1080:31:10;1760:184:23;5324:177:10;;;:::i;256:85:22:-;;;;;;:::i;:::-;;:::i;:::-;;1334:44:10;;;;;;:::i;:::-;;;;;;;;;;;;;;1748:41;;;;;;:::i;:::-;;;;;;;;;;;;;;1053:20;;;:::i;2675:373::-;;;;;;:::i;:::-;;:::i;3835:1483::-;;;;;;:::i;:::-;;:::i;1385:64::-;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;1028:18;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;2458:211::-;2558:10;2532:4;2548:21;;;:9;:21;;;;;;;;-1:-1:-1;;;;;2548:30:10;;;;;;;;;;:39;;;2603:37;2532:4;;2548:30;;2603:37;;;;2581:6;1391:25:23;;1379:2;1364:18;;1245:177;2603:37:10;;;;;;;;-1:-1:-1;2658:4:10;2458:211;;;;:::o;3054:592::-;-1:-1:-1;;;;;3206:15:10;;3172:4;3206:15;;;:9;:15;;;;;;;;3222:10;3206:27;;;;;;;;-1:-1:-1;;3284:28:10;;3280:80;;3344:16;3354:6;3344:7;:16;:::i;:::-;-1:-1:-1;;;;;3314:15:10;;;;;;:9;:15;;;;;;;;3330:10;3314:27;;;;;;;:46;3280:80;-1:-1:-1;;;;;3371:15:10;;;;;;:9;:15;;;;;:25;;3390:6;;3371:15;:25;;3390:6;;3371:25;:::i;:::-;;;;-1:-1:-1;;;;;;;3542:13:10;;;;;;;:9;:13;;;;;;;:23;;;;;;3591:26;3542:13;;3591:26;;;;;;;3559:6;1391:25:23;;1379:2;1364:18;;1245:177;3591:26:10;;;;;;;;-1:-1:-1;3635:4:10;;3054:592;-1:-1:-1;;;;3054:592:10:o;5324:177::-;5381:7;5424:16;5407:13;:33;:87;;5470:24;:22;:24::i;:::-;5400:94;;5324:177;:::o;5407:87::-;-1:-1:-1;5443:24:10;;5324:177::o;256:85:22:-;317:17;323:2;327:6;317:5;:17::i;:::-;256:85;;:::o;1053:20:10:-;;;;;;;:::i;2675:373::-;2771:10;2745:4;2761:21;;;:9;:21;;;;;:31;;2786:6;;2761:21;2745:4;;2761:31;;2786:6;;2761:31;:::i;:::-;;;;-1:-1:-1;;;;;;;2938:13:10;;;;;;:9;:13;;;;;;;:23;;;;;;2987:32;2996:10;;2987:32;;;;2955:6;1391:25:23;;1379:2;1364:18;;1245:177;3835:1483:10;4054:15;4042:8;:27;;4034:63;;;;-1:-1:-1;;;4034:63:10;;4134:2:23;4034:63:10;;;4116:21:23;4173:2;4153:18;;;4146:30;4212:25;4192:18;;;4185:53;4255:18;;4034:63:10;;;;;;;;;4262:24;4289:805;4425:18;:16;:18::i;:::-;-1:-1:-1;;;;;4870:13:10;;;;;;;:6;:13;;;;;;;;;:15;;;;;;;;4508:449;;4552:165;4508:449;;;4571:25:23;4650:18;;;4643:43;;;;4722:15;;;4702:18;;;4695:43;4754:18;;;4747:34;;;4797:19;;;4790:35;;;;4841:19;;;;4834:35;;;4508:449:10;;;;;;;;;;4543:19:23;;;4508:449:10;;;4469:514;;;;;;;;-1:-1:-1;;;4347:658:10;;;5138:27:23;5181:11;;;5174:27;;;;5217:12;;;5210:28;;;;5254:12;;4347:658:10;;;-1:-1:-1;;4347:658:10;;;;;;;;;4316:707;;4347:658;4316:707;;;;4289:805;;;;;;;;;5504:25:23;5577:4;5565:17;;5545:18;;;5538:45;5599:18;;;5592:34;;;5642:18;;;5635:34;;;5476:19;;4289:805:10;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;4289:805:10;;-1:-1:-1;;4289:805:10;;;-1:-1:-1;;;;;;;5117:30:10;;;;;;:59;;;5171:5;-1:-1:-1;;;;;5151:25:10;:16;-1:-1:-1;;;;;5151:25:10;;5117:59;5109:86;;;;-1:-1:-1;;;5109:86:10;;5882:2:23;5109:86:10;;;5864:21:23;5921:2;5901:18;;;5894:30;-1:-1:-1;;;5940:18:23;;;5933:44;5994:18;;5109:86:10;5680:338:23;5109:86:10;-1:-1:-1;;;;;5210:27:10;;;;;;;:9;:27;;;;;;;;:36;;;;;;;;;;;;;:44;;;5280:31;1391:25:23;;;5210:36:10;;5280:31;;;;;1364:18:23;5280:31:10;;;;;;;3835:1483;;;;;;;:::o;5507:446::-;5572:7;5669:95;5802:4;5786:22;;;;;;:::i;:::-;;;;;;;;;;5637:295;;;7520:25:23;;;;7561:18;;7554:34;;;;5830:14:10;7604:18:23;;;7597:34;5866:13:10;7647:18:23;;;7640:34;5909:4:10;7690:19:23;;;7683:61;7492:19;;5637:295:10;;;;;;;;;;;;5610:336;;;;;;5591:355;;5507:446;:::o;6147:325::-;6232:6;6217:11;;:21;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;;;6384:13:10;;;;;;:9;:13;;;;;;;;:23;;;;;;6433:32;1391:25:23;;;6433:32:10;;1364:18:23;6433:32:10;;;;;;;6147:325;;:::o;14:597:23:-;126:4;155:2;184;173:9;166:21;216:6;210:13;259:6;254:2;243:9;239:18;232:34;284:1;294:140;308:6;305:1;302:13;294:140;;;403:14;;;399:23;;393:30;369:17;;;388:2;365:26;358:66;323:10;;294:140;;;452:6;449:1;446:13;443:91;;;522:1;517:2;508:6;497:9;493:22;489:31;482:42;443:91;-1:-1:-1;595:2:23;574:15;-1:-1:-1;;570:29:23;555:45;;;;602:2;551:54;;14:597;-1:-1:-1;;;14:597:23:o;616:173::-;684:20;;-1:-1:-1;;;;;733:31:23;;723:42;;713:70;;779:1;776;769:12;713:70;616:173;;;:::o;794:254::-;862:6;870;923:2;911:9;902:7;898:23;894:32;891:52;;;939:1;936;929:12;891:52;962:29;981:9;962:29;:::i;:::-;952:39;1038:2;1023:18;;;;1010:32;;-1:-1:-1;;;794:254:23:o;1427:328::-;1504:6;1512;1520;1573:2;1561:9;1552:7;1548:23;1544:32;1541:52;;;1589:1;1586;1579:12;1541:52;1612:29;1631:9;1612:29;:::i;:::-;1602:39;;1660:38;1694:2;1683:9;1679:18;1660:38;:::i;:::-;1650:48;;1745:2;1734:9;1730:18;1717:32;1707:42;;1427:328;;;;;:::o;2131:186::-;2190:6;2243:2;2231:9;2222:7;2218:23;2214:32;2211:52;;;2259:1;2256;2249:12;2211:52;2282:29;2301:9;2282:29;:::i;:::-;2272:39;2131:186;-1:-1:-1;;;2131:186:23:o;2322:693::-;2433:6;2441;2449;2457;2465;2473;2481;2534:3;2522:9;2513:7;2509:23;2505:33;2502:53;;;2551:1;2548;2541:12;2502:53;2574:29;2593:9;2574:29;:::i;:::-;2564:39;;2622:38;2656:2;2645:9;2641:18;2622:38;:::i;:::-;2612:48;;2707:2;2696:9;2692:18;2679:32;2669:42;;2758:2;2747:9;2743:18;2730:32;2720:42;;2812:3;2801:9;2797:19;2784:33;2857:4;2850:5;2846:16;2839:5;2836:27;2826:55;;2877:1;2874;2867:12;2826:55;2322:693;;;;-1:-1:-1;2322:693:23;;;;2900:5;2952:3;2937:19;;2924:33;;-1:-1:-1;3004:3:23;2989:19;;;2976:33;;2322:693;-1:-1:-1;;2322:693:23:o;3020:260::-;3088:6;3096;3149:2;3137:9;3128:7;3124:23;3120:32;3117:52;;;3165:1;3162;3155:12;3117:52;3188:29;3207:9;3188:29;:::i;:::-;3178:39;;3236:38;3270:2;3259:9;3255:18;3236:38;:::i;:::-;3226:48;;3020:260;;;;;:::o;3285:380::-;3364:1;3360:12;;;;3407;;;3428:61;;3482:4;3474:6;3470:17;3460:27;;3428:61;3535:2;3527:6;3524:14;3504:18;3501:38;3498:161;;;3581:10;3576:3;3572:20;3569:1;3562:31;3616:4;3613:1;3606:15;3644:4;3641:1;3634:15;3498:161;;3285:380;;;:::o;3670:127::-;3731:10;3726:3;3722:20;3719:1;3712:31;3762:4;3759:1;3752:15;3786:4;3783:1;3776:15;3802:125;3842:4;3870:1;3867;3864:8;3861:34;;;3875:18;;:::i;:::-;-1:-1:-1;3912:9:23;;3802:125::o;6152:1104::-;6282:3;6311:1;6344:6;6338:13;6374:3;6396:1;6424:9;6420:2;6416:18;6406:28;;6484:2;6473:9;6469:18;6506;6496:61;;6550:4;6542:6;6538:17;6528:27;;6496:61;6576:2;6624;6616:6;6613:14;6593:18;6590:38;6587:165;;;-1:-1:-1;;;6651:33:23;;6707:4;6704:1;6697:15;6737:4;6658:3;6725:17;6587:165;6768:18;6795:104;;;;6913:1;6908:323;;;;6761:470;;6795:104;-1:-1:-1;;6828:24:23;;6816:37;;6873:16;;;;-1:-1:-1;6795:104:23;;6908:323;6099:1;6092:14;;;6136:4;6123:18;;7006:1;7020:165;7034:6;7031:1;7028:13;7020:165;;;7112:14;;7099:11;;;7092:35;7155:16;;;;7049:10;;7020:165;;;7024:3;;7214:6;7209:3;7205:16;7198:23;;6761:470;-1:-1:-1;7247:3:23;;6152:1104;-1:-1:-1;;;;;;;;6152:1104:23:o;7755:128::-;7795:3;7826:1;7822:6;7819:1;7816:13;7813:39;;;7832:18;;:::i;:::-;-1:-1:-1;7868:9:23;;7755:128::o","linkReferences":{},"immutableReferences":{"3499":[{"start":324,"length":32}],"3513":[{"start":1054,"length":32}],"3515":[{"start":1107,"length":32}]}},"methodIdentifiers":{"DOMAIN_SEPARATOR()":"3644e515","allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","mint(address,uint256)":"40c10f19","name()":"06fdde03","nonces(address)":"7ecebe00","permit(address,address,uint256,uint256,uint8,bytes32,bytes32)":"d505accf","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"}} \ No newline at end of file diff --git a/crates/cast/tests/fixtures/interface.json b/crates/cast/tests/fixtures/interface.json new file mode 100644 index 0000000000000..26163abeec0d9 --- /dev/null +++ b/crates/cast/tests/fixtures/interface.json @@ -0,0 +1 @@ +[{"type":"constructor","inputs":[{"name":"_integrationManager","type":"address","internalType":"address"},{"name":"_addressListRegistry","type":"address","internalType":"address"},{"name":"_aTokenListId","type":"uint256","internalType":"uint256"},{"name":"_pool","type":"address","internalType":"address"},{"name":"_referralCode","type":"uint16","internalType":"uint16"}],"stateMutability":"nonpayable"},{"type":"function","name":"getIntegrationManager","inputs":[],"outputs":[{"name":"integrationManager_","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"lend","inputs":[{"name":"_vaultProxy","type":"address","internalType":"address"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_assetData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"parseAssetsForAction","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"_selector","type":"bytes4","internalType":"bytes4"},{"name":"_actionData","type":"bytes","internalType":"bytes"}],"outputs":[{"name":"spendAssetsHandleType_","type":"uint8","internalType":"enum IIntegrationManager.SpendAssetsHandleType"},{"name":"spendAssets_","type":"address[]","internalType":"address[]"},{"name":"spendAssetAmounts_","type":"uint256[]","internalType":"uint256[]"},{"name":"incomingAssets_","type":"address[]","internalType":"address[]"},{"name":"minIncomingAssetAmounts_","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"type":"function","name":"redeem","inputs":[{"name":"_vaultProxy","type":"address","internalType":"address"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_assetData","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"}] \ No newline at end of file diff --git a/crates/cast/tests/fixtures/sign_typed_data.json b/crates/cast/tests/fixtures/sign_typed_data.json index a6002810a6ee5..8dc45f2e74097 100644 --- a/crates/cast/tests/fixtures/sign_typed_data.json +++ b/crates/cast/tests/fixtures/sign_typed_data.json @@ -1,38 +1 @@ -{ - "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!" - } -} \ No newline at end of file +{"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!"}} \ No newline at end of file diff --git a/crates/cast/tests/fixtures/storage_layout_complex.json b/crates/cast/tests/fixtures/storage_layout_complex.json new file mode 100644 index 0000000000000..2cad9dc8c221f --- /dev/null +++ b/crates/cast/tests/fixtures/storage_layout_complex.json @@ -0,0 +1,397 @@ +{ + "storage": [ + { + "astId": 3805, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_status", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 9499, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_generalPoolsBalances", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_bytes32,t_struct(IERC20ToBytes32Map)3177_storage)" + }, + { + "astId": 716, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_nextNonce", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_address,t_uint256)" + }, + { + "astId": 967, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_paused", + "offset": 0, + "slot": "3", + "type": "t_bool" + }, + { + "astId": 8639, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_authorizer", + "offset": 1, + "slot": "3", + "type": "t_contract(IAuthorizer)11086" + }, + { + "astId": 8645, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_approvedRelayers", + "offset": 0, + "slot": "4", + "type": "t_mapping(t_address,t_mapping(t_address,t_bool))" + }, + { + "astId": 5769, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_isPoolRegistered", + "offset": 0, + "slot": "5", + "type": "t_mapping(t_bytes32,t_bool)" + }, + { + "astId": 5771, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_nextPoolNonce", + "offset": 0, + "slot": "6", + "type": "t_uint256" + }, + { + "astId": 9915, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_minimalSwapInfoPoolsBalances", + "offset": 0, + "slot": "7", + "type": "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_bytes32))" + }, + { + "astId": 9919, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_minimalSwapInfoPoolsTokens", + "offset": 0, + "slot": "8", + "type": "t_mapping(t_bytes32,t_struct(AddressSet)3520_storage)" + }, + { + "astId": 10373, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_twoTokenPoolTokens", + "offset": 0, + "slot": "9", + "type": "t_mapping(t_bytes32,t_struct(TwoTokenPoolTokens)10369_storage)" + }, + { + "astId": 4007, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_poolAssetManagers", + "offset": 0, + "slot": "10", + "type": "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_address))" + }, + { + "astId": 8019, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_internalTokenBalance", + "offset": 0, + "slot": "11", + "type": "t_mapping(t_address,t_mapping(t_contract(IERC20)3793,t_uint256))" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_array(t_address)dyn_storage": { + "encoding": "dynamic_array", + "label": "address[]", + "numberOfBytes": "32", + "base": "t_address" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_contract(IAuthorizer)11086": { + "encoding": "inplace", + "label": "contract IAuthorizer", + "numberOfBytes": "20" + }, + "t_contract(IERC20)3793": { + "encoding": "inplace", + "label": "contract IERC20", + "numberOfBytes": "20" + }, + "t_mapping(t_address,t_bool)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_address,t_mapping(t_address,t_bool))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(address => bool))", + "numberOfBytes": "32", + "value": "t_mapping(t_address,t_bool)" + }, + "t_mapping(t_address,t_mapping(t_contract(IERC20)3793,t_uint256))": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => mapping(contract IERC20 => uint256))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3793,t_uint256)" + }, + "t_mapping(t_address,t_uint256)": { + "encoding": "mapping", + "key": "t_address", + "label": "mapping(address => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_bytes32,t_bool)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => bool)", + "numberOfBytes": "32", + "value": "t_bool" + }, + "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_address))": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => mapping(contract IERC20 => address))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3793,t_address)" + }, + "t_mapping(t_bytes32,t_mapping(t_contract(IERC20)3793,t_bytes32))": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => mapping(contract IERC20 => bytes32))", + "numberOfBytes": "32", + "value": "t_mapping(t_contract(IERC20)3793,t_bytes32)" + }, + "t_mapping(t_bytes32,t_struct(AddressSet)3520_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct EnumerableSet.AddressSet)", + "numberOfBytes": "32", + "value": "t_struct(AddressSet)3520_storage" + }, + "t_mapping(t_bytes32,t_struct(IERC20ToBytes32Map)3177_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct EnumerableMap.IERC20ToBytes32Map)", + "numberOfBytes": "32", + "value": "t_struct(IERC20ToBytes32Map)3177_storage" + }, + "t_mapping(t_bytes32,t_struct(TwoTokenPoolBalances)10360_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolBalances)", + "numberOfBytes": "32", + "value": "t_struct(TwoTokenPoolBalances)10360_storage" + }, + "t_mapping(t_bytes32,t_struct(TwoTokenPoolTokens)10369_storage)": { + "encoding": "mapping", + "key": "t_bytes32", + "label": "mapping(bytes32 => struct TwoTokenPoolsBalance.TwoTokenPoolTokens)", + "numberOfBytes": "32", + "value": "t_struct(TwoTokenPoolTokens)10369_storage" + }, + "t_mapping(t_contract(IERC20)3793,t_address)": { + "encoding": "mapping", + "key": "t_contract(IERC20)3793", + "label": "mapping(contract IERC20 => address)", + "numberOfBytes": "32", + "value": "t_address" + }, + "t_mapping(t_contract(IERC20)3793,t_bytes32)": { + "encoding": "mapping", + "key": "t_contract(IERC20)3793", + "label": "mapping(contract IERC20 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_mapping(t_contract(IERC20)3793,t_uint256)": { + "encoding": "mapping", + "key": "t_contract(IERC20)3793", + "label": "mapping(contract IERC20 => uint256)", + "numberOfBytes": "32", + "value": "t_uint256" + }, + "t_mapping(t_uint256,t_struct(IERC20ToBytes32MapEntry)3166_storage)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => struct EnumerableMap.IERC20ToBytes32MapEntry)", + "numberOfBytes": "32", + "value": "t_struct(IERC20ToBytes32MapEntry)3166_storage" + }, + "t_struct(AddressSet)3520_storage": { + "encoding": "inplace", + "label": "struct EnumerableSet.AddressSet", + "numberOfBytes": "64", + "members": [ + { + "astId": 3515, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_values", + "offset": 0, + "slot": "0", + "type": "t_array(t_address)dyn_storage" + }, + { + "astId": 3519, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_indexes", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_address,t_uint256)" + } + ] + }, + "t_struct(IERC20ToBytes32Map)3177_storage": { + "encoding": "inplace", + "label": "struct EnumerableMap.IERC20ToBytes32Map", + "numberOfBytes": "96", + "members": [ + { + "astId": 3168, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_length", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 3172, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_entries", + "offset": 0, + "slot": "1", + "type": "t_mapping(t_uint256,t_struct(IERC20ToBytes32MapEntry)3166_storage)" + }, + { + "astId": 3176, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_indexes", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_contract(IERC20)3793,t_uint256)" + } + ] + }, + "t_struct(IERC20ToBytes32MapEntry)3166_storage": { + "encoding": "inplace", + "label": "struct EnumerableMap.IERC20ToBytes32MapEntry", + "numberOfBytes": "64", + "members": [ + { + "astId": 3163, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_key", + "offset": 0, + "slot": "0", + "type": "t_contract(IERC20)3793" + }, + { + "astId": 3165, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "_value", + "offset": 0, + "slot": "1", + "type": "t_bytes32" + } + ] + }, + "t_struct(TwoTokenPoolBalances)10360_storage": { + "encoding": "inplace", + "label": "struct TwoTokenPoolsBalance.TwoTokenPoolBalances", + "numberOfBytes": "64", + "members": [ + { + "astId": 10357, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "sharedCash", + "offset": 0, + "slot": "0", + "type": "t_bytes32" + }, + { + "astId": 10359, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "sharedManaged", + "offset": 0, + "slot": "1", + "type": "t_bytes32" + } + ] + }, + "t_struct(TwoTokenPoolTokens)10369_storage": { + "encoding": "inplace", + "label": "struct TwoTokenPoolsBalance.TwoTokenPoolTokens", + "numberOfBytes": "96", + "members": [ + { + "astId": 10362, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "tokenA", + "offset": 0, + "slot": "0", + "type": "t_contract(IERC20)3793" + }, + { + "astId": 10364, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "tokenB", + "offset": 0, + "slot": "1", + "type": "t_contract(IERC20)3793" + }, + { + "astId": 10368, + "contract": "contracts/vault/Vault.sol:Vault", + "label": "balances", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_bytes32,t_struct(TwoTokenPoolBalances)10360_storage)" + } + ] + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + }, + "values": [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000006048a8c631fb7e77eca533cf9c29784e482391e7", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000000006e0", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] +} \ No newline at end of file diff --git a/crates/cast/tests/fixtures/storage_layout_simple.json b/crates/cast/tests/fixtures/storage_layout_simple.json new file mode 100644 index 0000000000000..35f4777d02b31 --- /dev/null +++ b/crates/cast/tests/fixtures/storage_layout_simple.json @@ -0,0 +1,36 @@ +{ + "storage": [ + { + "astId": 7, + "contract": "contracts/Create2Deployer.sol:Create2Deployer", + "label": "_owner", + "offset": 0, + "slot": "0", + "type": "t_address" + }, + { + "astId": 122, + "contract": "contracts/Create2Deployer.sol:Create2Deployer", + "label": "_paused", + "offset": 20, + "slot": "0", + "type": "t_bool" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + }, + "t_bool": { + "encoding": "inplace", + "label": "bool", + "numberOfBytes": "1" + } + }, + "values": [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] +} \ No newline at end of file diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml new file mode 100644 index 0000000000000..6965ebe0b646c --- /dev/null +++ b/crates/cheatcodes/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "foundry-cheatcodes" +description = "Foundry cheatcodes definitions and implementations" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-cheatcodes-spec.workspace = true +foundry-common.workspace = true +foundry-compilers.workspace = true +foundry-config.workspace = true +foundry-evm-core.workspace = true +foundry-evm-traces.workspace = true +foundry-wallets.workspace = true +forge-script-sequence.workspace = true + +alloy-dyn-abi.workspace = true +alloy-json-abi.workspace = true +alloy-primitives.workspace = true +alloy-genesis.workspace = true +alloy-sol-types.workspace = true +alloy-provider.workspace = true +alloy-rpc-types = { workspace = true, features = ["k256"] } +alloy-signer.workspace = true +alloy-signer-local = { workspace = true, features = [ + "mnemonic-all-languages", + "keystore", +] } +parking_lot.workspace = true +alloy-consensus = { workspace = true, features = ["k256"] } +alloy-network.workspace = true +alloy-rlp.workspace = true + +base64.workspace = true +dialoguer = "0.11" +eyre.workspace = true +itertools.workspace = true +jsonpath_lib.workspace = true +k256.workspace = true +memchr = "2.7" +p256 = "0.13" +ecdsa = "0.16" +rand.workspace = true +revm.workspace = true +revm-inspectors.workspace = true +semver.workspace = true +serde_json.workspace = true +thiserror.workspace = true +toml = { workspace = true, features = ["preserve_order"] } +tracing.workspace = true +walkdir.workspace = true +proptest.workspace = true +serde.workspace = true diff --git a/crates/cheatcodes/README.md b/crates/cheatcodes/README.md new file mode 100644 index 0000000000000..7f776392eb744 --- /dev/null +++ b/crates/cheatcodes/README.md @@ -0,0 +1,41 @@ +# foundry-cheatcodes + +Foundry cheatcodes definitions and implementations. + +## Structure + +- [`assets/`](./assets/): JSON interface and specification +- [`spec/`](./spec/src/lib.rs): Defines common traits and structs +- [`src/`](./src/lib.rs): Rust implementations of the cheatcodes + +## Overview + +All cheatcodes are defined in a single [`sol!`] macro call in [`spec/src/vm.rs`]. + +This, combined with the use of an internal [`Cheatcode`](../../crates/cheatcodes/spec/src/cheatcode.rs) derive macro, +allows us to generate both the Rust definitions and the JSON specification of the cheatcodes. + +Cheatcodes are manually implemented through the `Cheatcode` trait, which is called in the +`Cheatcodes` inspector implementation. + +See the [cheatcodes dev documentation](../../docs/dev/cheatcodes.md#cheatcodes-implementation) for more details. + +### JSON interface + +The JSON interface is guaranteed to be stable, and can be used by third-party tools to interact with +the Foundry cheatcodes externally. + +For example, here are some tools that make use of the JSON interface: +- Internally, this is used to generate [a simple Solidity interface](../../testdata/cheats/Vm.sol) for testing +- Used by [`forge-std`](https://github.com/foundry-rs/forge-std) to generate [user-friendly Solidity interfaces](https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol) +- (WIP) Used by [the Foundry book](https://github.com/foundry-rs/book) to generate [the cheatcodes reference](https://book.getfoundry.sh/cheatcodes) +- ... + +If you are making use of the JSON interface, please don't hesitate to open a PR to add your project to this list! + +### Adding a new cheatcode + +Please see the [cheatcodes dev documentation](../../docs/dev/cheatcodes.md#adding-a-new-cheatcode) on how to add new cheatcodes. + +[`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html +[`spec/src/vm.rs`]: ./spec/src/vm.rs diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json new file mode 100644 index 0000000000000..3d528b903bf18 --- /dev/null +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -0,0 +1,10584 @@ +{ + "errors": [ + { + "name": "CheatcodeError", + "description": "Error thrown by cheatcodes.", + "declaration": "error CheatcodeError(string message);" + } + ], + "events": [], + "enums": [ + { + "name": "CallerMode", + "description": "A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`.", + "variants": [ + { + "name": "None", + "description": "No caller modification is currently active." + }, + { + "name": "Broadcast", + "description": "A one time broadcast triggered by a `vm.broadcast()` call is currently active." + }, + { + "name": "RecurrentBroadcast", + "description": "A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active." + }, + { + "name": "Prank", + "description": "A one time prank triggered by a `vm.prank()` call is currently active." + }, + { + "name": "RecurrentPrank", + "description": "A recurrent prank triggered by a `vm.startPrank()` call is currently active." + } + ] + }, + { + "name": "AccountAccessKind", + "description": "The kind of account access that occurred.", + "variants": [ + { + "name": "Call", + "description": "The account was called." + }, + { + "name": "DelegateCall", + "description": "The account was called via delegatecall." + }, + { + "name": "CallCode", + "description": "The account was called via callcode." + }, + { + "name": "StaticCall", + "description": "The account was called via staticcall." + }, + { + "name": "Create", + "description": "The account was created." + }, + { + "name": "SelfDestruct", + "description": "The account was selfdestructed." + }, + { + "name": "Resume", + "description": "Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess)." + }, + { + "name": "Balance", + "description": "The account's balance was read." + }, + { + "name": "Extcodesize", + "description": "The account's codesize was read." + }, + { + "name": "Extcodehash", + "description": "The account's codehash was read." + }, + { + "name": "Extcodecopy", + "description": "The account's code was copied." + } + ] + }, + { + "name": "ForgeContext", + "description": "Forge execution contexts.", + "variants": [ + { + "name": "TestGroup", + "description": "Test group execution context (test, coverage or snapshot)." + }, + { + "name": "Test", + "description": "`forge test` execution context." + }, + { + "name": "Coverage", + "description": "`forge coverage` execution context." + }, + { + "name": "Snapshot", + "description": "`forge snapshot` execution context." + }, + { + "name": "ScriptGroup", + "description": "Script group execution context (dry run, broadcast or resume)." + }, + { + "name": "ScriptDryRun", + "description": "`forge script` execution context." + }, + { + "name": "ScriptBroadcast", + "description": "`forge script --broadcast` execution context." + }, + { + "name": "ScriptResume", + "description": "`forge script --resume` execution context." + }, + { + "name": "Unknown", + "description": "Unknown `forge` execution context." + } + ] + }, + { + "name": "BroadcastTxType", + "description": "The transaction type (`txType`) of the broadcast.", + "variants": [ + { + "name": "Call", + "description": "Represents a CALL broadcast tx." + }, + { + "name": "Create", + "description": "Represents a CREATE broadcast tx." + }, + { + "name": "Create2", + "description": "Represents a CREATE2 broadcast tx." + } + ] + } + ], + "structs": [ + { + "name": "Log", + "description": "An Ethereum log. Returned by `getRecordedLogs`.", + "fields": [ + { + "name": "topics", + "ty": "bytes32[]", + "description": "The topics of the log, including the signature, if any." + }, + { + "name": "data", + "ty": "bytes", + "description": "The raw data of the log." + }, + { + "name": "emitter", + "ty": "address", + "description": "The address of the log's emitter." + } + ] + }, + { + "name": "Rpc", + "description": "An RPC URL and its alias. Returned by `rpcUrlStructs`.", + "fields": [ + { + "name": "key", + "ty": "string", + "description": "The alias of the RPC URL." + }, + { + "name": "url", + "ty": "string", + "description": "The RPC URL." + } + ] + }, + { + "name": "EthGetLogs", + "description": "An RPC log object. Returned by `eth_getLogs`.", + "fields": [ + { + "name": "emitter", + "ty": "address", + "description": "The address of the log's emitter." + }, + { + "name": "topics", + "ty": "bytes32[]", + "description": "The topics of the log, including the signature, if any." + }, + { + "name": "data", + "ty": "bytes", + "description": "The raw data of the log." + }, + { + "name": "blockHash", + "ty": "bytes32", + "description": "The block hash." + }, + { + "name": "blockNumber", + "ty": "uint64", + "description": "The block number." + }, + { + "name": "transactionHash", + "ty": "bytes32", + "description": "The transaction hash." + }, + { + "name": "transactionIndex", + "ty": "uint64", + "description": "The transaction index in the block." + }, + { + "name": "logIndex", + "ty": "uint256", + "description": "The log index." + }, + { + "name": "removed", + "ty": "bool", + "description": "Whether the log was removed." + } + ] + }, + { + "name": "DirEntry", + "description": "A single entry in a directory listing. Returned by `readDir`.", + "fields": [ + { + "name": "errorMessage", + "ty": "string", + "description": "The error message, if any." + }, + { + "name": "path", + "ty": "string", + "description": "The path of the entry." + }, + { + "name": "depth", + "ty": "uint64", + "description": "The depth of the entry." + }, + { + "name": "isDir", + "ty": "bool", + "description": "Whether the entry is a directory." + }, + { + "name": "isSymlink", + "ty": "bool", + "description": "Whether the entry is a symlink." + } + ] + }, + { + "name": "FsMetadata", + "description": "Metadata information about a file.\n This structure is returned from the `fsMetadata` function and represents known\n metadata about a file such as its permissions, size, modification\n times, etc.", + "fields": [ + { + "name": "isDir", + "ty": "bool", + "description": "True if this metadata is for a directory." + }, + { + "name": "isSymlink", + "ty": "bool", + "description": "True if this metadata is for a symlink." + }, + { + "name": "length", + "ty": "uint256", + "description": "The size of the file, in bytes, this metadata is for." + }, + { + "name": "readOnly", + "ty": "bool", + "description": "True if this metadata is for a readonly (unwritable) file." + }, + { + "name": "modified", + "ty": "uint256", + "description": "The last modification time listed in this metadata." + }, + { + "name": "accessed", + "ty": "uint256", + "description": "The last access time of this metadata." + }, + { + "name": "created", + "ty": "uint256", + "description": "The creation time listed in this metadata." + } + ] + }, + { + "name": "Wallet", + "description": "A wallet with a public and private key.", + "fields": [ + { + "name": "addr", + "ty": "address", + "description": "The wallet's address." + }, + { + "name": "publicKeyX", + "ty": "uint256", + "description": "The wallet's public key `X`." + }, + { + "name": "publicKeyY", + "ty": "uint256", + "description": "The wallet's public key `Y`." + }, + { + "name": "privateKey", + "ty": "uint256", + "description": "The wallet's private key." + } + ] + }, + { + "name": "FfiResult", + "description": "The result of a `tryFfi` call.", + "fields": [ + { + "name": "exitCode", + "ty": "int32", + "description": "The exit code of the call." + }, + { + "name": "stdout", + "ty": "bytes", + "description": "The optionally hex-decoded `stdout` data." + }, + { + "name": "stderr", + "ty": "bytes", + "description": "The `stderr` data." + } + ] + }, + { + "name": "ChainInfo", + "description": "Information on the chain and fork.", + "fields": [ + { + "name": "forkId", + "ty": "uint256", + "description": "The fork identifier. Set to zero if no fork is active." + }, + { + "name": "chainId", + "ty": "uint256", + "description": "The chain ID of the current fork." + } + ] + }, + { + "name": "AccountAccess", + "description": "The result of a `stopAndReturnStateDiff` call.", + "fields": [ + { + "name": "chainInfo", + "ty": "ChainInfo", + "description": "The chain and fork the access occurred." + }, + { + "name": "kind", + "ty": "AccountAccessKind", + "description": "The kind of account access that determines what the account is.\n If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee.\n If kind is Create, then the account is the newly created account.\n If kind is SelfDestruct, then the account is the selfdestruct recipient.\n If kind is a Resume, then account represents a account context that has resumed." + }, + { + "name": "account", + "ty": "address", + "description": "The account that was accessed.\n It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT." + }, + { + "name": "accessor", + "ty": "address", + "description": "What accessed the account." + }, + { + "name": "initialized", + "ty": "bool", + "description": "If the account was initialized or empty prior to the access.\n An account is considered initialized if it has code, a\n non-zero nonce, or a non-zero balance." + }, + { + "name": "oldBalance", + "ty": "uint256", + "description": "The previous balance of the accessed account." + }, + { + "name": "newBalance", + "ty": "uint256", + "description": "The potential new balance of the accessed account.\n That is, all balance changes are recorded here, even if reverts occurred." + }, + { + "name": "deployedCode", + "ty": "bytes", + "description": "Code of the account deployed by CREATE." + }, + { + "name": "value", + "ty": "uint256", + "description": "Value passed along with the account access" + }, + { + "name": "data", + "ty": "bytes", + "description": "Input data provided to the CREATE or CALL" + }, + { + "name": "reverted", + "ty": "bool", + "description": "If this access reverted in either the current or parent context." + }, + { + "name": "storageAccesses", + "ty": "StorageAccess[]", + "description": "An ordered list of storage accesses made during an account access operation." + }, + { + "name": "depth", + "ty": "uint64", + "description": "Call depth traversed during the recording of state differences" + } + ] + }, + { + "name": "StorageAccess", + "description": "The storage accessed during an `AccountAccess`.", + "fields": [ + { + "name": "account", + "ty": "address", + "description": "The account whose storage was accessed." + }, + { + "name": "slot", + "ty": "bytes32", + "description": "The slot that was accessed." + }, + { + "name": "isWrite", + "ty": "bool", + "description": "If the access was a write." + }, + { + "name": "previousValue", + "ty": "bytes32", + "description": "The previous value of the slot." + }, + { + "name": "newValue", + "ty": "bytes32", + "description": "The new value of the slot." + }, + { + "name": "reverted", + "ty": "bool", + "description": "If the access was reverted." + } + ] + }, + { + "name": "Gas", + "description": "Gas used. Returned by `lastCallGas`.", + "fields": [ + { + "name": "gasLimit", + "ty": "uint64", + "description": "The gas limit of the call." + }, + { + "name": "gasTotalUsed", + "ty": "uint64", + "description": "The total gas used." + }, + { + "name": "gasMemoryUsed", + "ty": "uint64", + "description": "DEPRECATED: The amount of gas used for memory expansion. Ref: " + }, + { + "name": "gasRefunded", + "ty": "int64", + "description": "The amount of gas refunded." + }, + { + "name": "gasRemaining", + "ty": "uint64", + "description": "The amount of gas remaining." + } + ] + }, + { + "name": "DebugStep", + "description": "The result of the `stopDebugTraceRecording` call", + "fields": [ + { + "name": "stack", + "ty": "uint256[]", + "description": "The stack before executing the step of the run.\n stack\\[0\\] represents the top of the stack.\n and only stack data relevant to the opcode execution is contained." + }, + { + "name": "memoryInput", + "ty": "bytes", + "description": "The memory input data before executing the step of the run.\n only input data relevant to the opcode execution is contained.\n e.g. for MLOAD, it will have memory\\[offset:offset+32\\] copied here.\n the offset value can be get by the stack data." + }, + { + "name": "opcode", + "ty": "uint8", + "description": "The opcode that was accessed." + }, + { + "name": "depth", + "ty": "uint64", + "description": "The call depth of the step." + }, + { + "name": "isOutOfGas", + "ty": "bool", + "description": "Whether the call end up with out of gas error." + }, + { + "name": "contractAddr", + "ty": "address", + "description": "The contract address where the opcode is running" + } + ] + }, + { + "name": "BroadcastTxSummary", + "description": "Represents a transaction's broadcast details.", + "fields": [ + { + "name": "txHash", + "ty": "bytes32", + "description": "The hash of the transaction that was broadcasted" + }, + { + "name": "txType", + "ty": "BroadcastTxType", + "description": "Represent the type of transaction among CALL, CREATE, CREATE2" + }, + { + "name": "contractAddress", + "ty": "address", + "description": "The address of the contract that was called or created.\n This is address of the contract that is created if the txType is CREATE or CREATE2." + }, + { + "name": "blockNumber", + "ty": "uint64", + "description": "The block number the transaction landed in." + }, + { + "name": "success", + "ty": "bool", + "description": "Status of the transaction, retrieved from the transaction receipt." + } + ] + }, + { + "name": "SignedDelegation", + "description": "Holds a signed EIP-7702 authorization for an authority account to delegate to an implementation.", + "fields": [ + { + "name": "v", + "ty": "uint8", + "description": "The y-parity of the recovered secp256k1 signature (0 or 1)." + }, + { + "name": "r", + "ty": "bytes32", + "description": "First 32 bytes of the signature." + }, + { + "name": "s", + "ty": "bytes32", + "description": "Second 32 bytes of the signature." + }, + { + "name": "nonce", + "ty": "uint64", + "description": "The current nonce of the authority account at signing time.\n Used to ensure signature can't be replayed after account nonce changes." + }, + { + "name": "implementation", + "ty": "address", + "description": "Address of the contract implementation that will be delegated to.\n Gets encoded into delegation code: 0xef0100 || implementation." + } + ] + }, + { + "name": "PotentialRevert", + "description": "Represents a \"potential\" revert reason from a single subsequent call when using `vm.assumeNoReverts`.\n Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced\n as normal.", + "fields": [ + { + "name": "reverter", + "ty": "address", + "description": "The allowed origin of the revert opcode; address(0) allows reverts from any address" + }, + { + "name": "partialMatch", + "ty": "bool", + "description": "When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data" + }, + { + "name": "revertData", + "ty": "bytes", + "description": "The data to use to match encountered reverts" + } + ] + } + ], + "cheatcodes": [ + { + "func": { + "id": "_expectCheatcodeRevert_0", + "description": "Expects an error on next cheatcode call with any revert data.", + "declaration": "function _expectCheatcodeRevert() external;", + "visibility": "external", + "mutability": "", + "signature": "_expectCheatcodeRevert()", + "selector": "0x79a4f48a", + "selectorBytes": [ + 121, + 164, + 244, + 138 + ] + }, + "group": "testing", + "status": "internal", + "safety": "unsafe" + }, + { + "func": { + "id": "_expectCheatcodeRevert_1", + "description": "Expects an error on next cheatcode call that starts with the revert data.", + "declaration": "function _expectCheatcodeRevert(bytes4 revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "_expectCheatcodeRevert(bytes4)", + "selector": "0x884cb0ae", + "selectorBytes": [ + 136, + 76, + 176, + 174 + ] + }, + "group": "testing", + "status": "internal", + "safety": "unsafe" + }, + { + "func": { + "id": "_expectCheatcodeRevert_2", + "description": "Expects an error on next cheatcode call that exactly matches the revert data.", + "declaration": "function _expectCheatcodeRevert(bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "_expectCheatcodeRevert(bytes)", + "selector": "0x7843b44d", + "selectorBytes": [ + 120, + 67, + 180, + 77 + ] + }, + "group": "testing", + "status": "internal", + "safety": "unsafe" + }, + { + "func": { + "id": "accesses", + "description": "Gets all accessed reads and write slot from a `vm.record` session, for a given address.", + "declaration": "function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots);", + "visibility": "external", + "mutability": "", + "signature": "accesses(address)", + "selector": "0x65bc9481", + "selectorBytes": [ + 101, + 188, + 148, + 129 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "activeFork", + "description": "Returns the identifier of the currently active fork. Reverts if no fork is currently active.", + "declaration": "function activeFork() external view returns (uint256 forkId);", + "visibility": "external", + "mutability": "view", + "signature": "activeFork()", + "selector": "0x2f103f22", + "selectorBytes": [ + 47, + 16, + 63, + 34 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "addr", + "description": "Gets the address for a given private key.", + "declaration": "function addr(uint256 privateKey) external pure returns (address keyAddr);", + "visibility": "external", + "mutability": "pure", + "signature": "addr(uint256)", + "selector": "0xffa18649", + "selectorBytes": [ + 255, + 161, + 134, + 73 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "allowCheatcodes", + "description": "In forking mode, explicitly grant the given address cheatcode access.", + "declaration": "function allowCheatcodes(address account) external;", + "visibility": "external", + "mutability": "", + "signature": "allowCheatcodes(address)", + "selector": "0xea060291", + "selectorBytes": [ + 234, + 6, + 2, + 145 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "assertApproxEqAbsDecimal_0", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256)", + "selector": "0x045c55ce", + "selectorBytes": [ + 4, + 92, + 85, + 206 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbsDecimal_1", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(uint256,uint256,uint256,uint256,string)", + "selector": "0x60429eb2", + "selectorBytes": [ + 96, + 66, + 158, + 178 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbsDecimal_2", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256)", + "selector": "0x3d5bc8bc", + "selectorBytes": [ + 61, + 91, + 200, + 188 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbsDecimal_3", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbsDecimal(int256,int256,uint256,uint256,string)", + "selector": "0x6a5066d4", + "selectorBytes": [ + 106, + 80, + 102, + 212 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbs_0", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.", + "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbs(uint256,uint256,uint256)", + "selector": "0x16d207c6", + "selectorBytes": [ + 22, + 210, + 7, + 198 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbs_1", + "description": "Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbs(uint256,uint256,uint256,string)", + "selector": "0xf710b062", + "selectorBytes": [ + 247, + 16, + 176, + 98 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbs_2", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.", + "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbs(int256,int256,uint256)", + "selector": "0x240f839d", + "selectorBytes": [ + 36, + 15, + 131, + 157 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqAbs_3", + "description": "Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`.\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqAbs(int256,int256,uint256,string)", + "selector": "0x8289e621", + "selectorBytes": [ + 130, + 137, + 230, + 33 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRelDecimal_0", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256)", + "selector": "0x21ed2977", + "selectorBytes": [ + 33, + 237, + 41, + 119 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRelDecimal_1", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRelDecimal(uint256,uint256,uint256,uint256,string)", + "selector": "0x82d6c8fd", + "selectorBytes": [ + 130, + 214, + 200, + 253 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRelDecimal_2", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message.", + "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256)", + "selector": "0xabbf21cc", + "selectorBytes": [ + 171, + 191, + 33, + 204 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRelDecimal_3", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRelDecimal(int256,int256,uint256,uint256,string)", + "selector": "0xfccc11c4", + "selectorBytes": [ + 252, + 204, + 17, + 196 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRel_0", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%", + "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRel(uint256,uint256,uint256)", + "selector": "0x8cf25ef4", + "selectorBytes": [ + 140, + 242, + 94, + 244 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRel_1", + "description": "Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRel(uint256,uint256,uint256,string)", + "selector": "0x1ecb7d33", + "selectorBytes": [ + 30, + 203, + 125, + 51 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRel_2", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%", + "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRel(int256,int256,uint256)", + "selector": "0xfea2d14f", + "selectorBytes": [ + 254, + 162, + 209, + 79 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertApproxEqRel_3", + "description": "Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`.\n`maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100%\nIncludes error message into revert string on failure.", + "declaration": "function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertApproxEqRel(int256,int256,uint256,string)", + "selector": "0xef277d72", + "selectorBytes": [ + 239, + 39, + 125, + 114 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEqDecimal_0", + "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.", + "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEqDecimal(uint256,uint256,uint256)", + "selector": "0x27af7d9c", + "selectorBytes": [ + 39, + 175, + 125, + 156 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEqDecimal_1", + "description": "Asserts that two `uint256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEqDecimal(uint256,uint256,uint256,string)", + "selector": "0xd0cbbdef", + "selectorBytes": [ + 208, + 203, + 189, + 239 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEqDecimal_2", + "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.", + "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEqDecimal(int256,int256,uint256)", + "selector": "0x48016c04", + "selectorBytes": [ + 72, + 1, + 108, + 4 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEqDecimal_3", + "description": "Asserts that two `int256` values are equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEqDecimal(int256,int256,uint256,string)", + "selector": "0x7e77b0c5", + "selectorBytes": [ + 126, + 119, + 176, + 197 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_0", + "description": "Asserts that two `bool` values are equal.", + "declaration": "function assertEq(bool left, bool right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bool,bool)", + "selector": "0xf7fe3477", + "selectorBytes": [ + 247, + 254, + 52, + 119 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_1", + "description": "Asserts that two `bool` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bool left, bool right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bool,bool,string)", + "selector": "0x4db19e7e", + "selectorBytes": [ + 77, + 177, + 158, + 126 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_10", + "description": "Asserts that two `string` values are equal.", + "declaration": "function assertEq(string calldata left, string calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(string,string)", + "selector": "0xf320d963", + "selectorBytes": [ + 243, + 32, + 217, + 99 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_11", + "description": "Asserts that two `string` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(string calldata left, string calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(string,string,string)", + "selector": "0x36f656d8", + "selectorBytes": [ + 54, + 246, + 86, + 216 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_12", + "description": "Asserts that two `bytes` values are equal.", + "declaration": "function assertEq(bytes calldata left, bytes calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes,bytes)", + "selector": "0x97624631", + "selectorBytes": [ + 151, + 98, + 70, + 49 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_13", + "description": "Asserts that two `bytes` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes,bytes,string)", + "selector": "0xe24fed00", + "selectorBytes": [ + 226, + 79, + 237, + 0 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_14", + "description": "Asserts that two arrays of `bool` values are equal.", + "declaration": "function assertEq(bool[] calldata left, bool[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bool[],bool[])", + "selector": "0x707df785", + "selectorBytes": [ + 112, + 125, + 247, + 133 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_15", + "description": "Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bool[],bool[],string)", + "selector": "0xe48a8f8d", + "selectorBytes": [ + 228, + 138, + 143, + 141 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_16", + "description": "Asserts that two arrays of `uint256 values are equal.", + "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(uint256[],uint256[])", + "selector": "0x975d5a12", + "selectorBytes": [ + 151, + 93, + 90, + 18 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_17", + "description": "Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(uint256[],uint256[],string)", + "selector": "0x5d18c73a", + "selectorBytes": [ + 93, + 24, + 199, + 58 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_18", + "description": "Asserts that two arrays of `int256` values are equal.", + "declaration": "function assertEq(int256[] calldata left, int256[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(int256[],int256[])", + "selector": "0x711043ac", + "selectorBytes": [ + 113, + 16, + 67, + 172 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_19", + "description": "Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(int256[],int256[],string)", + "selector": "0x191f1b30", + "selectorBytes": [ + 25, + 31, + 27, + 48 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_2", + "description": "Asserts that two `uint256` values are equal.", + "declaration": "function assertEq(uint256 left, uint256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(uint256,uint256)", + "selector": "0x98296c54", + "selectorBytes": [ + 152, + 41, + 108, + 84 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_20", + "description": "Asserts that two arrays of `address` values are equal.", + "declaration": "function assertEq(address[] calldata left, address[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(address[],address[])", + "selector": "0x3868ac34", + "selectorBytes": [ + 56, + 104, + 172, + 52 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_21", + "description": "Asserts that two arrays of `address` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(address[],address[],string)", + "selector": "0x3e9173c5", + "selectorBytes": [ + 62, + 145, + 115, + 197 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_22", + "description": "Asserts that two arrays of `bytes32` values are equal.", + "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes32[],bytes32[])", + "selector": "0x0cc9ee84", + "selectorBytes": [ + 12, + 201, + 238, + 132 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_23", + "description": "Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes32[],bytes32[],string)", + "selector": "0xe03e9177", + "selectorBytes": [ + 224, + 62, + 145, + 119 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_24", + "description": "Asserts that two arrays of `string` values are equal.", + "declaration": "function assertEq(string[] calldata left, string[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(string[],string[])", + "selector": "0xcf1c049c", + "selectorBytes": [ + 207, + 28, + 4, + 156 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_25", + "description": "Asserts that two arrays of `string` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(string[],string[],string)", + "selector": "0xeff6b27d", + "selectorBytes": [ + 239, + 246, + 178, + 125 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_26", + "description": "Asserts that two arrays of `bytes` values are equal.", + "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes[],bytes[])", + "selector": "0xe5fb9b4a", + "selectorBytes": [ + 229, + 251, + 155, + 74 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_27", + "description": "Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes[],bytes[],string)", + "selector": "0xf413f0b6", + "selectorBytes": [ + 244, + 19, + 240, + 182 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_3", + "description": "Asserts that two `uint256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(uint256 left, uint256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(uint256,uint256,string)", + "selector": "0x88b44c85", + "selectorBytes": [ + 136, + 180, + 76, + 133 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_4", + "description": "Asserts that two `int256` values are equal.", + "declaration": "function assertEq(int256 left, int256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(int256,int256)", + "selector": "0xfe74f05b", + "selectorBytes": [ + 254, + 116, + 240, + 91 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_5", + "description": "Asserts that two `int256` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(int256 left, int256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(int256,int256,string)", + "selector": "0x714a2f13", + "selectorBytes": [ + 113, + 74, + 47, + 19 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_6", + "description": "Asserts that two `address` values are equal.", + "declaration": "function assertEq(address left, address right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(address,address)", + "selector": "0x515361f6", + "selectorBytes": [ + 81, + 83, + 97, + 246 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_7", + "description": "Asserts that two `address` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(address left, address right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(address,address,string)", + "selector": "0x2f2769d1", + "selectorBytes": [ + 47, + 39, + 105, + 209 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_8", + "description": "Asserts that two `bytes32` values are equal.", + "declaration": "function assertEq(bytes32 left, bytes32 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes32,bytes32)", + "selector": "0x7c84c69b", + "selectorBytes": [ + 124, + 132, + 198, + 155 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertEq_9", + "description": "Asserts that two `bytes32` values are equal and includes error message into revert string on failure.", + "declaration": "function assertEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertEq(bytes32,bytes32,string)", + "selector": "0xc1fa1ed0", + "selectorBytes": [ + 193, + 250, + 30, + 208 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertFalse_0", + "description": "Asserts that the given condition is false.", + "declaration": "function assertFalse(bool condition) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertFalse(bool)", + "selector": "0xa5982885", + "selectorBytes": [ + 165, + 152, + 40, + 133 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertFalse_1", + "description": "Asserts that the given condition is false and includes error message into revert string on failure.", + "declaration": "function assertFalse(bool condition, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertFalse(bool,string)", + "selector": "0x7ba04809", + "selectorBytes": [ + 123, + 160, + 72, + 9 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGeDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGeDecimal(uint256,uint256,uint256)", + "selector": "0x3d1fe08a", + "selectorBytes": [ + 61, + 31, + 224, + 138 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGeDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGeDecimal(uint256,uint256,uint256,string)", + "selector": "0x8bff9133", + "selectorBytes": [ + 139, + 255, + 145, + 51 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGeDecimal_2", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGeDecimal(int256,int256,uint256)", + "selector": "0xdc28c0f1", + "selectorBytes": [ + 220, + 40, + 192, + 241 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGeDecimal_3", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGeDecimal(int256,int256,uint256,string)", + "selector": "0x5df93c9b", + "selectorBytes": [ + 93, + 249, + 60, + 155 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGe_0", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.", + "declaration": "function assertGe(uint256 left, uint256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGe(uint256,uint256)", + "selector": "0xa8d4d1d9", + "selectorBytes": [ + 168, + 212, + 209, + 217 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGe_1", + "description": "Compares two `uint256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGe(uint256 left, uint256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGe(uint256,uint256,string)", + "selector": "0xe25242c0", + "selectorBytes": [ + 226, + 82, + 66, + 192 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGe_2", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.", + "declaration": "function assertGe(int256 left, int256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGe(int256,int256)", + "selector": "0x0a30b771", + "selectorBytes": [ + 10, + 48, + 183, + 113 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGe_3", + "description": "Compares two `int256` values. Expects first value to be greater than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGe(int256 left, int256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGe(int256,int256,string)", + "selector": "0xa84328dd", + "selectorBytes": [ + 168, + 67, + 40, + 221 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGtDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message.", + "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGtDecimal(uint256,uint256,uint256)", + "selector": "0xeccd2437", + "selectorBytes": [ + 236, + 205, + 36, + 55 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGtDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGtDecimal(uint256,uint256,uint256,string)", + "selector": "0x64949a8d", + "selectorBytes": [ + 100, + 148, + 154, + 141 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGtDecimal_2", + "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message.", + "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGtDecimal(int256,int256,uint256)", + "selector": "0x78611f0e", + "selectorBytes": [ + 120, + 97, + 31, + 14 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGtDecimal_3", + "description": "Compares two `int256` values. Expects first value to be greater than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGtDecimal(int256,int256,uint256,string)", + "selector": "0x04a5c7ab", + "selectorBytes": [ + 4, + 165, + 199, + 171 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGt_0", + "description": "Compares two `uint256` values. Expects first value to be greater than second.", + "declaration": "function assertGt(uint256 left, uint256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGt(uint256,uint256)", + "selector": "0xdb07fcd2", + "selectorBytes": [ + 219, + 7, + 252, + 210 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGt_1", + "description": "Compares two `uint256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGt(uint256 left, uint256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGt(uint256,uint256,string)", + "selector": "0xd9a3c4d2", + "selectorBytes": [ + 217, + 163, + 196, + 210 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGt_2", + "description": "Compares two `int256` values. Expects first value to be greater than second.", + "declaration": "function assertGt(int256 left, int256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGt(int256,int256)", + "selector": "0x5a362d45", + "selectorBytes": [ + 90, + 54, + 45, + 69 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertGt_3", + "description": "Compares two `int256` values. Expects first value to be greater than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertGt(int256 left, int256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertGt(int256,int256,string)", + "selector": "0xf8d33b9b", + "selectorBytes": [ + 248, + 211, + 59, + 155 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLeDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLeDecimal(uint256,uint256,uint256)", + "selector": "0xc304aab7", + "selectorBytes": [ + 195, + 4, + 170, + 183 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLeDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLeDecimal(uint256,uint256,uint256,string)", + "selector": "0x7fefbbe0", + "selectorBytes": [ + 127, + 239, + 187, + 224 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLeDecimal_2", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message.", + "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLeDecimal(int256,int256,uint256)", + "selector": "0x11d1364a", + "selectorBytes": [ + 17, + 209, + 54, + 74 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLeDecimal_3", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLeDecimal(int256,int256,uint256,string)", + "selector": "0xaa5cf788", + "selectorBytes": [ + 170, + 92, + 247, + 136 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLe_0", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.", + "declaration": "function assertLe(uint256 left, uint256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLe(uint256,uint256)", + "selector": "0x8466f415", + "selectorBytes": [ + 132, + 102, + 244, + 21 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLe_1", + "description": "Compares two `uint256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLe(uint256 left, uint256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLe(uint256,uint256,string)", + "selector": "0xd17d4b0d", + "selectorBytes": [ + 209, + 125, + 75, + 13 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLe_2", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.", + "declaration": "function assertLe(int256 left, int256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLe(int256,int256)", + "selector": "0x95fd154e", + "selectorBytes": [ + 149, + 253, + 21, + 78 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLe_3", + "description": "Compares two `int256` values. Expects first value to be less than or equal to second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLe(int256 left, int256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLe(int256,int256,string)", + "selector": "0x4dfe692c", + "selectorBytes": [ + 77, + 254, + 105, + 44 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLtDecimal_0", + "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message.", + "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLtDecimal(uint256,uint256,uint256)", + "selector": "0x2077337e", + "selectorBytes": [ + 32, + 119, + 51, + 126 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLtDecimal_1", + "description": "Compares two `uint256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLtDecimal(uint256,uint256,uint256,string)", + "selector": "0xa972d037", + "selectorBytes": [ + 169, + 114, + 208, + 55 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLtDecimal_2", + "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message.", + "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLtDecimal(int256,int256,uint256)", + "selector": "0xdbe8d88b", + "selectorBytes": [ + 219, + 232, + 216, + 139 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLtDecimal_3", + "description": "Compares two `int256` values. Expects first value to be less than second.\nFormats values with decimals in failure message. Includes error message into revert string on failure.", + "declaration": "function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLtDecimal(int256,int256,uint256,string)", + "selector": "0x40f0b4e0", + "selectorBytes": [ + 64, + 240, + 180, + 224 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLt_0", + "description": "Compares two `uint256` values. Expects first value to be less than second.", + "declaration": "function assertLt(uint256 left, uint256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLt(uint256,uint256)", + "selector": "0xb12fc005", + "selectorBytes": [ + 177, + 47, + 192, + 5 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLt_1", + "description": "Compares two `uint256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLt(uint256 left, uint256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLt(uint256,uint256,string)", + "selector": "0x65d5c135", + "selectorBytes": [ + 101, + 213, + 193, + 53 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLt_2", + "description": "Compares two `int256` values. Expects first value to be less than second.", + "declaration": "function assertLt(int256 left, int256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLt(int256,int256)", + "selector": "0x3e914080", + "selectorBytes": [ + 62, + 145, + 64, + 128 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertLt_3", + "description": "Compares two `int256` values. Expects first value to be less than second.\nIncludes error message into revert string on failure.", + "declaration": "function assertLt(int256 left, int256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertLt(int256,int256,string)", + "selector": "0x9ff531e3", + "selectorBytes": [ + 159, + 245, + 49, + 227 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEqDecimal_0", + "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.", + "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEqDecimal(uint256,uint256,uint256)", + "selector": "0x669efca7", + "selectorBytes": [ + 102, + 158, + 252, + 167 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEqDecimal_1", + "description": "Asserts that two `uint256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEqDecimal(uint256,uint256,uint256,string)", + "selector": "0xf5a55558", + "selectorBytes": [ + 245, + 165, + 85, + 88 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEqDecimal_2", + "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.", + "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEqDecimal(int256,int256,uint256)", + "selector": "0x14e75680", + "selectorBytes": [ + 20, + 231, + 86, + 128 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEqDecimal_3", + "description": "Asserts that two `int256` values are not equal, formatting them with decimals in failure message.\nIncludes error message into revert string on failure.", + "declaration": "function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEqDecimal(int256,int256,uint256,string)", + "selector": "0x33949f0b", + "selectorBytes": [ + 51, + 148, + 159, + 11 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_0", + "description": "Asserts that two `bool` values are not equal.", + "declaration": "function assertNotEq(bool left, bool right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bool,bool)", + "selector": "0x236e4d66", + "selectorBytes": [ + 35, + 110, + 77, + 102 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_1", + "description": "Asserts that two `bool` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bool left, bool right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bool,bool,string)", + "selector": "0x1091a261", + "selectorBytes": [ + 16, + 145, + 162, + 97 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_10", + "description": "Asserts that two `string` values are not equal.", + "declaration": "function assertNotEq(string calldata left, string calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(string,string)", + "selector": "0x6a8237b3", + "selectorBytes": [ + 106, + 130, + 55, + 179 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_11", + "description": "Asserts that two `string` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(string calldata left, string calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(string,string,string)", + "selector": "0x78bdcea7", + "selectorBytes": [ + 120, + 189, + 206, + 167 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_12", + "description": "Asserts that two `bytes` values are not equal.", + "declaration": "function assertNotEq(bytes calldata left, bytes calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes,bytes)", + "selector": "0x3cf78e28", + "selectorBytes": [ + 60, + 247, + 142, + 40 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_13", + "description": "Asserts that two `bytes` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes,bytes,string)", + "selector": "0x9507540e", + "selectorBytes": [ + 149, + 7, + 84, + 14 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_14", + "description": "Asserts that two arrays of `bool` values are not equal.", + "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bool[],bool[])", + "selector": "0x286fafea", + "selectorBytes": [ + 40, + 111, + 175, + 234 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_15", + "description": "Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bool[],bool[],string)", + "selector": "0x62c6f9fb", + "selectorBytes": [ + 98, + 198, + 249, + 251 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_16", + "description": "Asserts that two arrays of `uint256` values are not equal.", + "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(uint256[],uint256[])", + "selector": "0x56f29cba", + "selectorBytes": [ + 86, + 242, + 156, + 186 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_17", + "description": "Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(uint256[],uint256[],string)", + "selector": "0x9a7fbd8f", + "selectorBytes": [ + 154, + 127, + 189, + 143 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_18", + "description": "Asserts that two arrays of `int256` values are not equal.", + "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(int256[],int256[])", + "selector": "0x0b72f4ef", + "selectorBytes": [ + 11, + 114, + 244, + 239 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_19", + "description": "Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(int256[],int256[],string)", + "selector": "0xd3977322", + "selectorBytes": [ + 211, + 151, + 115, + 34 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_2", + "description": "Asserts that two `uint256` values are not equal.", + "declaration": "function assertNotEq(uint256 left, uint256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(uint256,uint256)", + "selector": "0xb7909320", + "selectorBytes": [ + 183, + 144, + 147, + 32 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_20", + "description": "Asserts that two arrays of `address` values are not equal.", + "declaration": "function assertNotEq(address[] calldata left, address[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(address[],address[])", + "selector": "0x46d0b252", + "selectorBytes": [ + 70, + 208, + 178, + 82 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_21", + "description": "Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(address[],address[],string)", + "selector": "0x72c7e0b5", + "selectorBytes": [ + 114, + 199, + 224, + 181 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_22", + "description": "Asserts that two arrays of `bytes32` values are not equal.", + "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes32[],bytes32[])", + "selector": "0x0603ea68", + "selectorBytes": [ + 6, + 3, + 234, + 104 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_23", + "description": "Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes32[],bytes32[],string)", + "selector": "0xb873634c", + "selectorBytes": [ + 184, + 115, + 99, + 76 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_24", + "description": "Asserts that two arrays of `string` values are not equal.", + "declaration": "function assertNotEq(string[] calldata left, string[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(string[],string[])", + "selector": "0xbdfacbe8", + "selectorBytes": [ + 189, + 250, + 203, + 232 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_25", + "description": "Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(string[],string[],string)", + "selector": "0xb67187f3", + "selectorBytes": [ + 182, + 113, + 135, + 243 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_26", + "description": "Asserts that two arrays of `bytes` values are not equal.", + "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes[],bytes[])", + "selector": "0xedecd035", + "selectorBytes": [ + 237, + 236, + 208, + 53 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_27", + "description": "Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes[],bytes[],string)", + "selector": "0x1dcd1f68", + "selectorBytes": [ + 29, + 205, + 31, + 104 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_3", + "description": "Asserts that two `uint256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(uint256 left, uint256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(uint256,uint256,string)", + "selector": "0x98f9bdbd", + "selectorBytes": [ + 152, + 249, + 189, + 189 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_4", + "description": "Asserts that two `int256` values are not equal.", + "declaration": "function assertNotEq(int256 left, int256 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(int256,int256)", + "selector": "0xf4c004e3", + "selectorBytes": [ + 244, + 192, + 4, + 227 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_5", + "description": "Asserts that two `int256` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(int256 left, int256 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(int256,int256,string)", + "selector": "0x4724c5b9", + "selectorBytes": [ + 71, + 36, + 197, + 185 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_6", + "description": "Asserts that two `address` values are not equal.", + "declaration": "function assertNotEq(address left, address right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(address,address)", + "selector": "0xb12e1694", + "selectorBytes": [ + 177, + 46, + 22, + 148 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_7", + "description": "Asserts that two `address` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(address left, address right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(address,address,string)", + "selector": "0x8775a591", + "selectorBytes": [ + 135, + 117, + 165, + 145 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_8", + "description": "Asserts that two `bytes32` values are not equal.", + "declaration": "function assertNotEq(bytes32 left, bytes32 right) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes32,bytes32)", + "selector": "0x898e83fc", + "selectorBytes": [ + 137, + 142, + 131, + 252 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertNotEq_9", + "description": "Asserts that two `bytes32` values are not equal and includes error message into revert string on failure.", + "declaration": "function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertNotEq(bytes32,bytes32,string)", + "selector": "0xb2332f51", + "selectorBytes": [ + 178, + 51, + 47, + 81 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertTrue_0", + "description": "Asserts that the given condition is true.", + "declaration": "function assertTrue(bool condition) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertTrue(bool)", + "selector": "0x0c9fd581", + "selectorBytes": [ + 12, + 159, + 213, + 129 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assertTrue_1", + "description": "Asserts that the given condition is true and includes error message into revert string on failure.", + "declaration": "function assertTrue(bool condition, string calldata error) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assertTrue(bool,string)", + "selector": "0xa34edc03", + "selectorBytes": [ + 163, + 78, + 220, + 3 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assume", + "description": "If the condition is false, discard this run's fuzz inputs and generate new ones.", + "declaration": "function assume(bool condition) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assume(bool)", + "selector": "0x4c63e562", + "selectorBytes": [ + 76, + 99, + 229, + 98 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assumeNoRevert_0", + "description": "Discard this run's fuzz inputs and generate new ones if next call reverted.", + "declaration": "function assumeNoRevert() external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assumeNoRevert()", + "selector": "0x285b366a", + "selectorBytes": [ + 40, + 91, + 54, + 106 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assumeNoRevert_1", + "description": "Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters.", + "declaration": "function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assumeNoRevert((address,bool,bytes))", + "selector": "0xd8591eeb", + "selectorBytes": [ + 216, + 89, + 30, + 235 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "assumeNoRevert_2", + "description": "Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters.", + "declaration": "function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assumeNoRevert((address,bool,bytes)[])", + "selector": "0x8a4592cc", + "selectorBytes": [ + 138, + 69, + 146, + 204 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "attachDelegation", + "description": "Designate the next call as an EIP-7702 transaction", + "declaration": "function attachDelegation(SignedDelegation calldata signedDelegation) external;", + "visibility": "external", + "mutability": "", + "signature": "attachDelegation((uint8,bytes32,bytes32,uint64,address))", + "selector": "0x14ae3519", + "selectorBytes": [ + 20, + 174, + 53, + 25 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "blobBaseFee", + "description": "Sets `block.blobbasefee`", + "declaration": "function blobBaseFee(uint256 newBlobBaseFee) external;", + "visibility": "external", + "mutability": "", + "signature": "blobBaseFee(uint256)", + "selector": "0x6d315d7e", + "selectorBytes": [ + 109, + 49, + 93, + 126 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "blobhashes", + "description": "Sets the blobhashes in the transaction.\nNot available on EVM versions before Cancun.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function blobhashes(bytes32[] calldata hashes) external;", + "visibility": "external", + "mutability": "", + "signature": "blobhashes(bytes32[])", + "selector": "0x129de7eb", + "selectorBytes": [ + 18, + 157, + 231, + 235 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "breakpoint_0", + "description": "Writes a breakpoint to jump to in the debugger.", + "declaration": "function breakpoint(string calldata char) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "breakpoint(string)", + "selector": "0xf0259e92", + "selectorBytes": [ + 240, + 37, + 158, + 146 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "breakpoint_1", + "description": "Writes a conditional breakpoint to jump to in the debugger.", + "declaration": "function breakpoint(string calldata char, bool value) external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "breakpoint(string,bool)", + "selector": "0xf7d39a8d", + "selectorBytes": [ + 247, + 211, + 154, + 141 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcastRawTransaction", + "description": "Takes a signed transaction and broadcasts it to the network.", + "declaration": "function broadcastRawTransaction(bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "broadcastRawTransaction(bytes)", + "selector": "0x8c0c72e0", + "selectorBytes": [ + 140, + 12, + 114, + 224 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcast_0", + "description": "Has the next call (at this call depth only) create transactions that can later be signed and sent onchain.\nBroadcasting address is determined by checking the following in order:\n1. If `--sender` argument was provided, that address is used.\n2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.\n3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.", + "declaration": "function broadcast() external;", + "visibility": "external", + "mutability": "", + "signature": "broadcast()", + "selector": "0xafc98040", + "selectorBytes": [ + 175, + 201, + 128, + 64 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcast_1", + "description": "Has the next call (at this call depth only) create a transaction with the address provided\nas the sender that can later be signed and sent onchain.", + "declaration": "function broadcast(address signer) external;", + "visibility": "external", + "mutability": "", + "signature": "broadcast(address)", + "selector": "0xe6962cdb", + "selectorBytes": [ + 230, + 150, + 44, + 219 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "broadcast_2", + "description": "Has the next call (at this call depth only) create a transaction with the private key\nprovided as the sender that can later be signed and sent onchain.", + "declaration": "function broadcast(uint256 privateKey) external;", + "visibility": "external", + "mutability": "", + "signature": "broadcast(uint256)", + "selector": "0xf67a965b", + "selectorBytes": [ + 246, + 122, + 150, + 91 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "chainId", + "description": "Sets `block.chainid`.", + "declaration": "function chainId(uint256 newChainId) external;", + "visibility": "external", + "mutability": "", + "signature": "chainId(uint256)", + "selector": "0x4049ddd2", + "selectorBytes": [ + 64, + 73, + 221, + 210 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "clearMockedCalls", + "description": "Clears all mocked calls.", + "declaration": "function clearMockedCalls() external;", + "visibility": "external", + "mutability": "", + "signature": "clearMockedCalls()", + "selector": "0x3fdf4e15", + "selectorBytes": [ + 63, + 223, + 78, + 21 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "cloneAccount", + "description": "Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state.", + "declaration": "function cloneAccount(address source, address target) external;", + "visibility": "external", + "mutability": "", + "signature": "cloneAccount(address,address)", + "selector": "0x533d61c9", + "selectorBytes": [ + 83, + 61, + 97, + 201 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "closeFile", + "description": "Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.\n`path` is relative to the project root.", + "declaration": "function closeFile(string calldata path) external;", + "visibility": "external", + "mutability": "", + "signature": "closeFile(string)", + "selector": "0x48c3241f", + "selectorBytes": [ + 72, + 195, + 36, + 31 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "coinbase", + "description": "Sets `block.coinbase`.", + "declaration": "function coinbase(address newCoinbase) external;", + "visibility": "external", + "mutability": "", + "signature": "coinbase(address)", + "selector": "0xff483c54", + "selectorBytes": [ + 255, + 72, + 60, + 84 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "computeCreate2Address_0", + "description": "Compute the address of a contract created with CREATE2 using the given CREATE2 deployer.", + "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "computeCreate2Address(bytes32,bytes32,address)", + "selector": "0xd323826a", + "selectorBytes": [ + 211, + 35, + 130, + 106 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "computeCreate2Address_1", + "description": "Compute the address of a contract created with CREATE2 using the default CREATE2 deployer.", + "declaration": "function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "computeCreate2Address(bytes32,bytes32)", + "selector": "0x890c283b", + "selectorBytes": [ + 137, + 12, + 40, + 59 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "computeCreateAddress", + "description": "Compute the address a contract will be deployed at for a given deployer address and nonce.", + "declaration": "function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "computeCreateAddress(address,uint256)", + "selector": "0x74637a7a", + "selectorBytes": [ + 116, + 99, + 122, + 122 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "contains", + "description": "Returns true if `search` is found in `subject`, false otherwise.", + "declaration": "function contains(string calldata subject, string calldata search) external returns (bool result);", + "visibility": "external", + "mutability": "", + "signature": "contains(string,string)", + "selector": "0x3fb18aec", + "selectorBytes": [ + 63, + 177, + 138, + 236 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "cool", + "description": "Marks the slots of an account and the account address as cold.", + "declaration": "function cool(address target) external;", + "visibility": "external", + "mutability": "", + "signature": "cool(address)", + "selector": "0x40ff9f21", + "selectorBytes": [ + 64, + 255, + 159, + 33 + ] + }, + "group": "evm", + "status": "experimental", + "safety": "unsafe" + }, + { + "func": { + "id": "copyFile", + "description": "Copies the contents of one file to another. This function will **overwrite** the contents of `to`.\nOn success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`.\nBoth `from` and `to` are relative to the project root.", + "declaration": "function copyFile(string calldata from, string calldata to) external returns (uint64 copied);", + "visibility": "external", + "mutability": "", + "signature": "copyFile(string,string)", + "selector": "0xa54a87d8", + "selectorBytes": [ + 165, + 74, + 135, + 216 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "copyStorage", + "description": "Utility cheatcode to copy storage of `from` contract to another `to` contract.", + "declaration": "function copyStorage(address from, address to) external;", + "visibility": "external", + "mutability": "", + "signature": "copyStorage(address,address)", + "selector": "0x203dac0d", + "selectorBytes": [ + 32, + 61, + 172, + 13 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createDir", + "description": "Creates a new, empty directory at the provided path.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- User lacks permissions to modify `path`.\n- A parent of the given path doesn't exist and `recursive` is false.\n- `path` already exists and `recursive` is false.\n`path` is relative to the project root.", + "declaration": "function createDir(string calldata path, bool recursive) external;", + "visibility": "external", + "mutability": "", + "signature": "createDir(string,bool)", + "selector": "0x168b64d3", + "selectorBytes": [ + 22, + 139, + 100, + 211 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createFork_0", + "description": "Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork.", + "declaration": "function createFork(string calldata urlOrAlias) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createFork(string)", + "selector": "0x31ba3498", + "selectorBytes": [ + 49, + 186, + 52, + 152 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createFork_1", + "description": "Creates a new fork with the given endpoint and block and returns the identifier of the fork.", + "declaration": "function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createFork(string,uint256)", + "selector": "0x6ba3ba2b", + "selectorBytes": [ + 107, + 163, + 186, + 43 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createFork_2", + "description": "Creates a new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, and returns the identifier of the fork.", + "declaration": "function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createFork(string,bytes32)", + "selector": "0x7ca29682", + "selectorBytes": [ + 124, + 162, + 150, + 130 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createSelectFork_0", + "description": "Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork.", + "declaration": "function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createSelectFork(string)", + "selector": "0x98680034", + "selectorBytes": [ + 152, + 104, + 0, + 52 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createSelectFork_1", + "description": "Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork.", + "declaration": "function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createSelectFork(string,uint256)", + "selector": "0x71ee464d", + "selectorBytes": [ + 113, + 238, + 70, + 77 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createSelectFork_2", + "description": "Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in,\nreplays all transaction mined in the block before the transaction, returns the identifier of the fork.", + "declaration": "function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId);", + "visibility": "external", + "mutability": "", + "signature": "createSelectFork(string,bytes32)", + "selector": "0x84d52b7a", + "selectorBytes": [ + 132, + 213, + 43, + 122 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "createWallet_0", + "description": "Derives a private key from the name, labels the account with that name, and returns the wallet.", + "declaration": "function createWallet(string calldata walletLabel) external returns (Wallet memory wallet);", + "visibility": "external", + "mutability": "", + "signature": "createWallet(string)", + "selector": "0x7404f1d2", + "selectorBytes": [ + 116, + 4, + 241, + 210 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createWallet_1", + "description": "Generates a wallet from the private key and returns the wallet.", + "declaration": "function createWallet(uint256 privateKey) external returns (Wallet memory wallet);", + "visibility": "external", + "mutability": "", + "signature": "createWallet(uint256)", + "selector": "0x7a675bb6", + "selectorBytes": [ + 122, + 103, + 91, + 182 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "createWallet_2", + "description": "Generates a wallet from the private key, labels the account with that name, and returns the wallet.", + "declaration": "function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet);", + "visibility": "external", + "mutability": "", + "signature": "createWallet(uint256,string)", + "selector": "0xed7c5462", + "selectorBytes": [ + 237, + 124, + 84, + 98 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deal", + "description": "Sets an address' balance.", + "declaration": "function deal(address account, uint256 newBalance) external;", + "visibility": "external", + "mutability": "", + "signature": "deal(address,uint256)", + "selector": "0xc88a5e6d", + "selectorBytes": [ + 200, + 138, + 94, + 109 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "deleteSnapshot", + "description": "`deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions.", + "declaration": "function deleteSnapshot(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "deleteSnapshot(uint256)", + "selector": "0xa6368557", + "selectorBytes": [ + 166, + 54, + 133, + 87 + ] + }, + "group": "evm", + "status": { + "deprecated": "replaced by `deleteStateSnapshot`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "deleteSnapshots", + "description": "`deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions.", + "declaration": "function deleteSnapshots() external;", + "visibility": "external", + "mutability": "", + "signature": "deleteSnapshots()", + "selector": "0x421ae469", + "selectorBytes": [ + 66, + 26, + 228, + 105 + ] + }, + "group": "evm", + "status": { + "deprecated": "replaced by `deleteStateSnapshots`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "deleteStateSnapshot", + "description": "Removes the snapshot with the given ID created by `snapshot`.\nTakes the snapshot ID to delete.\nReturns `true` if the snapshot was successfully deleted.\nReturns `false` if the snapshot does not exist.", + "declaration": "function deleteStateSnapshot(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "deleteStateSnapshot(uint256)", + "selector": "0x08d6b37a", + "selectorBytes": [ + 8, + 214, + 179, + 122 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "deleteStateSnapshots", + "description": "Removes _all_ snapshots previously created by `snapshot`.", + "declaration": "function deleteStateSnapshots() external;", + "visibility": "external", + "mutability": "", + "signature": "deleteStateSnapshots()", + "selector": "0xe0933c74", + "selectorBytes": [ + 224, + 147, + 60, + 116 + ] + }, + "group": "evm", + "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.\nAdditionally 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", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat the derivation path `m/44'/60'/0'/0/{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,uint32)", + "selector": "0x6229498b", + "selectorBytes": [ + 98, + 41, + 73, + 139 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deriveKey_1", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path)\nat `{derivationPath}{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,string,uint32)", + "selector": "0x6bcb2c1b", + "selectorBytes": [ + 107, + 203, + 44, + 27 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deriveKey_2", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat the derivation path `m/44'/60'/0'/0/{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,uint32,string)", + "selector": "0x32c8176d", + "selectorBytes": [ + 50, + 200, + 23, + 109 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deriveKey_3", + "description": "Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language\nat `{derivationPath}{index}`.", + "declaration": "function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey);", + "visibility": "external", + "mutability": "pure", + "signature": "deriveKey(string,string,uint32,string)", + "selector": "0x29233b1f", + "selectorBytes": [ + 41, + 35, + 59, + 31 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "difficulty", + "description": "Sets `block.difficulty`.\nNot available on EVM versions from Paris onwards. Use `prevrandao` instead.\nReverts if used on unsupported EVM versions.", + "declaration": "function difficulty(uint256 newDifficulty) external;", + "visibility": "external", + "mutability": "", + "signature": "difficulty(uint256)", + "selector": "0x46cc92d9", + "selectorBytes": [ + 70, + 204, + 146, + 217 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "dumpState", + "description": "Dump a genesis JSON file's `allocs` to disk.", + "declaration": "function dumpState(string calldata pathToStateJson) external;", + "visibility": "external", + "mutability": "", + "signature": "dumpState(string)", + "selector": "0x709ecd3f", + "selectorBytes": [ + 112, + 158, + 205, + 63 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "ensNamehash", + "description": "Returns ENS namehash for provided string.", + "declaration": "function ensNamehash(string calldata name) external pure returns (bytes32);", + "visibility": "external", + "mutability": "pure", + "signature": "ensNamehash(string)", + "selector": "0x8c374c65", + "selectorBytes": [ + 140, + 55, + 76, + 101 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envAddress_0", + "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envAddress(string calldata name) external view returns (address value);", + "visibility": "external", + "mutability": "view", + "signature": "envAddress(string)", + "selector": "0x350d56bf", + "selectorBytes": [ + 53, + 13, + 86, + 191 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envAddress_1", + "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envAddress(string,string)", + "selector": "0xad31b9fa", + "selectorBytes": [ + 173, + 49, + 185, + 250 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBool_0", + "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBool(string calldata name) external view returns (bool value);", + "visibility": "external", + "mutability": "view", + "signature": "envBool(string)", + "selector": "0x7ed1ec7d", + "selectorBytes": [ + 126, + 209, + 236, + 125 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBool_1", + "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBool(string,string)", + "selector": "0xaaaddeaf", + "selectorBytes": [ + 170, + 173, + 222, + 175 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes32_0", + "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes32(string calldata name) external view returns (bytes32 value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes32(string)", + "selector": "0x97949042", + "selectorBytes": [ + 151, + 148, + 144, + 66 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes32_1", + "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes32(string,string)", + "selector": "0x5af231c1", + "selectorBytes": [ + 90, + 242, + 49, + 193 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes_0", + "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes(string calldata name) external view returns (bytes memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes(string)", + "selector": "0x4d7baf06", + "selectorBytes": [ + 77, + 123, + 175, + 6 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envBytes_1", + "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envBytes(string,string)", + "selector": "0xddc2651b", + "selectorBytes": [ + 221, + 194, + 101, + 27 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envExists", + "description": "Gets the environment variable `name` and returns true if it exists, else returns false.", + "declaration": "function envExists(string calldata name) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "envExists(string)", + "selector": "0xce8365f9", + "selectorBytes": [ + 206, + 131, + 101, + 249 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envInt_0", + "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envInt(string calldata name) external view returns (int256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envInt(string)", + "selector": "0x892a0c61", + "selectorBytes": [ + 137, + 42, + 12, + 97 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envInt_1", + "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envInt(string,string)", + "selector": "0x42181150", + "selectorBytes": [ + 66, + 24, + 17, + 80 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_0", + "description": "Gets the environment variable `name` and parses it as `bool`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, bool defaultValue) external view returns (bool value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,bool)", + "selector": "0x4777f3cf", + "selectorBytes": [ + 71, + 119, + 243, + 207 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_1", + "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,uint256)", + "selector": "0x5e97348f", + "selectorBytes": [ + 94, + 151, + 52, + 143 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_10", + "description": "Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external view returns (address[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,address[])", + "selector": "0xc74e9deb", + "selectorBytes": [ + 199, + 78, + 157, + 235 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_11", + "description": "Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external view returns (bytes32[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,bytes32[])", + "selector": "0x2281f367", + "selectorBytes": [ + 34, + 129, + 243, + 103 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_12", + "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external view returns (string[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,string[])", + "selector": "0x859216bc", + "selectorBytes": [ + 133, + 146, + 22, + 188 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_13", + "description": "Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external view returns (bytes[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,bytes[])", + "selector": "0x64bc3e64", + "selectorBytes": [ + 100, + 188, + 62, + 100 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_2", + "description": "Gets the environment variable `name` and parses it as `int256`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, int256 defaultValue) external view returns (int256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,int256)", + "selector": "0xbbcb713e", + "selectorBytes": [ + 187, + 203, + 113, + 62 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_3", + "description": "Gets the environment variable `name` and parses it as `address`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, address defaultValue) external view returns (address value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,address)", + "selector": "0x561fe540", + "selectorBytes": [ + 86, + 31, + 229, + 64 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_4", + "description": "Gets the environment variable `name` and parses it as `bytes32`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,bytes32)", + "selector": "0xb4a85892", + "selectorBytes": [ + 180, + 168, + 88, + 146 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_5", + "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string)", + "selector": "0xd145736c", + "selectorBytes": [ + 209, + 69, + 115, + 108 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_6", + "description": "Gets the environment variable `name` and parses it as `bytes`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,bytes)", + "selector": "0xb3e47705", + "selectorBytes": [ + 179, + 228, + 119, + 5 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_7", + "description": "Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external view returns (bool[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,bool[])", + "selector": "0xeb85e83b", + "selectorBytes": [ + 235, + 133, + 232, + 59 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_8", + "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external view returns (uint256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,uint256[])", + "selector": "0x74318528", + "selectorBytes": [ + 116, + 49, + 133, + 40 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envOr_9", + "description": "Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`.\nReverts if the variable could not be parsed.\nReturns `defaultValue` if the variable was not found.", + "declaration": "function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external view returns (int256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envOr(string,string,int256[])", + "selector": "0x4700d74b", + "selectorBytes": [ + 71, + 0, + 215, + 75 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envString_0", + "description": "Gets the environment variable `name` and parses it as `string`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envString(string calldata name) external view returns (string memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envString(string)", + "selector": "0xf877cb19", + "selectorBytes": [ + 248, + 119, + 203, + 25 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envString_1", + "description": "Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envString(string calldata name, string calldata delim) external view returns (string[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envString(string,string)", + "selector": "0x14b02bc9", + "selectorBytes": [ + 20, + 176, + 43, + 201 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envUint_0", + "description": "Gets the environment variable `name` and parses it as `uint256`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envUint(string calldata name) external view returns (uint256 value);", + "visibility": "external", + "mutability": "view", + "signature": "envUint(string)", + "selector": "0xc1978d1f", + "selectorBytes": [ + 193, + 151, + 141, + 31 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "envUint_1", + "description": "Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`.\nReverts if the variable was not found or could not be parsed.", + "declaration": "function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value);", + "visibility": "external", + "mutability": "view", + "signature": "envUint(string,string)", + "selector": "0xf3dec099", + "selectorBytes": [ + 243, + 222, + 192, + 153 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "etch", + "description": "Sets an address' code.", + "declaration": "function etch(address target, bytes calldata newRuntimeBytecode) external;", + "visibility": "external", + "mutability": "", + "signature": "etch(address,bytes)", + "selector": "0xb4d6c782", + "selectorBytes": [ + 180, + 214, + 199, + 130 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "eth_getLogs", + "description": "Gets all the logs according to specified filter.", + "declaration": "function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs);", + "visibility": "external", + "mutability": "", + "signature": "eth_getLogs(uint256,uint256,address,bytes32[])", + "selector": "0x35e1349b", + "selectorBytes": [ + 53, + 225, + 52, + 155 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "exists", + "description": "Returns true if the given path points to an existing entity, else returns false.", + "declaration": "function exists(string calldata path) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "exists(string)", + "selector": "0x261a323e", + "selectorBytes": [ + 38, + 26, + 50, + 62 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "expectCallMinGas_0", + "description": "Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", + "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCallMinGas(address,uint256,uint64,bytes)", + "selector": "0x08e4e116", + "selectorBytes": [ + 8, + 228, + 225, + 22 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCallMinGas_1", + "description": "Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas.", + "declaration": "function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCallMinGas(address,uint256,uint64,bytes,uint64)", + "selector": "0xe13a1834", + "selectorBytes": [ + 225, + 58, + 24, + 52 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_0", + "description": "Expects a call to an address with the specified calldata.\nCalldata can either be a strict or a partial match.", + "declaration": "function expectCall(address callee, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,bytes)", + "selector": "0xbd6af434", + "selectorBytes": [ + 189, + 106, + 244, + 52 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_1", + "description": "Expects given number of calls to an address with the specified calldata.", + "declaration": "function expectCall(address callee, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,bytes,uint64)", + "selector": "0xc1adbbff", + "selectorBytes": [ + 193, + 173, + 187, + 255 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_2", + "description": "Expects a call to an address with the specified `msg.value` and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,bytes)", + "selector": "0xf30c7ba3", + "selectorBytes": [ + 243, + 12, + 123, + 163 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_3", + "description": "Expects given number of calls to an address with the specified `msg.value` and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,bytes,uint64)", + "selector": "0xa2b1a1ae", + "selectorBytes": [ + 162, + 177, + 161, + 174 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_4", + "description": "Expect a call to an address with the specified `msg.value`, gas, and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,uint64,bytes)", + "selector": "0x23361207", + "selectorBytes": [ + 35, + 54, + 18, + 7 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectCall_5", + "description": "Expects given number of calls to an address with the specified `msg.value`, gas, and calldata.", + "declaration": "function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectCall(address,uint256,uint64,bytes,uint64)", + "selector": "0x65b7b7cc", + "selectorBytes": [ + 101, + 183, + 183, + 204 + ] + }, + "group": "testing", + "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", + "description": "Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.).\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data (as specified by the booleans).", + "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(bool,bool,bool,bool)", + "selector": "0x491cc7c2", + "selectorBytes": [ + 73, + 28, + 199, + 194 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_1", + "description": "Same as the previous method, but also checks supplied address against emitting contract.", + "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(bool,bool,bool,bool,address)", + "selector": "0x81bad6f3", + "selectorBytes": [ + 129, + 186, + 214, + 243 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_2", + "description": "Prepare an expected log with all topic and data checks enabled.\nCall this function, then emit an event, then call a function. Internally after the call, we check if\nlogs were emitted in the expected order with the expected topics and data.", + "declaration": "function expectEmit() external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit()", + "selector": "0x440ed10d", + "selectorBytes": [ + 68, + 14, + 209, + 13 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_3", + "description": "Same as the previous method, but also checks supplied address against emitting contract.", + "declaration": "function expectEmit(address emitter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(address)", + "selector": "0x86b9620d", + "selectorBytes": [ + 134, + 185, + 98, + 13 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_4", + "description": "Expect a given number of logs with the provided topics.", + "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(bool,bool,bool,bool,uint64)", + "selector": "0x5e1d1c33", + "selectorBytes": [ + 94, + 29, + 28, + 51 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_5", + "description": "Expect a given number of logs from a specific emitter with the provided topics.", + "declaration": "function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(bool,bool,bool,bool,address,uint64)", + "selector": "0xc339d02c", + "selectorBytes": [ + 195, + 57, + 208, + 44 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_6", + "description": "Expect a given number of logs with all topic and data checks enabled.", + "declaration": "function expectEmit(uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(uint64)", + "selector": "0x4c74a335", + "selectorBytes": [ + 76, + 116, + 163, + 53 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectEmit_7", + "description": "Expect a given number of logs from a specific emitter with all topic and data checks enabled.", + "declaration": "function expectEmit(address emitter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectEmit(address,uint64)", + "selector": "0xb43aece3", + "selectorBytes": [ + 180, + 58, + 236, + 227 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectPartialRevert_0", + "description": "Expects an error on next call that starts with the revert data.", + "declaration": "function expectPartialRevert(bytes4 revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectPartialRevert(bytes4)", + "selector": "0x11fb5b9c", + "selectorBytes": [ + 17, + 251, + 91, + 156 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectPartialRevert_1", + "description": "Expects an error on next call to reverter address, that starts with the revert data.", + "declaration": "function expectPartialRevert(bytes4 revertData, address reverter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectPartialRevert(bytes4,address)", + "selector": "0x51aa008a", + "selectorBytes": [ + 81, + 170, + 0, + 138 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_0", + "description": "Expects an error on next call with any revert data.", + "declaration": "function expectRevert() external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert()", + "selector": "0xf4844814", + "selectorBytes": [ + 244, + 132, + 72, + 20 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_1", + "description": "Expects an error on next call that exactly matches the revert data.", + "declaration": "function expectRevert(bytes4 revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4)", + "selector": "0xc31eb0e0", + "selectorBytes": [ + 195, + 30, + 176, + 224 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_10", + "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data.", + "declaration": "function expectRevert(bytes4 revertData, address reverter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4,address,uint64)", + "selector": "0xb0762d73", + "selectorBytes": [ + 176, + 118, + 45, + 115 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_11", + "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data.", + "declaration": "function expectRevert(bytes calldata revertData, address reverter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes,address,uint64)", + "selector": "0xd345fb1f", + "selectorBytes": [ + 211, + 69, + 251, + 31 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_2", + "description": "Expects an error on next call that exactly matches the revert data.", + "declaration": "function expectRevert(bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes)", + "selector": "0xf28dceb3", + "selectorBytes": [ + 242, + 141, + 206, + 179 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_3", + "description": "Expects an error with any revert data on next call to reverter address.", + "declaration": "function expectRevert(address reverter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(address)", + "selector": "0xd814f38a", + "selectorBytes": [ + 216, + 20, + 243, + 138 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_4", + "description": "Expects an error from reverter address on next call, with any revert data.", + "declaration": "function expectRevert(bytes4 revertData, address reverter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4,address)", + "selector": "0x260bc5de", + "selectorBytes": [ + 38, + 11, + 197, + 222 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_5", + "description": "Expects an error from reverter address on next call, that exactly matches the revert data.", + "declaration": "function expectRevert(bytes calldata revertData, address reverter) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes,address)", + "selector": "0x61ebcf12", + "selectorBytes": [ + 97, + 235, + 207, + 18 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_6", + "description": "Expects a `count` number of reverts from the upcoming calls with any revert data or reverter.", + "declaration": "function expectRevert(uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(uint64)", + "selector": "0x4ee38244", + "selectorBytes": [ + 78, + 227, + 130, + 68 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_7", + "description": "Expects a `count` number of reverts from the upcoming calls that match the revert data.", + "declaration": "function expectRevert(bytes4 revertData, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes4,uint64)", + "selector": "0xe45ca72d", + "selectorBytes": [ + 228, + 92, + 167, + 45 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_8", + "description": "Expects a `count` number of reverts from the upcoming calls that exactly match the revert data.", + "declaration": "function expectRevert(bytes calldata revertData, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(bytes,uint64)", + "selector": "0x4994c273", + "selectorBytes": [ + 73, + 148, + 194, + 115 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectRevert_9", + "description": "Expects a `count` number of reverts from the upcoming calls from the reverter address.", + "declaration": "function expectRevert(address reverter, uint64 count) external;", + "visibility": "external", + "mutability": "", + "signature": "expectRevert(address,uint64)", + "selector": "0x1ff5f952", + "selectorBytes": [ + 31, + 245, + 249, + 82 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectSafeMemory", + "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other\nmemory is written to, the test will fail. Can be called multiple times to add more ranges to the set.", + "declaration": "function expectSafeMemory(uint64 min, uint64 max) external;", + "visibility": "external", + "mutability": "", + "signature": "expectSafeMemory(uint64,uint64)", + "selector": "0x6d016688", + "selectorBytes": [ + 109, + 1, + 102, + 136 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "expectSafeMemoryCall", + "description": "Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext.\nIf any other memory is written to, the test will fail. Can be called multiple times to add more ranges\nto the set.", + "declaration": "function expectSafeMemoryCall(uint64 min, uint64 max) external;", + "visibility": "external", + "mutability": "", + "signature": "expectSafeMemoryCall(uint64,uint64)", + "selector": "0x05838bf4", + "selectorBytes": [ + 5, + 131, + 139, + 244 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "fee", + "description": "Sets `block.basefee`.", + "declaration": "function fee(uint256 newBasefee) external;", + "visibility": "external", + "mutability": "", + "signature": "fee(uint256)", + "selector": "0x39b37ab0", + "selectorBytes": [ + 57, + 179, + 122, + 176 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "ffi", + "description": "Performs a foreign function call via the terminal.", + "declaration": "function ffi(string[] calldata commandInput) external returns (bytes memory result);", + "visibility": "external", + "mutability": "", + "signature": "ffi(string[])", + "selector": "0x89160467", + "selectorBytes": [ + 137, + 22, + 4, + 103 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "foundryVersionAtLeast", + "description": "Returns true if the current Foundry version is greater than or equal to the given version.\nThe given version string must be in the format `major.minor.patch`.\nThis is equivalent to `foundryVersionCmp(version) >= 0`.", + "declaration": "function foundryVersionAtLeast(string calldata version) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "foundryVersionAtLeast(string)", + "selector": "0x6248be1f", + "selectorBytes": [ + 98, + 72, + 190, + 31 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "foundryVersionCmp", + "description": "Compares the current Foundry version with the given version string.\nThe given version string must be in the format `major.minor.patch`.\nReturns:\n-1 if current Foundry version is less than the given version\n0 if current Foundry version equals the given version\n1 if current Foundry version is greater than the given version\nThis result can then be used with a comparison operator against `0`.\nFor example, to check if the current Foundry version is greater than or equal to `1.0.0`:\n`if (foundryVersionCmp(\"1.0.0\") >= 0) { ... }`", + "declaration": "function foundryVersionCmp(string calldata version) external view returns (int256);", + "visibility": "external", + "mutability": "view", + "signature": "foundryVersionCmp(string)", + "selector": "0xca7b0a09", + "selectorBytes": [ + 202, + 123, + 10, + 9 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "fsMetadata", + "description": "Given a path, query the file system to get information about a file, directory, etc.", + "declaration": "function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata);", + "visibility": "external", + "mutability": "view", + "signature": "fsMetadata(string)", + "selector": "0xaf368a08", + "selectorBytes": [ + 175, + 54, + 138, + 8 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getArtifactPathByCode", + "description": "Gets the artifact path from code (aka. creation code).", + "declaration": "function getArtifactPathByCode(bytes calldata code) external view returns (string memory path);", + "visibility": "external", + "mutability": "view", + "signature": "getArtifactPathByCode(bytes)", + "selector": "0xeb74848c", + "selectorBytes": [ + 235, + 116, + 132, + 140 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getArtifactPathByDeployedCode", + "description": "Gets the artifact path from deployed code (aka. runtime code).", + "declaration": "function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path);", + "visibility": "external", + "mutability": "view", + "signature": "getArtifactPathByDeployedCode(bytes)", + "selector": "0x6d853ba5", + "selectorBytes": [ + 109, + 133, + 59, + 165 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBlobBaseFee", + "description": "Gets the current `block.blobbasefee`.\nYou should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlobBaseFee() external view returns (uint256 blobBaseFee);", + "visibility": "external", + "mutability": "view", + "signature": "getBlobBaseFee()", + "selector": "0x1f6d6ef7", + "selectorBytes": [ + 31, + 109, + 110, + 247 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBlobhashes", + "description": "Gets the blockhashes from the current transaction.\nNot available on EVM versions before Cancun.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function getBlobhashes() external view returns (bytes32[] memory hashes);", + "visibility": "external", + "mutability": "view", + "signature": "getBlobhashes()", + "selector": "0xf56ff18b", + "selectorBytes": [ + 245, + 111, + 241, + 139 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "getBlockNumber", + "description": "Gets the current `block.number`.\nYou should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlockNumber() external view returns (uint256 height);", + "visibility": "external", + "mutability": "view", + "signature": "getBlockNumber()", + "selector": "0x42cbb15c", + "selectorBytes": [ + 66, + 203, + 177, + 92 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBlockTimestamp", + "description": "Gets the current `block.timestamp`.\nYou should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlockTimestamp() external view returns (uint256 timestamp);", + "visibility": "external", + "mutability": "view", + "signature": "getBlockTimestamp()", + "selector": "0x796b89b9", + "selectorBytes": [ + 121, + 107, + 137, + 185 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBroadcast", + "description": "Returns the most recent broadcast for the given contract on `chainId` matching `txType`.\nFor example:\nThe most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`.\nThe most recent call can be fetched by passing `txType` as `CALL`.", + "declaration": "function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory);", + "visibility": "external", + "mutability": "view", + "signature": "getBroadcast(string,uint64,uint8)", + "selector": "0x3dc90cb3", + "selectorBytes": [ + 61, + 201, + 12, + 179 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBroadcasts_0", + "description": "Returns all broadcasts for the given contract on `chainId` with the specified `txType`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", + "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory);", + "visibility": "external", + "mutability": "view", + "signature": "getBroadcasts(string,uint64,uint8)", + "selector": "0xf7afe919", + "selectorBytes": [ + 247, + 175, + 233, + 25 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getBroadcasts_1", + "description": "Returns all broadcasts for the given contract on `chainId`.\nSorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber.", + "declaration": "function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory);", + "visibility": "external", + "mutability": "view", + "signature": "getBroadcasts(string,uint64)", + "selector": "0xf2fa4a26", + "selectorBytes": [ + 242, + 250, + 74, + 38 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getCode", + "description": "Gets the creation bytecode 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 getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);", + "visibility": "external", + "mutability": "view", + "signature": "getCode(string)", + "selector": "0x8d1cc925", + "selectorBytes": [ + 141, + 28, + 201, + 37 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getDeployedCode", + "description": "Gets the deployed bytecode 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 getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode);", + "visibility": "external", + "mutability": "view", + "signature": "getDeployedCode(string)", + "selector": "0x3ebf73b4", + "selectorBytes": [ + 62, + 191, + 115, + 180 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getDeployment_0", + "description": "Returns the most recent deployment for the current `chainId`.", + "declaration": "function getDeployment(string calldata contractName) external view returns (address deployedAddress);", + "visibility": "external", + "mutability": "view", + "signature": "getDeployment(string)", + "selector": "0xa8091d97", + "selectorBytes": [ + 168, + 9, + 29, + 151 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getDeployment_1", + "description": "Returns the most recent deployment for the given contract on `chainId`", + "declaration": "function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress);", + "visibility": "external", + "mutability": "view", + "signature": "getDeployment(string,uint64)", + "selector": "0x0debd5d6", + "selectorBytes": [ + 13, + 235, + 213, + 214 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getDeployments", + "description": "Returns all deployments for the given contract on `chainId`\nSorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber.\nThe most recent deployment is the first element, and the oldest is the last.", + "declaration": "function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses);", + "visibility": "external", + "mutability": "view", + "signature": "getDeployments(string,uint64)", + "selector": "0x74e133dd", + "selectorBytes": [ + 116, + 225, + 51, + 221 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getFoundryVersion", + "description": "Returns the Foundry version.\nFormat: -+..\nSample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug\nNote: Build timestamps may vary slightly across platforms due to separate CI jobs.\nFor reliable version comparisons, use UNIX format (e.g., >= 1700000000)\nto compare timestamps while ignoring minor time differences.", + "declaration": "function getFoundryVersion() external view returns (string memory version);", + "visibility": "external", + "mutability": "view", + "signature": "getFoundryVersion()", + "selector": "0xea991bb5", + "selectorBytes": [ + 234, + 153, + 27, + 181 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getLabel", + "description": "Gets the label for the specified address.", + "declaration": "function getLabel(address account) external view returns (string memory currentLabel);", + "visibility": "external", + "mutability": "view", + "signature": "getLabel(address)", + "selector": "0x28a249b0", + "selectorBytes": [ + 40, + 162, + 73, + 176 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getMappingKeyAndParentOf", + "description": "Gets the map key and parent of a mapping at a given slot, for a given address.", + "declaration": "function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent);", + "visibility": "external", + "mutability": "", + "signature": "getMappingKeyAndParentOf(address,bytes32)", + "selector": "0x876e24e6", + "selectorBytes": [ + 135, + 110, + 36, + 230 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getMappingLength", + "description": "Gets the number of elements in the mapping at the given slot, for a given address.", + "declaration": "function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length);", + "visibility": "external", + "mutability": "", + "signature": "getMappingLength(address,bytes32)", + "selector": "0x2f2fd63f", + "selectorBytes": [ + 47, + 47, + 214, + 63 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getMappingSlotAt", + "description": "Gets the elements at index idx of the mapping at the given slot, for a given address. The\nindex must be less than the length of the mapping (i.e. the number of keys in the mapping).", + "declaration": "function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value);", + "visibility": "external", + "mutability": "", + "signature": "getMappingSlotAt(address,bytes32,uint256)", + "selector": "0xebc73ab4", + "selectorBytes": [ + 235, + 199, + 58, + 180 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getNonce_0", + "description": "Gets the nonce of an account.", + "declaration": "function getNonce(address account) external view returns (uint64 nonce);", + "visibility": "external", + "mutability": "view", + "signature": "getNonce(address)", + "selector": "0x2d0335ab", + "selectorBytes": [ + 45, + 3, + 53, + 171 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getNonce_1", + "description": "Get the nonce of a `Wallet`.", + "declaration": "function getNonce(Wallet calldata wallet) external returns (uint64 nonce);", + "visibility": "external", + "mutability": "", + "signature": "getNonce((address,uint256,uint256,uint256))", + "selector": "0xa5748aad", + "selectorBytes": [ + 165, + 116, + 138, + 173 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getRecordedLogs", + "description": "Gets all the recorded logs.", + "declaration": "function getRecordedLogs() external returns (Log[] memory logs);", + "visibility": "external", + "mutability": "", + "signature": "getRecordedLogs()", + "selector": "0x191553a4", + "selectorBytes": [ + 25, + 21, + 83, + 164 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getStateDiff", + "description": "Returns state diffs from current `vm.startStateDiffRecording` session.", + "declaration": "function getStateDiff() external view returns (string memory diff);", + "visibility": "external", + "mutability": "view", + "signature": "getStateDiff()", + "selector": "0x80df01cc", + "selectorBytes": [ + 128, + 223, + 1, + 204 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getStateDiffJson", + "description": "Returns state diffs from current `vm.startStateDiffRecording` session, in json format.", + "declaration": "function getStateDiffJson() external view returns (string memory diff);", + "visibility": "external", + "mutability": "view", + "signature": "getStateDiffJson()", + "selector": "0xf54fe009", + "selectorBytes": [ + 245, + 79, + 224, + 9 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "getWallets", + "description": "Returns addresses of available unlocked wallets in the script environment.", + "declaration": "function getWallets() external returns (address[] memory wallets);", + "visibility": "external", + "mutability": "", + "signature": "getWallets()", + "selector": "0xdb7a4605", + "selectorBytes": [ + 219, + 122, + 70, + 5 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "indexOf", + "description": "Returns the index of the first occurrence of a `key` in an `input` string.\nReturns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found.\nReturns 0 in case of an empty `key`.", + "declaration": "function indexOf(string calldata input, string calldata key) external pure returns (uint256);", + "visibility": "external", + "mutability": "pure", + "signature": "indexOf(string,string)", + "selector": "0x8a0807b7", + "selectorBytes": [ + 138, + 8, + 7, + 183 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isContext", + "description": "Returns true if `forge` command was executed in given context.", + "declaration": "function isContext(ForgeContext context) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "isContext(uint8)", + "selector": "0x64af255d", + "selectorBytes": [ + 100, + 175, + 37, + 93 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isDir", + "description": "Returns true if the path exists on disk and is pointing at a directory, else returns false.", + "declaration": "function isDir(string calldata path) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "isDir(string)", + "selector": "0x7d15d019", + "selectorBytes": [ + 125, + 21, + 208, + 25 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isFile", + "description": "Returns true if the path exists on disk and is pointing at a regular file, else returns false.", + "declaration": "function isFile(string calldata path) external view returns (bool result);", + "visibility": "external", + "mutability": "view", + "signature": "isFile(string)", + "selector": "0xe0eb04d4", + "selectorBytes": [ + 224, + 235, + 4, + 212 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "isPersistent", + "description": "Returns true if the account is marked as persistent.", + "declaration": "function isPersistent(address account) external view returns (bool persistent);", + "visibility": "external", + "mutability": "view", + "signature": "isPersistent(address)", + "selector": "0xd92d8efd", + "selectorBytes": [ + 217, + 45, + 142, + 253 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "keyExists", + "description": "Checks if `key` exists in a JSON object\n`keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions.", + "declaration": "function keyExists(string calldata json, string calldata key) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "keyExists(string,string)", + "selector": "0x528a683c", + "selectorBytes": [ + 82, + 138, + 104, + 60 + ] + }, + "group": "json", + "status": { + "deprecated": "replaced by `keyExistsJson`" + }, + "safety": "safe" + }, + { + "func": { + "id": "keyExistsJson", + "description": "Checks if `key` exists in a JSON object.", + "declaration": "function keyExistsJson(string calldata json, string calldata key) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "keyExistsJson(string,string)", + "selector": "0xdb4235f6", + "selectorBytes": [ + 219, + 66, + 53, + 246 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "keyExistsToml", + "description": "Checks if `key` exists in a TOML table.", + "declaration": "function keyExistsToml(string calldata toml, string calldata key) external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "keyExistsToml(string,string)", + "selector": "0x600903ad", + "selectorBytes": [ + 96, + 9, + 3, + 173 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "label", + "description": "Labels an address in call traces.", + "declaration": "function label(address account, string calldata newLabel) external;", + "visibility": "external", + "mutability": "", + "signature": "label(address,string)", + "selector": "0xc657c718", + "selectorBytes": [ + 198, + 87, + 199, + 24 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "lastCallGas", + "description": "Gets the gas used in the last call from the callee perspective.", + "declaration": "function lastCallGas() external view returns (Gas memory gas);", + "visibility": "external", + "mutability": "view", + "signature": "lastCallGas()", + "selector": "0x2b589b28", + "selectorBytes": [ + 43, + 88, + 155, + 40 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "load", + "description": "Loads a storage slot from an address.", + "declaration": "function load(address target, bytes32 slot) external view returns (bytes32 data);", + "visibility": "external", + "mutability": "view", + "signature": "load(address,bytes32)", + "selector": "0x667f9d70", + "selectorBytes": [ + 102, + 127, + 157, + 112 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "loadAllocs", + "description": "Load a genesis JSON file's `allocs` into the in-memory EVM state.", + "declaration": "function loadAllocs(string calldata pathToAllocsJson) external;", + "visibility": "external", + "mutability": "", + "signature": "loadAllocs(string)", + "selector": "0xb3a056d7", + "selectorBytes": [ + 179, + 160, + 86, + 215 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_0", + "description": "Marks that the account(s) should use persistent storage across fork swaps in a multifork setup\nMeaning, changes made to the state of this account will be kept when switching forks.", + "declaration": "function makePersistent(address account) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address)", + "selector": "0x57e22dde", + "selectorBytes": [ + 87, + 226, + 45, + 222 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_1", + "description": "See `makePersistent(address)`.", + "declaration": "function makePersistent(address account0, address account1) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address,address)", + "selector": "0x4074e0a8", + "selectorBytes": [ + 64, + 116, + 224, + 168 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_2", + "description": "See `makePersistent(address)`.", + "declaration": "function makePersistent(address account0, address account1, address account2) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address,address,address)", + "selector": "0xefb77a75", + "selectorBytes": [ + 239, + 183, + 122, + 117 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "makePersistent_3", + "description": "See `makePersistent(address)`.", + "declaration": "function makePersistent(address[] calldata accounts) external;", + "visibility": "external", + "mutability": "", + "signature": "makePersistent(address[])", + "selector": "0x1d9e269e", + "selectorBytes": [ + 29, + 158, + 38, + 158 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCallRevert_0", + "description": "Reverts a call to an address with specified revert data.", + "declaration": "function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCallRevert(address,bytes,bytes)", + "selector": "0xdbaad147", + "selectorBytes": [ + 219, + 170, + 209, + 71 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCallRevert_1", + "description": "Reverts a call to an address with a specific `msg.value`, with specified revert data.", + "declaration": "function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCallRevert(address,uint256,bytes,bytes)", + "selector": "0xd23cd037", + "selectorBytes": [ + 210, + 60, + 208, + 55 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCallRevert_2", + "description": "Reverts a call to an address with specified revert data.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", + "declaration": "function mockCallRevert(address callee, bytes4 data, bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCallRevert(address,bytes4,bytes)", + "selector": "0x2dfba5df", + "selectorBytes": [ + 45, + 251, + 165, + 223 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCallRevert_3", + "description": "Reverts a call to an address with a specific `msg.value`, with specified revert data.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", + "declaration": "function mockCallRevert(address callee, uint256 msgValue, bytes4 data, bytes calldata revertData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCallRevert(address,uint256,bytes4,bytes)", + "selector": "0x596c8f04", + "selectorBytes": [ + 89, + 108, + 143, + 4 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCall_0", + "description": "Mocks a call to an address, returning specified data.\nCalldata can either be strict or a partial match, e.g. if you only\npass a Solidity selector to the expected calldata, then the entire Solidity\nfunction will be mocked.", + "declaration": "function mockCall(address callee, bytes calldata data, bytes calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCall(address,bytes,bytes)", + "selector": "0xb96213e4", + "selectorBytes": [ + 185, + 98, + 19, + 228 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCall_1", + "description": "Mocks a call to an address with a specific `msg.value`, returning specified data.\nCalldata match takes precedence over `msg.value` in case of ambiguity.", + "declaration": "function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCall(address,uint256,bytes,bytes)", + "selector": "0x81409b91", + "selectorBytes": [ + 129, + 64, + 155, + 145 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCall_2", + "description": "Mocks a call to an address, returning specified data.\nCalldata can either be strict or a partial match, e.g. if you only\npass a Solidity selector to the expected calldata, then the entire Solidity\nfunction will be mocked.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", + "declaration": "function mockCall(address callee, bytes4 data, bytes calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCall(address,bytes4,bytes)", + "selector": "0x08e0c537", + "selectorBytes": [ + 8, + 224, + 197, + 55 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCall_3", + "description": "Mocks a call to an address with a specific `msg.value`, returning specified data.\nCalldata match takes precedence over `msg.value` in case of ambiguity.\nOverload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`.", + "declaration": "function mockCall(address callee, uint256 msgValue, bytes4 data, bytes calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCall(address,uint256,bytes4,bytes)", + "selector": "0xe7b36a3d", + "selectorBytes": [ + 231, + 179, + 106, + 61 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCalls_0", + "description": "Mocks multiple calls to an address, returning specified data for each call.", + "declaration": "function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCalls(address,bytes,bytes[])", + "selector": "0x5c5c3de9", + "selectorBytes": [ + 92, + 92, + 61, + 233 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockCalls_1", + "description": "Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call.", + "declaration": "function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external;", + "visibility": "external", + "mutability": "", + "signature": "mockCalls(address,uint256,bytes,bytes[])", + "selector": "0x08bcbae1", + "selectorBytes": [ + 8, + 188, + 186, + 225 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "mockFunction", + "description": "Whenever a call is made to `callee` with calldata `data`, this cheatcode instead calls\n`target` with the same calldata. This functionality is similar to a delegate call made to\n`target` contract from `callee`.\nCan be used to substitute a call to a function with another implementation that captures\nthe primary logic of the original function but is easier to reason about.\nIf calldata is not a strict match then partial match by selector is attempted.", + "declaration": "function mockFunction(address callee, address target, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "mockFunction(address,address,bytes)", + "selector": "0xadf84d21", + "selectorBytes": [ + 173, + 248, + 77, + 33 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "parseAddress", + "description": "Parses the given `string` into an `address`.", + "declaration": "function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseAddress(string)", + "selector": "0xc6ce059d", + "selectorBytes": [ + 198, + 206, + 5, + 157 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseBool", + "description": "Parses the given `string` into a `bool`.", + "declaration": "function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseBool(string)", + "selector": "0x974ef924", + "selectorBytes": [ + 151, + 78, + 249, + 36 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseBytes", + "description": "Parses the given `string` into `bytes`.", + "declaration": "function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseBytes(string)", + "selector": "0x8f5d232d", + "selectorBytes": [ + 143, + 93, + 35, + 45 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseBytes32", + "description": "Parses the given `string` into a `bytes32`.", + "declaration": "function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseBytes32(string)", + "selector": "0x087e6e81", + "selectorBytes": [ + 8, + 126, + 110, + 129 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseInt", + "description": "Parses the given `string` into a `int256`.", + "declaration": "function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseInt(string)", + "selector": "0x42346c5e", + "selectorBytes": [ + 66, + 52, + 108, + 94 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonAddress", + "description": "Parses a string of JSON data at `key` and coerces it to `address`.", + "declaration": "function parseJsonAddress(string calldata json, string calldata key) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonAddress(string,string)", + "selector": "0x1e19e657", + "selectorBytes": [ + 30, + 25, + 230, + 87 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonAddressArray", + "description": "Parses a string of JSON data at `key` and coerces it to `address[]`.", + "declaration": "function parseJsonAddressArray(string calldata json, string calldata key) external pure returns (address[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonAddressArray(string,string)", + "selector": "0x2fce7883", + "selectorBytes": [ + 47, + 206, + 120, + 131 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBool", + "description": "Parses a string of JSON data at `key` and coerces it to `bool`.", + "declaration": "function parseJsonBool(string calldata json, string calldata key) external pure returns (bool);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBool(string,string)", + "selector": "0x9f86dc91", + "selectorBytes": [ + 159, + 134, + 220, + 145 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBoolArray", + "description": "Parses a string of JSON data at `key` and coerces it to `bool[]`.", + "declaration": "function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBoolArray(string,string)", + "selector": "0x91f3b94f", + "selectorBytes": [ + 145, + 243, + 185, + 79 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytes", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes`.", + "declaration": "function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytes(string,string)", + "selector": "0xfd921be8", + "selectorBytes": [ + 253, + 146, + 27, + 232 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytes32", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes32`.", + "declaration": "function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytes32(string,string)", + "selector": "0x1777e59d", + "selectorBytes": [ + 23, + 119, + 229, + 157 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytes32Array", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes32[]`.", + "declaration": "function parseJsonBytes32Array(string calldata json, string calldata key) external pure returns (bytes32[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytes32Array(string,string)", + "selector": "0x91c75bc3", + "selectorBytes": [ + 145, + 199, + 91, + 195 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonBytesArray", + "description": "Parses a string of JSON data at `key` and coerces it to `bytes[]`.", + "declaration": "function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonBytesArray(string,string)", + "selector": "0x6631aa99", + "selectorBytes": [ + 102, + 49, + 170, + 153 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonInt", + "description": "Parses a string of JSON data at `key` and coerces it to `int256`.", + "declaration": "function parseJsonInt(string calldata json, string calldata key) external pure returns (int256);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonInt(string,string)", + "selector": "0x7b048ccd", + "selectorBytes": [ + 123, + 4, + 140, + 205 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonIntArray", + "description": "Parses a string of JSON data at `key` and coerces it to `int256[]`.", + "declaration": "function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonIntArray(string,string)", + "selector": "0x9983c28a", + "selectorBytes": [ + 153, + 131, + 194, + 138 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonKeys", + "description": "Returns an array of all the keys in a JSON object.", + "declaration": "function parseJsonKeys(string calldata json, string calldata key) external pure returns (string[] memory keys);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonKeys(string,string)", + "selector": "0x213e4198", + "selectorBytes": [ + 33, + 62, + 65, + 152 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonString", + "description": "Parses a string of JSON data at `key` and coerces it to `string`.", + "declaration": "function parseJsonString(string calldata json, string calldata key) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonString(string,string)", + "selector": "0x49c4fac8", + "selectorBytes": [ + 73, + 196, + 250, + 200 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonStringArray", + "description": "Parses a string of JSON data at `key` and coerces it to `string[]`.", + "declaration": "function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonStringArray(string,string)", + "selector": "0x498fdcf4", + "selectorBytes": [ + 73, + 143, + 220, + 244 + ] + }, + "group": "json", + "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", + "description": "Parses a string of JSON data at `key` and coerces it to `uint256`.", + "declaration": "function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonUint(string,string)", + "selector": "0xaddde2b6", + "selectorBytes": [ + 173, + 221, + 226, + 182 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJsonUintArray", + "description": "Parses a string of JSON data at `key` and coerces it to `uint256[]`.", + "declaration": "function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJsonUintArray(string,string)", + "selector": "0x522074ab", + "selectorBytes": [ + 82, + 32, + 116, + 171 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJson_0", + "description": "ABI-encodes a JSON object.", + "declaration": "function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJson(string)", + "selector": "0x6a82600a", + "selectorBytes": [ + 106, + 130, + 96, + 10 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseJson_1", + "description": "ABI-encodes a JSON object at `key`.", + "declaration": "function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData);", + "visibility": "external", + "mutability": "pure", + "signature": "parseJson(string,string)", + "selector": "0x85940ef1", + "selectorBytes": [ + 133, + 148, + 14, + 241 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlAddress", + "description": "Parses a string of TOML data at `key` and coerces it to `address`.", + "declaration": "function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlAddress(string,string)", + "selector": "0x65e7c844", + "selectorBytes": [ + 101, + 231, + 200, + 68 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlAddressArray", + "description": "Parses a string of TOML data at `key` and coerces it to `address[]`.", + "declaration": "function parseTomlAddressArray(string calldata toml, string calldata key) external pure returns (address[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlAddressArray(string,string)", + "selector": "0x65c428e7", + "selectorBytes": [ + 101, + 196, + 40, + 231 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBool", + "description": "Parses a string of TOML data at `key` and coerces it to `bool`.", + "declaration": "function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBool(string,string)", + "selector": "0xd30dced6", + "selectorBytes": [ + 211, + 13, + 206, + 214 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBoolArray", + "description": "Parses a string of TOML data at `key` and coerces it to `bool[]`.", + "declaration": "function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBoolArray(string,string)", + "selector": "0x127cfe9a", + "selectorBytes": [ + 18, + 124, + 254, + 154 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBytes", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes`.", + "declaration": "function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBytes(string,string)", + "selector": "0xd77bfdb9", + "selectorBytes": [ + 215, + 123, + 253, + 185 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBytes32", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes32`.", + "declaration": "function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBytes32(string,string)", + "selector": "0x8e214810", + "selectorBytes": [ + 142, + 33, + 72, + 16 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBytes32Array", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes32[]`.", + "declaration": "function parseTomlBytes32Array(string calldata toml, string calldata key) external pure returns (bytes32[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBytes32Array(string,string)", + "selector": "0x3e716f81", + "selectorBytes": [ + 62, + 113, + 111, + 129 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlBytesArray", + "description": "Parses a string of TOML data at `key` and coerces it to `bytes[]`.", + "declaration": "function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlBytesArray(string,string)", + "selector": "0xb197c247", + "selectorBytes": [ + 177, + 151, + 194, + 71 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlInt", + "description": "Parses a string of TOML data at `key` and coerces it to `int256`.", + "declaration": "function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlInt(string,string)", + "selector": "0xc1350739", + "selectorBytes": [ + 193, + 53, + 7, + 57 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlIntArray", + "description": "Parses a string of TOML data at `key` and coerces it to `int256[]`.", + "declaration": "function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlIntArray(string,string)", + "selector": "0xd3522ae6", + "selectorBytes": [ + 211, + 82, + 42, + 230 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlKeys", + "description": "Returns an array of all the keys in a TOML table.", + "declaration": "function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlKeys(string,string)", + "selector": "0x812a44b2", + "selectorBytes": [ + 129, + 42, + 68, + 178 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlString", + "description": "Parses a string of TOML data at `key` and coerces it to `string`.", + "declaration": "function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlString(string,string)", + "selector": "0x8bb8dd43", + "selectorBytes": [ + 139, + 184, + 221, + 67 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlStringArray", + "description": "Parses a string of TOML data at `key` and coerces it to `string[]`.", + "declaration": "function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlStringArray(string,string)", + "selector": "0x9f629281", + "selectorBytes": [ + 159, + 98, + 146, + 129 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlTypeArray", + "description": "Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`.", + "declaration": "function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlTypeArray(string,string,string)", + "selector": "0x49be3743", + "selectorBytes": [ + 73, + 190, + 55, + 67 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlType_0", + "description": "Parses a string of TOML data and coerces it to type corresponding to `typeDescription`.", + "declaration": "function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlType(string,string)", + "selector": "0x47fa5e11", + "selectorBytes": [ + 71, + 250, + 94, + 17 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlType_1", + "description": "Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`.", + "declaration": "function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlType(string,string,string)", + "selector": "0xf9fa5cdb", + "selectorBytes": [ + 249, + 250, + 92, + 219 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlUint", + "description": "Parses a string of TOML data at `key` and coerces it to `uint256`.", + "declaration": "function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlUint(string,string)", + "selector": "0xcc7b0487", + "selectorBytes": [ + 204, + 123, + 4, + 135 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseTomlUintArray", + "description": "Parses a string of TOML data at `key` and coerces it to `uint256[]`.", + "declaration": "function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory);", + "visibility": "external", + "mutability": "pure", + "signature": "parseTomlUintArray(string,string)", + "selector": "0xb5df27c8", + "selectorBytes": [ + 181, + 223, + 39, + 200 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseToml_0", + "description": "ABI-encodes a TOML table.", + "declaration": "function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData);", + "visibility": "external", + "mutability": "pure", + "signature": "parseToml(string)", + "selector": "0x592151f0", + "selectorBytes": [ + 89, + 33, + 81, + 240 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseToml_1", + "description": "ABI-encodes a TOML table at `key`.", + "declaration": "function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData);", + "visibility": "external", + "mutability": "pure", + "signature": "parseToml(string,string)", + "selector": "0x37736e08", + "selectorBytes": [ + 55, + 115, + 110, + 8 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "parseUint", + "description": "Parses the given `string` into a `uint256`.", + "declaration": "function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "parseUint(string)", + "selector": "0xfa91454d", + "selectorBytes": [ + 250, + 145, + 69, + 77 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "pauseGasMetering", + "description": "Pauses gas metering (i.e. gas usage is not counted). Noop if already paused.", + "declaration": "function pauseGasMetering() external;", + "visibility": "external", + "mutability": "", + "signature": "pauseGasMetering()", + "selector": "0xd1a5b36f", + "selectorBytes": [ + 209, + 165, + 179, + 111 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "pauseTracing", + "description": "Pauses collection of call traces. Useful in cases when you want to skip tracing of\ncomplex calls which are not useful for debugging.", + "declaration": "function pauseTracing() external view;", + "visibility": "external", + "mutability": "view", + "signature": "pauseTracing()", + "selector": "0xc94d1f90", + "selectorBytes": [ + 201, + 77, + 31, + 144 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "prank_0", + "description": "Sets the *next* call's `msg.sender` to be the input address.", + "declaration": "function prank(address msgSender) external;", + "visibility": "external", + "mutability": "", + "signature": "prank(address)", + "selector": "0xca669fa7", + "selectorBytes": [ + 202, + 102, + 159, + 167 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "prank_1", + "description": "Sets the *next* call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.", + "declaration": "function prank(address msgSender, address txOrigin) external;", + "visibility": "external", + "mutability": "", + "signature": "prank(address,address)", + "selector": "0x47e50cce", + "selectorBytes": [ + 71, + 229, + 12, + 206 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "prank_2", + "description": "Sets the *next* delegate call's `msg.sender` to be the input address.", + "declaration": "function prank(address msgSender, bool delegateCall) external;", + "visibility": "external", + "mutability": "", + "signature": "prank(address,bool)", + "selector": "0xa7f8bf5c", + "selectorBytes": [ + 167, + 248, + 191, + 92 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "prank_3", + "description": "Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input.", + "declaration": "function prank(address msgSender, address txOrigin, bool delegateCall) external;", + "visibility": "external", + "mutability": "", + "signature": "prank(address,address,bool)", + "selector": "0x7d73d042", + "selectorBytes": [ + 125, + 115, + 208, + 66 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "prevrandao_0", + "description": "Sets `block.prevrandao`.\nNot available on EVM versions before Paris. Use `difficulty` instead.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function prevrandao(bytes32 newPrevrandao) external;", + "visibility": "external", + "mutability": "", + "signature": "prevrandao(bytes32)", + "selector": "0x3b925549", + "selectorBytes": [ + 59, + 146, + 85, + 73 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "prevrandao_1", + "description": "Sets `block.prevrandao`.\nNot available on EVM versions before Paris. Use `difficulty` instead.\nIf used on unsupported EVM versions it will revert.", + "declaration": "function prevrandao(uint256 newPrevrandao) external;", + "visibility": "external", + "mutability": "", + "signature": "prevrandao(uint256)", + "selector": "0x9cb1c0d4", + "selectorBytes": [ + 156, + 177, + 192, + 212 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "projectRoot", + "description": "Get the path of the current project root.", + "declaration": "function projectRoot() external view returns (string memory path);", + "visibility": "external", + "mutability": "view", + "signature": "projectRoot()", + "selector": "0xd930a0e6", + "selectorBytes": [ + 217, + 48, + 160, + 230 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "prompt", + "description": "Prompts the user for a string value in the terminal.", + "declaration": "function prompt(string calldata promptText) external returns (string memory input);", + "visibility": "external", + "mutability": "", + "signature": "prompt(string)", + "selector": "0x47eaf474", + "selectorBytes": [ + 71, + 234, + 244, + 116 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "promptAddress", + "description": "Prompts the user for an address in the terminal.", + "declaration": "function promptAddress(string calldata promptText) external returns (address);", + "visibility": "external", + "mutability": "", + "signature": "promptAddress(string)", + "selector": "0x62ee05f4", + "selectorBytes": [ + 98, + 238, + 5, + 244 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "promptSecret", + "description": "Prompts the user for a hidden string value in the terminal.", + "declaration": "function promptSecret(string calldata promptText) external returns (string memory input);", + "visibility": "external", + "mutability": "", + "signature": "promptSecret(string)", + "selector": "0x1e279d41", + "selectorBytes": [ + 30, + 39, + 157, + 65 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "promptSecretUint", + "description": "Prompts the user for hidden uint256 in the terminal (usually pk).", + "declaration": "function promptSecretUint(string calldata promptText) external returns (uint256);", + "visibility": "external", + "mutability": "", + "signature": "promptSecretUint(string)", + "selector": "0x69ca02b7", + "selectorBytes": [ + 105, + 202, + 2, + 183 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "promptUint", + "description": "Prompts the user for uint256 in the terminal.", + "declaration": "function promptUint(string calldata promptText) external returns (uint256);", + "visibility": "external", + "mutability": "", + "signature": "promptUint(string)", + "selector": "0x652fd489", + "selectorBytes": [ + 101, + 47, + 212, + 137 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "publicKeyP256", + "description": "Derives secp256r1 public key from the provided `privateKey`.", + "declaration": "function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY);", + "visibility": "external", + "mutability": "pure", + "signature": "publicKeyP256(uint256)", + "selector": "0xc453949e", + "selectorBytes": [ + 196, + 83, + 148, + 158 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomAddress", + "description": "Returns a random `address`.", + "declaration": "function randomAddress() external returns (address);", + "visibility": "external", + "mutability": "", + "signature": "randomAddress()", + "selector": "0xd5bee9f5", + "selectorBytes": [ + 213, + 190, + 233, + 245 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomBool", + "description": "Returns a random `bool`.", + "declaration": "function randomBool() external view returns (bool);", + "visibility": "external", + "mutability": "view", + "signature": "randomBool()", + "selector": "0xcdc126bd", + "selectorBytes": [ + 205, + 193, + 38, + 189 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomBytes", + "description": "Returns a random byte array value of the given length.", + "declaration": "function randomBytes(uint256 len) external view returns (bytes memory);", + "visibility": "external", + "mutability": "view", + "signature": "randomBytes(uint256)", + "selector": "0x6c5d32a9", + "selectorBytes": [ + 108, + 93, + 50, + 169 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomBytes4", + "description": "Returns a random fixed-size byte array of length 4.", + "declaration": "function randomBytes4() external view returns (bytes4);", + "visibility": "external", + "mutability": "view", + "signature": "randomBytes4()", + "selector": "0x9b7cd579", + "selectorBytes": [ + 155, + 124, + 213, + 121 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomBytes8", + "description": "Returns a random fixed-size byte array of length 8.", + "declaration": "function randomBytes8() external view returns (bytes8);", + "visibility": "external", + "mutability": "view", + "signature": "randomBytes8()", + "selector": "0x0497b0a5", + "selectorBytes": [ + 4, + 151, + 176, + 165 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomInt_0", + "description": "Returns a random `int256` value.", + "declaration": "function randomInt() external view returns (int256);", + "visibility": "external", + "mutability": "view", + "signature": "randomInt()", + "selector": "0x111f1202", + "selectorBytes": [ + 17, + 31, + 18, + 2 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomInt_1", + "description": "Returns a random `int256` value of given bits.", + "declaration": "function randomInt(uint256 bits) external view returns (int256);", + "visibility": "external", + "mutability": "view", + "signature": "randomInt(uint256)", + "selector": "0x12845966", + "selectorBytes": [ + 18, + 132, + 89, + 102 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomUint_0", + "description": "Returns a random uint256 value.", + "declaration": "function randomUint() external returns (uint256);", + "visibility": "external", + "mutability": "", + "signature": "randomUint()", + "selector": "0x25124730", + "selectorBytes": [ + 37, + 18, + 71, + 48 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomUint_1", + "description": "Returns random uint256 value between the provided range (=min..=max).", + "declaration": "function randomUint(uint256 min, uint256 max) external returns (uint256);", + "visibility": "external", + "mutability": "", + "signature": "randomUint(uint256,uint256)", + "selector": "0xd61b051b", + "selectorBytes": [ + 214, + 27, + 5, + 27 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomUint_2", + "description": "Returns a random `uint256` value of given bits.", + "declaration": "function randomUint(uint256 bits) external view returns (uint256);", + "visibility": "external", + "mutability": "view", + "signature": "randomUint(uint256)", + "selector": "0xcf81e69c", + "selectorBytes": [ + 207, + 129, + 230, + 156 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readCallers", + "description": "Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification.", + "declaration": "function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin);", + "visibility": "external", + "mutability": "", + "signature": "readCallers()", + "selector": "0x4ad0bac9", + "selectorBytes": [ + 74, + 208, + 186, + 201 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "readDir_0", + "description": "Reads the directory at the given path recursively, up to `maxDepth`.\n`maxDepth` defaults to 1, meaning only the direct children of the given directory will be returned.\nFollows symbolic links if `followLinks` is true.", + "declaration": "function readDir(string calldata path) external view returns (DirEntry[] memory entries);", + "visibility": "external", + "mutability": "view", + "signature": "readDir(string)", + "selector": "0xc4bc59e0", + "selectorBytes": [ + 196, + 188, + 89, + 224 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readDir_1", + "description": "See `readDir(string)`.", + "declaration": "function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries);", + "visibility": "external", + "mutability": "view", + "signature": "readDir(string,uint64)", + "selector": "0x1497876c", + "selectorBytes": [ + 20, + 151, + 135, + 108 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readDir_2", + "description": "See `readDir(string)`.", + "declaration": "function readDir(string calldata path, uint64 maxDepth, bool followLinks) external view returns (DirEntry[] memory entries);", + "visibility": "external", + "mutability": "view", + "signature": "readDir(string,uint64,bool)", + "selector": "0x8102d70d", + "selectorBytes": [ + 129, + 2, + 215, + 13 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readFile", + "description": "Reads the entire content of file to string. `path` is relative to the project root.", + "declaration": "function readFile(string calldata path) external view returns (string memory data);", + "visibility": "external", + "mutability": "view", + "signature": "readFile(string)", + "selector": "0x60f9bb11", + "selectorBytes": [ + 96, + 249, + 187, + 17 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readFileBinary", + "description": "Reads the entire content of file as binary. `path` is relative to the project root.", + "declaration": "function readFileBinary(string calldata path) external view returns (bytes memory data);", + "visibility": "external", + "mutability": "view", + "signature": "readFileBinary(string)", + "selector": "0x16ed7bc4", + "selectorBytes": [ + 22, + 237, + 123, + 196 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readLine", + "description": "Reads next line of file to string.", + "declaration": "function readLine(string calldata path) external view returns (string memory line);", + "visibility": "external", + "mutability": "view", + "signature": "readLine(string)", + "selector": "0x70f55728", + "selectorBytes": [ + 112, + 245, + 87, + 40 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "readLink", + "description": "Reads a symbolic link, returning the path that the link points to.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- `path` is not a symbolic link.\n- `path` does not exist.", + "declaration": "function readLink(string calldata linkPath) external view returns (string memory targetPath);", + "visibility": "external", + "mutability": "view", + "signature": "readLink(string)", + "selector": "0x9f5684a2", + "selectorBytes": [ + 159, + 86, + 132, + 162 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "record", + "description": "Records all storage reads and writes.", + "declaration": "function record() external;", + "visibility": "external", + "mutability": "", + "signature": "record()", + "selector": "0x266cf109", + "selectorBytes": [ + 38, + 108, + 241, + 9 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "recordLogs", + "description": "Record all the transaction logs.", + "declaration": "function recordLogs() external;", + "visibility": "external", + "mutability": "", + "signature": "recordLogs()", + "selector": "0x41af2f52", + "selectorBytes": [ + 65, + 175, + 47, + 82 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rememberKey", + "description": "Adds a private key to the local forge wallet and returns the address.", + "declaration": "function rememberKey(uint256 privateKey) external returns (address keyAddr);", + "visibility": "external", + "mutability": "", + "signature": "rememberKey(uint256)", + "selector": "0x22100064", + "selectorBytes": [ + 34, + 16, + 0, + 100 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rememberKeys_0", + "description": "Derive a set number of wallets from a mnemonic at the derivation path `m/44'/60'/0'/0/{0..count}`.\nThe respective private keys are saved to the local forge wallet for later use and their addresses are returned.", + "declaration": "function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs);", + "visibility": "external", + "mutability": "", + "signature": "rememberKeys(string,string,uint32)", + "selector": "0x97cb9189", + "selectorBytes": [ + 151, + 203, + 145, + 137 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rememberKeys_1", + "description": "Derive a set number of wallets from a mnemonic in the specified language at the derivation path `m/44'/60'/0'/0/{0..count}`.\nThe respective private keys are saved to the local forge wallet for later use and their addresses are returned.", + "declaration": "function rememberKeys(string calldata mnemonic, string calldata derivationPath, string calldata language, uint32 count) external returns (address[] memory keyAddrs);", + "visibility": "external", + "mutability": "", + "signature": "rememberKeys(string,string,string,uint32)", + "selector": "0xf8d58eaf", + "selectorBytes": [ + 248, + 213, + 142, + 175 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "removeDir", + "description": "Removes a directory at the provided path.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- `path` doesn't exist.\n- `path` isn't a directory.\n- User lacks permissions to modify `path`.\n- The directory is not empty and `recursive` is false.\n`path` is relative to the project root.", + "declaration": "function removeDir(string calldata path, bool recursive) external;", + "visibility": "external", + "mutability": "", + "signature": "removeDir(string,bool)", + "selector": "0x45c62011", + "selectorBytes": [ + 69, + 198, + 32, + 17 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "removeFile", + "description": "Removes a file from the filesystem.\nThis cheatcode will revert in the following situations, but is not limited to just these cases:\n- `path` points to a directory.\n- The file doesn't exist.\n- The user lacks permissions to remove the file.\n`path` is relative to the project root.", + "declaration": "function removeFile(string calldata path) external;", + "visibility": "external", + "mutability": "", + "signature": "removeFile(string)", + "selector": "0xf1afe04d", + "selectorBytes": [ + 241, + 175, + 224, + 77 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "replace", + "description": "Replaces occurrences of `from` in the given `string` with `to`.", + "declaration": "function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "replace(string,string,string)", + "selector": "0xe00ad03e", + "selectorBytes": [ + 224, + 10, + 208, + 62 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "resetGasMetering", + "description": "Reset gas metering (i.e. gas usage is set to gas limit).", + "declaration": "function resetGasMetering() external;", + "visibility": "external", + "mutability": "", + "signature": "resetGasMetering()", + "selector": "0xbe367dd3", + "selectorBytes": [ + 190, + 54, + 125, + 211 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "resetNonce", + "description": "Resets the nonce of an account to 0 for EOAs and 1 for contract accounts.", + "declaration": "function resetNonce(address account) external;", + "visibility": "external", + "mutability": "", + "signature": "resetNonce(address)", + "selector": "0x1c72346d", + "selectorBytes": [ + 28, + 114, + 52, + 109 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "resumeGasMetering", + "description": "Resumes gas metering (i.e. gas usage is counted again). Noop if already on.", + "declaration": "function resumeGasMetering() external;", + "visibility": "external", + "mutability": "", + "signature": "resumeGasMetering()", + "selector": "0x2bcd50e0", + "selectorBytes": [ + 43, + 205, + 80, + 224 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "resumeTracing", + "description": "Unpauses collection of call traces.", + "declaration": "function resumeTracing() external view;", + "visibility": "external", + "mutability": "view", + "signature": "resumeTracing()", + "selector": "0x72a09ccb", + "selectorBytes": [ + 114, + 160, + 156, + 203 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "revertTo", + "description": "`revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions.", + "declaration": "function revertTo(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertTo(uint256)", + "selector": "0x44d7f0a4", + "selectorBytes": [ + 68, + 215, + 240, + 164 + ] + }, + "group": "evm", + "status": { + "deprecated": "replaced by `revertToState`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "revertToAndDelete", + "description": "`revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions.", + "declaration": "function revertToAndDelete(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertToAndDelete(uint256)", + "selector": "0x03e0aca9", + "selectorBytes": [ + 3, + 224, + 172, + 169 + ] + }, + "group": "evm", + "status": { + "deprecated": "replaced by `revertToStateAndDelete`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "revertToState", + "description": "Revert the state of the EVM to a previous snapshot\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted.\nReturns `false` if the snapshot does not exist.\n**Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`.", + "declaration": "function revertToState(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertToState(uint256)", + "selector": "0xc2527405", + "selectorBytes": [ + 194, + 82, + 116, + 5 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "revertToStateAndDelete", + "description": "Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted and deleted.\nReturns `false` if the snapshot does not exist.", + "declaration": "function revertToStateAndDelete(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertToStateAndDelete(uint256)", + "selector": "0x3a1985dc", + "selectorBytes": [ + 58, + 25, + 133, + 220 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "revokePersistent_0", + "description": "Revokes persistent status from the address, previously added via `makePersistent`.", + "declaration": "function revokePersistent(address account) external;", + "visibility": "external", + "mutability": "", + "signature": "revokePersistent(address)", + "selector": "0x997a0222", + "selectorBytes": [ + 153, + 122, + 2, + 34 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "revokePersistent_1", + "description": "See `revokePersistent(address)`.", + "declaration": "function revokePersistent(address[] calldata accounts) external;", + "visibility": "external", + "mutability": "", + "signature": "revokePersistent(address[])", + "selector": "0x3ce969e6", + "selectorBytes": [ + 60, + 233, + 105, + 230 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "roll", + "description": "Sets `block.height`.", + "declaration": "function roll(uint256 newHeight) external;", + "visibility": "external", + "mutability": "", + "signature": "roll(uint256)", + "selector": "0x1f7b4f30", + "selectorBytes": [ + 31, + 123, + 79, + 48 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "rollFork_0", + "description": "Updates the currently active fork to given block number\nThis is similar to `roll` but for the currently active fork.", + "declaration": "function rollFork(uint256 blockNumber) external;", + "visibility": "external", + "mutability": "", + "signature": "rollFork(uint256)", + "selector": "0xd9bbf3a1", + "selectorBytes": [ + 217, + 187, + 243, + 161 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "rollFork_1", + "description": "Updates the currently active fork to given transaction. This will `rollFork` with the number\nof the block the transaction was mined in and replays all transaction mined before it in the block.", + "declaration": "function rollFork(bytes32 txHash) external;", + "visibility": "external", + "mutability": "", + "signature": "rollFork(bytes32)", + "selector": "0x0f29772b", + "selectorBytes": [ + 15, + 41, + 119, + 43 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "rollFork_2", + "description": "Updates the given fork to given block number.", + "declaration": "function rollFork(uint256 forkId, uint256 blockNumber) external;", + "visibility": "external", + "mutability": "", + "signature": "rollFork(uint256,uint256)", + "selector": "0xd74c83a4", + "selectorBytes": [ + 215, + 76, + 131, + 164 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "rollFork_3", + "description": "Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block.", + "declaration": "function rollFork(uint256 forkId, bytes32 txHash) external;", + "visibility": "external", + "mutability": "", + "signature": "rollFork(uint256,bytes32)", + "selector": "0xf2830f7b", + "selectorBytes": [ + 242, + 131, + 15, + 123 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "rpcUrl", + "description": "Returns the RPC url for the given alias.", + "declaration": "function rpcUrl(string calldata rpcAlias) external view returns (string memory json);", + "visibility": "external", + "mutability": "view", + "signature": "rpcUrl(string)", + "selector": "0x975a6ce9", + "selectorBytes": [ + 151, + 90, + 108, + 233 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rpcUrlStructs", + "description": "Returns all rpc urls and their aliases as structs.", + "declaration": "function rpcUrlStructs() external view returns (Rpc[] memory urls);", + "visibility": "external", + "mutability": "view", + "signature": "rpcUrlStructs()", + "selector": "0x9d2ad72a", + "selectorBytes": [ + 157, + 42, + 215, + 42 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "rpcUrls", + "description": "Returns all rpc urls and their aliases `[alias, url][]`.", + "declaration": "function rpcUrls() external view returns (string[2][] memory urls);", + "visibility": "external", + "mutability": "view", + "signature": "rpcUrls()", + "selector": "0xa85a8418", + "selectorBytes": [ + 168, + 90, + 132, + 24 + ] + }, + "group": "testing", + "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", + "description": "Takes a fork identifier created by `createFork` and sets the corresponding forked state as active.", + "declaration": "function selectFork(uint256 forkId) external;", + "visibility": "external", + "mutability": "", + "signature": "selectFork(uint256)", + "selector": "0x9ebf6827", + "selectorBytes": [ + 158, + 191, + 104, + 39 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "serializeAddress_0", + "description": "See `serializeJson`.", + "declaration": "function serializeAddress(string calldata objectKey, string calldata valueKey, address value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeAddress(string,string,address)", + "selector": "0x972c6062", + "selectorBytes": [ + 151, + 44, + 96, + 98 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeAddress_1", + "description": "See `serializeJson`.", + "declaration": "function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeAddress(string,string,address[])", + "selector": "0x1e356e1a", + "selectorBytes": [ + 30, + 53, + 110, + 26 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeBool_0", + "description": "See `serializeJson`.", + "declaration": "function serializeBool(string calldata objectKey, string calldata valueKey, bool value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeBool(string,string,bool)", + "selector": "0xac22e971", + "selectorBytes": [ + 172, + 34, + 233, + 113 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeBool_1", + "description": "See `serializeJson`.", + "declaration": "function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeBool(string,string,bool[])", + "selector": "0x92925aa1", + "selectorBytes": [ + 146, + 146, + 90, + 161 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeBytes32_0", + "description": "See `serializeJson`.", + "declaration": "function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeBytes32(string,string,bytes32)", + "selector": "0x2d812b44", + "selectorBytes": [ + 45, + 129, + 43, + 68 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeBytes32_1", + "description": "See `serializeJson`.", + "declaration": "function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeBytes32(string,string,bytes32[])", + "selector": "0x201e43e2", + "selectorBytes": [ + 32, + 30, + 67, + 226 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeBytes_0", + "description": "See `serializeJson`.", + "declaration": "function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeBytes(string,string,bytes)", + "selector": "0xf21d52c7", + "selectorBytes": [ + 242, + 29, + 82, + 199 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeBytes_1", + "description": "See `serializeJson`.", + "declaration": "function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeBytes(string,string,bytes[])", + "selector": "0x9884b232", + "selectorBytes": [ + 152, + 132, + 178, + 50 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeInt_0", + "description": "See `serializeJson`.", + "declaration": "function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeInt(string,string,int256)", + "selector": "0x3f33db60", + "selectorBytes": [ + 63, + 51, + 219, + 96 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeInt_1", + "description": "See `serializeJson`.", + "declaration": "function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeInt(string,string,int256[])", + "selector": "0x7676e127", + "selectorBytes": [ + 118, + 118, + 225, + 39 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeJson", + "description": "Serializes a key and value to a JSON object stored in-memory that can be later written to a file.\nReturns the stringified version of the specific JSON file up to that moment.", + "declaration": "function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeJson(string,string)", + "selector": "0x9b3358b0", + "selectorBytes": [ + 155, + 51, + 88, + 176 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeJsonType_0", + "description": "See `serializeJson`.", + "declaration": "function serializeJsonType(string calldata typeDescription, bytes calldata 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 calldata 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", + "description": "See `serializeJson`.", + "declaration": "function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeString(string,string,string)", + "selector": "0x88da6d35", + "selectorBytes": [ + 136, + 218, + 109, + 53 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeString_1", + "description": "See `serializeJson`.", + "declaration": "function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeString(string,string,string[])", + "selector": "0x561cd6f3", + "selectorBytes": [ + 86, + 28, + 214, + 243 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeUintToHex", + "description": "See `serializeJson`.", + "declaration": "function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeUintToHex(string,string,uint256)", + "selector": "0xae5a2ae8", + "selectorBytes": [ + 174, + 90, + 42, + 232 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeUint_0", + "description": "See `serializeJson`.", + "declaration": "function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeUint(string,string,uint256)", + "selector": "0x129e9002", + "selectorBytes": [ + 18, + 158, + 144, + 2 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "serializeUint_1", + "description": "See `serializeJson`.", + "declaration": "function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json);", + "visibility": "external", + "mutability": "", + "signature": "serializeUint(string,string,uint256[])", + "selector": "0xfee9a469", + "selectorBytes": [ + 254, + 233, + 164, + 105 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "setArbitraryStorage", + "description": "Utility cheatcode to set arbitrary storage for given target address.", + "declaration": "function setArbitraryStorage(address target) external;", + "visibility": "external", + "mutability": "", + "signature": "setArbitraryStorage(address)", + "selector": "0xe1631837", + "selectorBytes": [ + 225, + 99, + 24, + 55 + ] + }, + "group": "utilities", + "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", + "description": "Sets environment variables.", + "declaration": "function setEnv(string calldata name, string calldata value) external;", + "visibility": "external", + "mutability": "", + "signature": "setEnv(string,string)", + "selector": "0x3d5923ee", + "selectorBytes": [ + 61, + 89, + 35, + 238 + ] + }, + "group": "environment", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "setNonce", + "description": "Sets the nonce of an account. Must be higher than the current nonce of the account.", + "declaration": "function setNonce(address account, uint64 newNonce) external;", + "visibility": "external", + "mutability": "", + "signature": "setNonce(address,uint64)", + "selector": "0xf8e18b57", + "selectorBytes": [ + 248, + 225, + 139, + 87 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "setNonceUnsafe", + "description": "Sets the nonce of an account to an arbitrary value.", + "declaration": "function setNonceUnsafe(address account, uint64 newNonce) external;", + "visibility": "external", + "mutability": "", + "signature": "setNonceUnsafe(address,uint64)", + "selector": "0x9b67b21c", + "selectorBytes": [ + 155, + 103, + 178, + 28 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "signAndAttachDelegation", + "description": "Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction", + "declaration": "function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation);", + "visibility": "external", + "mutability": "", + "signature": "signAndAttachDelegation(address,uint256)", + "selector": "0xc7fa7288", + "selectorBytes": [ + 199, + 250, + 114, + 136 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "signCompact_0", + "description": "Signs data with a `Wallet`.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.", + "declaration": "function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs);", + "visibility": "external", + "mutability": "", + "signature": "signCompact((address,uint256,uint256,uint256),bytes32)", + "selector": "0x3d0e292f", + "selectorBytes": [ + 61, + 14, + 41, + 47 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "signCompact_1", + "description": "Signs `digest` with `privateKey` using the secp256k1 curve.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.", + "declaration": "function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", + "visibility": "external", + "mutability": "pure", + "signature": "signCompact(uint256,bytes32)", + "selector": "0xcc2a781f", + "selectorBytes": [ + 204, + 42, + 120, + 31 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "signCompact_2", + "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.\nIf `--sender` is provided, the signer with provided address is used, otherwise,\nif exactly one signer is provided to the script, that signer is used.\nRaises error if signer passed through `--sender` does not match any unlocked signers or\nif `--sender` is not provided and not exactly one signer is passed to the script.", + "declaration": "function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", + "visibility": "external", + "mutability": "pure", + "signature": "signCompact(bytes32)", + "selector": "0xa282dc4b", + "selectorBytes": [ + 162, + 130, + 220, + 75 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "signCompact_3", + "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nReturns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the\nsignature's `s` value, and the recovery id `v` in a single bytes32.\nThis format reduces the signature size from 65 to 64 bytes.\nRaises error if none of the signers passed into the script have provided address.", + "declaration": "function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs);", + "visibility": "external", + "mutability": "pure", + "signature": "signCompact(address,bytes32)", + "selector": "0x8e2f97bf", + "selectorBytes": [ + 142, + 47, + 151, + 191 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "signDelegation", + "description": "Sign an EIP-7702 authorization for delegation", + "declaration": "function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation);", + "visibility": "external", + "mutability": "", + "signature": "signDelegation(address,uint256)", + "selector": "0x5b593c7b", + "selectorBytes": [ + 91, + 89, + 60, + 123 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "signP256", + "description": "Signs `digest` with `privateKey` using the secp256r1 curve.", + "declaration": "function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "signP256(uint256,bytes32)", + "selector": "0x83211b40", + "selectorBytes": [ + 131, + 33, + 27, + 64 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_0", + "description": "Signs data with a `Wallet`.", + "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "", + "signature": "sign((address,uint256,uint256,uint256),bytes32)", + "selector": "0xb25c5a25", + "selectorBytes": [ + 178, + 92, + 90, + 37 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_1", + "description": "Signs `digest` with `privateKey` using the secp256k1 curve.", + "declaration": "function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "sign(uint256,bytes32)", + "selector": "0xe341eaa4", + "selectorBytes": [ + 227, + 65, + 234, + 164 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_2", + "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nIf `--sender` is provided, the signer with provided address is used, otherwise,\nif exactly one signer is provided to the script, that signer is used.\nRaises error if signer passed through `--sender` does not match any unlocked signers or\nif `--sender` is not provided and not exactly one signer is passed to the script.", + "declaration": "function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "sign(bytes32)", + "selector": "0x799cd333", + "selectorBytes": [ + 121, + 156, + 211, + 51 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_3", + "description": "Signs `digest` with signer provided to script using the secp256k1 curve.\nRaises error if none of the signers passed into the script have provided address.", + "declaration": "function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s);", + "visibility": "external", + "mutability": "pure", + "signature": "sign(address,bytes32)", + "selector": "0x8c1aa205", + "selectorBytes": [ + 140, + 26, + 162, + 5 + ] + }, + "group": "crypto", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "skip_0", + "description": "Marks a test as skipped. Must be called at the top level of a test.", + "declaration": "function skip(bool skipTest) external;", + "visibility": "external", + "mutability": "", + "signature": "skip(bool)", + "selector": "0xdd82d13e", + "selectorBytes": [ + 221, + 130, + 209, + 62 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "skip_1", + "description": "Marks a test as skipped with a reason. Must be called at the top level of a test.", + "declaration": "function skip(bool skipTest, string calldata reason) external;", + "visibility": "external", + "mutability": "", + "signature": "skip(bool,string)", + "selector": "0xc42a80a7", + "selectorBytes": [ + 196, + 42, + 128, + 167 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "sleep", + "description": "Suspends execution of the main thread for `duration` milliseconds.", + "declaration": "function sleep(uint256 duration) external;", + "visibility": "external", + "mutability": "", + "signature": "sleep(uint256)", + "selector": "0xfa9d8713", + "selectorBytes": [ + 250, + 157, + 135, + 19 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "snapshot", + "description": "`snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions.", + "declaration": "function snapshot() external returns (uint256 snapshotId);", + "visibility": "external", + "mutability": "", + "signature": "snapshot()", + "selector": "0x9711715a", + "selectorBytes": [ + 151, + 17, + 113, + 90 + ] + }, + "group": "evm", + "status": { + "deprecated": "replaced by `snapshotState`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotGasLastCall_0", + "description": "Snapshot capture the gas usage of the last call by name from the callee perspective.", + "declaration": "function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "snapshotGasLastCall(string)", + "selector": "0xdd9fca12", + "selectorBytes": [ + 221, + 159, + 202, + 18 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotGasLastCall_1", + "description": "Snapshot capture the gas usage of the last call by name in a group from the callee perspective.", + "declaration": "function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "snapshotGasLastCall(string,string)", + "selector": "0x200c6772", + "selectorBytes": [ + 32, + 12, + 103, + 114 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotState", + "description": "Snapshot the current state of the evm.\nReturns the ID of the snapshot that was created.\nTo revert a snapshot use `revertToState`.", + "declaration": "function snapshotState() external returns (uint256 snapshotId);", + "visibility": "external", + "mutability": "", + "signature": "snapshotState()", + "selector": "0x9cd23835", + "selectorBytes": [ + 156, + 210, + 56, + 53 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotValue_0", + "description": "Snapshot capture an arbitrary numerical value by name.\nThe group name is derived from the contract name.", + "declaration": "function snapshotValue(string calldata name, uint256 value) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,uint256)", + "selector": "0x51db805a", + "selectorBytes": [ + 81, + 219, + 128, + 90 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotValue_1", + "description": "Snapshot capture an arbitrary numerical value by name in a group.", + "declaration": "function snapshotValue(string calldata group, string calldata name, uint256 value) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,string,uint256)", + "selector": "0x6d2b27d8", + "selectorBytes": [ + 109, + 43, + 39, + 216 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "split", + "description": "Splits the given `string` into an array of strings divided by the `delimiter`.", + "declaration": "function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs);", + "visibility": "external", + "mutability": "pure", + "signature": "split(string,string)", + "selector": "0x8bb75533", + "selectorBytes": [ + 139, + 183, + 85, + 51 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "startBroadcast_0", + "description": "Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain.\nBroadcasting address is determined by checking the following in order:\n1. If `--sender` argument was provided, that address is used.\n2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used.\n3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used.", + "declaration": "function startBroadcast() external;", + "visibility": "external", + "mutability": "", + "signature": "startBroadcast()", + "selector": "0x7fb5297f", + "selectorBytes": [ + 127, + 181, + 41, + 127 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "startBroadcast_1", + "description": "Has all subsequent calls (at this call depth only) create transactions with the address\nprovided that can later be signed and sent onchain.", + "declaration": "function startBroadcast(address signer) external;", + "visibility": "external", + "mutability": "", + "signature": "startBroadcast(address)", + "selector": "0x7fec2a8d", + "selectorBytes": [ + 127, + 236, + 42, + 141 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "startBroadcast_2", + "description": "Has all subsequent calls (at this call depth only) create transactions with the private key\nprovided that can later be signed and sent onchain.", + "declaration": "function startBroadcast(uint256 privateKey) external;", + "visibility": "external", + "mutability": "", + "signature": "startBroadcast(uint256)", + "selector": "0xce817d47", + "selectorBytes": [ + 206, + 129, + 125, + 71 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "startDebugTraceRecording", + "description": "Records the debug trace during the run.", + "declaration": "function startDebugTraceRecording() external;", + "visibility": "external", + "mutability": "", + "signature": "startDebugTraceRecording()", + "selector": "0x419c8832", + "selectorBytes": [ + 65, + 156, + 136, + 50 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "startMappingRecording", + "description": "Starts recording all map SSTOREs for later retrieval.", + "declaration": "function startMappingRecording() external;", + "visibility": "external", + "mutability": "", + "signature": "startMappingRecording()", + "selector": "0x3e9705c0", + "selectorBytes": [ + 62, + 151, + 5, + 192 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "startPrank_0", + "description": "Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called.", + "declaration": "function startPrank(address msgSender) external;", + "visibility": "external", + "mutability": "", + "signature": "startPrank(address)", + "selector": "0x06447d56", + "selectorBytes": [ + 6, + 68, + 125, + 86 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startPrank_1", + "description": "Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.", + "declaration": "function startPrank(address msgSender, address txOrigin) external;", + "visibility": "external", + "mutability": "", + "signature": "startPrank(address,address)", + "selector": "0x45b56078", + "selectorBytes": [ + 69, + 181, + 96, + 120 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startPrank_2", + "description": "Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called.", + "declaration": "function startPrank(address msgSender, bool delegateCall) external;", + "visibility": "external", + "mutability": "", + "signature": "startPrank(address,bool)", + "selector": "0x1cc0b435", + "selectorBytes": [ + 28, + 192, + 180, + 53 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startPrank_3", + "description": "Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input.", + "declaration": "function startPrank(address msgSender, address txOrigin, bool delegateCall) external;", + "visibility": "external", + "mutability": "", + "signature": "startPrank(address,address,bool)", + "selector": "0x4eb859b5", + "selectorBytes": [ + 78, + 184, + 89, + 181 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startSnapshotGas_0", + "description": "Start a snapshot capture of the current gas usage by name.\nThe group name is derived from the contract name.", + "declaration": "function startSnapshotGas(string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas(string)", + "selector": "0x3cad9d7b", + "selectorBytes": [ + 60, + 173, + 157, + 123 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startSnapshotGas_1", + "description": "Start a snapshot capture of the current gas usage by name in a group.", + "declaration": "function startSnapshotGas(string calldata group, string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas(string,string)", + "selector": "0x6cd0cc53", + "selectorBytes": [ + 108, + 208, + 204, + 83 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startStateDiffRecording", + "description": "Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order,\nalong with the context of the calls", + "declaration": "function startStateDiffRecording() external;", + "visibility": "external", + "mutability": "", + "signature": "startStateDiffRecording()", + "selector": "0xcf22e3c9", + "selectorBytes": [ + 207, + 34, + 227, + 201 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "stopAndReturnDebugTraceRecording", + "description": "Stop debug trace recording and returns the recorded debug trace.", + "declaration": "function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step);", + "visibility": "external", + "mutability": "", + "signature": "stopAndReturnDebugTraceRecording()", + "selector": "0xced398a2", + "selectorBytes": [ + 206, + 211, + 152, + 162 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "stopAndReturnStateDiff", + "description": "Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session.", + "declaration": "function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses);", + "visibility": "external", + "mutability": "", + "signature": "stopAndReturnStateDiff()", + "selector": "0xaa5cf90e", + "selectorBytes": [ + 170, + 92, + 249, + 14 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "stopBroadcast", + "description": "Stops collecting onchain transactions.", + "declaration": "function stopBroadcast() external;", + "visibility": "external", + "mutability": "", + "signature": "stopBroadcast()", + "selector": "0x76eadd36", + "selectorBytes": [ + 118, + 234, + 221, + 54 + ] + }, + "group": "scripting", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "stopExpectSafeMemory", + "description": "Stops all safe memory expectation in the current subcontext.", + "declaration": "function stopExpectSafeMemory() external;", + "visibility": "external", + "mutability": "", + "signature": "stopExpectSafeMemory()", + "selector": "0x0956441b", + "selectorBytes": [ + 9, + 86, + 68, + 27 + ] + }, + "group": "testing", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopMappingRecording", + "description": "Stops recording all map SSTOREs for later retrieval and clears the recorded data.", + "declaration": "function stopMappingRecording() external;", + "visibility": "external", + "mutability": "", + "signature": "stopMappingRecording()", + "selector": "0x0d4aae9b", + "selectorBytes": [ + 13, + 74, + 174, + 155 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "stopPrank", + "description": "Resets subsequent calls' `msg.sender` to be `address(this)`.", + "declaration": "function stopPrank() external;", + "visibility": "external", + "mutability": "", + "signature": "stopPrank()", + "selector": "0x90c5013b", + "selectorBytes": [ + 144, + 197, + 1, + 59 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_0", + "description": "Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start.", + "declaration": "function stopSnapshotGas() external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas()", + "selector": "0xf6402eda", + "selectorBytes": [ + 246, + 64, + 46, + 218 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_1", + "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.\nThe group name is derived from the contract name.", + "declaration": "function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas(string)", + "selector": "0x773b2805", + "selectorBytes": [ + 119, + 59, + 40, + 5 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_2", + "description": "Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start.", + "declaration": "function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas(string,string)", + "selector": "0x0c9db707", + "selectorBytes": [ + 12, + 157, + 183, + 7 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "store", + "description": "Stores a value to an address' storage slot.", + "declaration": "function store(address target, bytes32 slot, bytes32 value) external;", + "visibility": "external", + "mutability": "", + "signature": "store(address,bytes32,bytes32)", + "selector": "0x70ca10bb", + "selectorBytes": [ + 112, + 202, + 16, + 187 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "toBase64URL_0", + "description": "Encodes a `bytes` value to a base64url string.", + "declaration": "function toBase64URL(bytes calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64URL(bytes)", + "selector": "0xc8bd0e4a", + "selectorBytes": [ + 200, + 189, + 14, + 74 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64URL_1", + "description": "Encodes a `string` value to a base64url string.", + "declaration": "function toBase64URL(string calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64URL(string)", + "selector": "0xae3165b3", + "selectorBytes": [ + 174, + 49, + 101, + 179 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64_0", + "description": "Encodes a `bytes` value to a base64 string.", + "declaration": "function toBase64(bytes calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64(bytes)", + "selector": "0xa5cbfe65", + "selectorBytes": [ + 165, + 203, + 254, + 101 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toBase64_1", + "description": "Encodes a `string` value to a base64 string.", + "declaration": "function toBase64(string calldata data) external pure returns (string memory);", + "visibility": "external", + "mutability": "pure", + "signature": "toBase64(string)", + "selector": "0x3f8be2c8", + "selectorBytes": [ + 63, + 139, + 226, + 200 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toLowercase", + "description": "Converts the given `string` value to Lowercase.", + "declaration": "function toLowercase(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "toLowercase(string)", + "selector": "0x50bb0884", + "selectorBytes": [ + 80, + 187, + 8, + 132 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toString_0", + "description": "Converts the given value to a `string`.", + "declaration": "function toString(address value) external pure returns (string memory stringifiedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "toString(address)", + "selector": "0x56ca623e", + "selectorBytes": [ + 86, + 202, + 98, + 62 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toString_1", + "description": "Converts the given value to a `string`.", + "declaration": "function toString(bytes calldata value) external pure returns (string memory stringifiedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "toString(bytes)", + "selector": "0x71aad10d", + "selectorBytes": [ + 113, + 170, + 209, + 13 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toString_2", + "description": "Converts the given value to a `string`.", + "declaration": "function toString(bytes32 value) external pure returns (string memory stringifiedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "toString(bytes32)", + "selector": "0xb11a19e8", + "selectorBytes": [ + 177, + 26, + 25, + 232 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toString_3", + "description": "Converts the given value to a `string`.", + "declaration": "function toString(bool value) external pure returns (string memory stringifiedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "toString(bool)", + "selector": "0x71dce7da", + "selectorBytes": [ + 113, + 220, + 231, + 218 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toString_4", + "description": "Converts the given value to a `string`.", + "declaration": "function toString(uint256 value) external pure returns (string memory stringifiedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "toString(uint256)", + "selector": "0x6900a3ae", + "selectorBytes": [ + 105, + 0, + 163, + 174 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toString_5", + "description": "Converts the given value to a `string`.", + "declaration": "function toString(int256 value) external pure returns (string memory stringifiedValue);", + "visibility": "external", + "mutability": "pure", + "signature": "toString(int256)", + "selector": "0xa322c40e", + "selectorBytes": [ + 163, + 34, + 196, + 14 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "toUppercase", + "description": "Converts the given `string` value to Uppercase.", + "declaration": "function toUppercase(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "toUppercase(string)", + "selector": "0x074ae3d7", + "selectorBytes": [ + 7, + 74, + 227, + 215 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "transact_0", + "description": "Fetches the given transaction from the active fork and executes it on the current state.", + "declaration": "function transact(bytes32 txHash) external;", + "visibility": "external", + "mutability": "", + "signature": "transact(bytes32)", + "selector": "0xbe646da1", + "selectorBytes": [ + 190, + 100, + 109, + 161 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "transact_1", + "description": "Fetches the given transaction from the given fork and executes it on the current state.", + "declaration": "function transact(uint256 forkId, bytes32 txHash) external;", + "visibility": "external", + "mutability": "", + "signature": "transact(uint256,bytes32)", + "selector": "0x4d8abc4b", + "selectorBytes": [ + 77, + 138, + 188, + 75 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "trim", + "description": "Trims leading and trailing whitespace from the given `string` value.", + "declaration": "function trim(string calldata input) external pure returns (string memory output);", + "visibility": "external", + "mutability": "pure", + "signature": "trim(string)", + "selector": "0xb2dad155", + "selectorBytes": [ + 178, + 218, + 209, + 85 + ] + }, + "group": "string", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "tryFfi", + "description": "Performs a foreign function call via terminal and returns the exit code, stdout, and stderr.", + "declaration": "function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result);", + "visibility": "external", + "mutability": "", + "signature": "tryFfi(string[])", + "selector": "0xf45c1ce7", + "selectorBytes": [ + 244, + 92, + 28, + 231 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "txGasPrice", + "description": "Sets `tx.gasprice`.", + "declaration": "function txGasPrice(uint256 newGasPrice) external;", + "visibility": "external", + "mutability": "", + "signature": "txGasPrice(uint256)", + "selector": "0x48f50c0f", + "selectorBytes": [ + 72, + 245, + 12, + 15 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "unixTime", + "description": "Returns the time since unix epoch in milliseconds.", + "declaration": "function unixTime() external view returns (uint256 milliseconds);", + "visibility": "external", + "mutability": "view", + "signature": "unixTime()", + "selector": "0x625387dc", + "selectorBytes": [ + 98, + 83, + 135, + 220 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "warp", + "description": "Sets `block.timestamp`.", + "declaration": "function warp(uint256 newTimestamp) external;", + "visibility": "external", + "mutability": "", + "signature": "warp(uint256)", + "selector": "0xe5d6bf02", + "selectorBytes": [ + 229, + 214, + 191, + 2 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "writeFile", + "description": "Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does.\n`path` is relative to the project root.", + "declaration": "function writeFile(string calldata path, string calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "writeFile(string,string)", + "selector": "0x897e0a97", + "selectorBytes": [ + 137, + 126, + 10, + 151 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeFileBinary", + "description": "Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does.\n`path` is relative to the project root.", + "declaration": "function writeFileBinary(string calldata path, bytes calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "writeFileBinary(string,bytes)", + "selector": "0x1f21fc80", + "selectorBytes": [ + 31, + 33, + 252, + 128 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeJson_0", + "description": "Write a serialized JSON object to a file. If the file exists, it will be overwritten.", + "declaration": "function writeJson(string calldata json, string calldata path) external;", + "visibility": "external", + "mutability": "", + "signature": "writeJson(string,string)", + "selector": "0xe23cd19f", + "selectorBytes": [ + 226, + 60, + 209, + 159 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeJson_1", + "description": "Write a serialized JSON object to an **existing** JSON file, replacing a value with key = \nThis is useful to replace a specific value of a JSON file, without having to parse the entire thing.", + "declaration": "function writeJson(string calldata json, string calldata path, string calldata valueKey) external;", + "visibility": "external", + "mutability": "", + "signature": "writeJson(string,string,string)", + "selector": "0x35d6ad46", + "selectorBytes": [ + 53, + 214, + 173, + 70 + ] + }, + "group": "json", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeLine", + "description": "Writes line to file, creating a file if it does not exist.\n`path` is relative to the project root.", + "declaration": "function writeLine(string calldata path, string calldata data) external;", + "visibility": "external", + "mutability": "", + "signature": "writeLine(string,string)", + "selector": "0x619d897f", + "selectorBytes": [ + 97, + 157, + 137, + 127 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeToml_0", + "description": "Takes serialized JSON, converts to TOML and write a serialized TOML to a file.", + "declaration": "function writeToml(string calldata json, string calldata path) external;", + "visibility": "external", + "mutability": "", + "signature": "writeToml(string,string)", + "selector": "0xc0865ba7", + "selectorBytes": [ + 192, + 134, + 91, + 167 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "writeToml_1", + "description": "Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = \nThis is useful to replace a specific value of a TOML file, without having to parse the entire thing.", + "declaration": "function writeToml(string calldata json, string calldata path, string calldata valueKey) external;", + "visibility": "external", + "mutability": "", + "signature": "writeToml(string,string,string)", + "selector": "0x51ac6a33", + "selectorBytes": [ + 81, + 172, + 106, + 51 + ] + }, + "group": "toml", + "status": "stable", + "safety": "safe" + } + ] +} \ No newline at end of file diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json new file mode 100644 index 0000000000000..9ffc13d6121d8 --- /dev/null +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -0,0 +1,501 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Cheatcodes", + "description": "Foundry cheatcodes. Learn more: ", + "type": "object", + "required": [ + "cheatcodes", + "enums", + "errors", + "events", + "structs" + ], + "properties": { + "cheatcodes": { + "description": "All the cheatcodes.", + "type": "array", + "items": { + "$ref": "#/definitions/Cheatcode" + } + }, + "enums": { + "description": "Cheatcode enums.", + "type": "array", + "items": { + "$ref": "#/definitions/Enum" + } + }, + "errors": { + "description": "Cheatcode errors.", + "type": "array", + "items": { + "$ref": "#/definitions/Error" + } + }, + "events": { + "description": "Cheatcode events.", + "type": "array", + "items": { + "$ref": "#/definitions/Event" + } + }, + "structs": { + "description": "Cheatcode structs.", + "type": "array", + "items": { + "$ref": "#/definitions/Struct" + } + } + }, + "definitions": { + "Cheatcode": { + "description": "Specification of a single cheatcode. Extends [`Function`] with additional metadata.", + "type": "object", + "required": [ + "func", + "group", + "safety", + "status" + ], + "properties": { + "func": { + "description": "The Solidity function declaration.", + "allOf": [ + { + "$ref": "#/definitions/Function" + } + ] + }, + "group": { + "description": "The group that the cheatcode belongs to.", + "allOf": [ + { + "$ref": "#/definitions/Group" + } + ] + }, + "safety": { + "description": "Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an unexpected way.", + "allOf": [ + { + "$ref": "#/definitions/Safety" + } + ] + }, + "status": { + "description": "The current status of the cheatcode. E.g. whether it is stable or experimental, etc.", + "allOf": [ + { + "$ref": "#/definitions/Status" + } + ] + } + }, + "additionalProperties": false + }, + "Enum": { + "description": "A Solidity enumeration.", + "type": "object", + "required": [ + "description", + "name", + "variants" + ], + "properties": { + "description": { + "description": "The description of the enum. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "name": { + "description": "The name of the enum.", + "type": "string" + }, + "variants": { + "description": "The variants of the enum.", + "type": "array", + "items": { + "$ref": "#/definitions/EnumVariant" + } + } + } + }, + "EnumVariant": { + "description": "A variant of an [`Enum`].", + "type": "object", + "required": [ + "description", + "name" + ], + "properties": { + "description": { + "description": "The description of the variant. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "name": { + "description": "The name of the variant.", + "type": "string" + } + } + }, + "Error": { + "description": "A Solidity custom error.", + "type": "object", + "required": [ + "declaration", + "description", + "name" + ], + "properties": { + "declaration": { + "description": "The Solidity error declaration, including full type, parameter names, etc.", + "type": "string" + }, + "description": { + "description": "The description of the error. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "name": { + "description": "The name of the error.", + "type": "string" + } + } + }, + "Event": { + "description": "A Solidity event.", + "type": "object", + "required": [ + "declaration", + "description", + "name" + ], + "properties": { + "declaration": { + "description": "The Solidity event declaration, including full type, parameter names, etc.", + "type": "string" + }, + "description": { + "description": "The description of the event. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "name": { + "description": "The name of the event.", + "type": "string" + } + } + }, + "Function": { + "description": "Solidity function.", + "type": "object", + "required": [ + "declaration", + "description", + "id", + "mutability", + "selector", + "selectorBytes", + "signature", + "visibility" + ], + "properties": { + "declaration": { + "description": "The Solidity function declaration, including full type and parameter names, visibility, etc.", + "type": "string" + }, + "description": { + "description": "The description of the function. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "id": { + "description": "The function's unique identifier. This is the function name, optionally appended with an index if it is overloaded.", + "type": "string" + }, + "mutability": { + "description": "The Solidity function state mutability attribute.", + "allOf": [ + { + "$ref": "#/definitions/Mutability" + } + ] + }, + "selector": { + "description": "The hex-encoded, \"0x\"-prefixed 4-byte function selector, which is the Keccak-256 hash of `signature`.", + "type": "string" + }, + "selectorBytes": { + "description": "The 4-byte function selector as a byte array.", + "type": "array", + "items": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, + "maxItems": 4, + "minItems": 4 + }, + "signature": { + "description": "The standard function signature used to calculate `selector`. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector", + "type": "string" + }, + "visibility": { + "description": "The Solidity function visibility attribute. This is currently always `external`, but this may change in the future.", + "allOf": [ + { + "$ref": "#/definitions/Visibility" + } + ] + } + } + }, + "Group": { + "description": "Cheatcode groups. Initially derived and modified from inline comments in [`forge-std`'s `Vm.sol`][vmsol].\n\n[vmsol]: https://github.com/foundry-rs/forge-std/blob/dcb0d52bc4399d37a6545848e3b8f9d03c77b98d/src/Vm.sol", + "oneOf": [ + { + "description": "Cheatcodes that read from, or write to the current EVM execution state.\n\nExamples: any of the `record` cheatcodes, `chainId`, `coinbase`.\n\nSafety: ambiguous, depends on whether the cheatcode is read-only or not.", + "type": "string", + "enum": [ + "evm" + ] + }, + { + "description": "Cheatcodes that interact with how a test is run.\n\nExamples: `assume`, `skip`, `expectRevert`.\n\nSafety: ambiguous, depends on whether the cheatcode is read-only or not.", + "type": "string", + "enum": [ + "testing" + ] + }, + { + "description": "Cheatcodes that interact with how a script is run.\n\nExamples: `broadcast`, `startBroadcast`, `stopBroadcast`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "scripting" + ] + }, + { + "description": "Cheatcodes that interact with the OS or filesystem.\n\nExamples: `ffi`, `projectRoot`, `writeFile`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "filesystem" + ] + }, + { + "description": "Cheatcodes that interact with the program's environment variables.\n\nExamples: `setEnv`, `envBool`, `envOr`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "environment" + ] + }, + { + "description": "Utility cheatcodes that deal with string parsing and manipulation.\n\nExamples: `toString`. `parseBytes`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "string" + ] + }, + { + "description": "Utility cheatcodes that deal with parsing values from and converting values to JSON.\n\nExamples: `serializeJson`, `parseJsonUint`, `writeJson`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "json" + ] + }, + { + "description": "Utility cheatcodes that deal with parsing values from and converting values to TOML.\n\nExamples: `parseToml`, `writeToml`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "toml" + ] + }, + { + "description": "Cryptography-related cheatcodes.\n\nExamples: `sign*`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "crypto" + ] + }, + { + "description": "Generic, uncategorized utilities.\n\nExamples: `toString`, `parse*`, `serialize*`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "utilities" + ] + } + ] + }, + "Mutability": { + "description": "Solidity function state mutability attribute. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#state-mutability", + "oneOf": [ + { + "description": "Disallows modification or access of state.", + "type": "string", + "enum": [ + "pure" + ] + }, + { + "description": "Disallows modification of state.", + "type": "string", + "enum": [ + "view" + ] + }, + { + "description": "Allows modification of state.", + "type": "string", + "enum": [ + "" + ] + } + ] + }, + "Safety": { + "description": "Cheatcode safety.", + "oneOf": [ + { + "description": "The cheatcode is not safe to use in scripts.", + "type": "string", + "enum": [ + "unsafe" + ] + }, + { + "description": "The cheatcode is safe to use in scripts.", + "type": "string", + "enum": [ + "safe" + ] + } + ] + }, + "Status": { + "description": "The status of a cheatcode.", + "oneOf": [ + { + "description": "The cheatcode and its API is currently stable.", + "type": "string", + "enum": [ + "stable" + ] + }, + { + "description": "The cheatcode is unstable, meaning it may contain bugs and may break its API on any release.\n\nUse of experimental cheatcodes will result in a warning.", + "type": "string", + "enum": [ + "experimental" + ] + }, + { + "description": "The cheatcode has been deprecated, meaning it will be removed in a future release.\n\nContains the optional reason for deprecation.\n\nUse of deprecated cheatcodes is discouraged and will result in a warning.", + "type": "object", + "required": [ + "deprecated" + ], + "properties": { + "deprecated": { + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + { + "description": "The cheatcode has been removed and is no longer available for use.\n\nUse of removed cheatcodes will result in a hard error.", + "type": "string", + "enum": [ + "removed" + ] + }, + { + "description": "The cheatcode is only used internally for foundry testing and may be changed or removed at any time.\n\nUse of internal cheatcodes is discouraged and will result in a warning.", + "type": "string", + "enum": [ + "internal" + ] + } + ] + }, + "Struct": { + "description": "A Solidity struct.", + "type": "object", + "required": [ + "description", + "fields", + "name" + ], + "properties": { + "description": { + "description": "The description of the struct. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "fields": { + "description": "The fields of the struct.", + "type": "array", + "items": { + "$ref": "#/definitions/StructField" + } + }, + "name": { + "description": "The name of the struct.", + "type": "string" + } + } + }, + "StructField": { + "description": "A [`Struct`] field.", + "type": "object", + "required": [ + "description", + "name", + "ty" + ], + "properties": { + "description": { + "description": "The description of the field. This is a markdown string derived from the NatSpec documentation.", + "type": "string" + }, + "name": { + "description": "The name of the field.", + "type": "string" + }, + "ty": { + "description": "The type of the field.", + "type": "string" + } + } + }, + "Visibility": { + "description": "Solidity function visibility attribute. See the [Solidity docs] for more information.\n\n[Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#function-visibility", + "oneOf": [ + { + "description": "The function is only visible externally.", + "type": "string", + "enum": [ + "external" + ] + }, + { + "description": "Visible externally and internally.", + "type": "string", + "enum": [ + "public" + ] + }, + { + "description": "Only visible internally.", + "type": "string", + "enum": [ + "internal" + ] + }, + { + "description": "Only visible in the current contract", + "type": "string", + "enum": [ + "private" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/crates/cheatcodes/spec/Cargo.toml b/crates/cheatcodes/spec/Cargo.toml new file mode 100644 index 0000000000000..458f6b73067ad --- /dev/null +++ b/crates/cheatcodes/spec/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "foundry-cheatcodes-spec" +description = "Foundry cheatcodes specification" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-macros.workspace = true +alloy-sol-types = { workspace = true, features = ["json"] } +serde.workspace = true + +# schema +schemars = { version = "0.8", optional = true } + +[dev-dependencies] +serde_json.workspace = true + +[features] +schema = ["dep:schemars"] diff --git a/crates/cheatcodes/spec/src/cheatcode.rs b/crates/cheatcodes/spec/src/cheatcode.rs new file mode 100644 index 0000000000000..bce501d45d7bd --- /dev/null +++ b/crates/cheatcodes/spec/src/cheatcode.rs @@ -0,0 +1,200 @@ +use super::Function; +use alloy_sol_types::SolCall; +use serde::{Deserialize, Serialize}; + +/// Cheatcode definition trait. Implemented by all [`Vm`](crate::Vm) functions. +pub trait CheatcodeDef: std::fmt::Debug + Clone + SolCall { + /// The static cheatcode definition. + const CHEATCODE: &'static Cheatcode<'static>; +} + +/// Specification of a single cheatcode. Extends [`Function`] with additional metadata. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +#[non_exhaustive] +pub struct Cheatcode<'a> { + // Automatically-generated fields. + /// The Solidity function declaration. + #[serde(borrow)] + pub func: Function<'a>, + + // Manually-specified fields. + /// The group that the cheatcode belongs to. + pub group: Group, + /// The current status of the cheatcode. E.g. whether it is stable or experimental, etc. + pub status: Status<'a>, + /// Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an + /// unexpected way. + pub safety: Safety, +} + +/// The status of a cheatcode. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub enum Status<'a> { + /// The cheatcode and its API is currently stable. + Stable, + /// The cheatcode is unstable, meaning it may contain bugs and may break its API on any + /// release. + /// + /// Use of experimental cheatcodes will result in a warning. + Experimental, + /// The cheatcode has been deprecated, meaning it will be removed in a future release. + /// + /// Contains the optional reason for deprecation. + /// + /// Use of deprecated cheatcodes is discouraged and will result in a warning. + Deprecated(Option<&'a str>), + /// The cheatcode has been removed and is no longer available for use. + /// + /// Use of removed cheatcodes will result in a hard error. + Removed, + /// The cheatcode is only used internally for foundry testing and may be changed or removed at + /// any time. + /// + /// Use of internal cheatcodes is discouraged and will result in a warning. + Internal, +} + +/// Cheatcode groups. +/// Initially derived and modified from inline comments in [`forge-std`'s `Vm.sol`][vmsol]. +/// +/// [vmsol]: https://github.com/foundry-rs/forge-std/blob/dcb0d52bc4399d37a6545848e3b8f9d03c77b98d/src/Vm.sol +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub enum Group { + /// Cheatcodes that read from, or write to the current EVM execution state. + /// + /// Examples: any of the `record` cheatcodes, `chainId`, `coinbase`. + /// + /// Safety: ambiguous, depends on whether the cheatcode is read-only or not. + Evm, + /// Cheatcodes that interact with how a test is run. + /// + /// Examples: `assume`, `skip`, `expectRevert`. + /// + /// Safety: ambiguous, depends on whether the cheatcode is read-only or not. + Testing, + /// Cheatcodes that interact with how a script is run. + /// + /// Examples: `broadcast`, `startBroadcast`, `stopBroadcast`. + /// + /// Safety: safe. + Scripting, + /// Cheatcodes that interact with the OS or filesystem. + /// + /// Examples: `ffi`, `projectRoot`, `writeFile`. + /// + /// Safety: safe. + Filesystem, + /// Cheatcodes that interact with the program's environment variables. + /// + /// Examples: `setEnv`, `envBool`, `envOr`. + /// + /// Safety: safe. + Environment, + /// Utility cheatcodes that deal with string parsing and manipulation. + /// + /// Examples: `toString`. `parseBytes`. + /// + /// Safety: safe. + String, + /// Utility cheatcodes that deal with parsing values from and converting values to JSON. + /// + /// Examples: `serializeJson`, `parseJsonUint`, `writeJson`. + /// + /// Safety: safe. + Json, + /// Utility cheatcodes that deal with parsing values from and converting values to TOML. + /// + /// Examples: `parseToml`, `writeToml`. + /// + /// Safety: safe. + Toml, + /// Cryptography-related cheatcodes. + /// + /// Examples: `sign*`. + /// + /// Safety: safe. + Crypto, + /// Generic, uncategorized utilities. + /// + /// Examples: `toString`, `parse*`, `serialize*`. + /// + /// Safety: safe. + Utilities, +} + +impl Group { + /// Returns the safety of this cheatcode group. + /// + /// Some groups are inherently safe or unsafe, while others are ambiguous and will return + /// `None`. + #[inline] + pub const fn safety(self) -> Option { + match self { + Self::Evm | Self::Testing => None, + Self::Scripting | + Self::Filesystem | + Self::Environment | + Self::String | + Self::Json | + Self::Toml | + Self::Crypto | + Self::Utilities => Some(Safety::Safe), + } + } + + /// Returns this value as a string. + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Self::Evm => "evm", + Self::Testing => "testing", + Self::Scripting => "scripting", + Self::Filesystem => "filesystem", + Self::Environment => "environment", + Self::String => "string", + Self::Json => "json", + Self::Toml => "toml", + Self::Crypto => "crypto", + Self::Utilities => "utilities", + } + } +} + +// TODO: Find a better name for this +/// Cheatcode safety. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub enum Safety { + /// The cheatcode is not safe to use in scripts. + Unsafe, + /// The cheatcode is safe to use in scripts. + #[default] + Safe, +} + +impl Safety { + /// Returns this value as a string. + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Self::Safe => "safe", + Self::Unsafe => "unsafe", + } + } + + /// Returns whether this value is safe. + #[inline] + pub const fn is_safe(self) -> bool { + matches!(self, Self::Safe) + } +} diff --git a/crates/cheatcodes/spec/src/function.rs b/crates/cheatcodes/spec/src/function.rs new file mode 100644 index 0000000000000..4c747fe152ee5 --- /dev/null +++ b/crates/cheatcodes/spec/src/function.rs @@ -0,0 +1,110 @@ +use serde::{Deserialize, Serialize}; +use std::fmt; + +/// Solidity function. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +#[non_exhaustive] +pub struct Function<'a> { + /// The function's unique identifier. This is the function name, optionally appended with an + /// index if it is overloaded. + pub id: &'a str, + /// The description of the function. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, + /// The Solidity function declaration, including full type and parameter names, visibility, + /// etc. + pub declaration: &'a str, + /// The Solidity function visibility attribute. This is currently always `external`, but this + /// may change in the future. + pub visibility: Visibility, + /// The Solidity function state mutability attribute. + pub mutability: Mutability, + /// The standard function signature used to calculate `selector`. + /// See the [Solidity docs] for more information. + /// + /// [Solidity docs]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector + pub signature: &'a str, + /// The hex-encoded, "0x"-prefixed 4-byte function selector, + /// which is the Keccak-256 hash of `signature`. + pub selector: &'a str, + /// The 4-byte function selector as a byte array. + pub selector_bytes: [u8; 4], +} + +impl fmt::Display for Function<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.declaration) + } +} + +/// Solidity function visibility attribute. See the [Solidity docs] for more information. +/// +/// [Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#function-visibility +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub enum Visibility { + /// The function is only visible externally. + External, + /// Visible externally and internally. + Public, + /// Only visible internally. + Internal, + /// Only visible in the current contract + Private, +} + +impl fmt::Display for Visibility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl Visibility { + /// Returns the string representation of the visibility. + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Self::External => "external", + Self::Public => "public", + Self::Internal => "internal", + Self::Private => "private", + } + } +} + +/// Solidity function state mutability attribute. See the [Solidity docs] for more information. +/// +/// [Solidity docs]: https://docs.soliditylang.org/en/latest/contracts.html#state-mutability +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub enum Mutability { + /// Disallows modification or access of state. + Pure, + /// Disallows modification of state. + View, + /// Allows modification of state. + #[serde(rename = "")] + None, +} + +impl fmt::Display for Mutability { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +impl Mutability { + /// Returns the string representation of the mutability. + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Self::Pure => "pure", + Self::View => "view", + Self::None => "", + } + } +} diff --git a/crates/cheatcodes/spec/src/items.rs b/crates/cheatcodes/spec/src/items.rs new file mode 100644 index 0000000000000..5a32e534e34b8 --- /dev/null +++ b/crates/cheatcodes/spec/src/items.rs @@ -0,0 +1,121 @@ +use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, fmt}; + +/// A Solidity custom error. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct Error<'a> { + /// The name of the error. + pub name: &'a str, + /// The description of the error. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, + /// The Solidity error declaration, including full type, parameter names, etc. + pub declaration: &'a str, +} + +impl fmt::Display for Error<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.declaration) + } +} + +/// A Solidity event. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct Event<'a> { + /// The name of the event. + pub name: &'a str, + /// The description of the event. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, + /// The Solidity event declaration, including full type, parameter names, etc. + pub declaration: &'a str, +} + +impl fmt::Display for Event<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.declaration) + } +} + +/// A Solidity enumeration. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct Enum<'a> { + /// The name of the enum. + pub name: &'a str, + /// The description of the enum. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, + /// The variants of the enum. + #[serde(borrow)] + pub variants: Cow<'a, [EnumVariant<'a>]>, +} + +impl fmt::Display for Enum<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "enum {} {{ ", self.name)?; + for (i, variant) in self.variants.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + f.write_str(variant.name)?; + } + f.write_str(" }") + } +} + +/// A variant of an [`Enum`]. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct EnumVariant<'a> { + /// The name of the variant. + pub name: &'a str, + /// The description of the variant. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, +} + +/// A Solidity struct. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct Struct<'a> { + /// The name of the struct. + pub name: &'a str, + /// The description of the struct. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, + /// The fields of the struct. + #[serde(borrow)] + pub fields: Cow<'a, [StructField<'a>]>, +} + +impl fmt::Display for Struct<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "struct {} {{ ", self.name)?; + for field in self.fields.iter() { + write!(f, "{} {}; ", field.ty, field.name)?; + } + f.write_str("}") + } +} + +/// A [`Struct`] field. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct StructField<'a> { + /// The name of the field. + pub name: &'a str, + /// The type of the field. + pub ty: &'a str, + /// The description of the field. + /// This is a markdown string derived from the NatSpec documentation. + pub description: &'a str, +} diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs new file mode 100644 index 0000000000000..6dd6ee769f6b9 --- /dev/null +++ b/crates/cheatcodes/spec/src/lib.rs @@ -0,0 +1,192 @@ +//! Cheatcode specification for Foundry. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, fmt}; + +mod cheatcode; +pub use cheatcode::{Cheatcode, CheatcodeDef, Group, Safety, Status}; + +mod function; +pub use function::{Function, Mutability, Visibility}; + +mod items; +pub use items::{Enum, EnumVariant, Error, Event, Struct, StructField}; + +mod vm; +pub use vm::Vm; + +// The `cheatcodes.json` schema. +/// Foundry cheatcodes. Learn more: +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct Cheatcodes<'a> { + /// Cheatcode errors. + #[serde(borrow)] + pub errors: Cow<'a, [Error<'a>]>, + /// Cheatcode events. + #[serde(borrow)] + pub events: Cow<'a, [Event<'a>]>, + /// Cheatcode enums. + #[serde(borrow)] + pub enums: Cow<'a, [Enum<'a>]>, + /// Cheatcode structs. + #[serde(borrow)] + pub structs: Cow<'a, [Struct<'a>]>, + /// All the cheatcodes. + #[serde(borrow)] + pub cheatcodes: Cow<'a, [Cheatcode<'a>]>, +} + +impl fmt::Display for Cheatcodes<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for error in self.errors.iter() { + writeln!(f, "{error}")?; + } + for event in self.events.iter() { + writeln!(f, "{event}")?; + } + for enumm in self.enums.iter() { + writeln!(f, "{enumm}")?; + } + for strukt in self.structs.iter() { + writeln!(f, "{strukt}")?; + } + for cheatcode in self.cheatcodes.iter() { + writeln!(f, "{}", cheatcode.func)?; + } + Ok(()) + } +} + +impl Default for Cheatcodes<'static> { + fn default() -> Self { + Self::new() + } +} + +impl Cheatcodes<'static> { + /// Returns the default cheatcodes. + pub fn new() -> Self { + Self { + // unfortunately technology has not yet advanced to the point where we can get all + // items of a certain type in a module, so we have to hardcode them here + structs: Cow::Owned(vec![ + Vm::Log::STRUCT.clone(), + Vm::Rpc::STRUCT.clone(), + Vm::EthGetLogs::STRUCT.clone(), + Vm::DirEntry::STRUCT.clone(), + Vm::FsMetadata::STRUCT.clone(), + Vm::Wallet::STRUCT.clone(), + Vm::FfiResult::STRUCT.clone(), + Vm::ChainInfo::STRUCT.clone(), + Vm::AccountAccess::STRUCT.clone(), + Vm::StorageAccess::STRUCT.clone(), + Vm::Gas::STRUCT.clone(), + Vm::DebugStep::STRUCT.clone(), + Vm::BroadcastTxSummary::STRUCT.clone(), + Vm::SignedDelegation::STRUCT.clone(), + Vm::PotentialRevert::STRUCT.clone(), + ]), + enums: Cow::Owned(vec![ + Vm::CallerMode::ENUM.clone(), + Vm::AccountAccessKind::ENUM.clone(), + Vm::ForgeContext::ENUM.clone(), + Vm::BroadcastTxType::ENUM.clone(), + ]), + errors: Vm::VM_ERRORS.iter().copied().cloned().collect(), + events: Cow::Borrowed(&[]), + // events: Vm::VM_EVENTS.iter().copied().cloned().collect(), + cheatcodes: Vm::CHEATCODES.iter().copied().cloned().collect(), + } + } +} + +#[cfg(test)] +#[allow(clippy::disallowed_macros)] +mod tests { + use super::*; + use std::{fs, path::Path}; + + const JSON_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.json"); + #[cfg(feature = "schema")] + const SCHEMA_PATH: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/cheatcodes.schema.json"); + const IFACE_PATH: &str = + concat!(env!("CARGO_MANIFEST_DIR"), "/../../../testdata/cheats/Vm.sol"); + + /// Generates the `cheatcodes.json` file contents. + fn json_cheatcodes() -> String { + serde_json::to_string_pretty(&Cheatcodes::new()).unwrap() + } + + /// Generates the [cheatcodes](json_cheatcodes) JSON schema. + #[cfg(feature = "schema")] + fn json_schema() -> String { + serde_json::to_string_pretty(&schemars::schema_for!(Cheatcodes<'_>)).unwrap() + } + + fn sol_iface() -> String { + let mut cheats = Cheatcodes::new(); + cheats.errors = Default::default(); // Skip errors to allow <0.8.4. + let cheats = cheats.to_string().trim().replace('\n', "\n "); + format!( + "\ +// Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually. +// This interface is just for internal testing purposes. Use `forge-std` instead. + +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +interface Vm {{ + {cheats} +}} +" + ) + } + + #[test] + fn spec_up_to_date() { + ensure_file_contents(Path::new(JSON_PATH), &json_cheatcodes()); + } + + #[test] + #[cfg(feature = "schema")] + fn schema_up_to_date() { + ensure_file_contents(Path::new(SCHEMA_PATH), &json_schema()); + } + + #[test] + fn iface_up_to_date() { + ensure_file_contents(Path::new(IFACE_PATH), &sol_iface()); + } + + /// Checks that the `file` has the specified `contents`. If that is not the + /// case, updates the file and then fails the test. + fn ensure_file_contents(file: &Path, contents: &str) { + if let Ok(old_contents) = fs::read_to_string(file) { + if normalize_newlines(&old_contents) == normalize_newlines(contents) { + // File is already up to date. + return + } + } + + eprintln!("\n\x1b[31;1merror\x1b[0m: {} was not up-to-date, updating\n", file.display()); + if std::env::var("CI").is_ok() { + eprintln!(" NOTE: run `cargo cheats` locally and commit the updated files\n"); + } + if let Some(parent) = file.parent() { + let _ = fs::create_dir_all(parent); + } + fs::write(file, contents).unwrap(); + panic!("some file was not up to date and has been updated, simply re-run the tests"); + } + + fn normalize_newlines(s: &str) -> String { + s.replace("\r\n", "\n") + } +} diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs new file mode 100644 index 0000000000000..f2eaeb697a183 --- /dev/null +++ b/crates/cheatcodes/spec/src/vm.rs @@ -0,0 +1,2791 @@ +// We don't document function parameters individually so we can't enable `missing_docs` for this +// module. Instead, we emit custom diagnostics in `#[derive(Cheatcode)]`. +#![allow(missing_docs)] + +use super::*; +use crate::Vm::ForgeContext; +use alloy_sol_types::sol; +use foundry_macros::Cheatcode; +use std::fmt; + +sol! { +// Cheatcodes are marked as view/pure/none using the following rules: +// 0. A call's observable behaviour includes its return value, logs, reverts and state writes, +// 1. If you can influence a later call's observable behaviour, you're neither `view` nor `pure` +// (you are modifying some state be it the EVM, interpreter, filesystem, etc), +// 2. Otherwise if you can be influenced by an earlier call, or if reading some state, you're `view`, +// 3. Otherwise you're `pure`. + +/// Foundry cheatcodes interface. +#[derive(Debug, Cheatcode)] // Keep this list small to avoid unnecessary bloat. +#[sol(abi)] +interface Vm { + // ======== Types ======== + + /// Error thrown by cheatcodes. + error CheatcodeError(string message); + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode { + /// No caller modification is currently active. + None, + /// A one time broadcast triggered by a `vm.broadcast()` call is currently active. + Broadcast, + /// A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active. + RecurrentBroadcast, + /// A one time prank triggered by a `vm.prank()` call is currently active. + Prank, + /// A recurrent prank triggered by a `vm.startPrank()` call is currently active. + RecurrentPrank, + } + + /// The kind of account access that occurred. + enum AccountAccessKind { + /// The account was called. + Call, + /// The account was called via delegatecall. + DelegateCall, + /// The account was called via callcode. + CallCode, + /// The account was called via staticcall. + StaticCall, + /// The account was created. + Create, + /// The account was selfdestructed. + SelfDestruct, + /// Synthetic access indicating the current context has resumed after a previous sub-context (AccountAccess). + Resume, + /// The account's balance was read. + Balance, + /// The account's codesize was read. + Extcodesize, + /// The account's codehash was read. + Extcodehash, + /// The account's code was copied. + Extcodecopy, + } + + /// Forge execution contexts. + enum ForgeContext { + /// Test group execution context (test, coverage or snapshot). + TestGroup, + /// `forge test` execution context. + Test, + /// `forge coverage` execution context. + Coverage, + /// `forge snapshot` execution context. + Snapshot, + /// Script group execution context (dry run, broadcast or resume). + ScriptGroup, + /// `forge script` execution context. + ScriptDryRun, + /// `forge script --broadcast` execution context. + ScriptBroadcast, + /// `forge script --resume` execution context. + ScriptResume, + /// Unknown `forge` execution context. + Unknown, + } + + /// An Ethereum log. Returned by `getRecordedLogs`. + struct Log { + /// The topics of the log, including the signature, if any. + bytes32[] topics; + /// The raw data of the log. + bytes data; + /// The address of the log's emitter. + address emitter; + } + + /// Gas used. Returned by `lastCallGas`. + struct Gas { + /// The gas limit of the call. + uint64 gasLimit; + /// The total gas used. + uint64 gasTotalUsed; + /// DEPRECATED: The amount of gas used for memory expansion. Ref: + uint64 gasMemoryUsed; + /// The amount of gas refunded. + int64 gasRefunded; + /// The amount of gas remaining. + uint64 gasRemaining; + } + + /// An RPC URL and its alias. Returned by `rpcUrlStructs`. + struct Rpc { + /// The alias of the RPC URL. + string key; + /// The RPC URL. + string url; + } + + /// An RPC log object. Returned by `eth_getLogs`. + struct EthGetLogs { + /// The address of the log's emitter. + address emitter; + /// The topics of the log, including the signature, if any. + bytes32[] topics; + /// The raw data of the log. + bytes data; + /// The block hash. + bytes32 blockHash; + /// The block number. + uint64 blockNumber; + /// The transaction hash. + bytes32 transactionHash; + /// The transaction index in the block. + uint64 transactionIndex; + /// The log index. + uint256 logIndex; + /// Whether the log was removed. + bool removed; + } + + /// A single entry in a directory listing. Returned by `readDir`. + struct DirEntry { + /// The error message, if any. + string errorMessage; + /// The path of the entry. + string path; + /// The depth of the entry. + uint64 depth; + /// Whether the entry is a directory. + bool isDir; + /// Whether the entry is a symlink. + bool isSymlink; + } + + /// Metadata information about a file. + /// + /// This structure is returned from the `fsMetadata` function and represents known + /// metadata about a file such as its permissions, size, modification + /// times, etc. + struct FsMetadata { + /// True if this metadata is for a directory. + bool isDir; + /// True if this metadata is for a symlink. + bool isSymlink; + /// The size of the file, in bytes, this metadata is for. + uint256 length; + /// True if this metadata is for a readonly (unwritable) file. + bool readOnly; + /// The last modification time listed in this metadata. + uint256 modified; + /// The last access time of this metadata. + uint256 accessed; + /// The creation time listed in this metadata. + uint256 created; + } + + /// A wallet with a public and private key. + struct Wallet { + /// The wallet's address. + address addr; + /// The wallet's public key `X`. + uint256 publicKeyX; + /// The wallet's public key `Y`. + uint256 publicKeyY; + /// The wallet's private key. + uint256 privateKey; + } + + /// The result of a `tryFfi` call. + struct FfiResult { + /// The exit code of the call. + int32 exitCode; + /// The optionally hex-decoded `stdout` data. + bytes stdout; + /// The `stderr` data. + bytes stderr; + } + + /// Information on the chain and fork. + struct ChainInfo { + /// The fork identifier. Set to zero if no fork is active. + uint256 forkId; + /// The chain ID of the current fork. + uint256 chainId; + } + + /// The storage accessed during an `AccountAccess`. + struct StorageAccess { + /// The account whose storage was accessed. + address account; + /// The slot that was accessed. + bytes32 slot; + /// If the access was a write. + bool isWrite; + /// The previous value of the slot. + bytes32 previousValue; + /// The new value of the slot. + bytes32 newValue; + /// If the access was reverted. + bool reverted; + } + + /// The result of a `stopAndReturnStateDiff` call. + struct AccountAccess { + /// The chain and fork the access occurred. + ChainInfo chainInfo; + /// The kind of account access that determines what the account is. + /// If kind is Call, DelegateCall, StaticCall or CallCode, then the account is the callee. + /// If kind is Create, then the account is the newly created account. + /// If kind is SelfDestruct, then the account is the selfdestruct recipient. + /// If kind is a Resume, then account represents a account context that has resumed. + AccountAccessKind kind; + /// The account that was accessed. + /// It's either the account created, callee or a selfdestruct recipient for CREATE, CALL or SELFDESTRUCT. + address account; + /// What accessed the account. + address accessor; + /// If the account was initialized or empty prior to the access. + /// An account is considered initialized if it has code, a + /// non-zero nonce, or a non-zero balance. + bool initialized; + /// The previous balance of the accessed account. + uint256 oldBalance; + /// The potential new balance of the accessed account. + /// That is, all balance changes are recorded here, even if reverts occurred. + uint256 newBalance; + /// Code of the account deployed by CREATE. + bytes deployedCode; + /// Value passed along with the account access + uint256 value; + /// Input data provided to the CREATE or CALL + bytes data; + /// If this access reverted in either the current or parent context. + bool reverted; + /// An ordered list of storage accesses made during an account access operation. + StorageAccess[] storageAccesses; + /// Call depth traversed during the recording of state differences + uint64 depth; + } + + /// The result of the `stopDebugTraceRecording` call + struct DebugStep { + /// The stack before executing the step of the run. + /// stack\[0\] represents the top of the stack. + /// and only stack data relevant to the opcode execution is contained. + uint256[] stack; + /// The memory input data before executing the step of the run. + /// only input data relevant to the opcode execution is contained. + /// + /// e.g. for MLOAD, it will have memory\[offset:offset+32\] copied here. + /// the offset value can be get by the stack data. + bytes memoryInput; + /// The opcode that was accessed. + uint8 opcode; + /// The call depth of the step. + uint64 depth; + /// Whether the call end up with out of gas error. + bool isOutOfGas; + /// The contract address where the opcode is running + address contractAddr; + } + + /// The transaction type (`txType`) of the broadcast. + enum BroadcastTxType { + /// Represents a CALL broadcast tx. + Call, + /// Represents a CREATE broadcast tx. + Create, + /// Represents a CREATE2 broadcast tx. + Create2 + } + + /// Represents a transaction's broadcast details. + struct BroadcastTxSummary { + /// The hash of the transaction that was broadcasted + bytes32 txHash; + /// Represent the type of transaction among CALL, CREATE, CREATE2 + BroadcastTxType txType; + /// The address of the contract that was called or created. + /// This is address of the contract that is created if the txType is CREATE or CREATE2. + address contractAddress; + /// The block number the transaction landed in. + uint64 blockNumber; + /// Status of the transaction, retrieved from the transaction receipt. + bool success; + } + + /// Holds a signed EIP-7702 authorization for an authority account to delegate to an implementation. + struct SignedDelegation { + /// The y-parity of the recovered secp256k1 signature (0 or 1). + uint8 v; + /// First 32 bytes of the signature. + bytes32 r; + /// Second 32 bytes of the signature. + bytes32 s; + /// The current nonce of the authority account at signing time. + /// Used to ensure signature can't be replayed after account nonce changes. + uint64 nonce; + /// Address of the contract implementation that will be delegated to. + /// Gets encoded into delegation code: 0xef0100 || implementation. + address implementation; + } + + /// Represents a "potential" revert reason from a single subsequent call when using `vm.assumeNoReverts`. + /// Reverts that match will result in a FOUNDRY::ASSUME rejection, whereas unmatched reverts will be surfaced + /// as normal. + struct PotentialRevert { + /// The allowed origin of the revert opcode; address(0) allows reverts from any address + address reverter; + /// When true, only matches on the beginning of the revert data, otherwise, matches on entire revert data + bool partialMatch; + /// The data to use to match encountered reverts + bytes revertData; + } + + // ======== EVM ======== + + /// Gets the address for a given private key. + #[cheatcode(group = Evm, safety = Safe)] + function addr(uint256 privateKey) external pure returns (address keyAddr); + + /// Dump a genesis JSON file's `allocs` to disk. + #[cheatcode(group = Evm, safety = Unsafe)] + function dumpState(string calldata pathToStateJson) external; + + /// Gets the nonce of an account. + #[cheatcode(group = Evm, safety = Safe)] + function getNonce(address account) external view returns (uint64 nonce); + + /// Get the nonce of a `Wallet`. + #[cheatcode(group = Evm, safety = Safe)] + function getNonce(Wallet calldata wallet) external returns (uint64 nonce); + + /// Loads a storage slot from an address. + #[cheatcode(group = Evm, safety = Safe)] + function load(address target, bytes32 slot) external view returns (bytes32 data); + + /// Load a genesis JSON file's `allocs` into the in-memory EVM state. + #[cheatcode(group = Evm, safety = Unsafe)] + function loadAllocs(string calldata pathToAllocsJson) external; + + // -------- Record Debug Traces -------- + + /// Records the debug trace during the run. + #[cheatcode(group = Evm, safety = Safe)] + function startDebugTraceRecording() external; + + /// Stop debug trace recording and returns the recorded debug trace. + #[cheatcode(group = Evm, safety = Safe)] + function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step); + + + /// Clones a source account code, state, balance and nonce to a target account and updates in-memory EVM state. + #[cheatcode(group = Evm, safety = Unsafe)] + function cloneAccount(address source, address target) external; + + // -------- Record Storage -------- + + /// Records all storage reads and writes. + #[cheatcode(group = Evm, safety = Safe)] + function record() external; + + /// Gets all accessed reads and write slot from a `vm.record` session, for a given address. + #[cheatcode(group = Evm, safety = Safe)] + function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); + + /// Record all account accesses as part of CREATE, CALL or SELFDESTRUCT opcodes in order, + /// along with the context of the calls + #[cheatcode(group = Evm, safety = Safe)] + function startStateDiffRecording() external; + + /// Returns an ordered array of all account accesses from a `vm.startStateDiffRecording` session. + #[cheatcode(group = Evm, safety = Safe)] + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); + + /// Returns state diffs from current `vm.startStateDiffRecording` session. + #[cheatcode(group = Evm, safety = Safe)] + function getStateDiff() external view returns (string memory diff); + + /// Returns state diffs from current `vm.startStateDiffRecording` session, in json format. + #[cheatcode(group = Evm, safety = Safe)] + function getStateDiffJson() external view returns (string memory diff); + + // -------- Recording Map Writes -------- + + /// Starts recording all map SSTOREs for later retrieval. + #[cheatcode(group = Evm, safety = Safe)] + function startMappingRecording() external; + + /// Stops recording all map SSTOREs for later retrieval and clears the recorded data. + #[cheatcode(group = Evm, safety = Safe)] + function stopMappingRecording() external; + + /// Gets the number of elements in the mapping at the given slot, for a given address. + #[cheatcode(group = Evm, safety = Safe)] + function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); + + /// Gets the elements at index idx of the mapping at the given slot, for a given address. The + /// index must be less than the length of the mapping (i.e. the number of keys in the mapping). + #[cheatcode(group = Evm, safety = Safe)] + function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); + + /// Gets the map key and parent of a mapping at a given slot, for a given address. + #[cheatcode(group = Evm, safety = Safe)] + function getMappingKeyAndParentOf(address target, bytes32 elementSlot) + external + returns (bool found, bytes32 key, bytes32 parent); + + // -------- Block and Transaction Properties -------- + + /// Sets `block.chainid`. + #[cheatcode(group = Evm, safety = Unsafe)] + function chainId(uint256 newChainId) external; + + /// Sets `block.coinbase`. + #[cheatcode(group = Evm, safety = Unsafe)] + function coinbase(address newCoinbase) external; + + /// Sets `block.difficulty`. + /// Not available on EVM versions from Paris onwards. Use `prevrandao` instead. + /// Reverts if used on unsupported EVM versions. + #[cheatcode(group = Evm, safety = Unsafe)] + function difficulty(uint256 newDifficulty) external; + + /// Sets `block.basefee`. + #[cheatcode(group = Evm, safety = Unsafe)] + function fee(uint256 newBasefee) external; + + /// Sets `block.prevrandao`. + /// Not available on EVM versions before Paris. Use `difficulty` instead. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function prevrandao(bytes32 newPrevrandao) external; + /// Sets `block.prevrandao`. + /// Not available on EVM versions before Paris. Use `difficulty` instead. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function prevrandao(uint256 newPrevrandao) external; + + /// Sets the blobhashes in the transaction. + /// Not available on EVM versions before Cancun. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function blobhashes(bytes32[] calldata hashes) external; + + /// Gets the blockhashes from the current transaction. + /// Not available on EVM versions before Cancun. + /// If used on unsupported EVM versions it will revert. + #[cheatcode(group = Evm, safety = Unsafe)] + function getBlobhashes() external view returns (bytes32[] memory hashes); + + /// Sets `block.height`. + #[cheatcode(group = Evm, safety = Unsafe)] + function roll(uint256 newHeight) external; + + /// Gets the current `block.number`. + /// You should use this instead of `block.number` if you use `vm.roll`, as `block.number` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getBlockNumber() external view returns (uint256 height); + + /// Sets `tx.gasprice`. + #[cheatcode(group = Evm, safety = Unsafe)] + function txGasPrice(uint256 newGasPrice) external; + + /// Sets `block.timestamp`. + #[cheatcode(group = Evm, safety = Unsafe)] + function warp(uint256 newTimestamp) external; + + /// Gets the current `block.timestamp`. + /// You should use this instead of `block.timestamp` if you use `vm.warp`, as `block.timestamp` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getBlockTimestamp() external view returns (uint256 timestamp); + + /// Sets `block.blobbasefee` + #[cheatcode(group = Evm, safety = Unsafe)] + function blobBaseFee(uint256 newBlobBaseFee) external; + + /// Gets the current `block.blobbasefee`. + /// You should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function 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. + #[cheatcode(group = Evm, safety = Unsafe)] + function deal(address account, uint256 newBalance) external; + + /// Sets an address' code. + #[cheatcode(group = Evm, safety = Unsafe)] + function etch(address target, bytes calldata newRuntimeBytecode) external; + + /// Resets the nonce of an account to 0 for EOAs and 1 for contract accounts. + #[cheatcode(group = Evm, safety = Unsafe)] + function resetNonce(address account) external; + + /// Sets the nonce of an account. Must be higher than the current nonce of the account. + #[cheatcode(group = Evm, safety = Unsafe)] + function setNonce(address account, uint64 newNonce) external; + + /// Sets the nonce of an account to an arbitrary value. + #[cheatcode(group = Evm, safety = Unsafe)] + function setNonceUnsafe(address account, uint64 newNonce) external; + + /// Stores a value to an address' storage slot. + #[cheatcode(group = Evm, safety = Unsafe)] + function store(address target, bytes32 slot, bytes32 value) external; + + /// Marks the slots of an account and the account address as cold. + #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] + function cool(address target) external; + + // -------- Call Manipulation -------- + // --- Mocks --- + + /// Clears all mocked calls. + #[cheatcode(group = Evm, safety = Unsafe)] + function clearMockedCalls() external; + + /// Mocks a call to an address, returning specified data. + /// Calldata can either be strict or a partial match, e.g. if you only + /// pass a Solidity selector to the expected calldata, then the entire Solidity + /// function will be mocked. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCall(address callee, bytes calldata data, bytes calldata returnData) external; + + /// Mocks a call to an address with a specific `msg.value`, returning specified data. + /// Calldata match takes precedence over `msg.value` in case of ambiguity. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; + + /// Mocks a call to an address, returning specified data. + /// Calldata can either be strict or a partial match, e.g. if you only + /// pass a Solidity selector to the expected calldata, then the entire Solidity + /// function will be mocked. + /// + /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCall(address callee, bytes4 data, bytes calldata returnData) external; + + /// Mocks a call to an address with a specific `msg.value`, returning specified data. + /// Calldata match takes precedence over `msg.value` in case of ambiguity. + /// + /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCall(address callee, uint256 msgValue, bytes4 data, bytes calldata returnData) external; + + /// Mocks multiple calls to an address, returning specified data for each call. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external; + + /// Mocks multiple calls to an address with a specific `msg.value`, returning specified data for each call. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external; + + /// Reverts a call to an address with specified revert data. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external; + + /// Reverts a call to an address with a specific `msg.value`, with specified revert data. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) + external; + + /// Reverts a call to an address with specified revert data. + /// + /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCallRevert(address callee, bytes4 data, bytes calldata revertData) external; + + /// Reverts a call to an address with a specific `msg.value`, with specified revert data. + /// + /// Overload to pass the function selector directly `token.approve.selector` instead of `abi.encodeWithSelector(token.approve.selector)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockCallRevert(address callee, uint256 msgValue, bytes4 data, bytes calldata revertData) + external; + + /// Whenever a call is made to `callee` with calldata `data`, this cheatcode instead calls + /// `target` with the same calldata. This functionality is similar to a delegate call made to + /// `target` contract from `callee`. + /// Can be used to substitute a call to a function with another implementation that captures + /// the primary logic of the original function but is easier to reason about. + /// If calldata is not a strict match then partial match by selector is attempted. + #[cheatcode(group = Evm, safety = Unsafe)] + function mockFunction(address callee, address target, bytes calldata data) external; + + // --- Impersonation (pranks) --- + + /// Sets the *next* call's `msg.sender` to be the input address. + #[cheatcode(group = Evm, safety = Unsafe)] + function prank(address msgSender) external; + + /// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called. + #[cheatcode(group = Evm, safety = Unsafe)] + function startPrank(address msgSender) external; + + /// Sets the *next* call's `msg.sender` to be the input address, and the `tx.origin` to be the second input. + #[cheatcode(group = Evm, safety = Unsafe)] + function prank(address msgSender, address txOrigin) external; + + /// Sets all subsequent calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input. + #[cheatcode(group = Evm, safety = Unsafe)] + function startPrank(address msgSender, address txOrigin) external; + + /// Sets the *next* delegate call's `msg.sender` to be the input address. + #[cheatcode(group = Evm, safety = Unsafe)] + function prank(address msgSender, bool delegateCall) external; + + /// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called. + #[cheatcode(group = Evm, safety = Unsafe)] + function startPrank(address msgSender, bool delegateCall) external; + + /// Sets the *next* delegate call's `msg.sender` to be the input address, and the `tx.origin` to be the second input. + #[cheatcode(group = Evm, safety = Unsafe)] + function prank(address msgSender, address txOrigin, bool delegateCall) external; + + /// Sets all subsequent delegate calls' `msg.sender` to be the input address until `stopPrank` is called, and the `tx.origin` to be the second input. + #[cheatcode(group = Evm, safety = Unsafe)] + function startPrank(address msgSender, address txOrigin, bool delegateCall) external; + + /// Resets subsequent calls' `msg.sender` to be `address(this)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopPrank() external; + + /// Reads the current `msg.sender` and `tx.origin` from state and reports if there is any active caller modification. + #[cheatcode(group = Evm, safety = Unsafe)] + function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + + // ----- Arbitrary Snapshots ----- + + /// Snapshot capture an arbitrary numerical value by name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata name, uint256 value) external; + + /// Snapshot capture an arbitrary numerical value by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata group, string calldata name, uint256 value) external; + + // -------- Gas Snapshots -------- + + /// Snapshot capture the gas usage of the last call by name from the callee perspective. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed); + + /// Snapshot capture the gas usage of the last call by name in a group from the callee perspective. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed); + + /// Start a snapshot capture of the current gas usage by name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata name) external; + + /// Start a snapshot capture of the current gas usage by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata group, string calldata name) external; + + /// Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas() external returns (uint256 gasUsed); + + /// Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); + + /// Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); + + // -------- State Snapshots -------- + + /// `snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `snapshotState`")))] + function snapshot() external returns (uint256 snapshotId); + + /// Snapshot the current state of the evm. + /// Returns the ID of the snapshot that was created. + /// To revert a snapshot use `revertToState`. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotState() external returns (uint256 snapshotId); + + /// `revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `revertToState`")))] + function revertTo(uint256 snapshotId) external returns (bool success); + + /// Revert the state of the EVM to a previous snapshot + /// Takes the snapshot ID to revert to. + /// + /// Returns `true` if the snapshot was successfully reverted. + /// Returns `false` if the snapshot does not exist. + /// + /// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`. + #[cheatcode(group = Evm, safety = Unsafe)] + function revertToState(uint256 snapshotId) external returns (bool success); + + /// `revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `revertToStateAndDelete`")))] + function revertToAndDelete(uint256 snapshotId) external returns (bool success); + + /// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots + /// Takes the snapshot ID to revert to. + /// + /// Returns `true` if the snapshot was successfully reverted and deleted. + /// Returns `false` if the snapshot does not exist. + #[cheatcode(group = Evm, safety = Unsafe)] + function revertToStateAndDelete(uint256 snapshotId) external returns (bool success); + + /// `deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `deleteStateSnapshot`")))] + function deleteSnapshot(uint256 snapshotId) external returns (bool success); + + /// Removes the snapshot with the given ID created by `snapshot`. + /// Takes the snapshot ID to delete. + /// + /// Returns `true` if the snapshot was successfully deleted. + /// Returns `false` if the snapshot does not exist. + #[cheatcode(group = Evm, safety = Unsafe)] + function deleteStateSnapshot(uint256 snapshotId) external returns (bool success); + + /// `deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `deleteStateSnapshots`")))] + function deleteSnapshots() external; + + /// Removes _all_ snapshots previously created by `snapshot`. + #[cheatcode(group = Evm, safety = Unsafe)] + function deleteStateSnapshots() external; + + // -------- Forking -------- + // --- Creation and Selection --- + + /// Returns the identifier of the currently active fork. Reverts if no fork is currently active. + #[cheatcode(group = Evm, safety = Unsafe)] + function activeFork() external view returns (uint256 forkId); + + /// Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function createFork(string calldata urlOrAlias) external returns (uint256 forkId); + /// Creates a new fork with the given endpoint and block and returns the identifier of the fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); + /// Creates a new fork with the given endpoint and at the block the given transaction was mined in, + /// replays all transaction mined in the block before the transaction, and returns the identifier of the fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); + + /// Creates and also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId); + /// Creates and also selects a new fork with the given endpoint and block and returns the identifier of the fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); + /// Creates and also selects new fork with the given endpoint and at the block the given transaction was mined in, + /// replays all transaction mined in the block before the transaction, returns the identifier of the fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); + + /// Updates the currently active fork to given block number + /// This is similar to `roll` but for the currently active fork. + #[cheatcode(group = Evm, safety = Unsafe)] + function rollFork(uint256 blockNumber) external; + /// Updates the currently active fork to given transaction. This will `rollFork` with the number + /// of the block the transaction was mined in and replays all transaction mined before it in the block. + #[cheatcode(group = Evm, safety = Unsafe)] + function rollFork(bytes32 txHash) external; + /// Updates the given fork to given block number. + #[cheatcode(group = Evm, safety = Unsafe)] + function rollFork(uint256 forkId, uint256 blockNumber) external; + /// Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block. + #[cheatcode(group = Evm, safety = Unsafe)] + function rollFork(uint256 forkId, bytes32 txHash) external; + + /// Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. + #[cheatcode(group = Evm, safety = Unsafe)] + function selectFork(uint256 forkId) external; + + /// Fetches the given transaction from the active fork and executes it on the current state. + #[cheatcode(group = Evm, safety = Unsafe)] + function transact(bytes32 txHash) external; + /// Fetches the given transaction from the given fork and executes it on the current state. + #[cheatcode(group = Evm, safety = Unsafe)] + function transact(uint256 forkId, bytes32 txHash) external; + + /// Performs an Ethereum JSON-RPC request to the current fork URL. + #[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[] calldata topics) + external + returns (EthGetLogs[] memory logs); + + // --- Behavior --- + + /// In forking mode, explicitly grant the given address cheatcode access. + #[cheatcode(group = Evm, safety = Unsafe)] + function allowCheatcodes(address account) external; + + /// Marks that the account(s) should use persistent storage across fork swaps in a multifork setup + /// Meaning, changes made to the state of this account will be kept when switching forks. + #[cheatcode(group = Evm, safety = Unsafe)] + function makePersistent(address account) external; + /// See `makePersistent(address)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function makePersistent(address account0, address account1) external; + /// See `makePersistent(address)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function makePersistent(address account0, address account1, address account2) external; + /// See `makePersistent(address)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function makePersistent(address[] calldata accounts) external; + + /// Revokes persistent status from the address, previously added via `makePersistent`. + #[cheatcode(group = Evm, safety = Unsafe)] + function revokePersistent(address account) external; + /// See `revokePersistent(address)`. + #[cheatcode(group = Evm, safety = Unsafe)] + function revokePersistent(address[] calldata accounts) external; + + /// Returns true if the account is marked as persistent. + #[cheatcode(group = Evm, safety = Unsafe)] + function isPersistent(address account) external view returns (bool persistent); + + // -------- Record Logs -------- + + /// Record all the transaction logs. + #[cheatcode(group = Evm, safety = Safe)] + function recordLogs() external; + + /// Gets all the recorded logs. + #[cheatcode(group = Evm, safety = Safe)] + function getRecordedLogs() external returns (Log[] memory logs); + + // -------- Gas Metering -------- + + // It's recommend to use the `noGasMetering` modifier included with forge-std, instead of + // using these functions directly. + + /// Pauses gas metering (i.e. gas usage is not counted). Noop if already paused. + #[cheatcode(group = Evm, safety = Safe)] + function pauseGasMetering() external; + + /// Resumes gas metering (i.e. gas usage is counted again). Noop if already on. + #[cheatcode(group = Evm, safety = Safe)] + function resumeGasMetering() external; + + /// Reset gas metering (i.e. gas usage is set to gas limit). + #[cheatcode(group = Evm, safety = Safe)] + function resetGasMetering() external; + + // -------- Gas Measurement -------- + + /// Gets the gas used in the last call from the callee perspective. + #[cheatcode(group = Evm, safety = Safe)] + function lastCallGas() external view returns (Gas memory gas); + + // ======== Test Assertions and Utilities ======== + + /// If the condition is false, discard this run's fuzz inputs and generate new ones. + #[cheatcode(group = Testing, safety = Safe)] + function assume(bool condition) external pure; + + /// Discard this run's fuzz inputs and generate new ones if next call reverted. + #[cheatcode(group = Testing, safety = Safe)] + function assumeNoRevert() external pure; + + /// Discard this run's fuzz inputs and generate new ones if next call reverts with the potential revert parameters. + #[cheatcode(group = Testing, safety = Safe)] + function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; + + /// Discard this run's fuzz inputs and generate new ones if next call reverts with the any of the potential revert parameters. + #[cheatcode(group = Testing, safety = Safe)] + function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure; + + /// Writes a breakpoint to jump to in the debugger. + #[cheatcode(group = Testing, safety = Safe)] + function breakpoint(string calldata char) external pure; + + /// Writes a conditional breakpoint to jump to in the debugger. + #[cheatcode(group = Testing, safety = Safe)] + function breakpoint(string calldata char, bool value) external pure; + + /// Returns the Foundry version. + /// Format: -+.. + /// Sample output: 0.3.0-nightly+3cb96bde9b.1737036656.debug + /// Note: Build timestamps may vary slightly across platforms due to separate CI jobs. + /// For reliable version comparisons, use UNIX format (e.g., >= 1700000000) + /// to compare timestamps while ignoring minor time differences. + #[cheatcode(group = Testing, safety = Safe)] + function getFoundryVersion() external view returns (string memory version); + + /// Returns the RPC url for the given alias. + #[cheatcode(group = Testing, safety = Safe)] + function rpcUrl(string calldata rpcAlias) external view returns (string memory json); + + /// Returns all rpc urls and their aliases `[alias, url][]`. + #[cheatcode(group = Testing, safety = Safe)] + function rpcUrls() external view returns (string[2][] memory urls); + + /// Returns all rpc urls and their aliases as structs. + #[cheatcode(group = Testing, safety = Safe)] + function rpcUrlStructs() external view returns (Rpc[] memory urls); + + /// Suspends execution of the main thread for `duration` milliseconds. + #[cheatcode(group = Testing, safety = Safe)] + function sleep(uint256 duration) external; + + /// Expects a call to an address with the specified calldata. + /// Calldata can either be a strict or a partial match. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCall(address callee, bytes calldata data) external; + + /// Expects given number of calls to an address with the specified calldata. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCall(address callee, bytes calldata data, uint64 count) external; + + /// Expects a call to an address with the specified `msg.value` and calldata. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCall(address callee, uint256 msgValue, bytes calldata data) external; + + /// Expects given number of calls to an address with the specified `msg.value` and calldata. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCall(address callee, uint256 msgValue, bytes calldata data, uint64 count) external; + + /// Expect a call to an address with the specified `msg.value`, gas, and calldata. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data) external; + + /// Expects given number of calls to an address with the specified `msg.value`, gas, and calldata. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCall(address callee, uint256 msgValue, uint64 gas, bytes calldata data, uint64 count) external; + + /// Expect a call to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; + + /// Expect given number of calls to an address with the specified `msg.value` and calldata, and a *minimum* amount of gas. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) + external; + + /// Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData.). + /// Call this function, then emit an event, then call a function. Internally after the call, we check if + /// logs were emitted in the expected order with the expected topics and data (as specified by the booleans). + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit(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 expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter) + external; + + /// Prepare an expected log with all topic and data checks enabled. + /// Call this function, then emit an event, then call a function. Internally after the call, we check if + /// logs were emitted in the expected order with the expected topics and data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit() external; + + /// Same as the previous method, but also checks supplied address against emitting contract. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit(address emitter) external; + + /// Expect a given number of logs with the provided topics. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, uint64 count) external; + + /// Expect a given number of logs from a specific emitter with the provided topics. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter, uint64 count) + external; + + /// Expect a given number of logs with all topic and data checks enabled. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit(uint64 count) external; + + /// Expect a given number of logs from a specific emitter with all topic and data checks enabled. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectEmit(address emitter, uint64 count) 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; + + /// Expects an error on next call that exactly matches the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes4 revertData) external; + + /// Expects an error on next call that exactly matches the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes calldata revertData) external; + + /// Expects an error with any revert data on next call to reverter address. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(address reverter) external; + + /// Expects an error from reverter address on next call, with any revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes4 revertData, address reverter) external; + + /// Expects an error from reverter address on next call, that exactly matches the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes calldata revertData, address reverter) external; + + /// Expects a `count` number of reverts from the upcoming calls with any revert data or reverter. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls that match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes4 revertData, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls that exactly match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes calldata revertData, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls from the reverter address. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(address reverter, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls from the reverter address that match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes4 revertData, address reverter, uint64 count) external; + + /// Expects a `count` number of reverts from the upcoming calls from the reverter address that exactly match the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectRevert(bytes calldata revertData, address reverter, uint64 count) external; + + /// Expects an error on next call that starts with the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectPartialRevert(bytes4 revertData) external; + + /// Expects an error on next call to reverter address, that starts with the revert data. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectPartialRevert(bytes4 revertData, address reverter) external; + + /// Expects an error on next cheatcode call with any revert data. + #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] + function _expectCheatcodeRevert() external; + + /// Expects an error on next cheatcode call that starts with the revert data. + #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] + function _expectCheatcodeRevert(bytes4 revertData) external; + + /// Expects an error on next cheatcode call that exactly matches the revert data. + #[cheatcode(group = Testing, safety = Unsafe, status = Internal)] + function _expectCheatcodeRevert(bytes calldata revertData) external; + + /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other + /// memory is written to, the test will fail. Can be called multiple times to add more ranges to the set. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectSafeMemory(uint64 min, uint64 max) external; + + /// Stops all safe memory expectation in the current subcontext. + #[cheatcode(group = Testing, safety = Unsafe)] + function stopExpectSafeMemory() external; + + /// Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. + /// If any other memory is written to, the test will fail. Can be called multiple times to add more ranges + /// to the set. + #[cheatcode(group = Testing, safety = Unsafe)] + function expectSafeMemoryCall(uint64 min, uint64 max) external; + + /// Marks a test as skipped. Must be called at the top level of a test. + #[cheatcode(group = Testing, safety = Unsafe)] + function skip(bool skipTest) external; + + /// Marks a test as skipped with a reason. Must be called at the top level of a test. + #[cheatcode(group = Testing, safety = Unsafe)] + function skip(bool skipTest, string calldata reason) external; + + /// Asserts that the given condition is true. + #[cheatcode(group = Testing, safety = Safe)] + function assertTrue(bool condition) external pure; + + /// Asserts that the given condition is true and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertTrue(bool condition, string calldata error) external pure; + + /// Asserts that the given condition is false. + #[cheatcode(group = Testing, safety = Safe)] + function assertFalse(bool condition) external pure; + + /// Asserts that the given condition is false and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertFalse(bool condition, string calldata error) external pure; + + /// Asserts that two `bool` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool left, bool right) external pure; + + /// Asserts that two `bool` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool left, bool right, string calldata error) external pure; + + /// Asserts that two `uint256` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256 left, uint256 right) external pure; + + /// Asserts that two `uint256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256 left, uint256 right, string calldata error) external pure; + + /// Asserts that two `int256` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256 left, int256 right) external pure; + + /// Asserts that two `int256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256 left, int256 right, string calldata error) external pure; + + /// Asserts that two `address` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address left, address right) external pure; + + /// Asserts that two `address` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address left, address right, string calldata error) external pure; + + /// Asserts that two `bytes32` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32 left, bytes32 right) external pure; + + /// Asserts that two `bytes32` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + + /// Asserts that two `string` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string calldata left, string calldata right) external pure; + + /// Asserts that two `string` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string calldata left, string calldata right, string calldata error) external pure; + + /// Asserts that two `bytes` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes calldata left, bytes calldata right) external pure; + + /// Asserts that two `bytes` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bool` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool[] calldata left, bool[] calldata right) external pure; + + /// Asserts that two arrays of `bool` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `uint256 values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; + + /// Asserts that two arrays of `uint256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `int256` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256[] calldata left, int256[] calldata right) external pure; + + /// Asserts that two arrays of `int256` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `address` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address[] calldata left, address[] calldata right) external pure; + + /// Asserts that two arrays of `address` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes32` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + + /// Asserts that two arrays of `bytes32` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `string` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string[] calldata left, string[] calldata right) external pure; + + /// Asserts that two arrays of `string` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes` values are equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; + + /// Asserts that two arrays of `bytes` values are equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + + /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Asserts that two `uint256` values are equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Asserts that two `int256` values are equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Asserts that two `bool` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool left, bool right) external pure; + + /// Asserts that two `bool` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool left, bool right, string calldata error) external pure; + + /// Asserts that two `uint256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256 left, uint256 right) external pure; + + /// Asserts that two `uint256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + + /// Asserts that two `int256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256 left, int256 right) external pure; + + /// Asserts that two `int256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256 left, int256 right, string calldata error) external pure; + + /// Asserts that two `address` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address left, address right) external pure; + + /// Asserts that two `address` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address left, address right, string calldata error) external pure; + + /// Asserts that two `bytes32` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32 left, bytes32 right) external pure; + + /// Asserts that two `bytes32` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + + /// Asserts that two `string` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string calldata left, string calldata right) external pure; + + /// Asserts that two `string` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + + /// Asserts that two `bytes` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes calldata left, bytes calldata right) external pure; + + /// Asserts that two `bytes` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bool` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; + + /// Asserts that two arrays of `bool` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `uint256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; + + /// Asserts that two arrays of `uint256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `int256` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; + + /// Asserts that two arrays of `int256` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `address` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address[] calldata left, address[] calldata right) external pure; + + /// Asserts that two arrays of `address` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes32` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + + /// Asserts that two arrays of `bytes32` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `string` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string[] calldata left, string[] calldata right) external pure; + + /// Asserts that two arrays of `string` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + + /// Asserts that two arrays of `bytes` values are not equal. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; + + /// Asserts that two arrays of `bytes` values are not equal and includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + + /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Asserts that two `uint256` values are not equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Asserts that two `int256` values are not equal, formatting them with decimals in failure message. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGt(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be greater than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGe(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be greater than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLt(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be less than second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(uint256 left, uint256 right) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(uint256 left, uint256 right, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(int256 left, int256 right) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLe(int256 left, int256 right, string calldata error) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects first value to be less than or equal to second. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; + + /// Compares two `uint256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal( + uint256 left, + uint256 right, + uint256 maxDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; + + /// Compares two `int256` values. Expects difference to be less than or equal to `maxDelta`. + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqAbsDecimal( + int256 left, + int256 right, + uint256 maxDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + uint256 left, + uint256 right, + uint256 maxPercentDelta, + uint256 decimals + ) external pure; + + /// Compares two `uint256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + uint256 left, + uint256 right, + uint256 maxPercentDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + int256 left, + int256 right, + uint256 maxPercentDelta, + uint256 decimals + ) external pure; + + /// Compares two `int256` values. Expects relative difference in percents to be less than or equal to `maxPercentDelta`. + /// `maxPercentDelta` is an 18 decimal fixed point number, where 1e18 == 100% + /// Formats values with decimals in failure message. Includes error message into revert string on failure. + #[cheatcode(group = Testing, safety = Safe)] + function assertApproxEqRelDecimal( + int256 left, + int256 right, + uint256 maxPercentDelta, + uint256 decimals, + string calldata error + ) external pure; + + /// Returns true if the current Foundry version is greater than or equal to the given version. + /// The given version string must be in the format `major.minor.patch`. + /// + /// This is equivalent to `foundryVersionCmp(version) >= 0`. + #[cheatcode(group = Testing, safety = Safe)] + function foundryVersionAtLeast(string calldata version) external view returns (bool); + + /// Compares the current Foundry version with the given version string. + /// The given version string must be in the format `major.minor.patch`. + /// + /// Returns: + /// -1 if current Foundry version is less than the given version + /// 0 if current Foundry version equals the given version + /// 1 if current Foundry version is greater than the given version + /// + /// This result can then be used with a comparison operator against `0`. + /// For example, to check if the current Foundry version is greater than or equal to `1.0.0`: + /// `if (foundryVersionCmp("1.0.0") >= 0) { ... }` + #[cheatcode(group = Testing, safety = Safe)] + function foundryVersionCmp(string calldata version) external view returns (int256); + + // ======== OS and Filesystem ======== + + // -------- Metadata -------- + + /// Returns true if the given path points to an existing entity, else returns false. + #[cheatcode(group = Filesystem)] + function exists(string calldata path) external view returns (bool result); + + /// Given a path, query the file system to get information about a file, directory, etc. + #[cheatcode(group = Filesystem)] + function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + + /// Returns true if the path exists on disk and is pointing at a directory, else returns false. + #[cheatcode(group = Filesystem)] + function isDir(string calldata path) external view returns (bool result); + + /// Returns true if the path exists on disk and is pointing at a regular file, else returns false. + #[cheatcode(group = Filesystem)] + function isFile(string calldata path) external view returns (bool result); + + /// Get the path of the current project root. + #[cheatcode(group = Filesystem)] + function projectRoot() external view returns (string memory path); + + /// Returns the time since unix epoch in milliseconds. + #[cheatcode(group = Filesystem)] + function unixTime() external view returns (uint256 milliseconds); + + // -------- Reading and writing -------- + + /// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function closeFile(string calldata path) external; + + /// Copies the contents of one file to another. This function will **overwrite** the contents of `to`. + /// On success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`. + /// Both `from` and `to` are relative to the project root. + #[cheatcode(group = Filesystem)] + function copyFile(string calldata from, string calldata to) external returns (uint64 copied); + + /// Creates a new, empty directory at the provided path. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - User lacks permissions to modify `path`. + /// - A parent of the given path doesn't exist and `recursive` is false. + /// - `path` already exists and `recursive` is false. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function createDir(string calldata path, bool recursive) external; + + /// Reads the directory at the given path recursively, up to `maxDepth`. + /// `maxDepth` defaults to 1, meaning only the direct children of the given directory will be returned. + /// Follows symbolic links if `followLinks` is true. + #[cheatcode(group = Filesystem)] + function readDir(string calldata path) external view returns (DirEntry[] memory entries); + /// See `readDir(string)`. + #[cheatcode(group = Filesystem)] + function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); + /// See `readDir(string)`. + #[cheatcode(group = Filesystem)] + function readDir(string calldata path, uint64 maxDepth, bool followLinks) + external + view + returns (DirEntry[] memory entries); + + /// Reads the entire content of file to string. `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function readFile(string calldata path) external view returns (string memory data); + + /// Reads the entire content of file as binary. `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function readFileBinary(string calldata path) external view returns (bytes memory data); + + /// Reads next line of file to string. + #[cheatcode(group = Filesystem)] + function readLine(string calldata path) external view returns (string memory line); + + /// Reads a symbolic link, returning the path that the link points to. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - `path` is not a symbolic link. + /// - `path` does not exist. + #[cheatcode(group = Filesystem)] + function readLink(string calldata linkPath) external view returns (string memory targetPath); + + /// Removes a directory at the provided path. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - `path` doesn't exist. + /// - `path` isn't a directory. + /// - User lacks permissions to modify `path`. + /// - The directory is not empty and `recursive` is false. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function removeDir(string calldata path, bool recursive) external; + + /// Removes a file from the filesystem. + /// This cheatcode will revert in the following situations, but is not limited to just these cases: + /// - `path` points to a directory. + /// - The file doesn't exist. + /// - The user lacks permissions to remove the file. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function removeFile(string calldata path) external; + + /// Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function writeFile(string calldata path, string calldata data) external; + + /// Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function writeFileBinary(string calldata path, bytes calldata data) external; + + /// Writes line to file, creating a file if it does not exist. + /// `path` is relative to the project root. + #[cheatcode(group = Filesystem)] + function writeLine(string calldata path, string calldata data) external; + + /// Gets the artifact path from code (aka. creation code). + #[cheatcode(group = Filesystem)] + function getArtifactPathByCode(bytes calldata code) external view returns (string memory path); + + /// Gets the artifact path from deployed code (aka. runtime code). + #[cheatcode(group = Filesystem)] + function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path); + + /// Gets the creation 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)] + 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. + /// + /// Additionally 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)] + function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); + + /// Returns the most recent broadcast for the given contract on `chainId` matching `txType`. + /// + /// For example: + /// + /// The most recent deployment can be fetched by passing `txType` as `CREATE` or `CREATE2`. + /// + /// The most recent call can be fetched by passing `txType` as `CALL`. + #[cheatcode(group = Filesystem)] + function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); + + /// Returns all broadcasts for the given contract on `chainId` with the specified `txType`. + /// + /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. + #[cheatcode(group = Filesystem)] + function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); + + /// Returns all broadcasts for the given contract on `chainId`. + /// + /// Sorted such that the most recent broadcast is the first element, and the oldest is the last. i.e descending order of BroadcastTxSummary.blockNumber. + #[cheatcode(group = Filesystem)] + function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + + /// Returns the most recent deployment for the current `chainId`. + #[cheatcode(group = Filesystem)] + function getDeployment(string calldata contractName) external view returns (address deployedAddress); + + /// Returns the most recent deployment for the given contract on `chainId` + #[cheatcode(group = Filesystem)] + function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); + + /// Returns all deployments for the given contract on `chainId` + /// + /// Sorted in descending order of deployment time i.e descending order of BroadcastTxSummary.blockNumber. + /// + /// The most recent deployment is the first element, and the oldest is the last. + #[cheatcode(group = Filesystem)] + function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + + // -------- Foreign Function Interface -------- + + /// Performs a foreign function call via the terminal. + #[cheatcode(group = Filesystem)] + function ffi(string[] calldata commandInput) external returns (bytes memory result); + + /// Performs a foreign function call via terminal and returns the exit code, stdout, and stderr. + #[cheatcode(group = Filesystem)] + function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); + + // -------- User Interaction -------- + + /// Prompts the user for a string value in the terminal. + #[cheatcode(group = Filesystem)] + function prompt(string calldata promptText) external returns (string memory input); + + /// Prompts the user for a hidden string value in the terminal. + #[cheatcode(group = Filesystem)] + function promptSecret(string calldata promptText) external returns (string memory input); + + /// Prompts the user for hidden uint256 in the terminal (usually pk). + #[cheatcode(group = Filesystem)] + function promptSecretUint(string calldata promptText) external returns (uint256); + + /// Prompts the user for an address in the terminal. + #[cheatcode(group = Filesystem)] + function promptAddress(string calldata promptText) external returns (address); + + /// Prompts the user for uint256 in the terminal. + #[cheatcode(group = Filesystem)] + function promptUint(string calldata promptText) external returns (uint256); + + // ======== Environment Variables ======== + + /// Sets environment variables. + #[cheatcode(group = Environment)] + function setEnv(string calldata name, string calldata value) external; + + /// Gets the environment variable `name` and returns true if it exists, else returns false. + #[cheatcode(group = Environment)] + function envExists(string calldata name) external view returns (bool result); + + /// Gets the environment variable `name` and parses it as `bool`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envBool(string calldata name) external view returns (bool value); + /// Gets the environment variable `name` and parses it as `uint256`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envUint(string calldata name) external view returns (uint256 value); + /// Gets the environment variable `name` and parses it as `int256`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envInt(string calldata name) external view returns (int256 value); + /// Gets the environment variable `name` and parses it as `address`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envAddress(string calldata name) external view returns (address value); + /// Gets the environment variable `name` and parses it as `bytes32`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envBytes32(string calldata name) external view returns (bytes32 value); + /// Gets the environment variable `name` and parses it as `string`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envString(string calldata name) external view returns (string memory value); + /// Gets the environment variable `name` and parses it as `bytes`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envBytes(string calldata name) external view returns (bytes memory value); + + /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value); + /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); + /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); + /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); + /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); + /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envString(string calldata name, string calldata delim) external view returns (string[] memory value); + /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. + /// Reverts if the variable was not found or could not be parsed. + #[cheatcode(group = Environment)] + function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); + + /// Gets the environment variable `name` and parses it as `bool`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, bool defaultValue) external view returns (bool value); + /// Gets the environment variable `name` and parses it as `uint256`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value); + /// Gets the environment variable `name` and parses it as `int256`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, int256 defaultValue) external view returns (int256 value); + /// Gets the environment variable `name` and parses it as `address`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, address defaultValue) external view returns (address value); + /// Gets the environment variable `name` and parses it as `bytes32`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value); + /// Gets the environment variable `name` and parses it as `string`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value); + /// Gets the environment variable `name` and parses it as `bytes`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value); + + /// Gets the environment variable `name` and parses it as an array of `bool`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) + external view + returns (bool[] memory value); + /// Gets the environment variable `name` and parses it as an array of `uint256`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) + external view + returns (uint256[] memory value); + /// Gets the environment variable `name` and parses it as an array of `int256`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) + external view + returns (int256[] memory value); + /// Gets the environment variable `name` and parses it as an array of `address`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) + external view + returns (address[] memory value); + /// Gets the environment variable `name` and parses it as an array of `bytes32`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) + external view + returns (bytes32[] memory value); + /// Gets the environment variable `name` and parses it as an array of `string`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) + external view + returns (string[] memory value); + /// Gets the environment variable `name` and parses it as an array of `bytes`, delimited by `delim`. + /// Reverts if the variable could not be parsed. + /// Returns `defaultValue` if the variable was not found. + #[cheatcode(group = Environment)] + function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) + external view + returns (bytes[] memory value); + + /// Returns true if `forge` command was executed in given context. + #[cheatcode(group = Environment)] + function isContext(ForgeContext context) external view returns (bool result); + + // ======== Scripts ======== + + // -------- Broadcasting Transactions -------- + + /// Has the next call (at this call depth only) create transactions that can later be signed and sent onchain. + /// + /// Broadcasting address is determined by checking the following in order: + /// 1. If `--sender` argument was provided, that address is used. + /// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used. + /// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used. + #[cheatcode(group = Scripting)] + function broadcast() external; + + /// Has the next call (at this call depth only) create a transaction with the address provided + /// as the sender that can later be signed and sent onchain. + #[cheatcode(group = Scripting)] + function broadcast(address signer) external; + + /// Has the next call (at this call depth only) create a transaction with the private key + /// provided as the sender that can later be signed and sent onchain. + #[cheatcode(group = Scripting)] + function broadcast(uint256 privateKey) external; + + /// Has all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain. + /// + /// Broadcasting address is determined by checking the following in order: + /// 1. If `--sender` argument was provided, that address is used. + /// 2. If exactly one signer (e.g. private key, hw wallet, keystore) is set when `forge broadcast` is invoked, that signer is used. + /// 3. Otherwise, default foundry sender (1804c8AB1F12E6bbf3894d4083f33e07309d1f38) is used. + #[cheatcode(group = Scripting)] + function startBroadcast() external; + + /// Has all subsequent calls (at this call depth only) create transactions with the address + /// provided that can later be signed and sent onchain. + #[cheatcode(group = Scripting)] + function startBroadcast(address signer) external; + + /// Has all subsequent calls (at this call depth only) create transactions with the private key + /// provided that can later be signed and sent onchain. + #[cheatcode(group = Scripting)] + function startBroadcast(uint256 privateKey) external; + + /// Stops collecting onchain transactions. + #[cheatcode(group = Scripting)] + function stopBroadcast() external; + + /// Takes a signed transaction and broadcasts it to the network. + #[cheatcode(group = Scripting)] + function broadcastRawTransaction(bytes calldata data) external; + + /// Sign an EIP-7702 authorization for delegation + #[cheatcode(group = Scripting)] + function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); + + /// Designate the next call as an EIP-7702 transaction + #[cheatcode(group = Scripting)] + function attachDelegation(SignedDelegation calldata signedDelegation) external; + + /// Sign an EIP-7702 authorization and designate the next call as an EIP-7702 transaction + #[cheatcode(group = Scripting)] + function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); + + /// Returns addresses of available unlocked wallets in the script environment. + #[cheatcode(group = Scripting)] + function getWallets() external returns (address[] memory wallets); + + // ======== Utilities ======== + + // -------- Strings -------- + + /// Converts the given value to a `string`. + #[cheatcode(group = String)] + function toString(address value) external pure returns (string memory stringifiedValue); + /// Converts the given value to a `string`. + #[cheatcode(group = String)] + function toString(bytes calldata value) external pure returns (string memory stringifiedValue); + /// Converts the given value to a `string`. + #[cheatcode(group = String)] + function toString(bytes32 value) external pure returns (string memory stringifiedValue); + /// Converts the given value to a `string`. + #[cheatcode(group = String)] + function toString(bool value) external pure returns (string memory stringifiedValue); + /// Converts the given value to a `string`. + #[cheatcode(group = String)] + function toString(uint256 value) external pure returns (string memory stringifiedValue); + /// Converts the given value to a `string`. + #[cheatcode(group = String)] + function toString(int256 value) external pure returns (string memory stringifiedValue); + + /// Parses the given `string` into `bytes`. + #[cheatcode(group = String)] + function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue); + /// Parses the given `string` into an `address`. + #[cheatcode(group = String)] + function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); + /// Parses the given `string` into a `uint256`. + #[cheatcode(group = String)] + function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); + /// Parses the given `string` into a `int256`. + #[cheatcode(group = String)] + function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue); + /// Parses the given `string` into a `bytes32`. + #[cheatcode(group = String)] + function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue); + /// Parses the given `string` into a `bool`. + #[cheatcode(group = String)] + function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); + + /// Converts the given `string` value to Lowercase. + #[cheatcode(group = String)] + function toLowercase(string calldata input) external pure returns (string memory output); + /// Converts the given `string` value to Uppercase. + #[cheatcode(group = String)] + function toUppercase(string calldata input) external pure returns (string memory output); + /// Trims leading and trailing whitespace from the given `string` value. + #[cheatcode(group = String)] + function trim(string calldata input) external pure returns (string memory output); + /// Replaces occurrences of `from` in the given `string` with `to`. + #[cheatcode(group = String)] + function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); + /// Splits the given `string` into an array of strings divided by the `delimiter`. + #[cheatcode(group = String)] + function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); + /// Returns the index of the first occurrence of a `key` in an `input` string. + /// Returns `NOT_FOUND` (i.e. `type(uint256).max`) if the `key` is not found. + /// Returns 0 in case of an empty `key`. + #[cheatcode(group = String)] + function indexOf(string calldata input, string calldata key) external pure returns (uint256); + /// Returns true if `search` is found in `subject`, false otherwise. + #[cheatcode(group = String)] + function contains(string calldata subject, string calldata search) external returns (bool result); + + // ======== JSON Parsing and Manipulation ======== + + // -------- Reading -------- + + // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-json to understand the + // limitations and caveats of the JSON parsing cheats. + + /// Checks if `key` exists in a JSON object + /// `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + #[cheatcode(group = Json, status = Deprecated(Some("replaced by `keyExistsJson`")))] + function keyExists(string calldata json, string calldata key) external view returns (bool); + /// Checks if `key` exists in a JSON object. + #[cheatcode(group = Json)] + function keyExistsJson(string calldata json, string calldata key) external view returns (bool); + + /// ABI-encodes a JSON object. + #[cheatcode(group = Json)] + function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); + /// ABI-encodes a JSON object at `key`. + #[cheatcode(group = Json)] + function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); + + // The following parseJson cheatcodes will do type coercion, for the type that they indicate. + // For example, parseJsonUint will coerce all values to a uint256. That includes stringified numbers '12.' + // and hex numbers '0xEF.'. + // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not + // a JSON object. + + /// Parses a string of JSON data at `key` and coerces it to `uint256`. + #[cheatcode(group = Json)] + function parseJsonUint(string calldata json, string calldata key) external pure returns (uint256); + /// Parses a string of JSON data at `key` and coerces it to `uint256[]`. + #[cheatcode(group = Json)] + function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); + /// Parses a string of JSON data at `key` and coerces it to `int256`. + #[cheatcode(group = Json)] + function parseJsonInt(string calldata json, string calldata key) external pure returns (int256); + /// Parses a string of JSON data at `key` and coerces it to `int256[]`. + #[cheatcode(group = Json)] + function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory); + /// Parses a string of JSON data at `key` and coerces it to `bool`. + #[cheatcode(group = Json)] + function parseJsonBool(string calldata json, string calldata key) external pure returns (bool); + /// Parses a string of JSON data at `key` and coerces it to `bool[]`. + #[cheatcode(group = Json)] + function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory); + /// Parses a string of JSON data at `key` and coerces it to `address`. + #[cheatcode(group = Json)] + function parseJsonAddress(string calldata json, string calldata key) external pure returns (address); + /// Parses a string of JSON data at `key` and coerces it to `address[]`. + #[cheatcode(group = Json)] + function parseJsonAddressArray(string calldata json, string calldata key) + external + pure + returns (address[] memory); + /// Parses a string of JSON data at `key` and coerces it to `string`. + #[cheatcode(group = Json)] + function parseJsonString(string calldata json, string calldata key) external pure returns (string memory); + /// Parses a string of JSON data at `key` and coerces it to `string[]`. + #[cheatcode(group = Json)] + function parseJsonStringArray(string calldata json, string calldata key) external pure returns (string[] memory); + /// Parses a string of JSON data at `key` and coerces it to `bytes`. + #[cheatcode(group = Json)] + function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory); + /// Parses a string of JSON data at `key` and coerces it to `bytes[]`. + #[cheatcode(group = Json)] + function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory); + /// Parses a string of JSON data at `key` and coerces it to `bytes32`. + #[cheatcode(group = Json)] + function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32); + /// Parses a string of JSON data at `key` and coerces it to `bytes32[]`. + #[cheatcode(group = Json)] + function parseJsonBytes32Array(string calldata json, string calldata key) + external + 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); + + // -------- Writing -------- + + // NOTE: Please read https://book.getfoundry.sh/cheatcodes/serialize-json to understand how + // to use the serialization cheats. + + /// Serializes a key and value to a JSON object stored in-memory that can be later written to a file. + /// Returns the stringified version of the specific JSON file up to that moment. + #[cheatcode(group = Json)] + function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); + + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeBool(string calldata objectKey, string calldata valueKey, bool value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeAddress(string calldata objectKey, string calldata valueKey, address value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) + external + returns (string memory json); + + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) + external + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + 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 calldata value) + external + pure + returns (string memory json); + /// See `serializeJson`. + #[cheatcode(group = Json)] + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata 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. + + /// Write a serialized JSON object to a file. If the file exists, it will be overwritten. + #[cheatcode(group = Json)] + function writeJson(string calldata json, string calldata path) external; + + /// Write a serialized JSON object to an **existing** JSON file, replacing a value with key = + /// This is useful to replace a specific value of a JSON file, without having to parse the entire thing. + #[cheatcode(group = Json)] + function writeJson(string calldata json, string calldata path, string calldata valueKey) external; + + // ======== TOML Parsing and Manipulation ======== + + // -------- Reading -------- + + // NOTE: Please read https://book.getfoundry.sh/cheatcodes/parse-toml to understand the + // limitations and caveats of the TOML parsing cheat. + + /// Checks if `key` exists in a TOML table. + #[cheatcode(group = Toml)] + function keyExistsToml(string calldata toml, string calldata key) external view returns (bool); + + /// ABI-encodes a TOML table. + #[cheatcode(group = Toml)] + function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); + + /// ABI-encodes a TOML table at `key`. + #[cheatcode(group = Toml)] + function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); + + // The following parseToml cheatcodes will do type coercion, for the type that they indicate. + // For example, parseTomlUint will coerce all values to a uint256. That includes stringified numbers '12.' + // and hex numbers '0xEF.'. + // Type coercion works ONLY for discrete values or arrays. That means that the key must return a value or array, not + // a TOML table. + + /// Parses a string of TOML data at `key` and coerces it to `uint256`. + #[cheatcode(group = Toml)] + function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); + /// Parses a string of TOML data at `key` and coerces it to `uint256[]`. + #[cheatcode(group = Toml)] + function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); + /// Parses a string of TOML data at `key` and coerces it to `int256`. + #[cheatcode(group = Toml)] + function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256); + /// Parses a string of TOML data at `key` and coerces it to `int256[]`. + #[cheatcode(group = Toml)] + function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory); + /// Parses a string of TOML data at `key` and coerces it to `bool`. + #[cheatcode(group = Toml)] + function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool); + /// Parses a string of TOML data at `key` and coerces it to `bool[]`. + #[cheatcode(group = Toml)] + function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory); + /// Parses a string of TOML data at `key` and coerces it to `address`. + #[cheatcode(group = Toml)] + function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address); + /// Parses a string of TOML data at `key` and coerces it to `address[]`. + #[cheatcode(group = Toml)] + function parseTomlAddressArray(string calldata toml, string calldata key) + external + pure + returns (address[] memory); + /// Parses a string of TOML data at `key` and coerces it to `string`. + #[cheatcode(group = Toml)] + function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); + /// Parses a string of TOML data at `key` and coerces it to `string[]`. + #[cheatcode(group = Toml)] + function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); + /// Parses a string of TOML data at `key` and coerces it to `bytes`. + #[cheatcode(group = Toml)] + function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory); + /// Parses a string of TOML data at `key` and coerces it to `bytes[]`. + #[cheatcode(group = Toml)] + function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory); + /// Parses a string of TOML data at `key` and coerces it to `bytes32`. + #[cheatcode(group = Toml)] + function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32); + /// Parses a string of TOML data at `key` and coerces it to `bytes32[]`. + #[cheatcode(group = Toml)] + function parseTomlBytes32Array(string calldata toml, string calldata key) + external + pure + returns (bytes32[] memory); + + /// Parses a string of TOML data and coerces it to type corresponding to `typeDescription`. + #[cheatcode(group = Toml)] + function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory); + /// Parses a string of TOML data at `key` and coerces it to type corresponding to `typeDescription`. + #[cheatcode(group = Toml)] + function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + /// Parses a string of TOML data at `key` and coerces it to type array corresponding to `typeDescription`. + #[cheatcode(group = Toml)] + function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) + external + pure + returns (bytes memory); + + /// Returns an array of all the keys in a TOML table. + #[cheatcode(group = Toml)] + function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); + + // -------- Writing -------- + + // NOTE: Please read https://book.getfoundry.sh/cheatcodes/write-toml to understand how + // to use the TOML writing cheat. + + /// Takes serialized JSON, converts to TOML and write a serialized TOML to a file. + #[cheatcode(group = Toml)] + function writeToml(string calldata json, string calldata path) external; + + /// Takes serialized JSON, converts to TOML and write a serialized TOML table to an **existing** TOML file, replacing a value with key = + /// This is useful to replace a specific value of a TOML file, without having to parse the entire thing. + #[cheatcode(group = Toml)] + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + + // ======== Cryptography ======== + + // -------- Key Management -------- + + /// Derives a private key from the name, labels the account with that name, and returns the wallet. + #[cheatcode(group = Crypto)] + function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); + + /// Generates a wallet from the private key and returns the wallet. + #[cheatcode(group = Crypto)] + function createWallet(uint256 privateKey) external returns (Wallet memory wallet); + + /// Generates a wallet from the private key, labels the account with that name, and returns the wallet. + #[cheatcode(group = Crypto)] + function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); + + /// Signs data with a `Wallet`. + #[cheatcode(group = Crypto)] + function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs data with a `Wallet`. + /// + /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the + /// signature's `s` value, and the recovery id `v` in a single bytes32. + /// This format reduces the signature size from 65 to 64 bytes. + #[cheatcode(group = Crypto)] + function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs); + + /// Signs `digest` with `privateKey` using the secp256k1 curve. + #[cheatcode(group = Crypto)] + function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs `digest` with `privateKey` using the secp256k1 curve. + /// + /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the + /// signature's `s` value, and the recovery id `v` in a single bytes32. + /// This format reduces the signature size from 65 to 64 bytes. + #[cheatcode(group = Crypto)] + function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); + + /// Signs `digest` with signer provided to script using the secp256k1 curve. + /// + /// If `--sender` is provided, the signer with provided address is used, otherwise, + /// if exactly one signer is provided to the script, that signer is used. + /// + /// Raises error if signer passed through `--sender` does not match any unlocked signers or + /// if `--sender` is not provided and not exactly one signer is passed to the script. + #[cheatcode(group = Crypto)] + function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs `digest` with signer provided to script using the secp256k1 curve. + /// + /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the + /// signature's `s` value, and the recovery id `v` in a single bytes32. + /// This format reduces the signature size from 65 to 64 bytes. + /// + /// If `--sender` is provided, the signer with provided address is used, otherwise, + /// if exactly one signer is provided to the script, that signer is used. + /// + /// Raises error if signer passed through `--sender` does not match any unlocked signers or + /// if `--sender` is not provided and not exactly one signer is passed to the script. + #[cheatcode(group = Crypto)] + function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs); + + /// Signs `digest` with signer provided to script using the secp256k1 curve. + /// + /// Raises error if none of the signers passed into the script have provided address. + #[cheatcode(group = Crypto)] + function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// Signs `digest` with signer provided to script using the secp256k1 curve. + /// + /// Returns a compact signature (`r`, `vs`) as per EIP-2098, where `vs` encodes both the + /// signature's `s` value, and the recovery id `v` in a single bytes32. + /// This format reduces the signature size from 65 to 64 bytes. + /// + /// Raises error if none of the signers passed into the script have provided address. + #[cheatcode(group = Crypto)] + function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); + + /// Signs `digest` with `privateKey` using the secp256r1 curve. + #[cheatcode(group = Crypto)] + function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); + + /// Derives secp256r1 public key from the provided `privateKey`. + #[cheatcode(group = Crypto)] + function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); + + /// Derive a private key from a provided mnenomic string (or mnenomic file path) + /// at the derivation path `m/44'/60'/0'/0/{index}`. + #[cheatcode(group = Crypto)] + function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey); + /// Derive a private key from a provided mnenomic string (or mnenomic file path) + /// at `{derivationPath}{index}`. + #[cheatcode(group = Crypto)] + function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) + external + pure + returns (uint256 privateKey); + /// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language + /// at the derivation path `m/44'/60'/0'/0/{index}`. + #[cheatcode(group = Crypto)] + function deriveKey(string calldata mnemonic, uint32 index, string calldata language) + external + pure + returns (uint256 privateKey); + /// Derive a private key from a provided mnenomic string (or mnenomic file path) in the specified language + /// at `{derivationPath}{index}`. + #[cheatcode(group = Crypto)] + function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) + external + pure + returns (uint256 privateKey); + + /// Adds a private key to the local forge wallet and returns the address. + #[cheatcode(group = Crypto)] + function rememberKey(uint256 privateKey) external returns (address keyAddr); + + /// Derive a set number of wallets from a mnemonic at the derivation path `m/44'/60'/0'/0/{0..count}`. + /// + /// The respective private keys are saved to the local forge wallet for later use and their addresses are returned. + #[cheatcode(group = Crypto)] + function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs); + + /// Derive a set number of wallets from a mnemonic in the specified language at the derivation path `m/44'/60'/0'/0/{0..count}`. + /// + /// The respective private keys are saved to the local forge wallet for later use and their addresses are returned. + #[cheatcode(group = Crypto)] + function rememberKeys(string calldata mnemonic, string calldata derivationPath, string calldata language, uint32 count) + external + returns (address[] memory keyAddrs); + + // -------- Uncategorized Utilities -------- + + /// Labels an address in call traces. + #[cheatcode(group = Utilities)] + function label(address account, string calldata newLabel) external; + + /// Gets the label for the specified address. + #[cheatcode(group = Utilities)] + function getLabel(address account) external view returns (string memory currentLabel); + + /// Compute the address a contract will be deployed at for a given deployer address and nonce. + #[cheatcode(group = Utilities)] + function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); + + /// Compute the address of a contract created with CREATE2 using the given CREATE2 deployer. + #[cheatcode(group = Utilities)] + function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); + + /// Compute the address of a contract created with CREATE2 using the default CREATE2 deployer. + #[cheatcode(group = Utilities)] + function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); + + /// Encodes a `bytes` value to a base64 string. + #[cheatcode(group = Utilities)] + function toBase64(bytes calldata data) external pure returns (string memory); + + /// Encodes a `string` value to a base64 string. + #[cheatcode(group = Utilities)] + function toBase64(string calldata data) external pure returns (string memory); + + /// Encodes a `bytes` value to a base64url string. + #[cheatcode(group = Utilities)] + function toBase64URL(bytes calldata data) external pure returns (string memory); + + /// Encodes a `string` value to a base64url string. + #[cheatcode(group = Utilities)] + function toBase64URL(string calldata data) external pure returns (string memory); + + /// Returns ENS namehash for provided string. + #[cheatcode(group = Utilities)] + function ensNamehash(string calldata name) external pure returns (bytes32); + + /// Returns a random uint256 value. + #[cheatcode(group = Utilities)] + function randomUint() external returns (uint256); + + /// Returns random uint256 value between the provided range (=min..=max). + #[cheatcode(group = Utilities)] + function randomUint(uint256 min, uint256 max) external returns (uint256); + + /// Returns a random `uint256` value of given bits. + #[cheatcode(group = Utilities)] + function randomUint(uint256 bits) external view returns (uint256); + + /// Returns a random `address`. + #[cheatcode(group = Utilities)] + function randomAddress() external returns (address); + + /// Returns a random `int256` value. + #[cheatcode(group = Utilities)] + function randomInt() external view returns (int256); + + /// Returns a random `int256` value of given bits. + #[cheatcode(group = Utilities)] + function randomInt(uint256 bits) external view returns (int256); + + /// Returns a random `bool`. + #[cheatcode(group = Utilities)] + function randomBool() external view returns (bool); + + /// Returns a random byte array value of the given length. + #[cheatcode(group = Utilities)] + function randomBytes(uint256 len) external view returns (bytes memory); + + /// Returns a random fixed-size byte array of length 4. + #[cheatcode(group = Utilities)] + function randomBytes4() external view returns (bytes4); + + /// Returns a random fixed-size byte array of length 8. + #[cheatcode(group = Utilities)] + function randomBytes8() external view returns (bytes8); + + /// Pauses collection of call traces. Useful in cases when you want to skip tracing of + /// complex calls which are not useful for debugging. + #[cheatcode(group = Utilities)] + function pauseTracing() external view; + + /// Unpauses collection of call traces. + #[cheatcode(group = Utilities)] + function resumeTracing() external view; + + /// Utility cheatcode to copy storage of `from` contract to another `to` contract. + #[cheatcode(group = Utilities)] + function copyStorage(address from, address to) external; + + /// Utility cheatcode to set arbitrary storage for given target address. + #[cheatcode(group = Utilities)] + function setArbitraryStorage(address target) external; +} +} + +impl PartialEq for ForgeContext { + // Handles test group case (any of test, coverage or snapshot) + // and script group case (any of dry run, broadcast or resume). + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (_, Self::TestGroup) => { + matches!(self, Self::Test | Self::Snapshot | Self::Coverage) + } + (_, Self::ScriptGroup) => { + matches!(self, Self::ScriptDryRun | Self::ScriptBroadcast | Self::ScriptResume) + } + (Self::Test, Self::Test) | + (Self::Snapshot, Self::Snapshot) | + (Self::Coverage, Self::Coverage) | + (Self::ScriptDryRun, Self::ScriptDryRun) | + (Self::ScriptBroadcast, Self::ScriptBroadcast) | + (Self::ScriptResume, Self::ScriptResume) | + (Self::Unknown, Self::Unknown) => true, + _ => false, + } + } +} + +impl fmt::Display for Vm::CheatcodeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.message.fmt(f) + } +} + +impl fmt::Display for Vm::VmErrors { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::CheatcodeError(err) => err.fmt(f), + } + } +} + +#[track_caller] +const fn panic_unknown_safety() -> ! { + panic!("cannot determine safety from the group, add a `#[cheatcode(safety = ...)]` attribute") +} diff --git a/crates/cheatcodes/src/base64.rs b/crates/cheatcodes/src/base64.rs new file mode 100644 index 0000000000000..4aa4ba74a0e4b --- /dev/null +++ b/crates/cheatcodes/src/base64.rs @@ -0,0 +1,31 @@ +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_sol_types::SolValue; +use base64::prelude::*; + +impl Cheatcode for toBase64_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_STANDARD.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_STANDARD.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64URL_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + } +} + +impl Cheatcode for toBase64URL_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { data } = self; + Ok(BASE64_URL_SAFE.encode(data).abi_encode()) + } +} diff --git a/crates/evm/src/executor/inspector/cheatcodes/config.rs b/crates/cheatcodes/src/config.rs similarity index 55% rename from crates/evm/src/executor/inspector/cheatcodes/config.rs rename to crates/cheatcodes/src/config.rs index ebf5e4396c37c..3ab70f6e053fb 100644 --- a/crates/evm/src/executor/inspector/cheatcodes/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,21 +1,33 @@ -use super::{ensure, fmt_err, Result}; -use crate::executor::opts::EvmOpts; -use ethers::solc::{utils::canonicalize, ProjectPathsConfig}; -use foundry_common::fs::normalize_path; +use super::Result; +use crate::Vm::Rpc; +use alloy_primitives::{map::AddressHashMap, U256}; +use foundry_common::{fs::normalize_path, ContractsByArtifact}; +use foundry_compilers::{utils::canonicalize, ArtifactId, ProjectPathsConfig}; use foundry_config::{ cache::StorageCachingConfig, fs_permissions::FsAccessKind, Config, FsPermissions, - ResolvedRpcEndpoints, + ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, +}; +use foundry_evm_core::opts::EvmOpts; +use std::{ + path::{Path, PathBuf}, + time::Duration, }; -use std::path::{Path, PathBuf}; /// Additional, configurable context the `Cheatcodes` inspector has access to /// /// This is essentially a subset of various `Config` settings `Cheatcodes` needs to know. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct CheatsConfig { + /// Whether the FFI cheatcode is enabled. pub ffi: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + pub always_use_create_2_factory: bool, + /// Sets a timeout for vm.prompt cheatcodes + pub prompt_timeout: Duration, /// RPC storage caching settings determines what chains and endpoints to cache pub rpc_storage_caching: StorageCachingConfig, + /// Disables storage caching entirely. + pub no_storage_caching: bool, /// All known endpoints and their aliases pub rpc_endpoints: ResolvedRpcEndpoints, /// Project's paths as configured @@ -24,36 +36,74 @@ pub struct CheatsConfig { pub fs_permissions: FsPermissions, /// Project root pub root: PathBuf, + /// Absolute Path to broadcast dir i.e project_root/broadcast + pub broadcast: PathBuf, /// Paths (directories) where file reading/writing is allowed pub allowed_paths: Vec, /// How the evm was configured by the user pub evm_opts: EvmOpts, + /// Address labels from config + pub labels: AddressHashMap, + /// Artifacts which are guaranteed to be fresh (either recompiled or cached). + /// If Some, `vm.getDeployedCode` invocations are validated to be in scope of this list. + /// If None, no validation is performed. + pub available_artifacts: Option, + /// Currently running artifact. + pub running_artifact: Option, + /// Whether to enable legacy (non-reverting) assertions. + pub assertions_revert: bool, + /// Optional seed for the RNG algorithm. + pub seed: Option, + /// Whether to allow `expectRevert` to work for internal calls. + pub internal_expect_revert: bool, } -// === impl CheatsConfig === - impl CheatsConfig { /// Extracts the necessary settings from the Config - pub fn new(config: &Config, evm_opts: &EvmOpts) -> Self { - let mut allowed_paths = vec![config.__root.0.clone()]; - allowed_paths.extend(config.libs.clone()); - allowed_paths.extend(config.allow_paths.clone()); + pub fn new( + config: &Config, + evm_opts: EvmOpts, + available_artifacts: Option, + running_artifact: Option, + ) -> Self { + let mut allowed_paths = vec![config.root.clone()]; + allowed_paths.extend(config.libs.iter().cloned()); + allowed_paths.extend(config.allow_paths.iter().cloned()); let rpc_endpoints = config.rpc_endpoints.clone().resolved(); trace!(?rpc_endpoints, "using resolved rpc endpoints"); + // If user explicitly disabled safety checks, do not set available_artifacts + let available_artifacts = + if config.unchecked_cheatcode_artifacts { None } else { available_artifacts }; + Self { ffi: evm_opts.ffi, + always_use_create_2_factory: evm_opts.always_use_create_2_factory, + prompt_timeout: Duration::from_secs(config.prompt_timeout), rpc_storage_caching: config.rpc_storage_caching.clone(), + no_storage_caching: config.no_storage_caching, rpc_endpoints, paths: config.project_paths(), - fs_permissions: config.fs_permissions.clone().joined(&config.__root), - root: config.__root.0.clone(), + fs_permissions: config.fs_permissions.clone().joined(config.root.as_ref()), + root: config.root.clone(), + broadcast: config.root.clone().join(&config.broadcast), allowed_paths, - evm_opts: evm_opts.clone(), + evm_opts, + labels: config.labels.clone(), + available_artifacts, + running_artifact, + assertions_revert: config.assertions_revert, + seed: config.fuzz.seed, + internal_expect_revert: config.allow_internal_expect_revert, } } + /// Returns a new `CheatsConfig` configured with the given `Config` and `EvmOpts`. + pub fn clone_with(&self, config: &Config, evm_opts: EvmOpts) -> Self { + Self::new(config, evm_opts, self.available_artifacts.clone(), self.running_artifact.clone()) + } + /// Attempts to canonicalize (see [std::fs::canonicalize]) the path. /// /// Canonicalization fails for non-existing paths, in which case we just normalize the path. @@ -88,7 +138,8 @@ impl CheatsConfig { let normalized = self.normalized_path(path); ensure!( self.is_normalized_path_allowed(&normalized, kind), - "The path {path:?} is not allowed to be accessed for {kind} operations." + "the path {} is not allowed to be accessed for {kind} operations", + normalized.strip_prefix(&self.root).unwrap_or(path).display() ); Ok(normalized) } @@ -109,7 +160,7 @@ impl CheatsConfig { /// Same as [`Self::is_foundry_toml`] but returns an `Err` if [`Self::is_foundry_toml`] returns /// true pub fn ensure_not_foundry_toml(&self, path: impl AsRef) -> Result<()> { - ensure!(!self.is_foundry_toml(path), "Access to foundry.toml is not allowed."); + ensure!(!self.is_foundry_toml(path), "access to `foundry.toml` is not allowed"); Ok(()) } @@ -117,43 +168,64 @@ impl CheatsConfig { /// /// If `url_or_alias` is a known alias in the `ResolvedRpcEndpoints` then it returns the /// corresponding URL of that alias. otherwise this assumes `url_or_alias` is itself a URL - /// if it starts with a `http` or `ws` scheme + /// if it starts with a `http` or `ws` scheme. + /// + /// If the url is a path to an existing file, it is also considered a valid RPC URL, IPC path. /// /// # Errors /// /// - Returns an error if `url_or_alias` is a known alias but references an unresolved env var. /// - Returns an error if `url_or_alias` is not an alias but does not start with a `http` or - /// `scheme` - pub fn get_rpc_url(&self, url_or_alias: impl Into) -> Result { - let url_or_alias = url_or_alias.into(); - match self.rpc_endpoints.get(&url_or_alias) { - Some(Ok(url)) => Ok(url.clone()), - Some(Err(err)) => { - // try resolve again, by checking if env vars are now set - err.try_resolve().map_err(Into::into) - } - None => { - if !url_or_alias.starts_with("http") && !url_or_alias.starts_with("ws") { - Err(fmt_err!("invalid rpc url {url_or_alias}")) - } else { - Ok(url_or_alias) - } + /// `ws` `scheme` and is not a path to an existing file + pub fn rpc_endpoint(&self, url_or_alias: &str) -> Result { + if let Some(endpoint) = self.rpc_endpoints.get(url_or_alias) { + Ok(endpoint.clone().try_resolve()) + } else { + // check if it's a URL or a path to an existing file to an ipc socket + if url_or_alias.starts_with("http") || + url_or_alias.starts_with("ws") || + // check for existing ipc file + Path::new(url_or_alias).exists() + { + let url = RpcEndpointUrl::Env(url_or_alias.to_string()); + Ok(RpcEndpoint::new(url).resolve()) + } else { + Err(fmt_err!("invalid rpc url: {url_or_alias}")) } } } + /// Returns all the RPC urls and their alias. + pub fn rpc_urls(&self) -> Result> { + let mut urls = Vec::with_capacity(self.rpc_endpoints.len()); + for alias in self.rpc_endpoints.keys() { + let url = self.rpc_endpoint(alias)?.url()?; + urls.push(Rpc { key: alias.clone(), url }); + } + Ok(urls) + } } impl Default for CheatsConfig { fn default() -> Self { Self { ffi: false, + always_use_create_2_factory: false, + prompt_timeout: Duration::from_secs(120), rpc_storage_caching: Default::default(), + no_storage_caching: false, rpc_endpoints: Default::default(), paths: ProjectPathsConfig::builder().build_with_root("./"), fs_permissions: Default::default(), root: Default::default(), + broadcast: Default::default(), allowed_paths: vec![], evm_opts: Default::default(), + labels: Default::default(), + available_artifacts: Default::default(), + running_artifact: Default::default(), + assertions_revert: true, + seed: None, + internal_expect_revert: false, } } } @@ -165,8 +237,10 @@ mod tests { fn config(root: &str, fs_permissions: FsPermissions) -> CheatsConfig { CheatsConfig::new( - &Config { __root: PathBuf::from(root).into(), fs_permissions, ..Default::default() }, - &Default::default(), + &Config { root: root.into(), fs_permissions, ..Default::default() }, + Default::default(), + None, + None, ) } diff --git a/crates/cheatcodes/src/crypto.rs b/crates/cheatcodes/src/crypto.rs new file mode 100644 index 0000000000000..3fd13d9b06e9a --- /dev/null +++ b/crates/cheatcodes/src/crypto.rs @@ -0,0 +1,435 @@ +//! Implementations of [`Crypto`](spec::Group::Crypto) Cheatcodes. + +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_signer::{Signer, SignerSync}; +use alloy_signer_local::{ + coins_bip39::{ + ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean, + Portuguese, Spanish, Wordlist, + }, + LocalSigner, MnemonicBuilder, PrivateKeySigner, +}; +use alloy_sol_types::SolValue; +use k256::{ + ecdsa::SigningKey, + elliptic_curve::{bigint::ArrayEncoding, sec1::ToEncodedPoint}, +}; +use p256::ecdsa::{ + signature::hazmat::PrehashSigner, Signature as P256Signature, SigningKey as P256SigningKey, +}; + +/// The BIP32 default derivation path prefix. +const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; + +impl Cheatcode for createWallet_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { walletLabel } = self; + create_wallet(&U256::from_be_bytes(keccak256(walletLabel).0), Some(walletLabel), state) + } +} + +impl Cheatcode for createWallet_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { privateKey } = self; + create_wallet(privateKey, None, state) + } +} + +impl Cheatcode for createWallet_2Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { privateKey, walletLabel } = self; + create_wallet(privateKey, Some(walletLabel), state) + } +} + +impl Cheatcode for sign_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { wallet, digest } = self; + let sig = sign(&wallet.privateKey, digest)?; + Ok(encode_full_sig(sig)) + } +} + +impl Cheatcode for signCompact_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { wallet, digest } = self; + let sig = sign(&wallet.privateKey, digest)?; + Ok(encode_compact_sig(sig)) + } +} + +impl Cheatcode for deriveKey_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { mnemonic, index } = self; + derive_key::(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index) + } +} + +impl Cheatcode for deriveKey_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { mnemonic, derivationPath, index } = self; + derive_key::(mnemonic, derivationPath, *index) + } +} + +impl Cheatcode for deriveKey_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { mnemonic, index, language } = self; + derive_key_str(mnemonic, DEFAULT_DERIVATION_PATH_PREFIX, *index, language) + } +} + +impl Cheatcode for deriveKey_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { mnemonic, derivationPath, index, language } = self; + derive_key_str(mnemonic, derivationPath, *index, language) + } +} + +impl Cheatcode for rememberKeyCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { privateKey } = self; + let wallet = parse_wallet(privateKey)?; + let address = inject_wallet(state, wallet); + Ok(address.abi_encode()) + } +} + +impl Cheatcode for rememberKeys_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { mnemonic, derivationPath, count } = self; + let wallets = derive_wallets::(mnemonic, derivationPath, *count)?; + let mut addresses = Vec::
::with_capacity(wallets.len()); + for wallet in wallets { + let addr = inject_wallet(state, wallet); + addresses.push(addr); + } + + Ok(addresses.abi_encode()) + } +} + +impl Cheatcode for rememberKeys_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { mnemonic, derivationPath, language, count } = self; + let wallets = derive_wallets_str(mnemonic, derivationPath, language, *count)?; + let mut addresses = Vec::
::with_capacity(wallets.len()); + for wallet in wallets { + let addr = inject_wallet(state, wallet); + addresses.push(addr); + } + + Ok(addresses.abi_encode()) + } +} + +fn inject_wallet(state: &mut Cheatcodes, wallet: LocalSigner) -> Address { + let address = wallet.address(); + state.wallets().add_local_signer(wallet); + address +} + +impl Cheatcode for sign_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey, digest } = self; + let sig = sign(privateKey, digest)?; + Ok(encode_full_sig(sig)) + } +} + +impl Cheatcode for signCompact_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey, digest } = self; + let sig = sign(privateKey, digest)?; + Ok(encode_compact_sig(sig)) + } +} + +impl Cheatcode for sign_2Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { digest } = self; + let sig = sign_with_wallet(state, None, digest)?; + Ok(encode_full_sig(sig)) + } +} + +impl Cheatcode for signCompact_2Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { digest } = self; + let sig = sign_with_wallet(state, None, digest)?; + Ok(encode_compact_sig(sig)) + } +} + +impl Cheatcode for sign_3Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { signer, digest } = self; + let sig = sign_with_wallet(state, Some(*signer), digest)?; + Ok(encode_full_sig(sig)) + } +} + +impl Cheatcode for signCompact_3Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { signer, digest } = self; + let sig = sign_with_wallet(state, Some(*signer), digest)?; + Ok(encode_compact_sig(sig)) + } +} + +impl Cheatcode for signP256Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey, digest } = self; + sign_p256(privateKey, digest) + } +} + +impl Cheatcode for publicKeyP256Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey } = self; + let pub_key = + parse_private_key_p256(privateKey)?.verifying_key().as_affine().to_encoded_point(false); + let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into()); + let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into()); + + Ok((pub_key_x, pub_key_y).abi_encode()) + } +} + +/// Using a given private key, return its public ETH address, its public key affine x and y +/// coordinates, and its private key (see the 'Wallet' struct) +/// +/// If 'label' is set to 'Some()', assign that label to the associated ETH address in state +fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes) -> Result { + let key = parse_private_key(private_key)?; + let addr = alloy_signer::utils::secret_key_to_address(&key); + + let pub_key = key.verifying_key().as_affine().to_encoded_point(false); + let pub_key_x = U256::from_be_bytes((*pub_key.x().unwrap()).into()); + let pub_key_y = U256::from_be_bytes((*pub_key.y().unwrap()).into()); + + if let Some(label) = label { + state.labels.insert(addr, label.into()); + } + + Ok(Wallet { addr, publicKeyX: pub_key_x, publicKeyY: pub_key_y, privateKey: *private_key } + .abi_encode()) +} + +fn encode_full_sig(sig: alloy_primitives::PrimitiveSignature) -> Vec { + // Retrieve v, r and s from signature. + let v = U256::from(sig.v() as u64 + 27); + let r = B256::from(sig.r()); + let s = B256::from(sig.s()); + (v, r, s).abi_encode() +} + +fn encode_compact_sig(sig: alloy_primitives::PrimitiveSignature) -> Vec { + // Implement EIP-2098 compact signature. + let r = B256::from(sig.r()); + let mut vs = sig.s(); + vs.set_bit(255, sig.v()); + (r, vs).abi_encode() +} + +fn sign(private_key: &U256, digest: &B256) -> Result { + // The `ecrecover` precompile does not use EIP-155. No chain ID is needed. + let wallet = parse_wallet(private_key)?; + let sig = wallet.sign_hash_sync(digest)?; + debug_assert_eq!(sig.recover_address_from_prehash(digest)?, wallet.address()); + Ok(sig) +} + +fn sign_with_wallet( + state: &mut Cheatcodes, + signer: Option
, + digest: &B256, +) -> Result { + if state.wallets().is_empty() { + bail!("no wallets available"); + } + + let mut wallets = state.wallets().inner.lock(); + let maybe_provided_sender = wallets.provided_sender; + let signers = wallets.multi_wallet.signers()?; + + let signer = if let Some(signer) = signer { + signer + } else if let Some(provided_sender) = maybe_provided_sender { + provided_sender + } else if signers.len() == 1 { + *signers.keys().next().unwrap() + } else { + bail!("could not determine signer, there are multiple signers available use vm.sign(signer, digest) to specify one"); + }; + + let wallet = signers + .get(&signer) + .ok_or_else(|| fmt_err!("signer with address {signer} is not available"))?; + + let sig = foundry_common::block_on(wallet.sign_hash(digest))?; + debug_assert_eq!(sig.recover_address_from_prehash(digest)?, signer); + Ok(sig) +} + +fn sign_p256(private_key: &U256, digest: &B256) -> Result { + let signing_key = parse_private_key_p256(private_key)?; + let signature: P256Signature = signing_key.sign_prehash(digest.as_slice())?; + let r_bytes: [u8; 32] = signature.r().to_bytes().into(); + let s_bytes: [u8; 32] = signature.s().to_bytes().into(); + + Ok((r_bytes, s_bytes).abi_encode()) +} + +fn validate_private_key(private_key: &U256) -> Result<()> { + ensure!(*private_key != U256::ZERO, "private key cannot be 0"); + let order = U256::from_be_slice(&C::ORDER.to_be_byte_array()); + ensure!( + *private_key < U256::from_be_slice(&C::ORDER.to_be_byte_array()), + "private key must be less than the {curve:?} curve order ({order})", + curve = C::default(), + ); + + Ok(()) +} + +fn parse_private_key(private_key: &U256) -> Result { + validate_private_key::(private_key)?; + Ok(SigningKey::from_bytes((&private_key.to_be_bytes()).into())?) +} + +fn parse_private_key_p256(private_key: &U256) -> Result { + validate_private_key::(private_key)?; + Ok(P256SigningKey::from_bytes((&private_key.to_be_bytes()).into())?) +} + +pub(super) fn parse_wallet(private_key: &U256) -> Result { + parse_private_key(private_key).map(PrivateKeySigner::from) +} + +fn derive_key_str(mnemonic: &str, path: &str, index: u32, language: &str) -> Result { + match language { + "chinese_simplified" => derive_key::(mnemonic, path, index), + "chinese_traditional" => derive_key::(mnemonic, path, index), + "czech" => derive_key::(mnemonic, path, index), + "english" => derive_key::(mnemonic, path, index), + "french" => derive_key::(mnemonic, path, index), + "italian" => derive_key::(mnemonic, path, index), + "japanese" => derive_key::(mnemonic, path, index), + "korean" => derive_key::(mnemonic, path, index), + "portuguese" => derive_key::(mnemonic, path, index), + "spanish" => derive_key::(mnemonic, path, index), + _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")), + } +} + +fn derive_key(mnemonic: &str, path: &str, index: u32) -> Result { + fn derive_key_path(path: &str, index: u32) -> String { + let mut out = path.to_string(); + if !out.ends_with('/') { + out.push('/'); + } + out.push_str(&index.to_string()); + out + } + + let wallet = MnemonicBuilder::::default() + .phrase(mnemonic) + .derivation_path(derive_key_path(path, index))? + .build()?; + let private_key = U256::from_be_bytes(wallet.credential().to_bytes().into()); + Ok(private_key.abi_encode()) +} + +fn derive_wallets_str( + mnemonic: &str, + path: &str, + language: &str, + count: u32, +) -> Result>> { + match language { + "chinese_simplified" => derive_wallets::(mnemonic, path, count), + "chinese_traditional" => derive_wallets::(mnemonic, path, count), + "czech" => derive_wallets::(mnemonic, path, count), + "english" => derive_wallets::(mnemonic, path, count), + "french" => derive_wallets::(mnemonic, path, count), + "italian" => derive_wallets::(mnemonic, path, count), + "japanese" => derive_wallets::(mnemonic, path, count), + "korean" => derive_wallets::(mnemonic, path, count), + "portuguese" => derive_wallets::(mnemonic, path, count), + "spanish" => derive_wallets::(mnemonic, path, count), + _ => Err(fmt_err!("unsupported mnemonic language: {language:?}")), + } +} + +fn derive_wallets( + mnemonic: &str, + path: &str, + count: u32, +) -> Result>> { + let mut out = path.to_string(); + + if !out.ends_with('/') { + out.push('/'); + } + + let mut wallets = Vec::with_capacity(count as usize); + for idx in 0..count { + let wallet = MnemonicBuilder::::default() + .phrase(mnemonic) + .derivation_path(format!("{out}{idx}"))? + .build()?; + wallets.push(wallet); + } + + Ok(wallets) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{hex::FromHex, FixedBytes}; + use p256::ecdsa::signature::hazmat::PrehashVerifier; + + #[test] + fn test_sign_p256() { + use p256::ecdsa::VerifyingKey; + + let pk_u256: U256 = "1".parse().unwrap(); + let signing_key = P256SigningKey::from_bytes(&pk_u256.to_be_bytes().into()).unwrap(); + let digest = FixedBytes::from_hex( + "0x44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56", + ) + .unwrap(); + + let result = sign_p256(&pk_u256, &digest).unwrap(); + let result_bytes: [u8; 64] = result.try_into().unwrap(); + let signature = P256Signature::from_bytes(&result_bytes.into()).unwrap(); + let verifying_key = VerifyingKey::from(&signing_key); + assert!(verifying_key.verify_prehash(digest.as_slice(), &signature).is_ok()); + } + + #[test] + fn test_sign_p256_pk_too_large() { + // max n from https://neuromancer.sk/std/secg/secp256r1 + let pk = + "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551".parse().unwrap(); + let digest = FixedBytes::from_hex( + "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad", + ) + .unwrap(); + let result = sign_p256(&pk, &digest); + assert_eq!(result.err().unwrap().to_string(), "private key must be less than the NistP256 curve order (115792089210356248762697446949407573529996955224135760342422259061068512044369)"); + } + + #[test] + fn test_sign_p256_pk_0() { + let digest = FixedBytes::from_hex( + "0x54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad", + ) + .unwrap(); + let result = sign_p256(&U256::ZERO, &digest); + assert_eq!(result.err().unwrap().to_string(), "private key cannot be 0"); + } +} diff --git a/crates/cheatcodes/src/env.rs b/crates/cheatcodes/src/env.rs new file mode 100644 index 0000000000000..bba8069fb0ac5 --- /dev/null +++ b/crates/cheatcodes/src/env.rs @@ -0,0 +1,317 @@ +//! Implementations of [`Environment`](spec::Group::Environment) cheatcodes. + +use crate::{string, Cheatcode, Cheatcodes, Error, Result, Vm::*}; +use alloy_dyn_abi::DynSolType; +use alloy_sol_types::SolValue; +use std::{env, sync::OnceLock}; + +/// Stores the forge execution context for the duration of the program. +static FORGE_CONTEXT: OnceLock = OnceLock::new(); + +impl Cheatcode for setEnvCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name: key, value } = self; + if key.is_empty() { + Err(fmt_err!("environment variable key can't be empty")) + } else if key.contains('=') { + Err(fmt_err!("environment variable key can't contain equal sign `=`")) + } else if key.contains('\0') { + Err(fmt_err!("environment variable key can't contain NUL character `\\0`")) + } else if value.contains('\0') { + Err(fmt_err!("environment variable value can't contain NUL character `\\0`")) + } else { + env::set_var(key, value); + Ok(Default::default()) + } + } +} + +impl Cheatcode for envExistsCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + Ok(env::var(name).is_ok().abi_encode()) + } +} + +impl Cheatcode for envBool_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::Bool) + } +} + +impl Cheatcode for envUint_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for envInt_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::Int(256)) + } +} + +impl Cheatcode for envAddress_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::Address) + } +} + +impl Cheatcode for envBytes32_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for envString_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::String) + } +} + +impl Cheatcode for envBytes_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + env(name, &DynSolType::Bytes) + } +} + +impl Cheatcode for envBool_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::Bool) + } +} + +impl Cheatcode for envUint_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for envInt_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::Int(256)) + } +} + +impl Cheatcode for envAddress_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::Address) + } +} + +impl Cheatcode for envBytes32_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for envString_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::String) + } +} + +impl Cheatcode for envBytes_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim } = self; + env_array(name, delim, &DynSolType::Bytes) + } +} + +// bool +impl Cheatcode for envOr_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::Bool) + } +} + +// uint256 +impl Cheatcode for envOr_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::Uint(256)) + } +} + +// int256 +impl Cheatcode for envOr_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::Int(256)) + } +} + +// address +impl Cheatcode for envOr_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::Address) + } +} + +// bytes32 +impl Cheatcode for envOr_4Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::FixedBytes(32)) + } +} + +// string +impl Cheatcode for envOr_5Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::String) + } +} + +// bytes +impl Cheatcode for envOr_6Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, defaultValue } = self; + env_default(name, defaultValue, &DynSolType::Bytes) + } +} + +// bool[] +impl Cheatcode for envOr_7Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + env_array_default(name, delim, defaultValue, &DynSolType::Bool) + } +} + +// uint256[] +impl Cheatcode for envOr_8Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + env_array_default(name, delim, defaultValue, &DynSolType::Uint(256)) + } +} + +// int256[] +impl Cheatcode for envOr_9Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + env_array_default(name, delim, defaultValue, &DynSolType::Int(256)) + } +} + +// address[] +impl Cheatcode for envOr_10Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + env_array_default(name, delim, defaultValue, &DynSolType::Address) + } +} + +// bytes32[] +impl Cheatcode for envOr_11Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + env_array_default(name, delim, defaultValue, &DynSolType::FixedBytes(32)) + } +} + +// string[] +impl Cheatcode for envOr_12Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + env_array_default(name, delim, defaultValue, &DynSolType::String) + } +} + +// bytes[] +impl Cheatcode for envOr_13Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name, delim, defaultValue } = self; + let default = defaultValue.to_vec(); + env_array_default(name, delim, &default, &DynSolType::Bytes) + } +} + +impl Cheatcode for isContextCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { context } = self; + Ok((FORGE_CONTEXT.get() == Some(context)).abi_encode()) + } +} + +/// Set `forge` command current execution context for the duration of the program. +/// Execution context is immutable, subsequent calls of this function won't change the context. +pub fn set_execution_context(context: ForgeContext) { + let _ = FORGE_CONTEXT.set(context); +} + +fn env(key: &str, ty: &DynSolType) -> Result { + get_env(key).and_then(|val| string::parse(&val, ty).map_err(map_env_err(key, &val))) +} + +fn env_default(key: &str, default: &T, ty: &DynSolType) -> Result { + Ok(env(key, ty).unwrap_or_else(|_| default.abi_encode())) +} + +fn env_array(key: &str, delim: &str, ty: &DynSolType) -> Result { + get_env(key).and_then(|val| { + string::parse_array(val.split(delim).map(str::trim), ty).map_err(map_env_err(key, &val)) + }) +} + +fn env_array_default(key: &str, delim: &str, default: &T, ty: &DynSolType) -> Result { + Ok(env_array(key, delim, ty).unwrap_or_else(|_| default.abi_encode())) +} + +fn get_env(key: &str) -> Result { + match env::var(key) { + Ok(val) => Ok(val), + Err(env::VarError::NotPresent) => Err(fmt_err!("environment variable {key:?} not found")), + Err(env::VarError::NotUnicode(s)) => { + Err(fmt_err!("environment variable {key:?} was not valid unicode: {s:?}")) + } + } +} + +/// Converts the error message of a failed parsing attempt to a more user-friendly message that +/// doesn't leak the value. +fn map_env_err<'a>(key: &'a str, value: &'a str) -> impl FnOnce(Error) -> Error + 'a { + move |e| { + // failed parsing as type `uint256`: parser error: + // + // ^ + // expected at least one digit + let mut e = e.to_string(); + e = e.replacen(&format!("\"{value}\""), &format!("${key}"), 1); + e = e.replacen(&format!("\n{value}\n"), &format!("\n${key}\n"), 1); + Error::from(e) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_env_uint() { + let key = "parse_env_uint"; + let value = "t"; + env::set_var(key, value); + + let err = env(key, &DynSolType::Uint(256)).unwrap_err().to_string(); + assert_eq!(err.matches("$parse_env_uint").count(), 2, "{err:?}"); + env::remove_var(key); + } +} diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs new file mode 100644 index 0000000000000..b77c889d59ab7 --- /dev/null +++ b/crates/cheatcodes/src/error.rs @@ -0,0 +1,323 @@ +use crate::Vm; +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::{BackendError, DatabaseError}; +use foundry_wallets::error::WalletSignerError; +use k256::ecdsa::signature::Error as SignatureError; +use revm::primitives::EVMError; +use std::{borrow::Cow, fmt}; + +/// Cheatcode result type. +/// +/// Type alias with a default Ok type of [`Vec`], and default Err type of [`Error`]. +pub type Result, E = Error> = std::result::Result; + +macro_rules! fmt_err { + ($msg:literal $(,)?) => { + $crate::Error::fmt(::std::format_args!($msg)) + }; + ($err:expr $(,)?) => { + <$crate::Error as ::std::convert::From<_>>::from($err) + }; + ($fmt:expr, $($arg:tt)*) => { + $crate::Error::fmt(::std::format_args!($fmt, $($arg)*)) + }; +} + +macro_rules! bail { + ($msg:literal $(,)?) => { + return ::std::result::Result::Err(fmt_err!($msg)) + }; + ($err:expr $(,)?) => { + return ::std::result::Result::Err(fmt_err!($err)) + }; + ($fmt:expr, $($arg:tt)*) => { + return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*)) + }; +} + +macro_rules! ensure { + ($cond:expr $(,)?) => { + if !$cond { + return ::std::result::Result::Err($crate::Error::custom( + ::std::concat!("Condition failed: `", ::std::stringify!($cond), "`") + )); + } + }; + ($cond:expr, $msg:literal $(,)?) => { + if !$cond { + return ::std::result::Result::Err(fmt_err!($msg)); + } + }; + ($cond:expr, $err:expr $(,)?) => { + if !$cond { + return ::std::result::Result::Err(fmt_err!($err)); + } + }; + ($cond:expr, $fmt:expr, $($arg:tt)*) => { + if !$cond { + return ::std::result::Result::Err(fmt_err!($fmt, $($arg)*)); + } + }; +} + +macro_rules! ensure_not_precompile { + ($address:expr, $ctxt:expr) => { + if $ctxt.is_precompile($address) { + return Err($crate::error::precompile_error($address)); + } + }; +} + +#[cold] +pub(crate) fn precompile_error(address: &Address) -> Error { + fmt_err!("cannot use precompile {address} as an argument") +} + +/// Error thrown by cheatcodes. +// This uses a custom repr to minimize the size of the error. +// The repr is basically `enum { Cow<'static, str>, Cow<'static, [u8]> }` +pub struct Error { + /// If true, encode `data` as `Error(string)`, otherwise encode it directly as `bytes`. + is_str: bool, + /// Whether this was constructed from an owned byte vec, which means we have to drop the data + /// in `impl Drop`. + drop: bool, + /// The error data. Always a valid pointer, and never modified. + data: *const [u8], +} + +impl std::error::Error for Error {} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Error::")?; + self.kind().fmt(f) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.kind().fmt(f) + } +} + +/// Kind of cheatcode errors. +/// +/// Constructed by [`Error::kind`]. +#[derive(Debug)] +pub enum ErrorKind<'a> { + /// A string error, ABI-encoded as `CheatcodeError(string)`. + String(&'a str), + /// A raw bytes error. Does not get encoded. + Bytes(&'a [u8]), +} + +impl fmt::Display for ErrorKind<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::String(ss) => f.write_str(ss), + Self::Bytes(b) => f.write_str(&hex::encode_prefixed(b)), + } + } +} + +impl Error { + /// Creates a new error and ABI encodes it as `CheatcodeError(string)`. + pub fn encode(error: impl Into) -> Bytes { + error.into().abi_encode().into() + } + + /// Creates a new error with a custom message. + pub fn display(msg: impl fmt::Display) -> Self { + Self::fmt(format_args!("{msg}")) + } + + /// Creates a new error with a custom [`fmt::Arguments`] message. + pub fn fmt(args: fmt::Arguments<'_>) -> Self { + match args.as_str() { + Some(s) => Self::new_str(s), + None => Self::new_string(std::fmt::format(args)), + } + } + + /// ABI-encodes this error as `CheatcodeError(string)` if the inner message is a string, + /// otherwise returns the raw bytes. + pub fn abi_encode(&self) -> Vec { + match self.kind() { + ErrorKind::String(string) => Vm::CheatcodeError { message: string.into() }.abi_encode(), + ErrorKind::Bytes(bytes) => bytes.into(), + } + } + + /// Returns the kind of this error. + #[inline] + pub fn kind(&self) -> ErrorKind<'_> { + let data = self.data(); + if self.is_str { + debug_assert!(std::str::from_utf8(data).is_ok()); + ErrorKind::String(unsafe { std::str::from_utf8_unchecked(data) }) + } else { + ErrorKind::Bytes(data) + } + } + + /// Returns the raw data of this error. + #[inline] + pub fn data(&self) -> &[u8] { + unsafe { &*self.data } + } + + /// Returns `true` if this error is a human-readable string. + #[inline] + pub fn is_str(&self) -> bool { + self.is_str + } + + #[inline] + fn new_str(data: &'static str) -> Self { + Self::_new(true, false, data.as_bytes()) + } + + #[inline] + fn new_string(data: String) -> Self { + Self::_new(true, true, Box::into_raw(data.into_boxed_str().into_boxed_bytes())) + } + + #[inline] + fn new_bytes(data: &'static [u8]) -> Self { + Self::_new(false, false, data) + } + + #[inline] + fn new_vec(data: Vec) -> Self { + Self::_new(false, true, Box::into_raw(data.into_boxed_slice())) + } + + #[inline] + fn _new(is_str: bool, drop: bool, data: *const [u8]) -> Self { + debug_assert!(!data.is_null()); + Self { is_str, drop, data } + } +} + +impl Drop for Error { + fn drop(&mut self) { + if self.drop { + drop(unsafe { Box::<[u8]>::from_raw(self.data.cast_mut()) }); + } + } +} + +impl From> for Error { + fn from(value: Cow<'static, str>) -> Self { + match value { + Cow::Borrowed(str) => Self::new_str(str), + Cow::Owned(string) => Self::new_string(string), + } + } +} + +impl From for Error { + fn from(value: String) -> Self { + Self::new_string(value) + } +} + +impl From<&'static str> for Error { + fn from(value: &'static str) -> Self { + Self::new_str(value) + } +} + +impl From> for Error { + fn from(value: Cow<'static, [u8]>) -> Self { + match value { + Cow::Borrowed(bytes) => Self::new_bytes(bytes), + Cow::Owned(vec) => Self::new_vec(vec), + } + } +} + +impl From<&'static [u8]> for Error { + fn from(value: &'static [u8]) -> Self { + Self::new_bytes(value) + } +} + +impl From<&'static [u8; N]> for Error { + fn from(value: &'static [u8; N]) -> Self { + Self::new_bytes(value) + } +} + +impl From> for Error { + fn from(value: Vec) -> Self { + Self::new_vec(value) + } +} + +impl From for Error { + #[inline] + fn from(value: Bytes) -> Self { + Self::new_vec(value.into()) + } +} + +// So we can use `?` on `Result<_, Error>`. +macro_rules! impl_from { + ($($t:ty),* $(,)?) => {$( + impl From<$t> for Error { + fn from(value: $t) -> Self { + Self::display(value) + } + } + )*}; +} + +impl_from!( + alloy_sol_types::Error, + alloy_dyn_abi::Error, + alloy_primitives::SignatureError, + eyre::Report, + FsPathError, + hex::FromHexError, + BackendError, + DatabaseError, + jsonpath_lib::JsonPathError, + serde_json::Error, + SignatureError, + std::io::Error, + std::num::TryFromIntError, + std::str::Utf8Error, + std::string::FromUtf8Error, + UnresolvedEnvVarError, + LocalSignerError, + SignerError, + WalletSignerError, +); + +impl> From> for Error { + fn from(err: EVMError) -> Self { + Self::display(BackendError::from(err)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn encode() { + let error = Vm::CheatcodeError { message: "hello".into() }.abi_encode(); + assert_eq!(Error::from("hello").abi_encode(), error); + assert_eq!(Error::encode("hello"), error); + + assert_eq!(Error::from(b"hello").abi_encode(), b"hello"); + assert_eq!(Error::encode(b"hello"), b"hello"[..]); + } +} diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs new file mode 100644 index 0000000000000..a002bc5f0ff43 --- /dev/null +++ b/crates/cheatcodes/src/evm.rs @@ -0,0 +1,1198 @@ +//! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. + +use crate::{ + inspector::{InnerEcx, RecordDebugStepInfo}, + BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result, + Vm::*, +}; +use alloy_consensus::TxEnvelope; +use alloy_genesis::{Genesis, GenesisAccount}; +use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256}; +use alloy_rlp::Decodable; +use alloy_sol_types::SolValue; +use foundry_common::fs::{read_json_file, write_json_file}; +use foundry_evm_core::{ + backend::{DatabaseExt, RevertStateSnapshotAction}, + constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, +}; +use foundry_evm_traces::StackSnapshotType; +use rand::Rng; +use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; +use std::{ + collections::{btree_map::Entry, BTreeMap}, + fmt::Display, + path::Path, +}; + +mod record_debug_step; +use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace}; +use serde::Serialize; + +mod fork; +pub(crate) mod mapping; +pub(crate) mod mock; +pub(crate) mod prank; + +/// Records storage slots reads and writes. +#[derive(Clone, Debug, Default)] +pub struct RecordAccess { + /// Storage slots reads. + pub reads: HashMap>, + /// 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); + } +} + +/// Records the `snapshotGas*` cheatcodes. +#[derive(Clone, Debug)] +pub struct GasRecord { + /// The group name of the gas snapshot. + pub group: String, + /// The name of the gas snapshot. + pub name: String, + /// The total gas used in the gas snapshot. + pub gas_used: u64, + /// Depth at which the gas snapshot was taken. + pub depth: u64, +} + +/// Records `deal` cheatcodes +#[derive(Clone, Debug)] +pub struct DealRecord { + /// Target of the deal. + pub address: Address, + /// The balance of the address before deal was applied + pub old_balance: U256, + /// Balance after deal was applied + pub new_balance: U256, +} + +/// Storage slot diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct SlotStateDiff { + /// Initial storage value. + previous_value: B256, + /// Current storage value. + new_value: B256, +} + +/// Balance diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct BalanceDiff { + /// Initial storage value. + previous_value: U256, + /// Current storage value. + new_value: U256, +} + +/// Account state diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct AccountStateDiffs { + /// Address label, if any set. + label: Option, + /// Account balance changes. + balance_diff: Option, + /// State changes, per slot. + state_diff: BTreeMap, +} + +impl Display for AccountStateDiffs { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> { + // Print changed account. + if let Some(label) = &self.label { + writeln!(f, "label: {label}")?; + } + // Print balance diff if changed. + if let Some(balance_diff) = &self.balance_diff { + if balance_diff.previous_value != balance_diff.new_value { + writeln!( + f, + "- balance diff: {} → {}", + balance_diff.previous_value, balance_diff.new_value + )?; + } + } + // Print state diff if any. + if !&self.state_diff.is_empty() { + writeln!(f, "- state diff:")?; + for (slot, slot_changes) in &self.state_diff { + writeln!( + f, + "@ {slot}: {} → {}", + slot_changes.previous_value, slot_changes.new_value + )?; + } + } + + Ok(()) + } +} + +impl Cheatcode for addrCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { privateKey } = self; + let wallet = super::crypto::parse_wallet(privateKey)?; + Ok(wallet.address().abi_encode()) + } +} + +impl Cheatcode for getNonce_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + get_nonce(ccx, account) + } +} + +impl Cheatcode for getNonce_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { wallet } = self; + get_nonce(ccx, &wallet.addr) + } +} + +impl Cheatcode for loadCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { target, slot } = *self; + ensure_not_precompile!(&target, ccx); + ccx.ecx.load_account(target)?; + let mut val = ccx.ecx.sload(target, slot.into())?; + + if val.is_cold && val.data.is_zero() { + if ccx.state.has_arbitrary_storage(&target) { + // If storage slot is untouched and load from a target with arbitrary storage, + // then set random value for current slot. + let rand_value = ccx.state.rng().gen(); + ccx.state.arbitrary_storage.as_mut().unwrap().save( + ccx.ecx, + target, + slot.into(), + rand_value, + ); + val.data = rand_value; + } else if ccx.state.is_arbitrary_storage_copy(&target) { + // If storage slot is untouched and load from a target that copies storage from + // a source address with arbitrary storage, then copy existing arbitrary value. + // If no arbitrary value generated yet, then the random one is saved and set. + let rand_value = ccx.state.rng().gen(); + val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy( + ccx.ecx, + target, + slot.into(), + rand_value, + ); + } + } + + Ok(val.abi_encode()) + } +} + +impl Cheatcode for loadAllocsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { pathToAllocsJson } = self; + + let path = Path::new(pathToAllocsJson); + ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}"); + + // Let's first assume we're reading a file with only the allocs. + let allocs: BTreeMap = match read_json_file(path) { + Ok(allocs) => allocs, + Err(_) => { + // Let's try and read from a genesis file, and extract allocs. + let genesis = read_json_file::(path)?; + genesis.alloc + } + }; + + // Then, load the allocs into the database. + ccx.ecx + .db + .load_allocs(&allocs, &mut ccx.ecx.journaled_state) + .map(|()| Vec::default()) + .map_err(|e| fmt_err!("failed to load allocs: {e}")) + } +} + +impl Cheatcode for cloneAccountCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { source, target } = self; + + let account = ccx.ecx.journaled_state.load_account(*source, &mut ccx.ecx.db)?; + ccx.ecx.db.clone_account( + &genesis_account(account.data), + target, + &mut ccx.ecx.journaled_state, + )?; + // Cloned account should persist in forked envs. + ccx.ecx.db.add_persistent_account(*target); + Ok(Default::default()) + } +} + +impl Cheatcode for dumpStateCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { pathToStateJson } = self; + let path = Path::new(pathToStateJson); + + // Do not include system account or empty accounts in the dump. + let skip = |key: &Address, val: &Account| { + key == &CHEATCODE_ADDRESS || + key == &CALLER || + key == &HARDHAT_CONSOLE_ADDRESS || + key == &TEST_CONTRACT_ADDRESS || + key == &ccx.caller || + key == &ccx.state.config.evm_opts.sender || + val.is_empty() + }; + + let alloc = ccx + .ecx + .journaled_state + .state() + .iter_mut() + .filter(|(key, val)| !skip(key, val)) + .map(|(key, val)| (key, genesis_account(val))) + .collect::>(); + + write_json_file(path, &alloc)?; + Ok(Default::default()) + } +} + +impl Cheatcode for recordCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.accesses = Some(Default::default()); + Ok(Default::default()) + } +} + +impl Cheatcode for accessesCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { target } = *self; + let result = state + .accesses + .as_mut() + .map(|accesses| { + ( + &accesses.reads.entry(target).or_default()[..], + &accesses.writes.entry(target).or_default()[..], + ) + }) + .unwrap_or_default(); + Ok(result.abi_encode_params()) + } +} + +impl Cheatcode for recordLogsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.recorded_logs = Some(Default::default()); + Ok(Default::default()) + } +} + +impl Cheatcode for getRecordedLogsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode()) + } +} + +impl Cheatcode for pauseGasMeteringCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.gas_metering.paused = true; + Ok(Default::default()) + } +} + +impl Cheatcode for resumeGasMeteringCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.gas_metering.resume(); + Ok(Default::default()) + } +} + +impl Cheatcode for resetGasMeteringCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.gas_metering.reset(); + Ok(Default::default()) + } +} + +impl Cheatcode for lastCallGasCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + let Some(last_call_gas) = &state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + Ok(last_call_gas.abi_encode()) + } +} + +impl Cheatcode for chainIdCall { + 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(); + Ok(Default::default()) + } +} + +impl Cheatcode for coinbaseCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newCoinbase } = self; + ccx.ecx.env.block.coinbase = *newCoinbase; + Ok(Default::default()) + } +} + +impl Cheatcode for difficultyCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newDifficulty } = self; + ensure!( + ccx.ecx.spec_id() < SpecId::MERGE, + "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \ + see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" + ); + ccx.ecx.env.block.difficulty = *newDifficulty; + Ok(Default::default()) + } +} + +impl Cheatcode for feeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newBasefee } = self; + ccx.ecx.env.block.basefee = *newBasefee; + Ok(Default::default()) + } +} + +impl Cheatcode for prevrandao_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newPrevrandao } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::MERGE, + "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ + see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" + ); + ccx.ecx.env.block.prevrandao = Some(*newPrevrandao); + Ok(Default::default()) + } +} + +impl Cheatcode for prevrandao_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newPrevrandao } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::MERGE, + "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ + see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" + ); + ccx.ecx.env.block.prevrandao = Some((*newPrevrandao).into()); + Ok(Default::default()) + } +} + +impl Cheatcode for blobhashesCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { hashes } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobhashes` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.tx.blob_hashes.clone_from(hashes); + Ok(Default::default()) + } +} + +impl Cheatcode for getBlobhashesCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`getBlobhashes` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + Ok(ccx.ecx.env.tx.blob_hashes.clone().abi_encode()) + } +} + +impl Cheatcode for rollCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newHeight } = self; + ccx.ecx.env.block.number = *newHeight; + Ok(Default::default()) + } +} + +impl Cheatcode for getBlockNumberCall { + 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_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newGasPrice } = self; + ccx.ecx.env.tx.gas_price = *newGasPrice; + Ok(Default::default()) + } +} + +impl Cheatcode for warpCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newTimestamp } = self; + ccx.ecx.env.block.timestamp = *newTimestamp; + Ok(Default::default()) + } +} + +impl Cheatcode for getBlockTimestampCall { + 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_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newBlobBaseFee } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobBaseFee` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.block.set_blob_excess_gas_and_price( + (*newBlobBaseFee).to(), + ccx.ecx.spec_id() >= SpecId::PRAGUE, + ); + Ok(Default::default()) + } +} + +impl Cheatcode for getBlobBaseFeeCall { + 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_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account: address, newBalance: new_balance } = *self; + let account = journaled_account(ccx.ecx, address)?; + let old_balance = std::mem::replace(&mut account.info.balance, new_balance); + let record = DealRecord { address, old_balance, new_balance }; + ccx.state.eth_deals.push(record); + Ok(Default::default()) + } +} + +impl Cheatcode for etchCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { target, newRuntimeBytecode } = self; + ensure_not_precompile!(target, ccx); + ccx.ecx.load_account(*target)?; + let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(newRuntimeBytecode)); + ccx.ecx.journaled_state.set_code(*target, bytecode); + Ok(Default::default()) + } +} + +impl Cheatcode for resetNonceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + let account = journaled_account(ccx.ecx, *account)?; + // Per EIP-161, EOA nonces start at 0, but contract nonces + // start at 1. Comparing by code_hash instead of code + // to avoid hitting the case where account's code is None. + let empty = account.info.code_hash == KECCAK_EMPTY; + let nonce = if empty { 0 } else { 1 }; + account.info.nonce = nonce; + debug!(target: "cheatcodes", nonce, "reset"); + Ok(Default::default()) + } +} + +impl Cheatcode for setNonceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account, newNonce } = *self; + let account = journaled_account(ccx.ecx, account)?; + // nonce must increment only + let current = account.info.nonce; + ensure!( + newNonce >= current, + "new nonce ({newNonce}) must be strictly equal to or higher than the \ + account's current nonce ({current})" + ); + account.info.nonce = newNonce; + Ok(Default::default()) + } +} + +impl Cheatcode for setNonceUnsafeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account, newNonce } = *self; + let account = journaled_account(ccx.ecx, account)?; + account.info.nonce = newNonce; + Ok(Default::default()) + } +} + +impl Cheatcode for storeCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { target, slot, value } = *self; + ensure_not_precompile!(&target, ccx); + // ensure the account is touched + let _ = journaled_account(ccx.ecx, target)?; + ccx.ecx.sstore(target, slot.into(), value.into())?; + Ok(Default::default()) + } +} + +impl Cheatcode for coolCall { + 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(); + account.storage.clear(); + } + Ok(Default::default()) + } +} + +impl Cheatcode for readCallersCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + read_callers(ccx.state, &ccx.ecx.env.tx.caller) + } +} + +impl Cheatcode for snapshotValue_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, value } = self; + inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotValue_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name, value } = self; + inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotGasLastCall_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed) + } +} + +impl Cheatcode for snapshotGasLastCall_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, group } = self; + let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + inner_last_gas_snapshot( + ccx, + Some(group.clone()), + Some(name.clone()), + last_call_gas.gasTotalUsed, + ) + } +} + +impl Cheatcode for startSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + inner_start_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for startSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + } +} + +impl Cheatcode for stopSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_stop_gas_snapshot(ccx, None, None) + } +} + +impl Cheatcode for stopSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + inner_stop_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for stopSnapshotGas_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + } +} + +// Deprecated in favor of `snapshotStateCall` +impl Cheatcode for snapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_snapshot_state(ccx) + } +} + +impl Cheatcode for snapshotStateCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_snapshot_state(ccx) + } +} + +// Deprecated in favor of `revertToStateCall` +impl Cheatcode for revertToCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_revert_to_state(ccx, *snapshotId) + } +} + +impl Cheatcode for revertToStateCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_revert_to_state(ccx, *snapshotId) + } +} + +// Deprecated in favor of `revertToStateAndDeleteCall` +impl Cheatcode for revertToAndDeleteCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_revert_to_state_and_delete(ccx, *snapshotId) + } +} + +impl Cheatcode for revertToStateAndDeleteCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_revert_to_state_and_delete(ccx, *snapshotId) + } +} + +// Deprecated in favor of `deleteStateSnapshotCall` +impl Cheatcode for deleteSnapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_delete_state_snapshot(ccx, *snapshotId) + } +} + +impl Cheatcode for deleteStateSnapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_delete_state_snapshot(ccx, *snapshotId) + } +} + +// Deprecated in favor of `deleteStateSnapshotsCall` +impl Cheatcode for deleteSnapshotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_delete_state_snapshots(ccx) + } +} + +impl Cheatcode for deleteStateSnapshotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_delete_state_snapshots(ccx) + } +} + +impl Cheatcode for startStateDiffRecordingCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.recorded_account_diffs_stack = Some(Default::default()); + Ok(Default::default()) + } +} + +impl Cheatcode for stopAndReturnStateDiffCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + get_state_diff(state) + } +} + +impl Cheatcode for getStateDiffCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let mut diffs = String::new(); + let state_diffs = get_recorded_state_diffs(state); + for (address, state_diffs) in state_diffs { + diffs.push_str(&format!("{address}\n")); + diffs.push_str(&format!("{state_diffs}\n")); + } + Ok(diffs.abi_encode()) + } +} + +impl Cheatcode for getStateDiffJsonCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let state_diffs = get_recorded_state_diffs(state); + Ok(serde_json::to_string(&state_diffs)?.abi_encode()) + } +} + +impl Cheatcode for broadcastRawTransactionCall { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + let tx = TxEnvelope::decode(&mut self.data.as_ref()) + .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; + + ccx.ecx.db.transact_from_tx( + &tx.clone().into(), + (*ccx.ecx.env).clone(), + &mut ccx.ecx.journaled_state, + &mut *executor.get_inspector(ccx.state), + )?; + + if ccx.state.broadcast.is_some() { + ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ccx.db.active_fork_url(), + transaction: tx.try_into()?, + }); + } + + Ok(Default::default()) + } +} + +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()) + } +} + +impl Cheatcode for startDebugTraceRecordingCall { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else { + return Err(Error::from("no tracer initiated, consider adding -vvv flag")) + }; + + let mut info = RecordDebugStepInfo { + // will be updated later + start_node_idx: 0, + // keep the original config to revert back later + original_tracer_config: *tracer.config(), + }; + + // turn on tracer configuration for recording + tracer.update_config(|config| { + config + .set_steps(true) + .set_memory_snapshots(true) + .set_stack_snapshots(StackSnapshotType::Full) + }); + + // track where the recording starts + if let Some(last_node) = tracer.traces().nodes().last() { + info.start_node_idx = last_node.idx; + } + + ccx.state.record_debug_steps_info = Some(info); + Ok(Default::default()) + } +} + +impl Cheatcode for stopAndReturnDebugTraceRecordingCall { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else { + return Err(Error::from("no tracer initiated, consider adding -vvv flag")) + }; + + let Some(record_info) = ccx.state.record_debug_steps_info else { + return Err(Error::from("nothing recorded")) + }; + + // Use the trace nodes to flatten the call trace + let root = tracer.traces(); + let steps = flatten_call_trace(0, root, record_info.start_node_idx); + + let debug_steps: Vec = + steps.iter().map(|&step| convert_call_trace_to_debug_step(step)).collect(); + // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage. + if !record_info.original_tracer_config.record_steps { + tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| { + node.trace.steps = Vec::new(); + node.logs = Vec::new(); + node.ordering = Vec::new(); + }); + } + + // Revert the tracer config to the one before recording + tracer.update_config(|_config| record_info.original_tracer_config); + + // Clean up the recording info + ccx.state.record_debug_steps_info = None; + + Ok(debug_steps.abi_encode()) + } +} + +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()) +} + +fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result { + Ok(ccx.ecx.db.snapshot_state(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) +} + +fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { + let result = if let Some(journaled_state) = ccx.ecx.db.revert_state( + snapshot_id, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, + RevertStateSnapshotAction::RevertKeep, + ) { + // we reset the evm's journaled_state to the state of the snapshot previous state + ccx.ecx.journaled_state = journaled_state; + true + } else { + false + }; + Ok(result.abi_encode()) +} + +fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { + let result = if let Some(journaled_state) = ccx.ecx.db.revert_state( + snapshot_id, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, + RevertStateSnapshotAction::RevertRemove, + ) { + // we reset the evm's journaled_state to the state of the snapshot previous state + ccx.ecx.journaled_state = journaled_state; + true + } else { + false + }; + Ok(result.abi_encode()) +} + +fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { + let result = ccx.ecx.db.delete_state_snapshot(snapshot_id); + Ok(result.abi_encode()) +} + +fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result { + ccx.ecx.db.delete_state_snapshots(); + Ok(Default::default()) +} + +fn inner_value_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, + value: String, +) -> Result { + let (group, name) = derive_snapshot_name(ccx, group, name); + + ccx.state.gas_snapshots.entry(group).or_default().insert(name, value); + + Ok(Default::default()) +} + +fn inner_last_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, + value: u64, +) -> Result { + let (group, name) = derive_snapshot_name(ccx, group, name); + + ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string()); + + Ok(value.abi_encode()) +} + +fn inner_start_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, +) -> Result { + // Revert if there is an active gas snapshot as we can only have one active snapshot at a time. + if ccx.state.gas_metering.active_gas_snapshot.is_some() { + let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone(); + bail!("gas snapshot was already started with group: {group} and name: {name}"); + } + + let (group, name) = derive_snapshot_name(ccx, group, name); + + ccx.state.gas_metering.gas_records.push(GasRecord { + group: group.clone(), + name: name.clone(), + gas_used: 0, + depth: ccx.ecx.journaled_state.depth(), + }); + + ccx.state.gas_metering.active_gas_snapshot = Some((group, name)); + + ccx.state.gas_metering.start(); + + Ok(Default::default()) +} + +fn inner_stop_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, +) -> Result { + // If group and name are not provided, use the last snapshot group and name. + let (group, name) = group.zip(name).unwrap_or_else(|| { + let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone(); + (group, name) + }); + + if let Some(record) = ccx + .state + .gas_metering + .gas_records + .iter_mut() + .find(|record| record.group == group && record.name == name) + { + // Calculate the gas used since the snapshot was started. + // We subtract 171 from the gas used to account for gas used by the snapshot itself. + let value = record.gas_used.saturating_sub(171); + + ccx.state + .gas_snapshots + .entry(group.clone()) + .or_default() + .insert(name.clone(), value.to_string()); + + // Stop the gas metering. + ccx.state.gas_metering.stop(); + + // Remove the gas record. + ccx.state + .gas_metering + .gas_records + .retain(|record| record.group != group && record.name != name); + + // Clear last snapshot cache if we have an exact match. + if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot { + if snapshot_group == &group && snapshot_name == &name { + ccx.state.gas_metering.active_gas_snapshot = None; + } + } + + Ok(value.abi_encode()) + } else { + bail!("no gas snapshot was started with the name: {name} in group: {group}"); + } +} + +// Derives the snapshot group and name from the provided group and name or the running contract. +fn derive_snapshot_name( + ccx: &CheatsCtxt, + group: Option, + name: Option, +) -> (String, String) { + let group = group.unwrap_or_else(|| { + ccx.state.config.running_artifact.clone().expect("expected running contract").name + }); + let name = name.unwrap_or_else(|| "default".to_string()); + (group, name) +} + +/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and +/// `tx.origin`. +/// +/// Depending on the current caller mode, one of the following results will be returned: +/// - If there is an active prank: +/// - caller_mode will be equal to: +/// - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`. +/// - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`. +/// - `msg.sender` will be equal to the address set for the prank. +/// - `tx.origin` will be equal to the default sender address unless an alternative one has been +/// set when configuring the prank. +/// +/// - If there is an active broadcast: +/// - caller_mode will be equal to: +/// - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`. +/// - [CallerMode::RecurrentBroadcast] if the broadcast has been set with +/// `vm.startBroadcast(..)`. +/// - `msg.sender` and `tx.origin` will be equal to the address provided when setting the +/// broadcast. +/// +/// - If no caller modification is active: +/// - caller_mode will be equal to [CallerMode::None], +/// - `msg.sender` and `tx.origin` will be equal to the default sender address. +fn read_callers(state: &Cheatcodes, default_sender: &Address) -> Result { + let Cheatcodes { prank, broadcast, .. } = state; + + let mut mode = CallerMode::None; + let mut new_caller = default_sender; + let mut new_origin = default_sender; + if let Some(prank) = prank { + mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank }; + new_caller = &prank.new_caller; + if let Some(new) = &prank.new_origin { + new_origin = new; + } + } else if let Some(broadcast) = broadcast { + mode = if broadcast.single_call { + CallerMode::Broadcast + } else { + CallerMode::RecurrentBroadcast + }; + new_caller = &broadcast.new_origin; + new_origin = &broadcast.new_origin; + } + + Ok((mode, new_caller, new_origin).abi_encode_params()) +} + +/// Ensures the `Account` is loaded and touched. +pub(super) fn journaled_account<'a>( + ecx: InnerEcx<'a, '_, '_>, + addr: Address, +) -> Result<&'a mut Account> { + ecx.load_account(addr)?; + ecx.journaled_state.touch(&addr); + Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) +} + +/// Consumes recorded account accesses and returns them as an abi encoded +/// array of [AccountAccess]. If there are no accounts were +/// recorded as accessed, an abi encoded empty array is returned. +/// +/// In the case where `stopAndReturnStateDiff` is called at a lower +/// depth than `startStateDiffRecording`, multiple `Vec` +/// will be flattened, preserving the order of the accesses. +fn get_state_diff(state: &mut Cheatcodes) -> Result { + let res = state + .recorded_account_diffs_stack + .replace(Default::default()) + .unwrap_or_default() + .into_iter() + .flatten() + .collect::>(); + Ok(res.abi_encode()) +} + +/// Helper function that creates a `GenesisAccount` from a regular `Account`. +fn genesis_account(account: &Account) -> GenesisAccount { + GenesisAccount { + nonce: Some(account.info.nonce), + balance: account.info.balance, + code: account.info.code.as_ref().map(|o| o.original_bytes()), + storage: Some( + account + .storage + .iter() + .map(|(k, v)| (B256::from(*k), B256::from(v.present_value()))) + .collect(), + ), + private_key: None, + } +} + +/// Helper function to returns state diffs recorded for each changed account. +fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap { + let mut state_diffs: BTreeMap = BTreeMap::default(); + if let Some(records) = &state.recorded_account_diffs_stack { + records + .iter() + .flatten() + .filter(|account_access| { + !account_access.storageAccesses.is_empty() || + account_access.oldBalance != account_access.newBalance + }) + .for_each(|account_access| { + // Record account balance diffs. + if account_access.oldBalance != account_access.newBalance { + let account_diff = + state_diffs.entry(account_access.account).or_insert_with(|| { + AccountStateDiffs { + label: state.labels.get(&account_access.account).cloned(), + ..Default::default() + } + }); + // Update balance diff. Do not overwrite the initial balance if already set. + if let Some(diff) = &mut account_diff.balance_diff { + diff.new_value = account_access.newBalance; + } else { + account_diff.balance_diff = Some(BalanceDiff { + previous_value: account_access.oldBalance, + new_value: account_access.newBalance, + }); + } + } + + // Record account state diffs. + for storage_access in &account_access.storageAccesses { + if storage_access.isWrite && !storage_access.reverted { + let account_diff = state_diffs + .entry(storage_access.account) + .or_insert_with(|| AccountStateDiffs { + label: state.labels.get(&storage_access.account).cloned(), + ..Default::default() + }); + // Update state diff. Do not overwrite the initial value if already set. + match account_diff.state_diff.entry(storage_access.slot) { + Entry::Vacant(slot_state_diff) => { + slot_state_diff.insert(SlotStateDiff { + previous_value: storage_access.previousValue, + new_value: storage_access.newValue, + }); + } + Entry::Occupied(mut slot_state_diff) => { + slot_state_diff.get_mut().new_value = storage_access.newValue; + } + } + } + } + }); + } + state_diffs +} diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs new file mode 100644 index 0000000000000..e78aa3be9f697 --- /dev/null +++ b/crates/cheatcodes/src/evm/fork.rs @@ -0,0 +1,408 @@ +use crate::{ + json::json_value_to_token, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, + Result, Vm::*, +}; +use alloy_dyn_abi::DynSolValue; +use alloy_primitives::{B256, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::Filter; +use alloy_sol_types::SolValue; +use foundry_common::provider::ProviderBuilder; +use foundry_evm_core::fork::CreateFork; + +impl Cheatcode for activeForkCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ccx.ecx + .db + .active_fork_id() + .map(|id| id.abi_encode()) + .ok_or_else(|| fmt_err!("no active fork")) + } +} + +impl Cheatcode for createFork_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { urlOrAlias } = self; + create_fork(ccx, urlOrAlias, None) + } +} + +impl Cheatcode for createFork_1Call { + 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_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_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { urlOrAlias } = self; + create_select_fork(ccx, urlOrAlias, None) + } +} + +impl Cheatcode for createSelectFork_1Call { + 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_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_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { blockNumber } = self; + persist_caller(ccx); + ccx.ecx.db.roll_fork( + None, + (*blockNumber).to(), + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + )?; + Ok(Default::default()) + } +} + +impl Cheatcode for rollFork_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { txHash } = self; + persist_caller(ccx); + ccx.ecx.db.roll_fork_to_transaction( + None, + *txHash, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + )?; + Ok(Default::default()) + } +} + +impl Cheatcode for rollFork_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { forkId, blockNumber } = self; + persist_caller(ccx); + ccx.ecx.db.roll_fork( + Some(*forkId), + (*blockNumber).to(), + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + )?; + Ok(Default::default()) + } +} + +impl Cheatcode for rollFork_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { forkId, txHash } = self; + persist_caller(ccx); + ccx.ecx.db.roll_fork_to_transaction( + Some(*forkId), + *txHash, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + )?; + Ok(Default::default()) + } +} + +impl Cheatcode for selectForkCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { forkId } = self; + persist_caller(ccx); + check_broadcast(ccx.state)?; + + ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; + Ok(Default::default()) + } +} + +impl Cheatcode for transact_0Call { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + let Self { txHash } = *self; + transact(ccx, executor, txHash, None) + } +} + +impl Cheatcode for transact_1Call { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + let Self { forkId, txHash } = *self; + transact(ccx, executor, txHash, Some(forkId)) + } +} + +impl Cheatcode for allowCheatcodesCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + ccx.ecx.db.allow_cheatcode_access(*account); + Ok(Default::default()) + } +} + +impl Cheatcode for makePersistent_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + ccx.ecx.db.add_persistent_account(*account); + Ok(Default::default()) + } +} + +impl Cheatcode for makePersistent_1Call { + 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); + Ok(Default::default()) + } +} + +impl Cheatcode for makePersistent_2Call { + 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); + ccx.ecx.db.add_persistent_account(*account2); + Ok(Default::default()) + } +} + +impl Cheatcode for makePersistent_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { accounts } = self; + for account in accounts { + ccx.ecx.db.add_persistent_account(*account); + } + Ok(Default::default()) + } +} + +impl Cheatcode for revokePersistent_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + ccx.ecx.db.remove_persistent_account(account); + Ok(Default::default()) + } +} + +impl Cheatcode for revokePersistent_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { accounts } = self; + for account in accounts { + ccx.ecx.db.remove_persistent_account(account); + } + Ok(Default::default()) + } +} + +impl Cheatcode for isPersistentCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + Ok(ccx.ecx.db.is_persistent(account).abi_encode()) + } +} + +impl Cheatcode for rpc_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { method, params } = self; + let url = + ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + rpc_call(&url, method, params) + } +} + +impl Cheatcode for rpc_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { urlOrAlias, method, params } = self; + let url = state.config.rpc_endpoint(urlOrAlias)?.url()?; + rpc_call(&url, method, params) + } +} + +impl Cheatcode for eth_getLogsCall { + 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 { + bail!("blocks in block range must be less than 2^64 - 1") + }; + + if topics.len() > 4 { + bail!("topics array must contain at most 4 elements") + } + + let url = + ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + let provider = ProviderBuilder::new(&url).build()?; + let mut filter = Filter::new().address(*target).from_block(from_block).to_block(to_block); + for (i, &topic) in topics.iter().enumerate() { + filter.topics[i] = topic.into(); + } + + let logs = foundry_common::block_on(provider.get_logs(&filter)) + .map_err(|e| fmt_err!("failed to get logs: {e}"))?; + + let eth_logs = logs + .into_iter() + .map(|log| EthGetLogs { + emitter: log.address(), + topics: log.topics().to_vec(), + data: log.inner.data.data, + blockHash: log.block_hash.unwrap_or_default(), + blockNumber: log.block_number.unwrap_or_default(), + transactionHash: log.transaction_hash.unwrap_or_default(), + transactionIndex: log.transaction_index.unwrap_or_default(), + logIndex: U256::from(log.log_index.unwrap_or_default()), + removed: log.removed, + }) + .collect::>(); + + Ok(eth_logs.abi_encode()) + } +} + +/// Creates and then also selects the new fork +fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { + check_broadcast(ccx.state)?; + + let fork = create_fork_request(ccx, url_or_alias, block)?; + let id = ccx.ecx.db.create_select_fork(fork, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; + Ok(id.abi_encode()) +} + +/// Creates a new fork +fn create_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option) -> Result { + let fork = create_fork_request(ccx, url_or_alias, block)?; + let id = ccx.ecx.db.create_fork(fork)?; + Ok(id.abi_encode()) +} + +/// Creates and then also selects the new fork at the given transaction +fn create_select_fork_at_transaction( + ccx: &mut CheatsCtxt, + url_or_alias: &str, + transaction: &B256, +) -> Result { + check_broadcast(ccx.state)?; + + let fork = create_fork_request(ccx, url_or_alias, None)?; + let id = ccx.ecx.db.create_select_fork_at_transaction( + fork, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + *transaction, + )?; + Ok(id.abi_encode()) +} + +/// Creates a new fork at the given transaction +fn create_fork_at_transaction( + ccx: &mut CheatsCtxt, + url_or_alias: &str, + transaction: &B256, +) -> Result { + let fork = create_fork_request(ccx, url_or_alias, None)?; + let id = ccx.ecx.db.create_fork_at_transaction(fork, *transaction)?; + Ok(id.abi_encode()) +} + +/// Creates the request object for a new fork request +fn create_fork_request( + ccx: &mut CheatsCtxt, + url_or_alias: &str, + block: Option, +) -> Result { + persist_caller(ccx); + + let rpc_endpoint = ccx.state.config.rpc_endpoint(url_or_alias)?; + let url = rpc_endpoint.url()?; + let mut evm_opts = ccx.state.config.evm_opts.clone(); + evm_opts.fork_block_number = block; + evm_opts.fork_retries = rpc_endpoint.config.retries; + evm_opts.fork_retry_backoff = rpc_endpoint.config.retry_backoff; + if let Some(Ok(auth)) = rpc_endpoint.auth { + evm_opts.fork_headers = Some(vec![format!("Authorization: {auth}")]); + } + let fork = CreateFork { + enable_caching: !ccx.state.config.no_storage_caching && + ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), + url, + env: (*ccx.ecx.env).clone(), + evm_opts, + }; + Ok(fork) +} + +fn check_broadcast(state: &Cheatcodes) -> Result<()> { + if state.broadcast.is_none() { + Ok(()) + } else { + Err(fmt_err!("cannot select forks during a broadcast")) + } +} + +fn transact( + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, + transaction: B256, + fork_id: Option, +) -> Result { + ccx.ecx.db.transact( + fork_id, + transaction, + (*ccx.ecx.env).clone(), + &mut ccx.ecx.journaled_state, + &mut *executor.get_inspector(ccx.state), + )?; + Ok(Default::default()) +} + +// Helper to add the caller of fork cheat code as persistent account (in order to make sure that the +// state of caller contract is not lost when fork changes). +// Applies to create, select and roll forks actions. +// https://github.com/foundry-rs/foundry/issues/8004 +fn persist_caller(ccx: &mut CheatsCtxt) { + ccx.ecx.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 = convert_to_bytes( + &json_value_to_token(&result).map_err(|err| fmt_err!("failed to parse result: {err}"))?, + ); + + Ok(result_as_tokens.abi_encode()) +} + +/// Convert fixed bytes and address values to bytes in order to prevent encoding issues. +fn convert_to_bytes(token: &DynSolValue) -> DynSolValue { + match token { + // Convert fixed 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()), + // Convert tuple values to prevent encoding issues. + // See: + DynSolValue::Tuple(vals) => DynSolValue::Tuple(vals.iter().map(convert_to_bytes).collect()), + val => val.clone(), + } +} diff --git a/crates/cheatcodes/src/evm/mapping.rs b/crates/cheatcodes/src/evm/mapping.rs new file mode 100644 index 0000000000000..e8525908e847a --- /dev/null +++ b/crates/cheatcodes/src/evm/mapping.rs @@ -0,0 +1,143 @@ +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_primitives::{ + keccak256, + map::{AddressHashMap, B256HashMap}, + Address, B256, U256, +}; +use alloy_sol_types::SolValue; +use revm::interpreter::{opcode, Interpreter}; + +/// Recorded mapping slots. +#[derive(Clone, Debug, Default)] +pub struct MappingSlots { + /// Holds mapping parent (slots => slots) + pub parent_slots: B256HashMap, + + /// Holds mapping key (slots => key) + pub keys: B256HashMap, + + /// Holds mapping child (slots => slots[]) + pub children: B256HashMap>, + + /// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record + /// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in + /// `data_low`, higher 256 bits in `data_high`. + /// This is needed for mapping_key detect if the slot is for some mapping and record that. + pub seen_sha3: B256HashMap<(B256, B256)>, +} + +impl MappingSlots { + /// Tries to insert a mapping slot. Returns true if it was inserted. + pub fn insert(&mut self, slot: B256) -> bool { + match self.seen_sha3.get(&slot).copied() { + Some((key, parent)) => { + if self.keys.contains_key(&slot) { + return false + } + self.keys.insert(slot, key); + self.parent_slots.insert(slot, parent); + self.children.entry(parent).or_default().push(slot); + self.insert(parent); + true + } + None => false, + } + } +} + +impl Cheatcode for startMappingRecordingCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + if state.mapping_slots.is_none() { + state.mapping_slots = Some(Default::default()); + } + Ok(Default::default()) + } +} + +impl Cheatcode for stopMappingRecordingCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.mapping_slots = None; + Ok(Default::default()) + } +} + +impl Cheatcode for getMappingLengthCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { target, mappingSlot } = self; + let result = slot_child(state, target, mappingSlot).map(Vec::len).unwrap_or(0); + Ok((result as u64).abi_encode()) + } +} + +impl Cheatcode for getMappingSlotAtCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { target, mappingSlot, idx } = self; + let result = slot_child(state, target, mappingSlot) + .and_then(|set| set.get(idx.saturating_to::())) + .copied() + .unwrap_or_default(); + Ok(result.abi_encode()) + } +} + +impl Cheatcode for getMappingKeyAndParentOfCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { target, elementSlot: slot } = self; + let mut found = false; + let mut key = &B256::ZERO; + let mut parent = &B256::ZERO; + if let Some(slots) = mapping_slot(state, target) { + if let Some(key2) = slots.keys.get(slot) { + found = true; + key = key2; + parent = &slots.parent_slots[slot]; + } else if let Some((key2, parent2)) = slots.seen_sha3.get(slot) { + found = true; + key = key2; + parent = parent2; + } + } + Ok((found, key, parent).abi_encode_params()) + } +} + +fn mapping_slot<'a>(state: &'a Cheatcodes, target: &'a Address) -> Option<&'a MappingSlots> { + state.mapping_slots.as_ref()?.get(target) +} + +fn slot_child<'a>( + state: &'a Cheatcodes, + target: &'a Address, + slot: &'a B256, +) -> Option<&'a Vec> { + mapping_slot(state, target)?.children.get(slot) +} + +#[cold] +pub(crate) fn step(mapping_slots: &mut AddressHashMap, interpreter: &Interpreter) { + match interpreter.current_opcode() { + opcode::KECCAK256 => { + if interpreter.stack.peek(1) == Ok(U256::from(0x40)) { + let address = interpreter.contract.target_address; + let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); + let data = interpreter.shared_memory.slice(offset, 0x40); + let low = B256::from_slice(&data[..0x20]); + let high = B256::from_slice(&data[0x20..]); + let result = keccak256(data); + + mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high)); + } + } + opcode::SSTORE => { + if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.contract.target_address) + { + if let Ok(slot) = interpreter.stack.peek(0) { + mapping_slots.insert(slot.into()); + } + } + } + _ => {} + } +} diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs new file mode 100644 index 0000000000000..fcfea7a9ce87c --- /dev/null +++ b/crates/cheatcodes/src/evm/mock.rs @@ -0,0 +1,223 @@ +use crate::{inspector::InnerEcx, Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use alloy_primitives::{Address, Bytes, U256}; +use revm::{interpreter::InstructionResult, primitives::Bytecode}; +use std::{cmp::Ordering, collections::VecDeque}; + +/// Mocked call data. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +pub struct MockCallDataContext { + /// The partial calldata to match for mock + pub calldata: Bytes, + /// The value to match for mock + pub value: Option, +} + +/// Mocked return data. +#[derive(Clone, Debug)] +pub struct MockCallReturnData { + /// The return type for the mocked call + pub ret_type: InstructionResult, + /// Return data or error + pub data: Bytes, +} + +impl PartialOrd for MockCallDataContext { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for MockCallDataContext { + fn cmp(&self, other: &Self) -> Ordering { + // Calldata matching is reversed to ensure that a tighter match is + // returned if an exact match is not found. In case, there is + // a partial match to calldata that is more specific than + // a match to a msg.value, then the more specific calldata takes + // precedence. + self.calldata.cmp(&other.calldata).reverse().then(self.value.cmp(&other.value).reverse()) + } +} + +impl Cheatcode for clearMockedCallsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.mocked_calls = Default::default(); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCall_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, data, returnData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_call(ccx.state, callee, data, None, returnData, InstructionResult::Return); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCall_1Call { + 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); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCall_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, data, returnData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_call( + ccx.state, + callee, + &Bytes::from(*data), + None, + returnData, + InstructionResult::Return, + ); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCall_3Call { + 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, + &Bytes::from(*data), + Some(msgValue), + returnData, + InstructionResult::Return, + ); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCalls_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, data, returnData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_calls(ccx.state, callee, data, None, returnData, InstructionResult::Return); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCalls_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, msgValue, data, returnData } = self; + ccx.ecx.load_account(*callee)?; + mock_calls(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCallRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, data, revertData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_call(ccx.state, callee, data, None, revertData, InstructionResult::Revert); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCallRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, msgValue, data, revertData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_call(ccx.state, callee, data, Some(msgValue), revertData, InstructionResult::Revert); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCallRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, data, revertData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_call( + ccx.state, + callee, + &Bytes::from(*data), + None, + revertData, + InstructionResult::Revert, + ); + Ok(Default::default()) + } +} + +impl Cheatcode for mockCallRevert_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { callee, msgValue, data, revertData } = self; + let _ = make_acc_non_empty(callee, ccx.ecx)?; + + mock_call( + ccx.state, + callee, + &Bytes::from(*data), + Some(msgValue), + revertData, + InstructionResult::Revert, + ); + Ok(Default::default()) + } +} + +impl Cheatcode for mockFunctionCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, target, data } = self; + state.mocked_functions.entry(*callee).or_default().insert(data.clone(), *target); + + Ok(Default::default()) + } +} + +fn mock_call( + state: &mut Cheatcodes, + callee: &Address, + cdata: &Bytes, + value: Option<&U256>, + rdata: &Bytes, + ret_type: InstructionResult, +) { + mock_calls(state, callee, cdata, value, std::slice::from_ref(rdata), ret_type) +} + +fn mock_calls( + state: &mut Cheatcodes, + callee: &Address, + cdata: &Bytes, + value: Option<&U256>, + rdata_vec: &[Bytes], + ret_type: InstructionResult, +) { + state.mocked_calls.entry(*callee).or_default().insert( + MockCallDataContext { calldata: Bytes::copy_from_slice(cdata), value: value.copied() }, + rdata_vec + .iter() + .map(|rdata| MockCallReturnData { ret_type, data: rdata.clone() }) + .collect::>(), + ); +} + +// Etches a single byte onto the account if it is empty to circumvent the `extcodesize` +// check Solidity might perform. +fn make_acc_non_empty(callee: &Address, ecx: InnerEcx) -> Result { + let acc = ecx.load_account(*callee)?; + + let empty_bytecode = acc.info.code.as_ref().is_none_or(Bytecode::is_empty); + if empty_bytecode { + let code = Bytecode::new_raw(Bytes::from_static(&[0u8])); + ecx.journaled_state.set_code(*callee, code); + } + + Ok(Default::default()) +} diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs new file mode 100644 index 0000000000000..1d7ca5a079471 --- /dev/null +++ b/crates/cheatcodes/src/evm/prank.rs @@ -0,0 +1,165 @@ +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use alloy_primitives::Address; + +/// Prank information. +#[derive(Clone, Debug, Default)] +pub struct Prank { + /// Address of the contract that initiated the prank + pub prank_caller: Address, + /// Address of `tx.origin` when the prank was initiated + pub prank_origin: Address, + /// The address to assign to `msg.sender` + pub new_caller: Address, + /// The address to assign to `tx.origin` + pub new_origin: Option
, + /// The depth at which the prank was called + pub depth: u64, + /// Whether the prank stops by itself after the next call + pub single_call: bool, + /// Whether the prank should be be applied to delegate call + pub delegate_call: bool, + /// Whether the prank has been used yet (false if unused) + pub used: bool, +} + +impl Prank { + /// Create a new prank. + pub fn new( + prank_caller: Address, + prank_origin: Address, + new_caller: Address, + new_origin: Option
, + depth: u64, + single_call: bool, + delegate_call: bool, + ) -> Self { + Self { + prank_caller, + prank_origin, + new_caller, + new_origin, + depth, + single_call, + delegate_call, + used: false, + } + } + + /// Apply the prank by setting `used` to true iff it is false + /// Only returns self in the case it is updated (first application) + pub fn first_time_applied(&self) -> Option { + if self.used { + None + } else { + Some(Self { used: true, ..self.clone() }) + } + } +} + +impl Cheatcode for prank_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender } = self; + prank(ccx, msgSender, None, true, false) + } +} + +impl Cheatcode for startPrank_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender } = self; + prank(ccx, msgSender, None, false, false) + } +} + +impl Cheatcode for prank_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender, txOrigin } = self; + prank(ccx, msgSender, Some(txOrigin), true, false) + } +} + +impl Cheatcode for startPrank_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender, txOrigin } = self; + prank(ccx, msgSender, Some(txOrigin), false, false) + } +} + +impl Cheatcode for prank_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender, delegateCall } = self; + prank(ccx, msgSender, None, true, *delegateCall) + } +} + +impl Cheatcode for startPrank_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender, delegateCall } = self; + prank(ccx, msgSender, None, false, *delegateCall) + } +} + +impl Cheatcode for prank_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender, txOrigin, delegateCall } = self; + prank(ccx, msgSender, Some(txOrigin), true, *delegateCall) + } +} + +impl Cheatcode for startPrank_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { msgSender, txOrigin, delegateCall } = self; + prank(ccx, msgSender, Some(txOrigin), false, *delegateCall) + } +} + +impl Cheatcode for stopPrankCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.prank = None; + Ok(Default::default()) + } +} + +fn prank( + ccx: &mut CheatsCtxt, + new_caller: &Address, + new_origin: Option<&Address>, + single_call: bool, + delegate_call: bool, +) -> Result { + let prank = Prank::new( + ccx.caller, + ccx.ecx.env.tx.caller, + *new_caller, + new_origin.copied(), + ccx.ecx.journaled_state.depth(), + single_call, + delegate_call, + ); + + // Ensure that code exists at `msg.sender` if delegate calling. + if delegate_call { + let code = ccx.code(*new_caller)?; + ensure!(!code.is_empty(), "cannot `prank` delegate call from an EOA"); + } + + if let Some(Prank { used, single_call: current_single_call, .. }) = ccx.state.prank { + ensure!(used, "cannot overwrite a prank until it is applied at least once"); + // This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on. + // This should not be possible without first calling `stopPrank` + ensure!( + single_call == current_single_call, + "cannot override an ongoing prank with a single vm.prank; \ + use vm.startPrank to override the current prank" + ); + } + + ensure!( + ccx.state.broadcast.is_none(), + "cannot `prank` for a broadcasted transaction; \ + pass the desired `tx.origin` into the `broadcast` cheatcode call" + ); + + ccx.state.prank = Some(prank); + Ok(Default::default()) +} diff --git a/crates/cheatcodes/src/evm/record_debug_step.rs b/crates/cheatcodes/src/evm/record_debug_step.rs new file mode 100644 index 0000000000000..b9f0f89cb01ff --- /dev/null +++ b/crates/cheatcodes/src/evm/record_debug_step.rs @@ -0,0 +1,144 @@ +use alloy_primitives::{Bytes, U256}; + +use foundry_evm_traces::CallTraceArena; +use revm::interpreter::{InstructionResult, OpCode}; + +use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind}; +use revm_inspectors::tracing::types::{CallTraceStep, RecordedMemory, TraceMemberOrder}; +use spec::Vm::DebugStep; + +// Do a depth first traverse of the nodes and steps and return steps +// that are after `node_start_idx` +pub(crate) fn flatten_call_trace( + root: usize, + arena: &CallTraceArena, + node_start_idx: usize, +) -> Vec<&CallTraceStep> { + let mut steps = Vec::new(); + let mut record_started = false; + + // Start the recursion from the root node + recursive_flatten_call_trace(root, arena, node_start_idx, &mut record_started, &mut steps); + steps +} + +// Inner recursive function to process nodes. +// This implementation directly mutates `record_started` and `flatten_steps`. +// So the recursive call can change the `record_started` flag even for the parent +// unfinished processing, and append steps to the `flatten_steps` as the final result. +fn recursive_flatten_call_trace<'a>( + node_idx: usize, + arena: &'a CallTraceArena, + node_start_idx: usize, + record_started: &mut bool, + flatten_steps: &mut Vec<&'a CallTraceStep>, +) { + // Once node_idx exceeds node_start_idx, start recording steps + // for all the recursive processing. + if !*record_started && node_idx >= node_start_idx { + *record_started = true; + } + + let node = &arena.nodes()[node_idx]; + + for order in node.ordering.iter() { + match order { + TraceMemberOrder::Step(step_idx) => { + if *record_started { + let step = &node.trace.steps[*step_idx]; + flatten_steps.push(step); + } + } + TraceMemberOrder::Call(call_idx) => { + let child_node_idx = node.children[*call_idx]; + recursive_flatten_call_trace( + child_node_idx, + arena, + node_start_idx, + record_started, + flatten_steps, + ); + } + _ => {} + } + } +} + +// Function to convert CallTraceStep to DebugStep +pub(crate) fn convert_call_trace_to_debug_step(step: &CallTraceStep) -> DebugStep { + let opcode = step.op.get(); + let stack = get_stack_inputs_for_opcode(opcode, step.stack.as_ref()); + + let memory = get_memory_input_for_opcode(opcode, step.stack.as_ref(), step.memory.as_ref()); + + let is_out_of_gas = step.status == InstructionResult::OutOfGas || + step.status == InstructionResult::MemoryOOG || + step.status == InstructionResult::MemoryLimitOOG || + step.status == InstructionResult::PrecompileOOG || + step.status == InstructionResult::InvalidOperandOOG; + + DebugStep { + stack, + memoryInput: memory, + opcode: step.op.get(), + depth: step.depth, + isOutOfGas: is_out_of_gas, + contractAddr: step.contract, + } +} + +// The expected `stack` here is from the trace stack, where the top of the stack +// is the last value of the vector +fn get_memory_input_for_opcode( + opcode: u8, + stack: Option<&Vec>, + memory: Option<&RecordedMemory>, +) -> Bytes { + let mut memory_input = Bytes::new(); + let Some(stack_data) = stack else { return memory_input }; + let Some(memory_data) = memory else { return memory_input }; + + if let Some(accesses) = get_buffer_accesses(opcode, stack_data) { + if let Some((BufferKind::Memory, access)) = accesses.read { + memory_input = get_slice_from_memory(memory_data.as_bytes(), access.offset, access.len); + } + }; + + memory_input +} + +// The expected `stack` here is from the trace stack, where the top of the stack +// is the last value of the vector +fn get_stack_inputs_for_opcode(opcode: u8, stack: Option<&Vec>) -> Vec { + let mut inputs = Vec::new(); + + let Some(op) = OpCode::new(opcode) else { return inputs }; + let Some(stack_data) = stack else { return inputs }; + + let stack_input_size = op.inputs() as usize; + for i in 0..stack_input_size { + inputs.push(stack_data[stack_data.len() - 1 - i]); + } + inputs +} + +fn get_slice_from_memory(memory: &Bytes, start_index: usize, size: usize) -> Bytes { + let memory_len = memory.len(); + + let end_bound = start_index + size; + + // Return the bytes if data is within the range. + if start_index < memory_len && end_bound <= memory_len { + return memory.slice(start_index..end_bound); + } + + // Pad zero bytes if attempting to load memory partially out of range. + if start_index < memory_len && end_bound > memory_len { + let mut result = memory.slice(start_index..memory_len).to_vec(); + result.resize(size, 0u8); + return Bytes::from(result); + } + + // Return empty bytes with the size if not in range at all. + Bytes::from(vec![0u8; size]) +} diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs new file mode 100644 index 0000000000000..e1cb472b803a0 --- /dev/null +++ b/crates/cheatcodes/src/fs.rs @@ -0,0 +1,852 @@ +//! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. + +use super::string::parse; +use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; +use alloy_dyn_abi::DynSolType; +use alloy_json_abi::ContractObject; +use alloy_network::AnyTransactionReceipt; +use alloy_primitives::{hex, map::Entry, Bytes, U256}; +use alloy_provider::network::ReceiptResponse; +use alloy_sol_types::SolValue; +use dialoguer::{Input, Password}; +use forge_script_sequence::{BroadcastReader, TransactionWithMetadata}; +use foundry_common::fs; +use foundry_config::fs_permissions::FsAccessKind; +use revm::interpreter::CreateInputs; +use revm_inspectors::tracing::types::CallKind; +use semver::Version; +use std::{ + io::{BufRead, BufReader, Write}, + path::{Path, PathBuf}, + process::Command, + sync::mpsc, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; +use walkdir::WalkDir; + +impl Cheatcode for existsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + Ok(path.exists().abi_encode()) + } +} + +impl Cheatcode for fsMetadataCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + + let metadata = path.metadata()?; + + // These fields not available on all platforms; default to 0 + let [modified, accessed, created] = + [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| { + time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() + }); + + Ok(FsMetadata { + isDir: metadata.is_dir(), + isSymlink: metadata.is_symlink(), + length: U256::from(metadata.len()), + readOnly: metadata.permissions().readonly(), + modified: U256::from(modified), + accessed: U256::from(accessed), + created: U256::from(created), + } + .abi_encode()) + } +} + +impl Cheatcode for isDirCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + Ok(path.is_dir().abi_encode()) + } +} + +impl Cheatcode for isFileCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + Ok(path.is_file().abi_encode()) + } +} + +impl Cheatcode for projectRootCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + Ok(state.config.root.display().to_string().abi_encode()) + } +} + +impl Cheatcode for unixTimeCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self {} = self; + let difference = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| fmt_err!("failed getting Unix timestamp: {e}"))?; + Ok(difference.as_millis().abi_encode()) + } +} + +impl Cheatcode for closeFileCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + + state.context.opened_read_files.remove(&path); + + Ok(Default::default()) + } +} + +impl Cheatcode for copyFileCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { from, to } = self; + let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?; + let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?; + state.config.ensure_not_foundry_toml(&to)?; + + let n = fs::copy(from, to)?; + Ok(n.abi_encode()) + } +} + +impl Cheatcode for createDirCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, recursive } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; + if *recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?; + Ok(Default::default()) + } +} + +impl Cheatcode for readDir_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + read_dir(state, path.as_ref(), 1, false) + } +} + +impl Cheatcode for readDir_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, maxDepth } = self; + read_dir(state, path.as_ref(), *maxDepth, false) + } +} + +impl Cheatcode for readDir_2Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, maxDepth, followLinks } = self; + read_dir(state, path.as_ref(), *maxDepth, *followLinks) + } +} + +impl Cheatcode for readFileCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + Ok(fs::read_to_string(path)?.abi_encode()) + } +} + +impl Cheatcode for readFileBinaryCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + Ok(fs::read(path)?.abi_encode()) + } +} + +impl Cheatcode for readLineCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + + // Get reader for previously opened file to continue reading OR initialize new reader + let reader = match state.context.opened_read_files.entry(path.clone()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(BufReader::new(fs::open(path)?)), + }; + + let mut line: String = String::new(); + reader.read_line(&mut line)?; + + // Remove trailing newline character, preserving others for cases where it may be important + if line.ends_with('\n') { + line.pop(); + if line.ends_with('\r') { + line.pop(); + } + } + + Ok(line.abi_encode()) + } +} + +impl Cheatcode for readLinkCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { linkPath: path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let target = fs::read_link(path)?; + Ok(target.display().to_string().abi_encode()) + } +} + +impl Cheatcode for removeDirCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, recursive } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; + if *recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?; + Ok(Default::default()) + } +} + +impl Cheatcode for removeFileCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; + state.config.ensure_not_foundry_toml(&path)?; + + // also remove from the set if opened previously + state.context.opened_read_files.remove(&path); + + if state.fs_commit { + fs::remove_file(&path)?; + } + + Ok(Default::default()) + } +} + +impl Cheatcode for writeFileCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, data } = self; + write_file(state, path.as_ref(), data.as_bytes()) + } +} + +impl Cheatcode for writeFileBinaryCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, data } = self; + write_file(state, path.as_ref(), data) + } +} + +impl Cheatcode for writeLineCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { path, data: line } = self; + let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; + state.config.ensure_not_foundry_toml(&path)?; + + if state.fs_commit { + let mut file = std::fs::OpenOptions::new().append(true).create(true).open(path)?; + + writeln!(file, "{line}")?; + } + + Ok(Default::default()) + } +} + +impl Cheatcode for getArtifactPathByCodeCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { code } = self; + let (artifact_id, _) = state + .config + .available_artifacts + .as_ref() + .and_then(|artifacts| artifacts.find_by_creation_code(code)) + .ok_or_else(|| fmt_err!("no matching artifact found"))?; + + Ok(artifact_id.path.to_string_lossy().abi_encode()) + } +} + +impl Cheatcode for getArtifactPathByDeployedCodeCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { deployedCode } = self; + let (artifact_id, _) = state + .config + .available_artifacts + .as_ref() + .and_then(|artifacts| artifacts.find_by_deployed_code(deployedCode)) + .ok_or_else(|| fmt_err!("no matching artifact found"))?; + + Ok(artifact_id.path.to_string_lossy().abi_encode()) + } +} + +impl Cheatcode for getCodeCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { artifactPath: path } = self; + Ok(get_artifact_code(state, path, false)?.abi_encode()) + } +} + +impl Cheatcode for getDeployedCodeCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { artifactPath: path } = self; + Ok(get_artifact_code(state, path, true)?.abi_encode()) + } +} + +impl Cheatcode for deployCode_0Call { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + let Self { artifactPath: path } = self; + let bytecode = get_artifact_code(ccx.state, path, false)?; + let address = executor + .exec_create( + CreateInputs { + caller: ccx.caller, + scheme: revm::primitives::CreateScheme::Create, + value: U256::ZERO, + init_code: bytecode, + gas_limit: ccx.gas_limit, + }, + ccx, + )? + .address + .ok_or_else(|| fmt_err!("contract creation failed"))?; + + Ok(address.abi_encode()) + } +} + +impl Cheatcode for deployCode_1Call { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> 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 address = 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, + )? + .address + .ok_or_else(|| fmt_err!("contract creation failed"))?; + + Ok(address.abi_encode()) + } +} + +/// Returns the path to the json artifact depending on the input +/// +/// Can parse following input formats: +/// - `path/to/artifact.json` +/// - `path/to/contract.sol` +/// - `path/to/contract.sol:ContractName` +/// - `path/to/contract.sol:ContractName:0.8.23` +/// - `path/to/contract.sol:0.8.23` +/// - `ContractName` +/// - `ContractName:0.8.23` +fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { + let path = if path.ends_with(".json") { + PathBuf::from(path) + } else { + let mut parts = path.split(':'); + + let mut file = None; + let mut contract_name = None; + let mut version = None; + + let path_or_name = parts.next().unwrap(); + if path_or_name.contains('.') { + file = Some(PathBuf::from(path_or_name)); + if let Some(name_or_version) = parts.next() { + if name_or_version.contains('.') { + version = Some(name_or_version); + } else { + contract_name = Some(name_or_version); + version = parts.next(); + } + } + } else { + contract_name = Some(path_or_name); + version = parts.next(); + } + + let version = if let Some(version) = version { + Some(Version::parse(version).map_err(|e| fmt_err!("failed parsing version: {e}"))?) + } else { + None + }; + + // Use available artifacts list if present + if let Some(artifacts) = &state.config.available_artifacts { + let filtered = artifacts + .iter() + .filter(|(id, _)| { + // name might be in the form of "Counter.0.8.23" + let id_name = id.name.split('.').next().unwrap(); + + if let Some(path) = &file { + if !id.source.ends_with(path) { + return false; + } + } + if let Some(name) = contract_name { + if id_name != name { + return false; + } + } + if let Some(ref version) = version { + if id.version.minor != version.minor || + id.version.major != version.major || + id.version.patch != version.patch + { + return false; + } + } + true + }) + .collect::>(); + + let artifact = match &filtered[..] { + [] => Err(fmt_err!("no matching artifact found")), + [artifact] => Ok(*artifact), + filtered => { + let mut filtered = filtered.to_vec(); + // If we know the current script/test contract solc version, try to filter by it + state + .config + .running_artifact + .as_ref() + .and_then(|running| { + // Firstly filter by version + filtered.retain(|(id, _)| id.version == running.version); + + // Return artifact if only one matched + if filtered.len() == 1 { + return Some(filtered[0]) + } + + // Try filtering by profile as well + filtered.retain(|(id, _)| id.profile == running.profile); + + if filtered.len() == 1 { + Some(filtered[0]) + } else { + None + } + }) + .ok_or_else(|| fmt_err!("multiple matching artifacts found")) + } + }?; + + let maybe_bytecode = if deployed { + artifact.1.deployed_bytecode().cloned() + } else { + artifact.1.bytecode().cloned() + }; + + return maybe_bytecode + .ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")); + } else { + let path_in_artifacts = + match (file.map(|f| f.to_string_lossy().to_string()), contract_name) { + (Some(file), Some(contract_name)) => { + PathBuf::from(format!("{file}/{contract_name}.json")) + } + (None, Some(contract_name)) => { + PathBuf::from(format!("{contract_name}.sol/{contract_name}.json")) + } + (Some(file), None) => { + let name = file.replace(".sol", ""); + PathBuf::from(format!("{file}/{name}.json")) + } + _ => bail!("invalid artifact path"), + }; + + state.config.paths.artifacts.join(path_in_artifacts) + } + }; + + let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let data = fs::read_to_string(path)?; + let artifact = serde_json::from_str::(&data)?; + let maybe_bytecode = if deployed { artifact.deployed_bytecode } else { artifact.bytecode }; + maybe_bytecode.ok_or_else(|| fmt_err!("no bytecode for contract; is it abstract or unlinked?")) +} + +impl Cheatcode for ffiCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { commandInput: input } = self; + + let output = ffi(state, input)?; + // TODO: check exit code? + if !output.stderr.is_empty() { + let stderr = String::from_utf8_lossy(&output.stderr); + error!(target: "cheatcodes", ?input, ?stderr, "non-empty stderr"); + } + // we already hex-decoded the stdout in `ffi` + Ok(output.stdout.abi_encode()) + } +} + +impl Cheatcode for tryFfiCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { commandInput: input } = self; + ffi(state, input).map(|res| res.abi_encode()) + } +} + +impl Cheatcode for promptCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + prompt(state, text, prompt_input).map(|res| res.abi_encode()) + } +} + +impl Cheatcode for promptSecretCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + prompt(state, text, prompt_password).map(|res| res.abi_encode()) + } +} + +impl Cheatcode for promptSecretUintCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + parse(&prompt(state, text, prompt_password)?, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for promptAddressCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + parse(&prompt(state, text, prompt_input)?, &DynSolType::Address) + } +} + +impl Cheatcode for promptUintCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { promptText: text } = self; + parse(&prompt(state, text, prompt_input)?, &DynSolType::Uint(256)) + } +} + +pub(super) fn write_file(state: &Cheatcodes, path: &Path, contents: &[u8]) -> Result { + let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; + // write access to foundry.toml is not allowed + state.config.ensure_not_foundry_toml(&path)?; + + if state.fs_commit { + fs::write(path, contents)?; + } + + Ok(Default::default()) +} + +fn read_dir(state: &Cheatcodes, path: &Path, max_depth: u64, follow_links: bool) -> Result { + let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let paths: Vec = WalkDir::new(root) + .min_depth(1) + .max_depth(max_depth.try_into().unwrap_or(usize::MAX)) + .follow_links(follow_links) + .contents_first(false) + .same_file_system(true) + .sort_by_file_name() + .into_iter() + .map(|entry| match entry { + Ok(entry) => DirEntry { + errorMessage: String::new(), + path: entry.path().display().to_string(), + depth: entry.depth() as u64, + isDir: entry.file_type().is_dir(), + isSymlink: entry.path_is_symlink(), + }, + Err(e) => DirEntry { + errorMessage: e.to_string(), + path: e.path().map(|p| p.display().to_string()).unwrap_or_default(), + depth: e.depth() as u64, + isDir: false, + isSymlink: false, + }, + }) + .collect(); + Ok(paths.abi_encode()) +} + +fn ffi(state: &Cheatcodes, input: &[String]) -> Result { + ensure!( + state.config.ffi, + "FFI is disabled; add the `--ffi` flag to allow tests to call external commands" + ); + ensure!(!input.is_empty() && !input[0].is_empty(), "can't execute empty command"); + let mut cmd = Command::new(&input[0]); + cmd.args(&input[1..]); + + debug!(target: "cheatcodes", ?cmd, "invoking ffi"); + + let output = cmd + .current_dir(&state.config.root) + .output() + .map_err(|err| fmt_err!("failed to execute command {cmd:?}: {err}"))?; + + // The stdout might be encoded on valid hex, or it might just be a string, + // so we need to determine which it is to avoid improperly encoding later. + let trimmed_stdout = String::from_utf8(output.stdout)?; + let trimmed_stdout = trimmed_stdout.trim(); + let encoded_stdout = if let Ok(hex) = hex::decode(trimmed_stdout) { + hex + } else { + trimmed_stdout.as_bytes().to_vec() + }; + Ok(FfiResult { + exitCode: output.status.code().unwrap_or(69), + stdout: encoded_stdout.into(), + stderr: output.stderr.into(), + }) +} + +fn prompt_input(prompt_text: &str) -> Result { + Input::new().allow_empty(true).with_prompt(prompt_text).interact_text() +} + +fn prompt_password(prompt_text: &str) -> Result { + Password::new().with_prompt(prompt_text).interact() +} + +fn prompt( + state: &Cheatcodes, + prompt_text: &str, + input: fn(&str) -> Result, +) -> Result { + let text_clone = prompt_text.to_string(); + let timeout = state.config.prompt_timeout; + let (tx, rx) = mpsc::channel(); + + thread::spawn(move || { + let _ = tx.send(input(&text_clone)); + }); + + match rx.recv_timeout(timeout) { + Ok(res) => res.map_err(|err| { + let _ = sh_println!(); + err.to_string().into() + }), + Err(_) => { + let _ = sh_eprintln!(); + Err("Prompt timed out".into()) + } + } +} + +impl Cheatcode for getBroadcastCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { contractName, chainId, txType } = self; + + let latest_broadcast = latest_broadcast( + contractName, + *chainId, + &state.config.broadcast, + vec![map_broadcast_tx_type(*txType)], + )?; + + Ok(latest_broadcast.abi_encode()) + } +} + +impl Cheatcode for getBroadcasts_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { contractName, chainId, txType } = self; + + let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? + .with_tx_type(map_broadcast_tx_type(*txType)); + + let broadcasts = reader.read()?; + + let summaries = broadcasts + .into_iter() + .flat_map(|broadcast| { + let results = reader.into_tx_receipts(broadcast); + parse_broadcast_results(results) + }) + .collect::>(); + + Ok(summaries.abi_encode()) + } +} + +impl Cheatcode for getBroadcasts_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { contractName, chainId } = self; + + let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)?; + + let broadcasts = reader.read()?; + + let summaries = broadcasts + .into_iter() + .flat_map(|broadcast| { + let results = reader.into_tx_receipts(broadcast); + parse_broadcast_results(results) + }) + .collect::>(); + + Ok(summaries.abi_encode()) + } +} + +impl Cheatcode for getDeployment_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { contractName } = self; + let chain_id = ccx.ecx.env.cfg.chain_id; + + let latest_broadcast = latest_broadcast( + contractName, + chain_id, + &ccx.state.config.broadcast, + vec![CallKind::Create, CallKind::Create2], + )?; + + Ok(latest_broadcast.contractAddress.abi_encode()) + } +} + +impl Cheatcode for getDeployment_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { contractName, chainId } = self; + + let latest_broadcast = latest_broadcast( + contractName, + *chainId, + &state.config.broadcast, + vec![CallKind::Create, CallKind::Create2], + )?; + + Ok(latest_broadcast.contractAddress.abi_encode()) + } +} + +impl Cheatcode for getDeploymentsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { contractName, chainId } = self; + + let reader = BroadcastReader::new(contractName.clone(), *chainId, &state.config.broadcast)? + .with_tx_type(CallKind::Create) + .with_tx_type(CallKind::Create2); + + let broadcasts = reader.read()?; + + let summaries = broadcasts + .into_iter() + .flat_map(|broadcast| { + let results = reader.into_tx_receipts(broadcast); + parse_broadcast_results(results) + }) + .collect::>(); + + let deployed_addresses = + summaries.into_iter().map(|summary| summary.contractAddress).collect::>(); + + Ok(deployed_addresses.abi_encode()) + } +} + +fn map_broadcast_tx_type(tx_type: BroadcastTxType) -> CallKind { + match tx_type { + BroadcastTxType::Call => CallKind::Call, + BroadcastTxType::Create => CallKind::Create, + BroadcastTxType::Create2 => CallKind::Create2, + _ => unreachable!("invalid tx type"), + } +} + +fn parse_broadcast_results( + results: Vec<(TransactionWithMetadata, AnyTransactionReceipt)>, +) -> Vec { + results + .into_iter() + .map(|(tx, receipt)| BroadcastTxSummary { + txHash: receipt.transaction_hash, + blockNumber: receipt.block_number.unwrap_or_default(), + txType: match tx.opcode { + CallKind::Call => BroadcastTxType::Call, + CallKind::Create => BroadcastTxType::Create, + CallKind::Create2 => BroadcastTxType::Create2, + _ => unreachable!("invalid tx type"), + }, + contractAddress: tx.contract_address.unwrap_or_default(), + success: receipt.status(), + }) + .collect() +} + +fn latest_broadcast( + contract_name: &String, + chain_id: u64, + broadcast_path: &Path, + filters: Vec, +) -> Result { + let mut reader = BroadcastReader::new(contract_name.clone(), chain_id, broadcast_path)?; + + for filter in filters { + reader = reader.with_tx_type(filter); + } + + let broadcast = reader.read_latest()?; + + let results = reader.into_tx_receipts(broadcast); + + let summaries = parse_broadcast_results(results); + + summaries + .first() + .ok_or_else(|| fmt_err!("no deployment found for {contract_name} on chain {chain_id}")) + .cloned() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::CheatsConfig; + use std::sync::Arc; + + fn cheats() -> Cheatcodes { + let config = CheatsConfig { + ffi: true, + root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")), + ..Default::default() + }; + Cheatcodes::new(Arc::new(config)) + } + + #[test] + fn test_ffi_hex() { + let msg = b"gm"; + let cheats = cheats(); + let args = ["echo".to_string(), hex::encode(msg)]; + let output = ffi(&cheats, &args).unwrap(); + assert_eq!(output.stdout, Bytes::from(msg)); + } + + #[test] + fn test_ffi_string() { + let msg = "gm"; + let cheats = cheats(); + let args = ["echo".to_string(), msg.to_string()]; + let output = ffi(&cheats, &args).unwrap(); + assert_eq!(output.stdout, Bytes::from(msg.as_bytes())); + } + + #[test] + fn test_artifact_parsing() { + let s = include_str!("../../evm/test-data/solc-obj.json"); + let artifact: ContractObject = serde_json::from_str(s).unwrap(); + assert!(artifact.bytecode.is_some()); + + let artifact: ContractObject = serde_json::from_str(s).unwrap(); + assert!(artifact.deployed_bytecode.is_some()); + } +} diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs new file mode 100644 index 0000000000000..cf6248c8f02bf --- /dev/null +++ b/crates/cheatcodes/src/inspector.rs @@ -0,0 +1,2278 @@ +//! Cheatcode EVM inspector. + +use crate::{ + evm::{ + mapping::{self, MappingSlots}, + mock::{MockCallDataContext, MockCallReturnData}, + prank::Prank, + DealRecord, GasRecord, RecordAccess, + }, + inspector::utils::CommonCreateInput, + script::{Broadcast, Wallets}, + test::{ + assume::AssumeNoRevert, + expect::{ + self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmitTracker, + ExpectedRevert, ExpectedRevertKind, + }, + revert_handlers, + }, + utils::IgnoredTraces, + CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, + Vm::{self, AccountAccess}, +}; +use alloy_primitives::{ + hex, + map::{AddressHashMap, HashMap}, + Address, Bytes, Log, TxKind, B256, U256, +}; +use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; +use alloy_sol_types::{SolCall, SolInterface, SolValue}; +use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_evm_core::{ + abi::Vm::stopExpectSafeMemoryCall, + backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, + constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, + utils::new_evm_with_existing_context, + InspectorExt, +}; +use foundry_evm_traces::{TracingInspector, TracingInspectorConfig}; +use foundry_wallets::multi_wallet::MultiWallet; +use itertools::Itertools; +use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; +use rand::Rng; +use revm::{ + interpreter::{ + opcode as op, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, + EOFCreateInputs, EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterAction, + InterpreterResult, + }, + primitives::{ + BlockEnv, CreateScheme, EVMError, EvmStorageSlot, SignedAuthorization, SpecId, + EOF_MAGIC_BYTES, + }, + EvmContext, InnerEvmContext, Inspector, +}; +use serde_json::Value; +use std::{ + cmp::max, + collections::{BTreeMap, VecDeque}, + fs::File, + io::BufReader, + ops::Range, + path::PathBuf, + sync::Arc, +}; + +mod utils; + +pub type Ecx<'a, 'b, 'c> = &'a mut EvmContext<&'b mut (dyn DatabaseExt + 'c)>; +pub type InnerEcx<'a, 'b, 'c> = &'a mut InnerEvmContext<&'b mut (dyn DatabaseExt + 'c)>; + +/// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to +/// [Cheatcodes]. +/// +/// This is needed for cases when inspector itself needs mutable access to [Cheatcodes] state and +/// allows us to correctly execute arbitrary EVM frames from inside cheatcode implementations. +pub trait CheatcodesExecutor { + /// Core trait method accepting mutable reference to [Cheatcodes] and returning + /// [revm::Inspector]. + fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box; + + /// Obtains [revm::Evm] instance and executes the given CREATE frame. + fn exec_create( + &mut self, + inputs: CreateInputs, + ccx: &mut CheatsCtxt, + ) -> Result> { + with_evm(self, ccx, |evm| { + evm.context.evm.inner.journaled_state.depth += 1; + + // Handle EOF bytecode + let first_frame_or_result = if evm.handler.cfg.spec_id.is_enabled_in(SpecId::OSAKA) && + inputs.scheme == CreateScheme::Create && + inputs.init_code.starts_with(&EOF_MAGIC_BYTES) + { + evm.handler.execution().eofcreate( + &mut evm.context, + Box::new(EOFCreateInputs::new( + inputs.caller, + inputs.value, + inputs.gas_limit, + EOFCreateKind::Tx { initdata: inputs.init_code }, + )), + )? + } else { + 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(_) => unreachable!(), + revm::FrameResult::Create(create) | revm::FrameResult::EOFCreate(create) => create, + }; + + evm.context.evm.inner.journaled_state.depth -= 1; + + Ok(outcome) + }) + } + + fn console_log(&mut self, ccx: &mut CheatsCtxt, msg: &str) { + self.get_inspector(ccx.state).console_log(msg); + } + + /// Returns a mutable reference to the tracing inspector if it is available. + fn tracing_inspector(&mut self) -> Option<&mut Option> { + None + } +} + +/// Constructs [revm::Evm] and runs a given closure with it. +fn with_evm( + executor: &mut E, + ccx: &mut CheatsCtxt, + f: F, +) -> Result> +where + E: CheatcodesExecutor + ?Sized, + F: for<'a, 'b> FnOnce( + &mut revm::Evm<'_, &'b mut dyn InspectorExt, &'a mut dyn DatabaseExt>, + ) -> Result>, +{ + let mut inspector = executor.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, + }; + + let mut evm = new_evm_with_existing_context(inner, &mut *inspector); + + 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; + + Ok(res) +} + +/// 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>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box { + Box::new(cheats) + } +} + +macro_rules! try_or_return { + ($e:expr) => { + match $e { + Ok(v) => v, + Err(_) => return, + } + }; +} + +/// Contains additional, test specific resources that should be kept for the duration of the test +#[derive(Debug, Default)] +pub struct Context { + /// Buffered readers for files opened for reading (path => BufReader mapping) + pub opened_read_files: HashMap>, +} + +/// Every time we clone `Context`, we want it to be empty +impl Clone for Context { + fn clone(&self) -> Self { + Default::default() + } +} + +impl Context { + /// Clears the context. + #[inline] + pub fn clear(&mut self) { + self.opened_read_files.clear(); + } +} + +/// Helps collecting transactions from different forks. +#[derive(Clone, Debug)] +pub struct BroadcastableTransaction { + /// The optional RPC URL. + pub rpc: Option, + /// The transaction to broadcast. + pub transaction: TransactionMaybeSigned, +} + +#[derive(Clone, Debug, Copy)] +pub struct RecordDebugStepInfo { + /// The debug trace node index when the recording starts. + pub start_node_idx: usize, + /// The original tracer config when the recording starts. + pub original_tracer_config: TracingInspectorConfig, +} + +/// Holds gas metering state. +#[derive(Clone, Debug, Default)] +pub struct GasMetering { + /// True if gas metering is paused. + pub paused: bool, + /// True if gas metering was resumed or reset during the test. + /// Used to reconcile gas when frame ends (if spent less than refunded). + pub touched: bool, + /// True if gas metering should be reset to frame limit. + pub reset: bool, + /// Stores paused gas frames. + pub paused_frames: Vec, + + /// The group and name of the active snapshot. + pub active_gas_snapshot: Option<(String, String)>, + + /// Cache of the amount of gas used in previous call. + /// This is used by the `lastCallGas` cheatcode. + pub last_call_gas: Option, + + /// True if gas recording is enabled. + pub recording: bool, + /// The gas used in the last frame. + pub last_gas_used: u64, + /// Gas records for the active snapshots. + pub gas_records: Vec, +} + +impl GasMetering { + /// Start the gas recording. + pub fn start(&mut self) { + self.recording = true; + } + + /// Stop the gas recording. + pub fn stop(&mut self) { + self.recording = false; + } + + /// Resume paused gas metering. + pub fn resume(&mut self) { + if self.paused { + self.paused = false; + self.touched = true; + } + self.paused_frames.clear(); + } + + /// Reset gas to limit. + pub fn reset(&mut self) { + self.paused = false; + self.touched = true; + self.reset = true; + self.paused_frames.clear(); + } +} + +/// Holds data about arbitrary storage. +#[derive(Clone, Debug, Default)] +pub struct ArbitraryStorage { + /// Mapping of arbitrary storage addresses to generated values (slot, arbitrary value). + /// (SLOADs return random value if storage slot wasn't accessed). + /// Changed values are recorded and used to copy storage to different addresses. + pub values: HashMap>, + /// Mapping of address with storage copied to arbitrary storage address source. + pub copies: HashMap, +} + +impl ArbitraryStorage { + /// Marks an address with arbitrary storage. + pub fn mark_arbitrary(&mut self, address: &Address) { + self.values.insert(*address, HashMap::default()); + } + + /// Maps an address that copies storage with the arbitrary storage address. + pub fn mark_copy(&mut self, from: &Address, to: &Address) { + if self.values.contains_key(from) { + self.copies.insert(*to, *from); + } + } + + /// Saves arbitrary storage value for a given address: + /// - store value in changed values cache. + /// - update account's storage with given value. + pub fn save(&mut self, ecx: InnerEcx, address: Address, slot: U256, data: U256) { + self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data); + if let Ok(mut account) = ecx.load_account(address) { + account.storage.insert(slot, EvmStorageSlot::new(data)); + } + } + + /// Copies arbitrary storage value from source address to the given target address: + /// - if a value is present in arbitrary values cache, then update target storage and return + /// existing value. + /// - if no value was yet generated for given slot, then save new value in cache and update both + /// source and target storages. + pub fn copy(&mut self, ecx: InnerEcx, target: Address, slot: U256, new_value: U256) -> U256 { + let source = self.copies.get(&target).expect("missing arbitrary copy target entry"); + let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage"); + let value = match storage_cache.get(&slot) { + Some(value) => *value, + None => { + storage_cache.insert(slot, new_value); + // Update source storage with new value. + if let Ok(mut source_account) = ecx.load_account(*source) { + source_account.storage.insert(slot, EvmStorageSlot::new(new_value)); + } + new_value + } + }; + // Update target storage with new value. + if let Ok(mut target_account) = ecx.load_account(target) { + target_account.storage.insert(slot, EvmStorageSlot::new(value)); + } + value + } +} + +/// List of transactions that can be broadcasted. +pub type BroadcastableTransactions = VecDeque; + +/// An EVM inspector that handles calls to various cheatcodes, each with their own behavior. +/// +/// Cheatcodes can be called by contracts during execution to modify the VM environment, such as +/// mocking addresses, signatures and altering call reverts. +/// +/// Executing cheatcodes can be very powerful. Most cheatcodes are limited to evm internals, but +/// there are also cheatcodes like `ffi` which can execute arbitrary commands or `writeFile` and +/// `readFile` which can manipulate files of the filesystem. Therefore, several restrictions are +/// implemented for these cheatcodes: +/// - `ffi`, and file cheatcodes are _always_ opt-in (via foundry config) and never enabled by +/// default: all respective cheatcode handlers implement the appropriate checks +/// - File cheatcodes require explicit permissions which paths are allowed for which operation, see +/// `Config.fs_permission` +/// - Only permitted accounts are allowed to execute cheatcodes in forking mode, this ensures no +/// contract deployed on the live network is able to execute cheatcodes by simply calling the +/// cheatcode address: by default, the caller, test contract and newly deployed contracts are +/// allowed to execute cheatcodes +#[derive(Clone, Debug)] +pub struct Cheatcodes { + /// The block environment + /// + /// Used in the cheatcode handler to overwrite the block environment separately from the + /// execution block environment. + pub block: Option, + + /// Currently active EIP-7702 delegation that will be consumed when building the next + /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during + /// transaction construction. + pub active_delegation: Option, + + /// The gas price. + /// + /// Used in the cheatcode handler to overwrite the gas price separately from the gas price + /// in the execution environment. + pub gas_price: Option, + + /// Address labels + pub labels: AddressHashMap, + + /// Prank information + pub prank: Option, + + /// Expected revert information + pub expected_revert: Option, + + /// Assume next call can revert and discard fuzz run if it does. + pub assume_no_revert: Option, + + /// Additional diagnostic for reverts + pub fork_revert_diagnostic: Option, + + /// Recorded storage reads and writes + pub accesses: Option, + + /// Recorded account accesses (calls, creates) organized by relative call depth, where the + /// topmost vector corresponds to accesses at the depth at which account access recording + /// began. Each vector in the matrix represents a list of accesses at a specific call + /// depth. Once that call context has ended, the last vector is removed from the matrix and + /// merged into the previous vector. + pub recorded_account_diffs_stack: Option>>, + + /// The information of the debug step recording. + pub record_debug_steps_info: Option, + + /// Recorded logs + pub recorded_logs: Option>, + + /// Mocked calls + // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext` + pub mocked_calls: HashMap>>, + + /// Mocked functions. Maps target address to be mocked to pair of (calldata, mock address). + pub mocked_functions: HashMap>, + + /// Expected calls + pub expected_calls: ExpectedCallTracker, + /// Expected emits + pub expected_emits: ExpectedEmitTracker, + + /// Map of context depths to memory offset ranges that may be written to within the call depth. + pub allowed_mem_writes: HashMap>>, + + /// Current broadcasting information + pub broadcast: Option, + + /// Scripting based transactions + pub broadcastable_transactions: BroadcastableTransactions, + + /// Additional, user configurable context this Inspector has access to when inspecting a call. + pub config: Arc, + + /// Test-scoped context holding data that needs to be reset every test run + pub context: Context, + + /// Whether to commit FS changes such as file creations, writes and deletes. + /// Used to prevent duplicate changes file executing non-committing calls. + pub fs_commit: bool, + + /// Serialized JSON values. + // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic. + pub serialized_jsons: BTreeMap>, + + /// All recorded ETH `deal`s. + pub eth_deals: Vec, + + /// Gas metering state. + pub gas_metering: GasMetering, + + /// Contains gas snapshots made over the course of a test suite. + // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic. + pub gas_snapshots: BTreeMap>, + + /// Mapping slots. + pub mapping_slots: Option>, + + /// The current program counter. + pub pc: usize, + /// Breakpoints supplied by the `breakpoint` cheatcode. + /// `char -> (address, pc)` + pub breakpoints: Breakpoints, + + /// Optional cheatcodes `TestRunner`. Used for generating random values from uint and int + /// strategies. + test_runner: Option, + + /// Ignored traces. + pub ignored_traces: IgnoredTraces, + + /// Addresses with arbitrary storage. + pub arbitrary_storage: Option, + + /// Deprecated cheatcodes mapped to the reason. Used to report warnings on test results. + pub deprecated: HashMap<&'static str, Option<&'static str>>, + /// Unlocked wallets used in scripts and testing of scripts. + pub wallets: Option, +} + +// This is not derived because calling this in `fn new` with `..Default::default()` creates a second +// `CheatsConfig` which is unused, and inside it `ProjectPathsConfig` is relatively expensive to +// create. +impl Default for Cheatcodes { + fn default() -> Self { + Self::new(Arc::default()) + } +} + +impl Cheatcodes { + /// Creates a new `Cheatcodes` with the given settings. + pub fn new(config: Arc) -> Self { + Self { + fs_commit: true, + labels: config.labels.clone(), + config, + block: Default::default(), + active_delegation: Default::default(), + gas_price: Default::default(), + prank: Default::default(), + expected_revert: Default::default(), + assume_no_revert: Default::default(), + fork_revert_diagnostic: Default::default(), + accesses: Default::default(), + recorded_account_diffs_stack: Default::default(), + recorded_logs: Default::default(), + record_debug_steps_info: Default::default(), + mocked_calls: Default::default(), + mocked_functions: Default::default(), + expected_calls: Default::default(), + expected_emits: Default::default(), + allowed_mem_writes: Default::default(), + broadcast: Default::default(), + broadcastable_transactions: Default::default(), + context: Default::default(), + serialized_jsons: Default::default(), + eth_deals: Default::default(), + gas_metering: Default::default(), + gas_snapshots: Default::default(), + mapping_slots: Default::default(), + pc: Default::default(), + breakpoints: Default::default(), + test_runner: Default::default(), + ignored_traces: Default::default(), + arbitrary_storage: Default::default(), + deprecated: Default::default(), + wallets: Default::default(), + } + } + + /// Returns the configured wallets if available, else creates a new instance. + pub fn wallets(&mut self) -> &Wallets { + self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None)) + } + + /// Sets the unlocked wallets. + pub fn set_wallets(&mut self, wallets: Wallets) { + self.wallets = Some(wallets); + } + + /// Decodes the input data and applies the cheatcode. + fn apply_cheatcode( + &mut self, + ecx: Ecx, + call: &CallInputs, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + // decode the cheatcode call + let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| { + if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e { + let msg = format!( + "unknown cheatcode with selector {selector}; \ + you may have a mismatch between the `Vm` interface (likely in `forge-std`) \ + and the `forge` version" + ); + return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg)); + } + e + })?; + + let caller = call.caller; + + // ensure the caller is allowed to execute cheatcodes, + // but only if the backend is in forking mode + ecx.db.ensure_cheatcode_access_forking_mode(&caller)?; + + apply_dispatch( + &decoded, + &mut CheatsCtxt { + state: self, + ecx: &mut ecx.inner, + precompiles: &mut ecx.precompiles, + gas_limit: call.gas_limit, + caller, + }, + executor, + ) + } + + /// 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. + fn allow_cheatcodes_on_create(&self, ecx: InnerEcx, caller: Address, created_address: Address) { + if ecx.journaled_state.depth <= 1 || ecx.db.has_cheatcode_access(&caller) { + ecx.db.allow_cheatcode_access(created_address); + } + } + + /// Called when there was a revert. + /// + /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's + /// revert would run into issues. + pub fn on_revert(&mut self, ecx: Ecx) { + trace!(deals=?self.eth_deals.len(), "rolling back deals"); + + // Delay revert clean up until expected revert is handled, if set. + if self.expected_revert.is_some() { + return; + } + + // we only want to apply cleanup top level + if ecx.journaled_state.depth() > 0 { + return; + } + + // Roll back all previously applied deals + // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine + // which rolls back any transfers. + while let Some(record) = self.eth_deals.pop() { + if let Some(acc) = ecx.journaled_state.state.get_mut(&record.address) { + acc.info.balance = record.old_balance; + } + } + } + + // common create functionality for both legacy and EOF. + fn create_common(&mut self, ecx: Ecx, mut input: Input) -> Option + where + Input: CommonCreateInput, + { + let ecx = &mut ecx.inner; + let gas = Gas::new(input.gas_limit()); + + // Apply our prank + if let Some(prank) = &self.prank { + if ecx.journaled_state.depth() >= prank.depth && input.caller() == prank.prank_caller { + // At the target depth we set `msg.sender` + if ecx.journaled_state.depth() == prank.depth { + input.set_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 && + input.caller() == broadcast.original_caller + { + if let Err(err) = + ecx.journaled_state.load_account(broadcast.new_origin, &mut ecx.db) + { + 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 { + input.set_caller(broadcast.new_origin); + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, input.gas_limit()); + + let account = &ecx.journaled_state.state()[&broadcast.new_origin]; + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ecx.db.active_fork_url(), + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to: None, + value: Some(input.value()), + input: TransactionInput::new(input.init_code()), + nonce: Some(account.info.nonce), + gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, + ..Default::default() + } + .into(), + }); + + input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); + } + } + } + + // Allow cheatcodes from the address of the new contract + let address = input.allow_cheatcodes(self, ecx); + + // If `recordAccountAccesses` has been called, record the create + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + recorded_account_diffs_stack.push(vec![AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.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(), + }]); + } + + None + } + + // common create_end functionality for both legacy and EOF. + fn create_end_common(&mut self, ecx: Ecx, mut outcome: CreateOutcome) -> CreateOutcome +where { + 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); + } + } + } + + // Clean up broadcasts + 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 { + std::mem::take(&mut self.broadcast); + } + } + } + + // 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 mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match revert_handlers::handle_expect_revert( + false, + true, + self.config.internal_expect_revert, + &expected_revert, + outcome.result.result, + outcome.result.output.clone(), + &self.config.available_artifacts, + ) { + Ok((address, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } + + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + outcome + } + Err(err) => { + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + outcome + } + }; + } + } + + // 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 + } + + pub fn call_with_executor( + &mut self, + ecx: Ecx, + call: &mut CallInputs, + executor: &mut impl CheatcodesExecutor, + ) -> Option { + 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; + let account = match super::evm::journaled_account(ecx, sender) { + Ok(account) => account, + Err(err) => { + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: err.abi_encode().into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) + } + }; + let prev = account.info.nonce; + account.info.nonce = prev.saturating_sub(1); + + trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); + } + + if call.target_address == CHEATCODE_ADDRESS { + return match self.apply_cheatcode(ecx, call, executor) { + Ok(retdata) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: retdata.into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + Err(err) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: err.abi_encode().into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + }; + } + + let ecx = &mut ecx.inner; + + if call.target_address == HARDHAT_CONSOLE_ADDRESS { + return None; + } + + // Handle expected calls + + // Grab the different calldatas expected. + if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address) + { + // Match every partial/full calldata + for (calldata, (expected, actual_count)) in expected_calls_for_target { + // Increment actual times seen if... + // The calldata is at most, as big as this call's input, and + if calldata.len() <= call.input.len() && + // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and + *calldata == call.input[..calldata.len()] && + // The value matches, if provided + expected + .value.is_none_or(|value| Some(value) == call.transfer_value()) && + // The gas matches, if provided + expected.gas.is_none_or(|gas| gas == call.gas_limit) && + // The minimum gas matches, if provided + expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit) + { + *actual_count += 1; + } + } + } + + // Handle mocked calls + if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) { + let ctx = + MockCallDataContext { calldata: call.input.clone(), value: call.transfer_value() }; + + if let Some(return_data_queue) = match mocks.get_mut(&ctx) { + Some(queue) => Some(queue), + None => mocks + .iter_mut() + .find(|(mock, _)| { + call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) && + mock.value.is_none_or(|value| Some(value) == call.transfer_value()) + }) + .map(|(_, v)| v), + } { + if let Some(return_data) = if return_data_queue.len() == 1 { + // If the mocked calls stack has a single element in it, don't empty it + return_data_queue.front().map(|x| x.to_owned()) + } else { + // Else, we pop the front element + return_data_queue.pop_front() + } { + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }); + } + } + } + + // Apply our prank + if let Some(prank) = &self.prank { + // Apply delegate call, `call.caller`` will not equal `prank.prank_caller` + if let CallScheme::DelegateCall | CallScheme::ExtDelegateCall = call.scheme { + if prank.delegate_call { + call.target_address = prank.new_caller; + call.caller = prank.new_caller; + let acc = ecx.journaled_state.account(prank.new_caller); + call.value = CallValue::Apparent(acc.info.balance); + if let Some(new_origin) = prank.new_origin { + ecx.env.tx.caller = new_origin; + } + } + } + + if ecx.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.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.env.tx.caller = new_origin; + prank_applied = true; + } + + // If prank applied for first time, then update + if prank_applied { + if let Some(applied_prank) = prank.first_time_applied() { + self.prank = Some(applied_prank); + } + } + } + } + + // Apply our broadcast + if let Some(broadcast) = &self.broadcast { + // We only apply a broadcast *to a specific depth*. + // + // 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.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.env.tx.caller = broadcast.new_origin; + + call.caller = broadcast.new_origin; + // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here + // because we only need the from, to, value, and data. We can later change this + // 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.load_account(broadcast.new_origin) { + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }); + } + + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit); + + let account = + ecx.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + + let mut tx_req = TransactionRequest { + from: Some(broadcast.new_origin), + to: Some(TxKind::from(Some(call.target_address))), + value: call.transfer_value(), + input: TransactionInput::new(call.input.clone()), + nonce: Some(account.info.nonce), + chain_id: Some(ecx.env.cfg.chain_id), + gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, + ..Default::default() + }; + + if let Some(auth_list) = self.active_delegation.take() { + tx_req.authorization_list = Some(vec![auth_list]); + } else { + tx_req.authorization_list = None; + } + + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ecx.db.active_fork_url(), + transaction: tx_req.into(), + }); + debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call"); + + // Explicitly increment nonce if calls are not isolated. + if !self.config.evm_opts.isolate { + let prev = account.info.nonce; + 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"; + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(msg), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }); + } + } + } + + // Record called accounts if `startStateDiffRecording` has been called + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero + // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code + let initialized; + let old_balance; + if let Ok(acc) = ecx.load_account(call.target_address) { + initialized = acc.info.exists(); + old_balance = acc.info.balance; + } else { + initialized = false; + old_balance = U256::ZERO; + } + let kind = match call.scheme { + CallScheme::Call => crate::Vm::AccountAccessKind::Call, + CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode, + CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall, + CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall, + CallScheme::ExtCall => crate::Vm::AccountAccessKind::Call, + CallScheme::ExtStaticCall => crate::Vm::AccountAccessKind::StaticCall, + CallScheme::ExtDelegateCall => crate::Vm::AccountAccessKind::DelegateCall, + }; + // Record this call by pushing it to a new pending vector; all subsequent calls at + // that depth will be pushed to the same vector. When the call ends, the + // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be + // updated with the revert status of this call, since the EVM does not mark accounts + // 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.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), + }, + accessor: call.caller, + account: call.bytecode_address, + kind, + initialized, + oldBalance: old_balance, + newBalance: U256::ZERO, // updated on call_end + value: call.call_value(), + data: call.input.clone(), + reverted: false, + deployedCode: Bytes::new(), + storageAccesses: vec![], // updated on step + depth: ecx.journaled_state.depth(), + }]); + } + + None + } + + pub fn rng(&mut self) -> &mut impl Rng { + self.test_runner().rng() + } + + pub fn test_runner(&mut self) -> &mut TestRunner { + self.test_runner.get_or_insert_with(|| match self.config.seed { + Some(seed) => TestRunner::new_with_rng( + proptest::test_runner::Config::default(), + TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()), + ), + None => TestRunner::new(proptest::test_runner::Config::default()), + }) + } + + /// Returns existing or set a default `ArbitraryStorage` option. + /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage. + pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage { + self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default) + } + + /// Whether the given address has arbitrary storage. + pub fn has_arbitrary_storage(&self, address: &Address) -> bool { + match &self.arbitrary_storage { + Some(storage) => storage.values.contains_key(address), + None => false, + } + } + + /// Whether the given address is a copy of an address with arbitrary storage. + pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool { + match &self.arbitrary_storage { + Some(storage) => storage.copies.contains_key(address), + None => false, + } + } +} + +impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { + #[inline] + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + // 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; + } + + // Record gas for current frame. + if self.gas_metering.paused { + self.gas_metering.paused_frames.push(interpreter.gas); + } + + // `expectRevert`: track the max call depth during `expectRevert` + if let Some(expected) = &mut self.expected_revert { + expected.max_depth = max(ecx.journaled_state.depth(), expected.max_depth); + } + } + + #[inline] + fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + self.pc = interpreter.program_counter(); + + // `pauseGasMetering`: pause / resume interpreter gas. + if self.gas_metering.paused { + self.meter_gas(interpreter); + } + + // `resetGasMetering`: reset interpreter gas. + if self.gas_metering.reset { + self.meter_gas_reset(interpreter); + } + + // `record`: record storage reads and writes. + if self.accesses.is_some() { + self.record_accesses(interpreter); + } + + // `startStateDiffRecording`: record granular ordered storage accesses. + if self.recorded_account_diffs_stack.is_some() { + self.record_state_diffs(interpreter, ecx); + } + + // `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); + } + + // `snapshotGas*`: take a snapshot of the current gas. + if self.gas_metering.recording { + self.meter_gas_record(interpreter, ecx); + } + } + + #[inline] + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + if self.gas_metering.paused { + self.meter_gas_end(interpreter); + } + + if self.gas_metering.touched { + self.meter_gas_check(interpreter); + } + + // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage. + if self.arbitrary_storage.is_some() { + self.arbitrary_storage_end(interpreter, ecx); + } + } + + fn log(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: &Log) { + if !self.expected_emits.is_empty() { + expect::handle_expect_emit(self, log, interpreter); + } + + // `recordLogs` + if let Some(storage_recorded_logs) = &mut self.recorded_logs { + storage_recorded_logs.push(Vm::Log { + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, + }); + } + } + + fn call(&mut self, ecx: Ecx, inputs: &mut CallInputs) -> Option { + Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor) + } + + fn call_end(&mut self, ecx: Ecx, 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 applied for cheatcodes in the `call` hook. + // This should be placed before the revert handling, because we might exit early there + if !cheatcode_call { + // Clean up pranks + 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 assume no revert cheatcode. + if let Some(assume_no_revert) = &mut self.assume_no_revert { + // Record current reverter address before processing the expect revert if call reverted, + // expect revert is set with expected reverter address and no actual reverter set yet. + if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() { + assume_no_revert.reverted_by = Some(call.target_address); + } + // allow multiple cheatcode calls at the same depth + if ecx.journaled_state.depth() <= assume_no_revert.depth && !cheatcode_call { + // Discard run if we're at the same depth as cheatcode, call reverted, and no + // specific reason was supplied + if outcome.result.is_revert() { + let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap(); + return match revert_handlers::handle_assume_no_revert( + &assume_no_revert, + outcome.result.result, + &outcome.result.output, + &self.config.available_artifacts, + ) { + // if result is Ok, it was an anticipated revert; return an "assume" error + // to reject this run + Ok(_) => { + outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into(); + outcome + } + // if result is Error, it was an unanticipated revert; should revert + // normally + Err(error) => { + trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = error.abi_encode().into(); + outcome + } + } + } else { + // Call didn't revert, reset `assume_no_revert` state. + self.assume_no_revert = None; + return outcome; + } + } + } + + // Handle expected reverts. + if let Some(expected_revert) = &mut self.expected_revert { + // Record current reverter address and call scheme before processing the expect revert + // if call reverted. + if outcome.result.is_revert() { + // Record current reverter address if expect revert is set with expected reverter + // address and no actual reverter was set yet or if we're expecting more than one + // revert. + if expected_revert.reverter.is_some() && + (expected_revert.reverted_by.is_none() || expected_revert.count > 1) + { + expected_revert.reverted_by = Some(call.target_address); + } + } + + if ecx.journaled_state.depth() <= expected_revert.depth { + let needs_processing = match expected_revert.kind { + ExpectedRevertKind::Default => !cheatcode_call, + // `pending_processing` == true means that we're in the `call_end` hook for + // `vm.expectCheatcodeRevert` and shouldn't expect revert here + ExpectedRevertKind::Cheatcode { pending_processing } => { + cheatcode_call && !pending_processing + } + }; + + if needs_processing { + let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match revert_handlers::handle_expect_revert( + cheatcode_call, + false, + self.config.internal_expect_revert, + &expected_revert, + outcome.result.result, + outcome.result.output.clone(), + &self.config.available_artifacts, + ) { + Err(error) => { + trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = error.abi_encode().into(); + outcome + } + Ok((_, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome + } + }; + } + + // Flip `pending_processing` flag for cheatcode revert expectations, marking that + // we've exited the `expectCheatcodeRevert` call scope + if let ExpectedRevertKind::Cheatcode { pending_processing } = + &mut self.expected_revert.as_mut().unwrap().kind + { + *pending_processing = false; + } + } + } + + // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode + // invocations + if cheatcode_call { + return outcome; + } + + // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to + // retrieve the gas usage of the last call. + let gas = outcome.result.gas; + self.gas_metering.last_call_gas = Some(crate::Vm::Gas { + gasLimit: gas.limit(), + gasTotalUsed: gas.spent(), + gasMemoryUsed: 0, + gasRefunded: gas.refunded(), + gasRemaining: gas.remaining(), + }); + + // 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_recorded_depth = + recorded_account_diffs_stack.pop().expect("missing CALL 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_recorded_depth.iter_mut().for_each(|element| { + element.reverted = true; + element + .storageAccesses + .iter_mut() + .for_each(|storage_access| storage_access.reverted = true); + }) + } + let call_access = last_recorded_depth.first_mut().expect("empty AccountAccesses"); + // Assert that we're at the correct depth before recording post-call state changes. + // Depending on the depth the cheat was called at, there may not be any pending + // calls to update if execution has percolated up to a higher depth. + if call_access.depth == ecx.journaled_state.depth() { + if let Ok(acc) = ecx.load_account(call.target_address) { + debug_assert!(access_is_call(call_access.kind)); + call_access.newBalance = acc.info.balance; + } + } + // Merge the last depth's AccountAccesses into the AccountAccesses at the current + // depth, or push them back onto the pending vector if higher depths were not + // recorded. This preserves ordering of accesses. + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.append(&mut last_recorded_depth); + } else { + recorded_account_diffs_stack.push(last_recorded_depth); + } + } + } + + // At the end of the call, + // we need to check if we've found all the emits. + // We know we've found all the expected emits in the right order + // if the queue is fully matched. + // If it's not fully matched, then either: + // 1. Not enough events were emitted (we'll know this because the amount of times we + // inspected events will be less than the size of the queue) 2. The wrong events + // were emitted (The inspected events should match the size of the queue, but still some + // events will not be matched) + + // First, check that we're at the call depth where the emits were declared from. + let should_check_emits = self + .expected_emits + .iter() + .any(|(expected, _)| expected.depth == ecx.journaled_state.depth()) && + // Ignore staticcalls + !call.is_static; + if should_check_emits { + let expected_counts = self + .expected_emits + .iter() + .filter_map(|(expected, count_map)| { + let count = match expected.address { + Some(emitter) => match count_map.get(&emitter) { + Some(log_count) => expected + .log + .as_ref() + .map(|l| log_count.count(l)) + .unwrap_or_else(|| log_count.count_unchecked()), + None => 0, + }, + None => match &expected.log { + Some(log) => count_map.values().map(|logs| logs.count(log)).sum(), + None => count_map.values().map(|logs| logs.count_unchecked()).sum(), + }, + }; + + if count != expected.count { + Some((expected, count)) + } else { + None + } + }) + .collect::>(); + + // Not all emits were matched. + 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; + } + + if !expected_counts.is_empty() { + let msg = if outcome.result.is_ok() { + let (expected, count) = expected_counts.first().unwrap(); + format!("log emitted {count} times, expected {}", expected.count) + } else { + "expected an emit, but the call reverted instead. \ + ensure you're testing the happy path when using `expectEmit`" + .to_string() + }; + + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + return outcome; + } + + // All emits were found, we're good. + // Clear the queue, as we expect the user to declare more events for the next call + // if they wanna match further events. + self.expected_emits.clear() + } + + // this will ensure we don't have false positives when trying to diagnose reverts in fork + // mode + let diag = self.fork_revert_diagnostic.take(); + + // if there's a revert and a previous call was diagnosed as fork related revert then we can + // return a better error here + if outcome.result.is_revert() { + if let Some(err) = diag { + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + return outcome; + } + } + + // try to diagnose reverts in multi-fork mode where a call is made to an address that does + // not exist + if let TxKind::Call(test_contract) = ecx.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() && + outcome.result.result == InstructionResult::Stop && + call.target_address != test_contract + { + self.fork_revert_diagnostic = + ecx.db.diagnose_revert(call.target_address, &ecx.journaled_state); + } + } + + // If the depth is 0, then this is the root call terminating + if ecx.journaled_state.depth() == 0 { + // If we already have a revert, we shouldn't run the below logic as it can obfuscate an + // earlier error that happened first with unrelated information about + // another error when using cheatcodes. + if outcome.result.is_revert() { + return outcome; + } + + // If there's not a revert, we can continue on to run the last logic for expect* + // cheatcodes. Match expected calls + for (address, calldatas) in &self.expected_calls { + // Loop over each address, and for each address, loop over each calldata it expects. + for (calldata, (expected, actual_count)) in calldatas { + // Grab the values we expect to see + let ExpectedCallData { gas, min_gas, value, count, call_type } = expected; + + let failed = match call_type { + // If the cheatcode was called with a `count` argument, + // we must check that the EVM performed a CALL with this calldata exactly + // `count` times. + ExpectedCallType::Count => *count != *actual_count, + // If the cheatcode was called without a `count` argument, + // we must check that the EVM performed a CALL with this calldata at least + // `count` times. The amount of times to check was + // the amount of time the cheatcode was called. + ExpectedCallType::NonCount => *count > *actual_count, + }; + if failed { + let expected_values = [ + Some(format!("data {}", hex::encode_prefixed(calldata))), + value.as_ref().map(|v| format!("value {v}")), + gas.map(|g| format!("gas {g}")), + min_gas.map(|g| format!("minimum gas {g}")), + ] + .into_iter() + .flatten() + .join(", "); + let but = if outcome.result.is_ok() { + let s = if *actual_count == 1 { "" } else { "s" }; + format!("was called {actual_count} time{s}") + } else { + "the call reverted instead; \ + ensure you're testing the happy path when using `expectCall`" + .to_string() + }; + let s = if *count == 1 { "" } else { "s" }; + let msg = format!( + "expected call to {address} with {expected_values} \ + to be called {count} time{s}, but {but}" + ); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + + return outcome; + } + } + } + // Check if we have any leftover expected emits + // First, if any emits were found at the root call, then we its ok and we remove them. + self.expected_emits.retain(|(expected, _)| expected.count > 0 && !expected.found); + // If not empty, we got mismatched emits + if !self.expected_emits.is_empty() { + let msg = if outcome.result.is_ok() { + "expected an emit, but no logs were emitted afterwards. \ + you might have mismatched events or not enough events were emitted" + } else { + "expected an emit, but the call reverted instead. \ + ensure you're testing the happy path when using `expectEmit`" + }; + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + return outcome; + } + } + + outcome + } + + fn create(&mut self, ecx: Ecx, call: &mut CreateInputs) -> Option { + self.create_common(ecx, call) + } + + fn create_end( + &mut self, + ecx: Ecx, + _call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.create_end_common(ecx, outcome) + } + + fn eofcreate(&mut self, ecx: Ecx, call: &mut EOFCreateInputs) -> Option { + self.create_common(ecx, call) + } + + fn eofcreate_end( + &mut self, + ecx: Ecx, + _call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.create_end_common(ecx, outcome) + } +} + +impl InspectorExt for Cheatcodes { + fn should_use_create2_factory(&mut self, ecx: Ecx, 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 + }; + + ecx.journaled_state.depth() == target_depth && + (self.broadcast.is_some() || self.config.always_use_create_2_factory) + } else { + false + } + } + + fn create2_deployer(&self) -> Address { + self.config.evm_opts.create2_deployer + } +} + +impl Cheatcodes { + #[cold] + fn meter_gas(&mut self, interpreter: &mut Interpreter) { + if let Some(paused_gas) = self.gas_metering.paused_frames.last() { + // Keep gas constant if paused. + interpreter.gas = *paused_gas; + } else { + // Record frame paused gas. + self.gas_metering.paused_frames.push(interpreter.gas); + } + } + + #[cold] + fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + if matches!(interpreter.instruction_result, InstructionResult::Continue) { + self.gas_metering.gas_records.iter_mut().for_each(|record| { + if ecx.journaled_state.depth() == record.depth { + // Skip the first opcode of the first call frame as it includes the gas cost of + // creating the snapshot. + if self.gas_metering.last_gas_used != 0 { + let gas_diff = + interpreter.gas.spent().saturating_sub(self.gas_metering.last_gas_used); + record.gas_used = record.gas_used.saturating_add(gas_diff); + } + + // Update `last_gas_used` to the current spent gas for the next iteration to + // compare against. + self.gas_metering.last_gas_used = interpreter.gas.spent(); + } + }); + } + } + + #[cold] + fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { + // Remove recorded gas if we exit frame. + if will_exit(interpreter.instruction_result) { + self.gas_metering.paused_frames.pop(); + } + } + + #[cold] + fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) { + interpreter.gas = Gas::new(interpreter.gas().limit()); + self.gas_metering.reset = false; + } + + #[cold] + fn meter_gas_check(&mut self, interpreter: &mut Interpreter) { + if will_exit(interpreter.instruction_result) { + // Reset gas if spent is less than refunded. + // This can happen if gas was paused / resumed or reset. + // https://github.com/foundry-rs/foundry/issues/4370 + if interpreter.gas.spent() < + u64::try_from(interpreter.gas.refunded()).unwrap_or_default() + { + interpreter.gas = Gas::new(interpreter.gas.limit()); + } + } + } + + /// Generates or copies arbitrary values for storage slots. + /// Invoked in inspector `step_end` (when the current opcode is not executed), if current opcode + /// to execute is `SLOAD` and storage slot is cold. + /// Ensures that in next step (when `SLOAD` opcode is executed) an arbitrary value is returned: + /// - copies the existing arbitrary storage value (or the new generated one if no value in + /// cache) from mapped source address to the target address. + /// - generates arbitrary value and saves it in target address storage. + #[cold] + fn arbitrary_storage_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + let (key, target_address) = if interpreter.current_opcode() == op::SLOAD { + (try_or_return!(interpreter.stack().peek(0)), interpreter.contract().target_address) + } else { + return + }; + + let Ok(value) = ecx.sload(target_address, key) else { + return; + }; + + if value.is_cold && value.data.is_zero() { + if self.has_arbitrary_storage(&target_address) { + let arbitrary_value = self.rng().gen(); + self.arbitrary_storage.as_mut().unwrap().save( + &mut ecx.inner, + target_address, + key, + arbitrary_value, + ); + } else if self.is_arbitrary_storage_copy(&target_address) { + let arbitrary_value = self.rng().gen(); + self.arbitrary_storage.as_mut().unwrap().copy( + &mut ecx.inner, + target_address, + key, + arbitrary_value, + ); + } + } + } + + /// 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() { + op::SLOAD => { + let key = try_or_return!(interpreter.stack().peek(0)); + access.record_read(interpreter.contract().target_address, key); + } + op::SSTORE => { + let key = try_or_return!(interpreter.stack().peek(0)); + access.record_write(interpreter.contract().target_address, key); + } + _ => {} + } + } + + #[cold] + fn record_state_diffs(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; + match interpreter.current_opcode() { + op::SELFDESTRUCT => { + // Ensure that we're not selfdestructing a context recording was initiated on + let Some(last) = account_accesses.last_mut() else { return }; + + // get previous balance and initialized status of the target account + 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(); + + // load balance of this account + let value = ecx + .balance(interpreter.contract().target_address) + .map(|b| b.data) + .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(), + }); + } + + op::SLOAD => { + let Some(last) = account_accesses.last_mut() else { return }; + + let key = try_or_return!(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.data; + } + } + 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()); + } + op::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.data; + } + } + + 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()); + } + + // Record account accesses via the EXT family of opcodes + op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => { + let kind = match interpreter.current_opcode() { + op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy, + op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize, + op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash, + op::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; + } + 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 { + account_accesses.push(vec![account_access]); + } + } + _ => {} + } + } + + /// 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; + }; + + // 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 // + //////////////////////////////////////////////////////////////// + + op::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 + } + } + op::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 // + //////////////////////////////////////////////////////////////// + + op::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 // + //////////////////////////////////////////////////////////////// + + op::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 + } + } + + $(op::$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), + ); + } +} + +/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write, +/// and sets the return range to the revert string's location in memory. +/// +/// This will set the interpreter's next action to a return with the revert string as the output. +/// And trigger a revert. +fn disallowed_mem_write( + dest_offset: u64, + size: u64, + interpreter: &mut Interpreter, + ranges: &[Range], +) { + let revert_string = format!( + "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}", + dest_offset, + size, + ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ") + ); + + interpreter.instruction_result = InstructionResult::Revert; + interpreter.next_action = InterpreterAction::Return { + result: InterpreterResult { + output: Error::encode(revert_string), + gas: interpreter.gas, + result: InstructionResult::Revert, + }, + }; +} + +// Determines if the gas limit on a given call was manually set in the script and should therefore +// not be overwritten by later estimations +fn check_if_fixed_gas_limit(ecx: InnerEcx, call_gas_limit: u64) -> bool { + // If the gas limit was not set in the source code it is set to the estimated gas left at the + // time of the call, which should be rather close to configured gas limit. + // TODO: Find a way to reliably make this determination. + // For example by generating it in the compilation or EVM simulation process + U256::from(ecx.env.tx.gas_limit) > ecx.env.block.gas_limit && + U256::from(call_gas_limit) <= ecx.env.block.gas_limit + // Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic + // gas too low" failure when simulated on chain + && call_gas_limit > 2300 +} + +/// Returns true if the kind of account access is a call. +fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool { + matches!( + kind, + crate::Vm::AccountAccessKind::Call | + crate::Vm::AccountAccessKind::StaticCall | + crate::Vm::AccountAccessKind::CallCode | + crate::Vm::AccountAccessKind::DelegateCall + ) +} + +/// Appends an AccountAccess that resumes the recording of the current context. +fn append_storage_access( + last: &mut Vec, + storage_access: crate::Vm::StorageAccess, + storage_depth: u64, +) { + // 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 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 dyn CheatcodesExecutor, +) -> Result { + let cheat = calls_as_dyn_cheatcode(calls); + + let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheat.id()).entered(); + trace!(target: "cheatcodes", cheat = ?cheat.as_debug(), "applying"); + + if let spec::Status::Deprecated(replacement) = *cheat.status() { + ccx.state.deprecated.insert(cheat.signature(), replacement); + } + + // Apply the cheatcode. + let mut result = cheat.dyn_apply(ccx, executor); + + // Format the error message to include the cheatcode name. + if let Err(e) = &mut result { + if e.is_str() { + let name = cheat.name(); + // Skip showing the cheatcode name for: + // - assertions: too verbose, and can already be inferred from the error message + // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl` + if !name.contains("assert") && name != "rpcUrl" { + *e = fmt_err!("vm.{name}: {e}"); + } + } + } + + trace!( + target: "cheatcodes", + return = %match &result { + Ok(b) => hex::encode(b), + Err(e) => e.to_string(), + } + ); + + result +} + +fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode { + macro_rules! as_dyn { + ($($variant:ident),*) => { + match calls { + $(Vm::VmCalls::$variant(cheat) => cheat,)* + } + }; + } + vm_calls!(as_dyn) +} + +/// Helper function to check if frame execution will exit. +fn will_exit(ir: InstructionResult) -> bool { + !matches!(ir, InstructionResult::Continue | InstructionResult::CallOrCreate) +} diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs new file mode 100644 index 0000000000000..a0d7820aa3e27 --- /dev/null +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -0,0 +1,96 @@ +use super::InnerEcx; +use crate::inspector::Cheatcodes; +use alloy_primitives::{Address, Bytes, U256}; +use revm::interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}; + +/// 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: InnerEcx) -> 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: InnerEcx) -> 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: InnerEcx) -> 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 new file mode 100644 index 0000000000000..6ad36e4742901 --- /dev/null +++ b/crates/cheatcodes/src/json.rs @@ -0,0 +1,741 @@ +//! Implementations of [`Json`](spec::Group::Json) cheatcodes. + +use crate::{string, Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_dyn_abi::{eip712_parser::EncodeType, DynSolType, DynSolValue, Resolver}; +use alloy_primitives::{hex, Address, B256, I256}; +use alloy_sol_types::SolValue; +use foundry_common::fs; +use foundry_config::fs_permissions::FsAccessKind; +use serde_json::{Map, Value}; +use std::{borrow::Cow, collections::BTreeMap}; + +impl Cheatcode for keyExistsCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + check_json_key_exists(json, key) + } +} + +impl Cheatcode for keyExistsJsonCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + check_json_key_exists(json, key) + } +} + +impl Cheatcode for parseJson_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json } = self; + parse_json(json, "$") + } +} + +impl Cheatcode for parseJson_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json(json, key) + } +} + +impl Cheatcode for parseJsonUintCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for parseJsonUintArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) + } +} + +impl Cheatcode for parseJsonIntCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Int(256)) + } +} + +impl Cheatcode for parseJsonIntArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) + } +} + +impl Cheatcode for parseJsonBoolCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Bool) + } +} + +impl Cheatcode for parseJsonBoolArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bool))) + } +} + +impl Cheatcode for parseJsonAddressCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Address) + } +} + +impl Cheatcode for parseJsonAddressArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Address))) + } +} + +impl Cheatcode for parseJsonStringCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::String) + } +} + +impl Cheatcode for parseJsonStringArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::String))) + } +} + +impl Cheatcode for parseJsonBytesCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Bytes) + } +} + +impl Cheatcode for parseJsonBytesArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) + } +} + +impl Cheatcode for parseJsonBytes32Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for parseJsonBytes32ArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_coerce(json, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) + } +} + +impl Cheatcode for parseJsonType_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + 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()) + } +} + +impl Cheatcode for parseJsonKeysCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { json, key } = self; + parse_json_keys(json, key) + } +} + +impl Cheatcode for serializeJsonCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, value } = self; + *state.serialized_jsons.entry(objectKey.into()).or_default() = serde_json::from_str(value)?; + Ok(value.abi_encode()) + } +} + +impl Cheatcode for serializeBool_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, (*value).into()) + } +} + +impl Cheatcode for serializeUint_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, (*value).into()) + } +} + +impl Cheatcode for serializeInt_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, (*value).into()) + } +} + +impl Cheatcode for serializeAddress_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, (*value).into()) + } +} + +impl Cheatcode for serializeBytes32_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, DynSolValue::FixedBytes(*value, 32)) + } +} + +impl Cheatcode for serializeString_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, value.clone().into()) + } +} + +impl Cheatcode for serializeBytes_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, value } = self; + serialize_json(state, objectKey, valueKey, value.to_vec().into()) + } +} + +impl Cheatcode for serializeBool_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { objectKey, valueKey, values } = self; + 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, + 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, + 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, + 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, + 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, + 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; + 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) + } +} + +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, valueKey, hex.into()) + } +} + +impl Cheatcode for writeJson_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json, path } = self; + let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned())); + let json_string = serde_json::to_string_pretty(&json)?; + super::fs::write_file(state, path.as_ref(), json_string.as_bytes()) + } +} + +impl Cheatcode for writeJson_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json, path, valueKey } = self; + let json = serde_json::from_str(json).unwrap_or_else(|_| Value::String(json.to_owned())); + + let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let data_s = fs::read_to_string(data_path)?; + let data = serde_json::from_str(&data_s)?; + let value = + jsonpath_lib::replace_with(data, &canonicalize_json_path(valueKey), &mut |_| { + Some(json.clone()) + })?; + + let json_string = serde_json::to_string_pretty(&value)?; + super::fs::write_file(state, path.as_ref(), json_string.as_bytes()) + } +} + +pub(super) fn check_json_key_exists(json: &str, key: &str) -> Result { + let json = parse_json_str(json)?; + let values = select(&json, key)?; + let exists = !values.is_empty(); + Ok(exists.abi_encode()) +} + +pub(super) fn parse_json(json: &str, path: &str) -> Result { + let value = parse_json_str(json)?; + let selected = select(&value, path)?; + let sol = json_to_sol(&selected)?; + Ok(encode(sol)) +} + +pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { + let json = parse_json_str(json)?; + let [value] = select(&json, path)?[..] else { + bail!("path {path:?} must return exactly one JSON value"); + }; + + 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 + }; + + 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)?; + let [value] = values[..] else { + bail!("key {key:?} must return exactly one JSON object"); + }; + let Value::Object(object) = value else { + bail!("JSON value at {key:?} is not an object"); + }; + let keys = object.keys().collect::>(); + Ok(keys.abi_encode()) +} + +fn parse_json_str(json: &str) -> Result { + serde_json::from_str(json).map_err(|e| fmt_err!("failed parsing JSON: {e}")) +} + +fn json_to_sol(json: &[&Value]) -> Result> { + let mut sol = Vec::with_capacity(json.len()); + for value in json { + sol.push(json_value_to_token(value)?); + } + Ok(sol) +} + +fn select<'a>(value: &'a Value, mut path: &str) -> Result> { + // Handle the special case of the root key + if path == "." { + path = "$"; + } + // format error with debug string because json_path errors may contain newlines + jsonpath_lib::select(value, &canonicalize_json_path(path)) + .map_err(|e| fmt_err!("failed selecting from JSON: {:?}", e.to_string())) +} + +fn encode(values: Vec) -> Vec { + // Double `abi_encode` is intentional + let bytes = match &values[..] { + [] => Vec::new(), + [one] => one.abi_encode(), + _ => DynSolValue::Array(values).abi_encode(), + }; + bytes.abi_encode() +} + +/// Canonicalize a json path key to always start from the root of the document. +/// Read more about json path syntax: +pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { + if !path.starts_with('$') { + format!("${path}").into() + } else { + path.into() + } +} + +/// 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 +/// Tuple +#[instrument(target = "cheatcodes", level = "trace", ret)] +pub(super) fn json_value_to_token(value: &Value) -> Result { + match value { + Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)), + Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)), + Value::Array(array) => { + array.iter().map(json_value_to_token).collect::>().map(DynSolValue::Array) + } + value @ Value::Object(_) => { + // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) + let ordered_object: BTreeMap = + serde_json::from_value(value.clone()).unwrap(); + ordered_object + .values() + .map(json_value_to_token) + .collect::>() + .map(DynSolValue::Tuple) + } + Value::Number(number) => { + if let Some(f) = number.as_f64() { + // Check if the number has decimal digits because the EVM does not support floating + // point math + if f.fract() == 0.0 { + // Use the string representation of the `serde_json` Number type instead of + // calling f.to_string(), because some numbers are wrongly rounded up after + // being convented to f64. + // Example: 18446744073709551615 becomes 18446744073709552000 after parsing it + // to f64. + let s = number.to_string(); + + // Coerced to scientific notation, so short-circuit to using fallback. + // This will not have a problem with hex numbers, as for parsing these + // We'd need to prefix this with 0x. + // See also + if s.contains('e') { + // Calling Number::to_string with powers of ten formats the number using + // scientific notation and causes from_dec_str to fail. Using format! with + // f64 keeps the full number representation. + // Example: 100000000000000000000 becomes 1e20 when Number::to_string is + // used. + let fallback_s = f.to_string(); + if let Ok(n) = fallback_s.parse() { + return Ok(DynSolValue::Uint(n, 256)); + } + if let Ok(n) = I256::from_dec_str(&fallback_s) { + return Ok(DynSolValue::Int(n, 256)); + } + } + + if let Ok(n) = s.parse() { + return Ok(DynSolValue::Uint(n, 256)); + } + if let Ok(n) = s.parse() { + return Ok(DynSolValue::Int(n, 256)); + } + } + } + + Err(fmt_err!("unsupported JSON number: {number}")) + } + Value::String(string) => { + if let Some(mut val) = string.strip_prefix("0x") { + let s; + if val.len() == 39 { + return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into()) + } + if val.len() % 2 != 0 { + s = format!("0{val}"); + val = &s[..]; + } + 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 stringified JSON objects, so we try to parse it like + // one first. + if let Ok(map) = serde_json::from_str(&s) { + Ok(Value::Object(map)) + } else { + Ok(Value::String(s)) + } + } + DynSolValue::Bytes(b) => Ok(Value::String(hex::encode_prefixed(b))), + DynSolValue::FixedBytes(b, size) => Ok(Value::String(hex::encode_prefixed(&b[..size]))), + DynSolValue::Int(i, _) => { + // 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 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 +/// any type, even a new object in itself. The function will return a stringified version of the +/// object, so that the user can use that as a value to a new invocation of the same function with a +/// new object key. This enables the user to reuse the same function to crate arbitrarily complex +/// object structures (JSON). +fn serialize_json( + state: &mut Cheatcodes, + object_key: &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(); + map.insert(value_key.into(), value); + let stringified = serde_json::to_string(map).unwrap(); + Ok(stringified.abi_encode()) +} + +/// Resolves a [DynSolType] from user input. +pub(super) 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().is_some_and(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()) + } + + // 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); + } + + #[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); + } + } +} diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs new file mode 100644 index 0000000000000..732f55d7e2533 --- /dev/null +++ b/crates/cheatcodes/src/lib.rs @@ -0,0 +1,171 @@ +//! # foundry-cheatcodes +//! +//! Foundry cheatcodes implementations. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![allow(elided_lifetimes_in_paths)] // Cheats context uses 3 lifetimes + +#[macro_use] +extern crate foundry_common; + +#[macro_use] +pub extern crate foundry_cheatcodes_spec as spec; + +#[macro_use] +extern crate tracing; + +use alloy_primitives::Address; +use foundry_evm_core::backend::DatabaseExt; +use revm::{ContextPrecompiles, InnerEvmContext}; +use spec::Status; + +pub use config::CheatsConfig; +pub use error::{Error, ErrorKind, Result}; +pub use inspector::{ + BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, Context, +}; +pub use spec::{CheatcodeDef, Vm}; +pub use Vm::ForgeContext; + +#[macro_use] +mod error; + +mod base64; + +mod config; + +mod crypto; + +mod version; + +mod env; +pub use env::set_execution_context; + +mod evm; + +mod fs; + +mod inspector; + +mod json; + +mod script; +pub use script::{Wallets, WalletsInner}; + +mod string; + +mod test; +pub use test::expect::ExpectedCallTracker; + +mod toml; + +mod utils; + +/// Cheatcode implementation. +pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { + /// Applies this cheatcode to the given state. + /// + /// Implement this function if you don't need access to the EVM data. + fn apply(&self, state: &mut Cheatcodes) -> Result { + let _ = state; + unimplemented!("{}", Self::CHEATCODE.func.id) + } + + /// Applies this cheatcode to the given context. + /// + /// Implement this function if you need access to the EVM data. + #[inline(always)] + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + self.apply(ccx.state) + } + + /// 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 dyn CheatcodesExecutor) -> Result { + let _ = executor; + self.apply_stateful(ccx) + } +} + +pub(crate) trait DynCheatcode: 'static { + fn cheatcode(&self) -> &'static spec::Cheatcode<'static>; + + fn as_debug(&self) -> &dyn std::fmt::Debug; + + fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result; +} + +impl DynCheatcode for T { + #[inline] + fn cheatcode(&self) -> &'static spec::Cheatcode<'static> { + Self::CHEATCODE + } + + #[inline] + fn as_debug(&self) -> &dyn std::fmt::Debug { + self + } + + #[inline] + fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + self.apply_full(ccx, executor) + } +} + +impl dyn DynCheatcode { + pub(crate) fn name(&self) -> &'static str { + self.cheatcode().func.signature.split('(').next().unwrap() + } + + pub(crate) fn id(&self) -> &'static str { + self.cheatcode().func.id + } + + pub(crate) fn signature(&self) -> &'static str { + self.cheatcode().func.signature + } + + pub(crate) fn status(&self) -> &Status<'static> { + &self.cheatcode().status + } +} + +/// The cheatcode context, used in `Cheatcode`. +pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { + /// The cheatcodes inspector state. + pub(crate) state: &'cheats mut Cheatcodes, + /// The EVM data. + pub(crate) ecx: &'evm mut InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, + /// The precompiles context. + pub(crate) precompiles: &'evm mut ContextPrecompiles<&'db mut (dyn DatabaseExt + 'db2)>, + /// The original `msg.sender`. + pub(crate) caller: Address, + /// Gas limit of the current cheatcode call. + pub(crate) gas_limit: u64, +} + +impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { + type Target = InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.ecx + } +} + +impl std::ops::DerefMut for CheatsCtxt<'_, '_, '_, '_> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.ecx + } +} + +impl CheatsCtxt<'_, '_, '_, '_> { + #[inline] + pub(crate) fn is_precompile(&self, address: &Address) -> bool { + self.precompiles.contains(address) + } +} diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs new file mode 100644 index 0000000000000..0749d0d41ba48 --- /dev/null +++ b/crates/cheatcodes/src/script.rs @@ -0,0 +1,293 @@ +//! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes. + +use crate::{Cheatcode, CheatsCtxt, Result, Vm::*}; +use alloy_primitives::{Address, PrimitiveSignature, B256, U256}; +use alloy_rpc_types::Authorization; +use alloy_signer::SignerSync; +use alloy_signer_local::PrivateKeySigner; +use alloy_sol_types::SolValue; +use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; +use parking_lot::Mutex; +use revm::primitives::{Bytecode, SignedAuthorization}; +use std::sync::Arc; + +impl Cheatcode for broadcast_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + broadcast(ccx, None, true) + } +} + +impl Cheatcode for broadcast_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { signer } = self; + broadcast(ccx, Some(signer), true) + } +} + +impl Cheatcode for broadcast_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { privateKey } = self; + broadcast_key(ccx, privateKey, true) + } +} + +impl Cheatcode for attachDelegationCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { signedDelegation } = self; + let SignedDelegation { v, r, s, nonce, implementation } = signedDelegation; + + let auth = Authorization { + address: *implementation, + nonce: *nonce, + chain_id: U256::from(ccx.ecx.env.cfg.chain_id), + }; + let signed_auth = SignedAuthorization::new_unchecked( + auth, + *v, + U256::from_be_bytes(r.0), + U256::from_be_bytes(s.0), + ); + write_delegation(ccx, signed_auth.clone())?; + ccx.state.active_delegation = Some(signed_auth); + Ok(Default::default()) + } +} + +impl Cheatcode for signDelegationCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { implementation, privateKey } = self; + let signer = PrivateKeySigner::from_bytes(&B256::from(*privateKey))?; + let authority = signer.address(); + let (auth, nonce) = create_auth(ccx, *implementation, authority)?; + let sig = signer.sign_hash_sync(&auth.signature_hash())?; + Ok(sig_to_delegation(sig, nonce, *implementation).abi_encode()) + } +} + +impl Cheatcode for signAndAttachDelegationCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { implementation, privateKey } = self; + let signer = PrivateKeySigner::from_bytes(&B256::from(*privateKey))?; + let authority = signer.address(); + let (auth, nonce) = create_auth(ccx, *implementation, authority)?; + let sig = signer.sign_hash_sync(&auth.signature_hash())?; + let signed_auth = sig_to_auth(sig, auth); + write_delegation(ccx, signed_auth.clone())?; + ccx.state.active_delegation = Some(signed_auth); + Ok(sig_to_delegation(sig, nonce, *implementation).abi_encode()) + } +} + +fn create_auth( + ccx: &mut CheatsCtxt, + implementation: Address, + authority: Address, +) -> Result<(Authorization, u64)> { + let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?; + let nonce = authority_acc.data.info.nonce; + Ok(( + Authorization { + address: implementation, + nonce, + chain_id: U256::from(ccx.ecx.env.cfg.chain_id), + }, + nonce, + )) +} + +fn write_delegation(ccx: &mut CheatsCtxt, auth: SignedAuthorization) -> Result<()> { + let authority = auth.recover_authority().map_err(|e| format!("{e}"))?; + let authority_acc = ccx.ecx.journaled_state.load_account(authority, &mut ccx.ecx.db)?; + if authority_acc.data.info.nonce != auth.nonce { + return Err("invalid nonce".into()); + } + authority_acc.data.info.nonce += 1; + let bytecode = Bytecode::new_eip7702(*auth.address()); + ccx.ecx.journaled_state.set_code(authority, bytecode); + Ok(()) +} + +fn sig_to_delegation( + sig: PrimitiveSignature, + nonce: u64, + implementation: Address, +) -> SignedDelegation { + SignedDelegation { + v: sig.v() as u8, + r: sig.r().into(), + s: sig.s().into(), + nonce, + implementation, + } +} + +fn sig_to_auth(sig: PrimitiveSignature, auth: Authorization) -> SignedAuthorization { + SignedAuthorization::new_unchecked(auth, sig.v() as u8, sig.r(), sig.s()) +} + +impl Cheatcode for startBroadcast_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + broadcast(ccx, None, false) + } +} + +impl Cheatcode for startBroadcast_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { signer } = self; + broadcast(ccx, Some(signer), false) + } +} + +impl Cheatcode for startBroadcast_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { privateKey } = self; + broadcast_key(ccx, privateKey, false) + } +} + +impl Cheatcode for stopBroadcastCall { + 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"); + }; + debug!(target: "cheatcodes", ?broadcast, "stopped"); + Ok(Default::default()) + } +} + +impl Cheatcode for getWalletsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let wallets = ccx.state.wallets().signers().unwrap_or_default(); + Ok(wallets.abi_encode()) + } +} + +#[derive(Clone, Debug, Default)] +pub struct Broadcast { + /// Address of the transaction origin + pub new_origin: Address, + /// Original caller + pub original_caller: Address, + /// Original `tx.origin` + pub original_origin: Address, + /// Depth of the broadcast + pub depth: u64, + /// Whether the prank stops by itself after the next call + pub single_call: bool, +} + +/// Contains context for wallet management. +#[derive(Debug)] +pub struct WalletsInner { + /// All signers in scope of the script. + pub multi_wallet: MultiWallet, + /// Optional signer provided as `--sender` flag. + pub provided_sender: Option
, +} + +/// Clonable wrapper around [`WalletsInner`]. +#[derive(Debug, Clone)] +pub struct Wallets { + /// Inner data. + pub inner: Arc>, +} + +impl Wallets { + #[allow(missing_docs)] + pub fn new(multi_wallet: MultiWallet, provided_sender: Option
) -> Self { + Self { inner: Arc::new(Mutex::new(WalletsInner { multi_wallet, provided_sender })) } + } + + /// Consumes [Wallets] and returns [MultiWallet]. + /// + /// Panics if [Wallets] is still in use. + pub fn into_multi_wallet(self) -> MultiWallet { + Arc::into_inner(self.inner) + .map(|m| m.into_inner().multi_wallet) + .unwrap_or_else(|| panic!("not all instances were dropped")) + } + + /// Locks inner Mutex and adds a signer to the [MultiWallet]. + pub fn add_private_key(&self, private_key: &B256) -> Result<()> { + self.add_local_signer(PrivateKeySigner::from_bytes(private_key)?); + Ok(()) + } + + /// Locks inner Mutex and adds a signer to the [MultiWallet]. + pub fn add_local_signer(&self, wallet: PrivateKeySigner) { + self.inner.lock().multi_wallet.add_signer(WalletSigner::Local(wallet)); + } + + /// Locks inner Mutex and returns all signer addresses in the [MultiWallet]. + pub fn signers(&self) -> Result> { + Ok(self.inner.lock().multi_wallet.signers()?.keys().cloned().collect()) + } + + /// Number of signers in the [MultiWallet]. + pub fn len(&self) -> usize { + let mut inner = self.inner.lock(); + let signers = inner.multi_wallet.signers(); + if signers.is_err() { + return 0; + } + signers.unwrap().len() + } + + /// Whether the [MultiWallet] is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// Sets up broadcasting from a script using `new_origin` as the sender. +fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bool) -> Result { + ensure!( + ccx.state.prank.is_none(), + "you have an active prank; broadcasting and pranks are not compatible" + ); + ensure!(ccx.state.broadcast.is_none(), "a broadcast is active already"); + + let mut new_origin = new_origin.cloned(); + + if new_origin.is_none() { + let mut wallets = ccx.state.wallets().inner.lock(); + if let Some(provided_sender) = wallets.provided_sender { + new_origin = Some(provided_sender); + } else { + let signers = wallets.multi_wallet.signers()?; + if signers.len() == 1 { + let address = signers.keys().next().unwrap(); + new_origin = Some(*address); + } + } + } + + let broadcast = Broadcast { + new_origin: new_origin.unwrap_or(ccx.ecx.env.tx.caller), + original_caller: ccx.caller, + original_origin: ccx.ecx.env.tx.caller, + depth: ccx.ecx.journaled_state.depth(), + single_call, + }; + debug!(target: "cheatcodes", ?broadcast, "started"); + ccx.state.broadcast = Some(broadcast); + Ok(Default::default()) +} + +/// Sets up broadcasting from a script with the sender derived from `private_key`. +/// Adds this private key to `state`'s `wallets` vector to later be used for signing +/// if broadcast is successful. +fn broadcast_key(ccx: &mut CheatsCtxt, private_key: &U256, single_call: bool) -> Result { + let wallet = super::crypto::parse_wallet(private_key)?; + let new_origin = wallet.address(); + + let result = broadcast(ccx, Some(&new_origin), single_call); + if result.is_ok() { + let wallets = ccx.state.wallets(); + wallets.add_local_signer(wallet); + } + result +} diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs new file mode 100644 index 0000000000000..080d9bc0820ff --- /dev/null +++ b/crates/cheatcodes/src/string.rs @@ -0,0 +1,205 @@ +//! Implementations of [`String`](spec::Group::String) cheatcodes. + +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::{hex, U256}; +use alloy_sol_types::SolValue; + +// address +impl Cheatcode for toString_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { value } = self; + Ok(value.to_string().abi_encode()) + } +} + +// bytes +impl Cheatcode for toString_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { value } = self; + Ok(value.to_string().abi_encode()) + } +} + +// bytes32 +impl Cheatcode for toString_2Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { value } = self; + Ok(value.to_string().abi_encode()) + } +} + +// bool +impl Cheatcode for toString_3Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { value } = self; + Ok(value.to_string().abi_encode()) + } +} + +// uint256 +impl Cheatcode for toString_4Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { value } = self; + Ok(value.to_string().abi_encode()) + } +} + +// int256 +impl Cheatcode for toString_5Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { value } = self; + Ok(value.to_string().abi_encode()) + } +} + +impl Cheatcode for parseBytesCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { stringifiedValue } = self; + parse(stringifiedValue, &DynSolType::Bytes) + } +} + +impl Cheatcode for parseAddressCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { stringifiedValue } = self; + parse(stringifiedValue, &DynSolType::Address) + } +} + +impl Cheatcode for parseUintCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { stringifiedValue } = self; + parse(stringifiedValue, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for parseIntCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { stringifiedValue } = self; + parse(stringifiedValue, &DynSolType::Int(256)) + } +} + +impl Cheatcode for parseBytes32Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { stringifiedValue } = self; + parse(stringifiedValue, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for parseBoolCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { stringifiedValue } = self; + parse(stringifiedValue, &DynSolType::Bool) + } +} + +impl Cheatcode for toLowercaseCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.to_lowercase().abi_encode()) + } +} + +impl Cheatcode for toUppercaseCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.to_uppercase().abi_encode()) + } +} + +impl Cheatcode for trimCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input } = self; + Ok(input.trim().abi_encode()) + } +} + +impl Cheatcode for replaceCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, from, to } = self; + Ok(input.replace(from, to).abi_encode()) + } +} + +impl Cheatcode for splitCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, delimiter } = self; + let parts: Vec<&str> = input.split(delimiter).collect(); + Ok(parts.abi_encode()) + } +} + +impl Cheatcode for indexOfCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { input, key } = self; + Ok(input.find(key).map(U256::from).unwrap_or(U256::MAX).abi_encode()) + } +} + +impl Cheatcode for containsCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { subject, search } = self; + Ok(subject.contains(search).abi_encode()) + } +} + +pub(super) fn parse(s: &str, ty: &DynSolType) -> Result { + parse_value(s, ty).map(|v| v.abi_encode()) +} + +pub(super) fn parse_array(values: I, ty: &DynSolType) -> Result +where + I: IntoIterator, + S: AsRef, +{ + let mut values = values.into_iter(); + match values.next() { + Some(first) if !first.as_ref().is_empty() => std::iter::once(first) + .chain(values) + .map(|s| parse_value(s.as_ref(), ty)) + .collect::, _>>() + .map(|vec| DynSolValue::Array(vec).abi_encode()), + // return the empty encoded Bytes when values is empty or the first element is empty + _ => Ok("".abi_encode()), + } +} + +#[instrument(target = "cheatcodes", level = "debug", skip(ty), fields(%ty), ret)] +pub(super) fn parse_value(s: &str, ty: &DynSolType) -> Result { + match ty.coerce_str(s) { + Ok(value) => Ok(value), + Err(e) => match parse_value_fallback(s, ty) { + Some(Ok(value)) => Ok(value), + Some(Err(e2)) => Err(fmt_err!("failed parsing {s:?} as type `{ty}`: {e2}")), + None => Err(fmt_err!("failed parsing {s:?} as type `{ty}`: {e}")), + }, + } +} + +// More lenient parsers than `coerce_str`. +fn parse_value_fallback(s: &str, ty: &DynSolType) -> Option> { + match ty { + DynSolType::Bool => { + let b = match s { + "1" => true, + "0" => false, + s if s.eq_ignore_ascii_case("true") => true, + s if s.eq_ignore_ascii_case("false") => false, + _ => return None, + }; + return Some(Ok(DynSolValue::Bool(b))); + } + DynSolType::Int(_) | + DynSolType::Uint(_) | + DynSolType::FixedBytes(_) | + DynSolType::Bytes => { + if !s.starts_with("0x") && hex::check_raw(s) { + return Some(Err("missing hex prefix (\"0x\") for hex string")); + } + } + _ => {} + } + None +} diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs new file mode 100644 index 0000000000000..c12f4609bdef2 --- /dev/null +++ b/crates/cheatcodes/src/test.rs @@ -0,0 +1,102 @@ +//! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. + +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use alloy_primitives::Address; +use alloy_sol_types::SolValue; +use foundry_common::version::SEMVER_VERSION; +use foundry_evm_core::constants::MAGIC_SKIP; + +pub(crate) mod assert; +pub(crate) mod assume; +pub(crate) mod expect; +pub(crate) mod revert_handlers; + +impl Cheatcode for breakpoint_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { char } = self; + breakpoint(ccx.state, &ccx.caller, char, true) + } +} + +impl Cheatcode for breakpoint_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { char, value } = self; + breakpoint(ccx.state, &ccx.caller, char, *value) + } +} + +impl Cheatcode for getFoundryVersionCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self {} = self; + Ok(SEMVER_VERSION.abi_encode()) + } +} + +impl Cheatcode for rpcUrlCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { rpcAlias } = self; + let url = state.config.rpc_endpoint(rpcAlias)?.url()?.abi_encode(); + Ok(url) + } +} + +impl Cheatcode for rpcUrlsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.config.rpc_urls().map(|urls| urls.abi_encode()) + } +} + +impl Cheatcode for rpcUrlStructsCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + state.config.rpc_urls().map(|urls| urls.abi_encode()) + } +} + +impl Cheatcode for sleepCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { duration } = self; + let sleep_duration = std::time::Duration::from_millis(duration.saturating_to()); + std::thread::sleep(sleep_duration); + Ok(Default::default()) + } +} + +impl Cheatcode for skip_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { skipTest } = *self; + skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) + } +} + +impl Cheatcode for skip_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { skipTest, reason } = self; + if *skipTest { + // Skip should not work if called deeper than at test level. + // Since we're not returning the magic skip bytes, this will cause a test failure. + ensure!(ccx.ecx.journaled_state.depth() <= 1, "`skip` can only be used at test level"); + Err([MAGIC_SKIP, reason.as_bytes()].concat().into()) + } else { + Ok(Default::default()) + } + } +} + +/// Adds or removes the given breakpoint to the state. +fn breakpoint(state: &mut Cheatcodes, caller: &Address, s: &str, add: bool) -> Result { + let mut chars = s.chars(); + let (Some(point), None) = (chars.next(), chars.next()) else { + bail!("breakpoints must be exactly one character"); + }; + ensure!(point.is_alphabetic(), "only alphabetic characters are accepted as breakpoints"); + + if add { + state.breakpoints.insert(point, (*caller, state.pc)); + } else { + state.breakpoints.remove(&point); + } + + Ok(Default::default()) +} diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs new file mode 100644 index 0000000000000..a61cc4b2a2ece --- /dev/null +++ b/crates/cheatcodes/src/test/assert.rs @@ -0,0 +1,634 @@ +use crate::{CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; +use alloy_primitives::{hex, I256, U256}; +use foundry_evm_core::{ + abi::console::{format_units_int, format_units_uint}, + backend::GLOBAL_FAIL_SLOT, + constants::CHEATCODE_ADDRESS, +}; +use itertools::Itertools; +use std::fmt::{Debug, Display}; + +const EQ_REL_DELTA_RESOLUTION: U256 = U256::from_limbs([18, 0, 0, 0]); + +#[derive(Debug, thiserror::Error)] +#[error("assertion failed")] +struct SimpleAssertionError; + +#[derive(thiserror::Error, Debug)] +enum ComparisonAssertionError<'a, T> { + Ne { left: &'a T, right: &'a T }, + Eq { left: &'a T, right: &'a T }, + Ge { left: &'a T, right: &'a T }, + Gt { left: &'a T, right: &'a T }, + Le { left: &'a T, right: &'a T }, + Lt { left: &'a T, right: &'a T }, +} + +macro_rules! format_values { + ($self:expr, $format_fn:expr) => { + match $self { + Self::Ne { left, right } => format!("{} == {}", $format_fn(left), $format_fn(right)), + Self::Eq { left, right } => format!("{} != {}", $format_fn(left), $format_fn(right)), + Self::Ge { left, right } => format!("{} < {}", $format_fn(left), $format_fn(right)), + Self::Gt { left, right } => format!("{} <= {}", $format_fn(left), $format_fn(right)), + Self::Le { left, right } => format!("{} > {}", $format_fn(left), $format_fn(right)), + Self::Lt { left, right } => format!("{} >= {}", $format_fn(left), $format_fn(right)), + } + }; +} + +impl ComparisonAssertionError<'_, T> { + fn format_for_values(&self) -> String { + format_values!(self, T::to_string) + } +} + +impl ComparisonAssertionError<'_, Vec> { + fn format_for_arrays(&self) -> String { + let formatter = |v: &Vec| format!("[{}]", v.iter().format(", ")); + format_values!(self, formatter) + } +} + +impl ComparisonAssertionError<'_, U256> { + fn format_with_decimals(&self, decimals: &U256) -> String { + let formatter = |v: &U256| format_units_uint(v, decimals); + format_values!(self, formatter) + } +} + +impl ComparisonAssertionError<'_, I256> { + fn format_with_decimals(&self, decimals: &U256) -> String { + let formatter = |v: &I256| format_units_int(v, decimals); + format_values!(self, formatter) + } +} + +#[derive(thiserror::Error, Debug)] +#[error("{left} !~= {right} (max delta: {max_delta}, real delta: {real_delta})")] +struct EqAbsAssertionError { + left: T, + right: T, + max_delta: D, + real_delta: D, +} + +impl EqAbsAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_uint(&self.left, decimals), + format_units_uint(&self.right, decimals), + format_units_uint(&self.max_delta, decimals), + format_units_uint(&self.real_delta, decimals), + ) + } +} + +impl EqAbsAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_int(&self.left, decimals), + format_units_int(&self.right, decimals), + format_units_uint(&self.max_delta, decimals), + format_units_uint(&self.real_delta, decimals), + ) + } +} + +fn format_delta_percent(delta: &U256) -> String { + format!("{}%", format_units_uint(delta, &(EQ_REL_DELTA_RESOLUTION - U256::from(2)))) +} + +#[derive(Debug)] +enum EqRelDelta { + Defined(U256), + Undefined, +} + +impl Display for EqRelDelta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Defined(delta) => write!(f, "{}", format_delta_percent(delta)), + Self::Undefined => write!(f, "undefined"), + } + } +} + +#[derive(thiserror::Error, Debug)] +#[error( + "{left} !~= {right} (max delta: {}, real delta: {})", + format_delta_percent(max_delta), + real_delta +)] +struct EqRelAssertionFailure { + left: T, + right: T, + max_delta: U256, + real_delta: EqRelDelta, +} + +#[derive(thiserror::Error, Debug)] +enum EqRelAssertionError { + #[error(transparent)] + Failure(Box>), + #[error("overflow in delta calculation")] + Overflow, +} + +impl EqRelAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + match self { + Self::Failure(f) => format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_uint(&f.left, decimals), + format_units_uint(&f.right, decimals), + format_delta_percent(&f.max_delta), + &f.real_delta, + ), + Self::Overflow => self.to_string(), + } + } +} + +impl EqRelAssertionError { + fn format_with_decimals(&self, decimals: &U256) -> String { + match self { + Self::Failure(f) => format!( + "{} !~= {} (max delta: {}, real delta: {})", + format_units_int(&f.left, decimals), + format_units_int(&f.right, decimals), + format_delta_percent(&f.max_delta), + &f.real_delta, + ), + Self::Overflow => self.to_string(), + } + } +} + +type ComparisonResult<'a, T> = Result, ComparisonAssertionError<'a, T>>; + +fn handle_assertion_result( + result: core::result::Result, ERR>, + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, + error_formatter: impl Fn(&ERR) -> String, + error_msg: Option<&str>, + format_error: bool, +) -> Result { + match result { + Ok(_) => Ok(Default::default()), + Err(err) => { + let error_msg = error_msg.unwrap_or("assertion failed"); + let msg = if format_error { + format!("{error_msg}: {}", error_formatter(&err)) + } else { + error_msg.to_string() + }; + if ccx.state.config.assertions_revert { + Err(msg.into()) + } else { + executor.console_log(ccx, &msg); + ccx.ecx.sstore(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT, U256::from(1))?; + Ok(Default::default()) + } + } + } +} + +/// Implements [crate::Cheatcode] for pairs of cheatcodes. +/// +/// Accepts a list of pairs of cheatcodes, where the first cheatcode is the one that doesn't contain +/// a custom error message, and the second one contains it at `error` field. +/// +/// 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 dyn CheatcodesExecutor, + ) -> Result { + let Self { $($arg),* } = self; + handle_assertion_result($body, ccx, executor, $error_formatter, None, $format_error) + } + } + + impl crate::Cheatcode for $with_error { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + let Self { $($arg),*, error} = self; + handle_assertion_result($body, ccx, executor, $error_formatter, Some(error), $format_error) + } + } + }; +} + +impl_assertions! { + |condition| assert_true(*condition), + false, + (assertTrue_0Call, assertTrue_1Call), +} + +impl_assertions! { + |condition| assert_false(*condition), + false, + (assertFalse_0Call, assertFalse_1Call), +} + +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_assertions! { + |left, right| assert_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)), + |e| e.format_for_values(), + (assertEq_12Call, assertEq_13Call), +} + +impl_assertions! { + |left, right| assert_eq(left, right), + |e| e.format_for_arrays(), + (assertEq_14Call, assertEq_15Call), + (assertEq_16Call, assertEq_17Call), + (assertEq_18Call, assertEq_19Call), + (assertEq_20Call, assertEq_21Call), + (assertEq_22Call, assertEq_23Call), + (assertEq_24Call, assertEq_25Call), +} + +impl_assertions! { + |left, right| assert_eq( + &left.iter().map(hex::encode_prefixed).collect::>(), + &right.iter().map(hex::encode_prefixed).collect::>(), + ), + |e| e.format_for_arrays(), + (assertEq_26Call, assertEq_27Call), +} + +impl_assertions! { + |left, right, decimals| assert_eq(left, right), + |e| e.format_with_decimals(decimals), + (assertEqDecimal_0Call, assertEqDecimal_1Call), + (assertEqDecimal_2Call, assertEqDecimal_3Call), +} + +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_assertions! { + |left, right| assert_not_eq(&hex::encode_prefixed(left), &hex::encode_prefixed(right)), + |e| e.format_for_values(), + (assertNotEq_12Call, assertNotEq_13Call), +} + +impl_assertions! { + |left, right| assert_not_eq(left, right), + |e| e.format_for_arrays(), + (assertNotEq_14Call, assertNotEq_15Call), + (assertNotEq_16Call, assertNotEq_17Call), + (assertNotEq_18Call, assertNotEq_19Call), + (assertNotEq_20Call, assertNotEq_21Call), + (assertNotEq_22Call, assertNotEq_23Call), + (assertNotEq_24Call, assertNotEq_25Call), +} + +impl_assertions! { + |left, right| assert_not_eq( + &left.iter().map(hex::encode_prefixed).collect::>(), + &right.iter().map(hex::encode_prefixed).collect::>(), + ), + |e| e.format_for_arrays(), + (assertNotEq_26Call, assertNotEq_27Call), +} + +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_assertions! { + |left, right| assert_gt(left, right), + |e| e.format_for_values(), + (assertGt_0Call, assertGt_1Call), + (assertGt_2Call, assertGt_3Call), +} + +impl_assertions! { + |left, right, decimals| assert_gt(left, right), + |e| e.format_with_decimals(decimals), + (assertGtDecimal_0Call, assertGtDecimal_1Call), + (assertGtDecimal_2Call, assertGtDecimal_3Call), +} + +impl_assertions! { + |left, right| assert_ge(left, right), + |e| e.format_for_values(), + (assertGe_0Call, assertGe_1Call), + (assertGe_2Call, assertGe_3Call), +} + +impl_assertions! { + |left, right, decimals| assert_ge(left, right), + |e| e.format_with_decimals(decimals), + (assertGeDecimal_0Call, assertGeDecimal_1Call), + (assertGeDecimal_2Call, assertGeDecimal_3Call), +} + +impl_assertions! { + |left, right| assert_lt(left, right), + |e| e.format_for_values(), + (assertLt_0Call, assertLt_1Call), + (assertLt_2Call, assertLt_3Call), +} + +impl_assertions! { + |left, right, decimals| assert_lt(left, right), + |e| e.format_with_decimals(decimals), + (assertLtDecimal_0Call, assertLtDecimal_1Call), + (assertLtDecimal_2Call, assertLtDecimal_3Call), +} + +impl_assertions! { + |left, right| assert_le(left, right), + |e| e.format_for_values(), + (assertLe_0Call, assertLe_1Call), + (assertLe_2Call, assertLe_3Call), +} + +impl_assertions! { + |left, right, decimals| assert_le(left, right), + |e| e.format_with_decimals(decimals), + (assertLeDecimal_0Call, assertLeDecimal_1Call), + (assertLeDecimal_2Call, assertLeDecimal_3Call), +} + +impl_assertions! { + |left, right, maxDelta| uint_assert_approx_eq_abs(*left, *right, *maxDelta), + (assertApproxEqAbs_0Call, assertApproxEqAbs_1Call), +} + +impl_assertions! { + |left, right, maxDelta| int_assert_approx_eq_abs(*left, *right, *maxDelta), + (assertApproxEqAbs_2Call, assertApproxEqAbs_3Call), +} + +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_assertions! { + |left, right, decimals, maxDelta| int_assert_approx_eq_abs(*left, *right, *maxDelta), + |e| e.format_with_decimals(decimals), + (assertApproxEqAbsDecimal_2Call, assertApproxEqAbsDecimal_3Call), +} + +impl_assertions! { + |left, right, maxPercentDelta| uint_assert_approx_eq_rel(*left, *right, *maxPercentDelta), + (assertApproxEqRel_0Call, assertApproxEqRel_1Call), +} + +impl_assertions! { + |left, right, maxPercentDelta| int_assert_approx_eq_rel(*left, *right, *maxPercentDelta), + (assertApproxEqRel_2Call, assertApproxEqRel_3Call), +} + +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_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> { + if condition { + Ok(Default::default()) + } else { + Err(SimpleAssertionError) + } +} + +fn assert_false(condition: bool) -> Result, SimpleAssertionError> { + if !condition { + Ok(Default::default()) + } else { + Err(SimpleAssertionError) + } +} + +fn assert_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left == right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Eq { left, right }) + } +} + +fn assert_not_eq<'a, T: PartialEq>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left != right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Ne { left, right }) + } +} + +fn get_delta_uint(left: U256, right: U256) -> U256 { + if left > right { + left - right + } else { + right - left + } +} + +fn get_delta_int(left: I256, right: I256) -> U256 { + let (left_sign, left_abs) = left.into_sign_and_abs(); + let (right_sign, right_abs) = right.into_sign_and_abs(); + + if left_sign == right_sign { + if left_abs > right_abs { + left_abs - right_abs + } else { + right_abs - left_abs + } + } else { + left_abs + right_abs + } +} + +fn uint_assert_approx_eq_abs( + left: U256, + right: U256, + max_delta: U256, +) -> Result, Box>> { + let delta = get_delta_uint(left, right); + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) + } +} + +fn int_assert_approx_eq_abs( + left: I256, + right: I256, + max_delta: U256, +) -> Result, Box>> { + let delta = get_delta_int(left, right); + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(Box::new(EqAbsAssertionError { left, right, max_delta, real_delta: delta })) + } +} + +fn uint_assert_approx_eq_rel( + left: U256, + right: U256, + max_delta: U256, +) -> Result, EqRelAssertionError> { + if right.is_zero() { + if left.is_zero() { + return Ok(Default::default()) + } else { + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))) + }; + } + + let delta = get_delta_uint(left, right) + .checked_mul(U256::pow(U256::from(10), EQ_REL_DELTA_RESOLUTION)) + .ok_or(EqRelAssertionError::Overflow)? / + right; + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Defined(delta), + }))) + } +} + +fn int_assert_approx_eq_rel( + left: I256, + right: I256, + max_delta: U256, +) -> Result, EqRelAssertionError> { + if right.is_zero() { + if left.is_zero() { + return Ok(Default::default()) + } else { + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))) + } + } + + let (_, abs_right) = right.into_sign_and_abs(); + let delta = get_delta_int(left, right) + .checked_mul(U256::pow(U256::from(10), EQ_REL_DELTA_RESOLUTION)) + .ok_or(EqRelAssertionError::Overflow)? / + abs_right; + + if delta <= max_delta { + Ok(Default::default()) + } else { + Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Defined(delta), + }))) + } +} + +fn assert_gt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left > right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Gt { left, right }) + } +} + +fn assert_ge<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left >= right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Ge { left, right }) + } +} + +fn assert_lt<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left < right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Lt { left, right }) + } +} + +fn assert_le<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> ComparisonResult<'a, T> { + if left <= right { + Ok(Default::default()) + } else { + Err(ComparisonAssertionError::Le { left, right }) + } +} diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs new file mode 100644 index 0000000000000..74bd79e0964b6 --- /dev/null +++ b/crates/cheatcodes/src/test/assume.rs @@ -0,0 +1,98 @@ +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; +use alloy_primitives::Address; +use foundry_evm_core::constants::MAGIC_ASSUME; +use spec::Vm::{ + assumeCall, assumeNoRevert_0Call, assumeNoRevert_1Call, assumeNoRevert_2Call, PotentialRevert, +}; +use std::fmt::Debug; + +#[derive(Clone, Debug)] +pub struct AssumeNoRevert { + /// The call depth at which the cheatcode was added. + pub depth: u64, + /// Acceptable revert parameters for the next call, to be thrown out if they are encountered; + /// reverts with parameters not specified here will count as normal reverts and not rejects + /// towards the counter. + pub reasons: Vec, + /// Address that reverted the call. + pub reverted_by: Option
, +} + +/// Parameters for a single anticipated revert, to be thrown out if encountered. +#[derive(Clone, Debug)] +pub struct AcceptableRevertParameters { + /// The expected revert data returned by the revert + pub reason: Vec, + /// If true then only the first 4 bytes of expected data returned by the revert are checked. + pub partial_match: bool, + /// Contract expected to revert next call. + pub reverter: Option
, +} + +impl AcceptableRevertParameters { + fn from(potential_revert: &PotentialRevert) -> Self { + Self { + reason: potential_revert.revertData.to_vec(), + partial_match: potential_revert.partialMatch, + reverter: if potential_revert.reverter == Address::ZERO { + None + } else { + Some(potential_revert.reverter) + }, + } + } +} + +impl Cheatcode for assumeCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { condition } = self; + if *condition { + Ok(Default::default()) + } else { + Err(Error::from(MAGIC_ASSUME)) + } + } +} + +impl Cheatcode for assumeNoRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + assume_no_revert(ccx.state, ccx.ecx.journaled_state.depth(), vec![]) + } +} + +impl Cheatcode for assumeNoRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { potentialRevert } = self; + assume_no_revert( + ccx.state, + ccx.ecx.journaled_state.depth(), + vec![AcceptableRevertParameters::from(potentialRevert)], + ) + } +} + +impl Cheatcode for assumeNoRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { potentialReverts } = self; + assume_no_revert( + ccx.state, + ccx.ecx.journaled_state.depth(), + potentialReverts.iter().map(AcceptableRevertParameters::from).collect(), + ) + } +} + +fn assume_no_revert( + state: &mut Cheatcodes, + depth: u64, + parameters: Vec, +) -> Result { + ensure!( + state.assume_no_revert.is_none(), + "you must make another external call prior to calling assumeNoRevert again" + ); + + state.assume_no_revert = Some(AssumeNoRevert { depth, reasons: parameters, reverted_by: None }); + + Ok(Default::default()) +} diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs new file mode 100644 index 0000000000000..11d0e65a02379 --- /dev/null +++ b/crates/cheatcodes/src/test/expect.rs @@ -0,0 +1,953 @@ +use std::collections::VecDeque; + +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*}; +use alloy_primitives::{ + map::{hash_map::Entry, AddressHashMap, HashMap}, + Address, Bytes, LogData as RawLog, U256, +}; +use revm::interpreter::{InstructionResult, Interpreter, InterpreterAction, InterpreterResult}; + +use super::revert_handlers::RevertParameters; +/// Tracks the expected calls per address. +/// +/// For each address, we track the expected calls per call data. We track it in such manner +/// so that we don't mix together calldatas that only contain selectors and calldatas that contain +/// selector and arguments (partial and full matches). +/// +/// This then allows us to customize the matching behavior for each call data on the +/// `ExpectedCallData` struct and track how many times we've actually seen the call on the second +/// element of the tuple. +pub type ExpectedCallTracker = HashMap>; + +#[derive(Clone, Debug)] +pub struct ExpectedCallData { + /// The expected value sent in the call + pub value: Option, + /// The expected gas supplied to the call + pub gas: Option, + /// The expected *minimum* gas supplied to the call + pub min_gas: Option, + /// The number of times the call is expected to be made. + /// If the type of call is `NonCount`, this is the lower bound for the number of calls + /// that must be seen. + /// If the type of call is `Count`, this is the exact number of calls that must be seen. + pub count: u64, + /// The type of expected call. + pub call_type: ExpectedCallType, +} + +/// The type of expected call. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ExpectedCallType { + /// The call is expected to be made at least once. + NonCount, + /// The exact number of calls expected. + Count, +} + +/// The type of expected revert. +#[derive(Clone, Debug)] +pub enum ExpectedRevertKind { + /// Expects revert from the next non-cheatcode call. + Default, + /// Expects revert from the next cheatcode call. + /// + /// The `pending_processing` flag is used to track whether we have exited + /// `expectCheatcodeRevert` context or not. + /// We have to track it to avoid expecting `expectCheatcodeRevert` call to revert itself. + Cheatcode { pending_processing: bool }, +} + +#[derive(Clone, Debug)] +pub struct ExpectedRevert { + /// The expected data returned by the revert, None being any. + pub reason: Option>, + /// The depth at which the revert is expected. + pub depth: u64, + /// The type of expected revert. + pub kind: ExpectedRevertKind, + /// If true then only the first 4 bytes of expected data returned by the revert are checked. + pub partial_match: bool, + /// Contract expected to revert next call. + pub reverter: Option
, + /// Address that reverted the call. + pub reverted_by: Option
, + /// Max call depth reached during next call execution. + pub max_depth: u64, + /// Number of times this revert is expected. + pub count: u64, + /// Actual number of times this revert has been seen. + pub actual_count: u64, +} + +#[derive(Clone, Debug)] +pub struct ExpectedEmit { + /// The depth at which we expect this emit to have occurred + pub depth: u64, + /// The log we expect + pub log: Option, + /// The checks to perform: + /// ```text + /// ┌───────┬───────┬───────┬───────┬────┐ + /// │topic 0│topic 1│topic 2│topic 3│data│ + /// └───────┴───────┴───────┴───────┴────┘ + /// ``` + 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, + /// Number of times the log is expected to be emitted + pub count: u64, +} + +impl Cheatcode for expectCall_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, data } = self; + expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount) + } +} + +impl Cheatcode for expectCall_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, data, count } = self; + expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count) + } +} + +impl Cheatcode for expectCall_2Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, msgValue, data } = self; + expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount) + } +} + +impl Cheatcode for expectCall_3Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, msgValue, data, count } = self; + expect_call( + state, + callee, + data, + Some(msgValue), + None, + None, + *count, + ExpectedCallType::Count, + ) + } +} + +impl Cheatcode for expectCall_4Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, msgValue, gas, data } = self; + expect_call( + state, + callee, + data, + Some(msgValue), + Some(*gas), + None, + 1, + ExpectedCallType::NonCount, + ) + } +} + +impl Cheatcode for expectCall_5Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, msgValue, gas, data, count } = self; + expect_call( + state, + callee, + data, + Some(msgValue), + Some(*gas), + None, + *count, + ExpectedCallType::Count, + ) + } +} + +impl Cheatcode for expectCallMinGas_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, msgValue, minGas, data } = self; + expect_call( + state, + callee, + data, + Some(msgValue), + None, + Some(*minGas), + 1, + ExpectedCallType::NonCount, + ) + } +} + +impl Cheatcode for expectCallMinGas_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { callee, msgValue, minGas, data, count } = self; + expect_call( + state, + callee, + data, + Some(msgValue), + None, + Some(*minGas), + *count, + ExpectedCallType::Count, + ) + } +} + +impl Cheatcode for expectEmit_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; + expect_emit( + ccx.state, + ccx.ecx.journaled_state.depth(), + [true, checkTopic1, checkTopic2, checkTopic3, checkData], + None, + false, + 1, + ) + } +} + +impl Cheatcode for expectEmit_1Call { + 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(), + [true, checkTopic1, checkTopic2, checkTopic3, checkData], + Some(emitter), + false, + 1, + ) + } +} + +impl Cheatcode for expectEmit_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false, 1) + } +} + +impl Cheatcode for expectEmit_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), false, 1) + } +} + +impl Cheatcode for expectEmit_4Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { checkTopic1, checkTopic2, checkTopic3, checkData, count } = *self; + expect_emit( + ccx.state, + ccx.ecx.journaled_state.depth(), + [true, checkTopic1, checkTopic2, checkTopic3, checkData], + None, + false, + count, + ) + } +} + +impl Cheatcode for expectEmit_5Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter, count } = *self; + expect_emit( + ccx.state, + ccx.ecx.journaled_state.depth(), + [true, checkTopic1, checkTopic2, checkTopic3, checkData], + Some(emitter), + false, + count, + ) + } +} + +impl Cheatcode for expectEmit_6Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { count } = *self; + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false, count) + } +} + +impl Cheatcode for expectEmit_7Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { emitter, count } = *self; + expect_emit( + ccx.state, + ccx.ecx.journaled_state.depth(), + [true; 5], + Some(emitter), + false, + count, + ) + } +} + +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, + 1, + ) + } +} + +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, + 1, + ) + } +} + +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, 1) + } +} + +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, 1) + } +} + +impl Cheatcode for expectRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, 1) + } +} + +impl Cheatcode for expectRevert_1Call { + 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, + false, + None, + 1, + ) + } +} + +impl Cheatcode for expectRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + false, + false, + None, + 1, + ) + } +} + +impl Cheatcode for expectRevert_3Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { reverter } = self; + expect_revert( + ccx.state, + None, + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + 1, + ) + } +} + +impl Cheatcode for expectRevert_4Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter } = self; + expect_revert( + ccx.state, + Some(revertData.as_ref()), + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + 1, + ) + } +} + +impl Cheatcode for expectRevert_5Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + 1, + ) + } +} + +impl Cheatcode for expectRevert_6Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { count } = self; + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None, *count) + } +} + +impl Cheatcode for expectRevert_7Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, count } = self; + expect_revert( + ccx.state, + Some(revertData.as_ref()), + ccx.ecx.journaled_state.depth(), + false, + false, + None, + *count, + ) + } +} + +impl Cheatcode for expectRevert_8Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, count } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + false, + false, + None, + *count, + ) + } +} + +impl Cheatcode for expectRevert_9Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { reverter, count } = self; + expect_revert( + ccx.state, + None, + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + *count, + ) + } +} + +impl Cheatcode for expectRevert_10Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter, count } = self; + expect_revert( + ccx.state, + Some(revertData.as_ref()), + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + *count, + ) + } +} + +impl Cheatcode for expectRevert_11Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter, count } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + false, + false, + Some(*reverter), + *count, + ) + } +} + +impl Cheatcode for expectPartialRevert_0Call { + 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, + true, + None, + 1, + ) + } +} + +impl Cheatcode for expectPartialRevert_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData, reverter } = self; + expect_revert( + ccx.state, + Some(revertData.as_ref()), + ccx.ecx.journaled_state.depth(), + false, + true, + Some(*reverter), + 1, + ) + } +} + +impl Cheatcode for _expectCheatcodeRevert_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None, 1) + } +} + +impl Cheatcode for _expectCheatcodeRevert_1Call { + 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, + false, + None, + 1, + ) + } +} + +impl Cheatcode for _expectCheatcodeRevert_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { revertData } = self; + expect_revert( + ccx.state, + Some(revertData), + ccx.ecx.journaled_state.depth(), + true, + false, + None, + 1, + ) + } +} + +impl Cheatcode for expectSafeMemoryCall { + 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_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); + Ok(Default::default()) + } +} + +impl Cheatcode for expectSafeMemoryCallCall { + 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) + } +} + +impl RevertParameters for ExpectedRevert { + fn reverter(&self) -> Option
{ + self.reverter + } + + fn reason(&self) -> Option<&[u8]> { + self.reason.as_deref() + } + + fn partial_match(&self) -> bool { + self.partial_match + } +} + +/// Handles expected calls specified by the `expectCall` cheatcodes. +/// +/// It can handle calls in two ways: +/// - If the cheatcode was used with a `count` argument, it will expect the call to be made exactly +/// `count` times. e.g. `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f), 4)` +/// will expect the call to address(0xc4f3) with selector `0xd34db33f` to be made exactly 4 times. +/// If the amount of calls is less or more than 4, the test will fail. Note that the `count` +/// argument cannot be overwritten with another `vm.expectCall`. If this is attempted, +/// `expectCall` will revert. +/// - If the cheatcode was used without a `count` argument, it will expect the call to be made at +/// least the amount of times the cheatcode was called. This means that `vm.expectCall` without a +/// count argument can be called many times, but cannot be called with a `count` argument after it +/// was called without one. If the latter happens, `expectCall` will revert. e.g +/// `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f))` will expect the call to +/// address(0xc4f3) and selector `0xd34db33f` to be made at least once. If the amount of calls is +/// 0, the test will fail. If the call is made more than once, the test will pass. +#[allow(clippy::too_many_arguments)] // It is what it is +fn expect_call( + state: &mut Cheatcodes, + target: &Address, + calldata: &Bytes, + value: Option<&U256>, + mut gas: Option, + mut min_gas: Option, + count: u64, + call_type: ExpectedCallType, +) -> Result { + let expecteds = state.expected_calls.entry(*target).or_default(); + + if let Some(val) = value { + if *val > U256::ZERO { + // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas + // to ensure that the basic fallback function can be called. + let positive_value_cost_stipend = 2300; + if let Some(gas) = &mut gas { + *gas += positive_value_cost_stipend; + } + if let Some(min_gas) = &mut min_gas { + *min_gas += positive_value_cost_stipend; + } + } + } + + match call_type { + ExpectedCallType::Count => { + // Get the expected calls for this target. + // In this case, as we're using counted expectCalls, we should not be able to set them + // more than once. + ensure!( + !expecteds.contains_key(calldata), + "counted expected calls can only bet set once" + ); + expecteds.insert( + calldata.clone(), + (ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0), + ); + } + ExpectedCallType::NonCount => { + // Check if the expected calldata exists. + // If it does, increment the count by one as we expect to see it one more time. + match expecteds.entry(calldata.clone()) { + Entry::Occupied(mut entry) => { + let (expected, _) = entry.get_mut(); + // Ensure we're not overwriting a counted expectCall. + ensure!( + expected.call_type == ExpectedCallType::NonCount, + "cannot overwrite a counted expectCall with a non-counted expectCall" + ); + expected.count += 1; + } + // If it does not exist, then create it. + Entry::Vacant(entry) => { + entry.insert(( + ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, + 0, + )); + } + } + } + } + + Ok(Default::default()) +} + +fn expect_emit( + state: &mut Cheatcodes, + depth: u64, + checks: [bool; 5], + address: Option
, + anonymous: bool, + count: u64, +) -> Result { + let expected_emit = + ExpectedEmit { depth, checks, address, found: false, log: None, anonymous, count }; + if let Some(found_emit_pos) = state.expected_emits.iter().position(|(emit, _)| emit.found) { + // The order of emits already found (back of queue) should not be modified, hence push any + // new emit before first found emit. + state.expected_emits.insert(found_emit_pos, (expected_emit, Default::default())); + } else { + // If no expected emits then push new one at the back of queue. + state.expected_emits.push_back((expected_emit, Default::default())); + } + + Ok(Default::default()) +} + +pub(crate) fn handle_expect_emit( + state: &mut Cheatcodes, + log: &alloy_primitives::Log, + interpreter: &mut Interpreter, +) { + // Fill or check the expected emits. + // We expect for emit checks to be filled as they're declared (from oldest to newest), + // so we fill them and push them to the back of the queue. + // If the user has properly filled all the emits, they'll end up in their original order. + // If not, the queue will not be in the order the events will be intended to be filled, + // and we'll be able to later detect this and bail. + + // First, we can return early if all events have been matched. + // This allows a contract to arbitrarily emit more events than expected (additive behavior), + // as long as all the previous events were matched in the order they were expected to be. + if state.expected_emits.iter().all(|(expected, _)| expected.found) { + return + } + + let should_fill_logs = state.expected_emits.iter().any(|(expected, _)| expected.log.is_none()); + let index_to_fill_or_check = if should_fill_logs { + // If there's anything to fill, we start with the last event to match in the queue + // (without taking into account events already matched). + state + .expected_emits + .iter() + .position(|(emit, _)| emit.found) + .unwrap_or(state.expected_emits.len()) + .saturating_sub(1) + } else { + // Otherwise, if all expected logs are filled, we start to check any unmatched event + // in the declared order, so we start from the front (like a queue). + 0 + }; + + let (mut event_to_fill_or_check, mut count_map) = state + .expected_emits + .remove(index_to_fill_or_check) + .expect("we should have an emit to fill or check"); + + let Some(expected) = &event_to_fill_or_check.log else { + // Unless the caller is trying to match an anonymous event, the first topic must be + // filled. + if event_to_fill_or_check.anonymous || !log.topics().is_empty() { + event_to_fill_or_check.log = Some(log.data.clone()); + // If we only filled the expected log then we put it back at the same position. + state + .expected_emits + .insert(index_to_fill_or_check, (event_to_fill_or_check, count_map)); + } else { + interpreter.instruction_result = InstructionResult::Revert; + interpreter.next_action = InterpreterAction::Return { + result: InterpreterResult { + output: Error::encode("use vm.expectEmitAnonymous to match anonymous events"), + gas: interpreter.gas, + result: InstructionResult::Revert, + }, + }; + } + return + }; + + // Increment/set `count` for `log.address` and `log.data` + match count_map.entry(log.address) { + Entry::Occupied(mut entry) => { + // Checks and inserts the log into the map. + // If the log doesn't pass the checks, it is ignored and `count` is not incremented. + let log_count_map = entry.get_mut(); + log_count_map.insert(&log.data); + } + Entry::Vacant(entry) => { + let mut log_count_map = LogCountMap::new(&event_to_fill_or_check); + + if log_count_map.satisfies_checks(&log.data) { + log_count_map.insert(&log.data); + + // Entry is only inserted if it satisfies the checks. + entry.insert(log_count_map); + } + } + } + + event_to_fill_or_check.found = || -> bool { + if !checks_topics_and_data(event_to_fill_or_check.checks, expected, log) { + return false + } + + // Maybe match source address. + if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) { + return false; + } + + let expected_count = event_to_fill_or_check.count; + + match event_to_fill_or_check.address { + Some(emitter) => count_map + .get(&emitter) + .is_some_and(|log_map| log_map.count(&log.data) >= expected_count), + None => count_map + .values() + .find(|log_map| log_map.satisfies_checks(&log.data)) + .is_some_and(|map| map.count(&log.data) >= expected_count), + } + }(); + + // If we found the event, we can push it to the back of the queue + // and begin expecting the next event. + if event_to_fill_or_check.found { + state.expected_emits.push_back((event_to_fill_or_check, count_map)); + } else { + // We did not match this event, so we need to keep waiting for the right one to + // appear. + state.expected_emits.push_front((event_to_fill_or_check, count_map)); + } +} + +/// Handles expected emits specified by the `expectEmit` cheatcodes. +/// +/// The second element of the tuple counts the number of times the log has been emitted by a +/// particular address +pub type ExpectedEmitTracker = VecDeque<(ExpectedEmit, AddressHashMap)>; + +#[derive(Clone, Debug, Default)] +pub struct LogCountMap { + checks: [bool; 5], + expected_log: RawLog, + map: HashMap, +} + +impl LogCountMap { + /// Instantiates `LogCountMap`. + fn new(expected_emit: &ExpectedEmit) -> Self { + Self { + checks: expected_emit.checks, + expected_log: expected_emit.log.clone().expect("log should be filled here"), + map: Default::default(), + } + } + + /// Inserts a log into the map and increments the count. + /// + /// The log must pass all checks against the expected log for the count to increment. + /// + /// Returns true if the log was inserted and count was incremented. + fn insert(&mut self, log: &RawLog) -> bool { + // If its already in the map, increment the count without checking. + if self.map.contains_key(log) { + self.map.entry(log.clone()).and_modify(|c| *c += 1); + + return true + } + + if !self.satisfies_checks(log) { + return false + } + + self.map.entry(log.clone()).and_modify(|c| *c += 1).or_insert(1); + + true + } + + /// Checks the incoming raw log against the expected logs topics and data. + fn satisfies_checks(&self, log: &RawLog) -> bool { + checks_topics_and_data(self.checks, &self.expected_log, log) + } + + pub fn count(&self, log: &RawLog) -> u64 { + if !self.satisfies_checks(log) { + return 0 + } + + self.count_unchecked() + } + + pub fn count_unchecked(&self) -> u64 { + self.map.values().sum() + } +} + +fn expect_revert( + state: &mut Cheatcodes, + reason: Option<&[u8]>, + depth: u64, + cheatcode: bool, + partial_match: bool, + reverter: Option
, + count: u64, +) -> Result { + ensure!( + state.expected_revert.is_none(), + "you must call another function prior to expecting a second revert" + ); + state.expected_revert = Some(ExpectedRevert { + reason: reason.map(<[_]>::to_vec), + depth, + kind: if cheatcode { + ExpectedRevertKind::Cheatcode { pending_processing: true } + } else { + ExpectedRevertKind::Default + }, + partial_match, + reverter, + reverted_by: None, + max_depth: depth, + count, + actual_count: 0, + }); + Ok(Default::default()) +} + +fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool { + if log.topics().len() != expected.topics().len() { + return false + } + + // Check topics. + if !log + .topics() + .iter() + .enumerate() + .filter(|(i, _)| checks[*i]) + .all(|(i, topic)| topic == &expected.topics()[i]) + { + return false + } + + // Check data + if checks[4] && expected.data.as_ref() != log.data.as_ref() { + return false + } + + true +} + +fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) -> Result { + ensure!(start < end, "memory range start ({start}) is greater than end ({end})"); + #[allow(clippy::single_range_in_vec_init)] // Wanted behaviour + let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]); + offsets.push(start..end); + Ok(Default::default()) +} diff --git a/crates/cheatcodes/src/test/revert_handlers.rs b/crates/cheatcodes/src/test/revert_handlers.rs new file mode 100644 index 0000000000000..4368d0dc2a261 --- /dev/null +++ b/crates/cheatcodes/src/test/revert_handlers.rs @@ -0,0 +1,243 @@ +use crate::{Error, Result}; +use alloy_primitives::{address, hex, Address, Bytes}; +use alloy_sol_types::{SolError, SolValue}; +use foundry_common::ContractsByArtifact; +use foundry_evm_core::decode::RevertDecoder; +use revm::interpreter::{return_ok, InstructionResult}; +use spec::Vm; + +use super::{ + assume::{AcceptableRevertParameters, AssumeNoRevert}, + expect::ExpectedRevert, +}; + +/// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. +/// Solidity will see a successful call and attempt to decode the return data. Therefore, we need +/// to populate the return with dummy bytes so the decode doesn't fail. +/// +/// 8192 bytes was arbitrarily chosen because it is long enough for return values up to 256 words in +/// size. +static DUMMY_CALL_OUTPUT: Bytes = Bytes::from_static(&[0u8; 8192]); + +/// Same reasoning as [DUMMY_CALL_OUTPUT], but for creates. +const DUMMY_CREATE_ADDRESS: Address = address!("0000000000000000000000000000000000000001"); + +fn stringify(data: &[u8]) -> String { + if let Ok(s) = String::abi_decode(data, true) { + return s; + } + if data.is_ascii() { + return std::str::from_utf8(data).unwrap().to_owned(); + } + hex::encode_prefixed(data) +} + +/// Common parameters for expected or assumed reverts. Allows for code reuse. +pub(crate) trait RevertParameters { + fn reverter(&self) -> Option
; + fn reason(&self) -> Option<&[u8]>; + fn partial_match(&self) -> bool; +} + +impl RevertParameters for AcceptableRevertParameters { + fn reverter(&self) -> Option
{ + self.reverter + } + + fn reason(&self) -> Option<&[u8]> { + Some(&self.reason) + } + + fn partial_match(&self) -> bool { + self.partial_match + } +} + +/// Core logic for handling reverts that may or may not be expected (or assumed). +fn handle_revert( + is_cheatcode: bool, + revert_params: &impl RevertParameters, + status: InstructionResult, + retdata: &Bytes, + known_contracts: &Option, + reverter: Option<&Address>, +) -> Result<(), Error> { + // If expected reverter address is set then check it matches the actual reverter. + if let (Some(expected_reverter), Some(&actual_reverter)) = (revert_params.reverter(), reverter) + { + if expected_reverter != actual_reverter { + return Err(fmt_err!( + "Reverter != expected reverter: {} != {}", + actual_reverter, + expected_reverter + )); + } + } + + let expected_reason = revert_params.reason(); + // If None, accept any revert. + let Some(expected_reason) = expected_reason else { + return Ok(()); + }; + + if !expected_reason.is_empty() && retdata.is_empty() { + bail!("call reverted as expected, but without data"); + } + + let mut actual_revert: Vec = retdata.to_vec(); + + // Compare only the first 4 bytes if partial match. + if revert_params.partial_match() && actual_revert.get(..4) == expected_reason.get(..4) { + return Ok(()); + } + + // Try decoding as known errors. + actual_revert = decode_revert(actual_revert); + + if actual_revert == expected_reason || + (is_cheatcode && memchr::memmem::find(&actual_revert, expected_reason).is_some()) + { + Ok(()) + } else { + let (actual, expected) = if let Some(contracts) = known_contracts { + let decoder = RevertDecoder::new().with_abis(contracts.iter().map(|(_, c)| &c.abi)); + ( + &decoder.decode(actual_revert.as_slice(), Some(status)), + &decoder.decode(expected_reason, Some(status)), + ) + } else { + (&stringify(&actual_revert), &stringify(expected_reason)) + }; + Err(fmt_err!("Error != expected error: {} != {}", actual, expected,)) + } +} + +pub(crate) fn handle_assume_no_revert( + assume_no_revert: &AssumeNoRevert, + status: InstructionResult, + retdata: &Bytes, + known_contracts: &Option, +) -> Result<()> { + // if a generic AssumeNoRevert, return Ok(). Otherwise, iterate over acceptable reasons and try + // to match against any, otherwise, return an Error with the revert data + if assume_no_revert.reasons.is_empty() { + Ok(()) + } else { + assume_no_revert + .reasons + .iter() + .find_map(|reason| { + handle_revert( + false, + reason, + status, + retdata, + known_contracts, + assume_no_revert.reverted_by.as_ref(), + ) + .ok() + }) + .ok_or_else(|| retdata.clone().into()) + } +} + +pub(crate) fn handle_expect_revert( + is_cheatcode: bool, + is_create: bool, + internal_expect_revert: bool, + expected_revert: &ExpectedRevert, + status: InstructionResult, + retdata: Bytes, + known_contracts: &Option, +) -> Result<(Option
, Bytes)> { + let success_return = || { + if is_create { + (Some(DUMMY_CREATE_ADDRESS), Bytes::new()) + } else { + (None, DUMMY_CALL_OUTPUT.clone()) + } + }; + + // Check depths if it's not an expect cheatcode call and if internal expect reverts not enabled. + if !is_cheatcode && !internal_expect_revert { + ensure!( + expected_revert.max_depth > expected_revert.depth, + "call didn't revert at a lower depth than cheatcode call depth" + ); + } + + if expected_revert.count == 0 { + if expected_revert.reverter.is_none() && expected_revert.reason.is_none() { + ensure!( + matches!(status, return_ok!()), + "call reverted when it was expected not to revert" + ); + return Ok(success_return()); + } + + // Flags to track if the reason and reverter match. + let mut reason_match = expected_revert.reason.as_ref().map(|_| false); + let mut reverter_match = expected_revert.reverter.as_ref().map(|_| false); + + // Reverter check + if let (Some(expected_reverter), Some(actual_reverter)) = + (expected_revert.reverter, expected_revert.reverted_by) + { + if expected_reverter == actual_reverter { + reverter_match = Some(true); + } + } + + // Reason check + let expected_reason = expected_revert.reason.as_deref(); + if let Some(expected_reason) = expected_reason { + let mut actual_revert: Vec = retdata.into(); + actual_revert = decode_revert(actual_revert); + + if actual_revert == expected_reason { + reason_match = Some(true); + } + }; + + match (reason_match, reverter_match) { + (Some(true), Some(true)) => Err(fmt_err!( + "expected 0 reverts with reason: {}, from address: {}, but got one", + &stringify(expected_reason.unwrap_or_default()), + expected_revert.reverter.unwrap() + )), + (Some(true), None) => Err(fmt_err!( + "expected 0 reverts with reason: {}, but got one", + &stringify(expected_reason.unwrap_or_default()) + )), + (None, Some(true)) => Err(fmt_err!( + "expected 0 reverts from address: {}, but got one", + expected_revert.reverter.unwrap() + )), + _ => Ok(success_return()), + } + } else { + ensure!(!matches!(status, return_ok!()), "next call did not revert as expected"); + + handle_revert( + is_cheatcode, + expected_revert, + status, + &retdata, + known_contracts, + expected_revert.reverted_by.as_ref(), + )?; + Ok(success_return()) + } +} + +fn decode_revert(revert: Vec) -> Vec { + if matches!( + revert.get(..4).map(|s| s.try_into().unwrap()), + Some(Vm::CheatcodeError::SELECTOR | alloy_sol_types::Revert::SELECTOR) + ) { + if let Ok(decoded) = Vec::::abi_decode(&revert[4..], false) { + return decoded; + } + } + revert +} diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs new file mode 100644 index 0000000000000..b55ef2d16eedf --- /dev/null +++ b/crates/cheatcodes/src/toml.rs @@ -0,0 +1,266 @@ +//! Implementations of [`Toml`](spec::Group::Toml) cheatcodes. + +use crate::{ + json::{ + canonicalize_json_path, check_json_key_exists, parse_json, parse_json_coerce, + parse_json_keys, resolve_type, + }, + Cheatcode, Cheatcodes, Result, + Vm::*, +}; +use alloy_dyn_abi::DynSolType; +use alloy_sol_types::SolValue; +use foundry_common::fs; +use foundry_config::fs_permissions::FsAccessKind; +use serde_json::Value as JsonValue; +use toml::Value as TomlValue; + +impl Cheatcode for keyExistsTomlCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + check_json_key_exists(&toml_to_json_string(toml)?, key) + } +} + +impl Cheatcode for parseToml_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml } = self; + parse_toml(toml, "$") + } +} + +impl Cheatcode for parseToml_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml(toml, key) + } +} + +impl Cheatcode for parseTomlUintCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Uint(256)) + } +} + +impl Cheatcode for parseTomlUintArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Uint(256)))) + } +} + +impl Cheatcode for parseTomlIntCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Int(256)) + } +} + +impl Cheatcode for parseTomlIntArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Int(256)))) + } +} + +impl Cheatcode for parseTomlBoolCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Bool) + } +} + +impl Cheatcode for parseTomlBoolArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bool))) + } +} + +impl Cheatcode for parseTomlAddressCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Address) + } +} + +impl Cheatcode for parseTomlAddressArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Address))) + } +} + +impl Cheatcode for parseTomlStringCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::String) + } +} + +impl Cheatcode for parseTomlStringArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::String))) + } +} + +impl Cheatcode for parseTomlBytesCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Bytes) + } +} + +impl Cheatcode for parseTomlBytesArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::Bytes))) + } +} + +impl Cheatcode for parseTomlBytes32Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::FixedBytes(32)) + } +} + +impl Cheatcode for parseTomlBytes32ArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(DynSolType::FixedBytes(32)))) + } +} + +impl Cheatcode for parseTomlType_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, typeDescription } = self; + parse_toml_coerce(toml, "$", &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseTomlType_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key, typeDescription } = self; + parse_toml_coerce(toml, key, &resolve_type(typeDescription)?).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseTomlTypeArrayCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key, typeDescription } = self; + let ty = resolve_type(typeDescription)?; + parse_toml_coerce(toml, key, &DynSolType::Array(Box::new(ty))).map(|v| v.abi_encode()) + } +} + +impl Cheatcode for parseTomlKeysCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml_keys(toml, key) + } +} + +impl Cheatcode for writeToml_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json, path } = self; + let value = + serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); + + let toml_string = format_json_to_toml(value)?; + super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) + } +} + +impl Cheatcode for writeToml_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { json, path, valueKey } = self; + let json = + serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); + + let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let toml_data = fs::read_to_string(data_path)?; + let json_data: JsonValue = + toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?; + let value = + jsonpath_lib::replace_with(json_data, &canonicalize_json_path(valueKey), &mut |_| { + Some(json.clone()) + })?; + + let toml_string = format_json_to_toml(value)?; + super::fs::write_file(state, path.as_ref(), toml_string.as_bytes()) + } +} + +/// Parse +fn parse_toml_str(toml: &str) -> Result { + toml::from_str(toml).map_err(|e| fmt_err!("failed parsing TOML: {e}")) +} + +/// Parse a TOML string and return the value at the given path. +fn parse_toml(toml: &str, key: &str) -> Result { + parse_json(&toml_to_json_string(toml)?, key) +} + +/// Parse a TOML string and return the value at the given path, coercing it to the given type. +fn parse_toml_coerce(toml: &str, key: &str, ty: &DynSolType) -> Result { + parse_json_coerce(&toml_to_json_string(toml)?, key, ty) +} + +/// Parse a TOML string and return an array of all keys at the given path. +fn parse_toml_keys(toml: &str, key: &str) -> Result { + parse_json_keys(&toml_to_json_string(toml)?, key) +} + +/// Convert a TOML string to a JSON string. +fn toml_to_json_string(toml: &str) -> Result { + let toml = parse_toml_str(toml)?; + let json = toml_to_json_value(toml); + serde_json::to_string(&json).map_err(|e| fmt_err!("failed to serialize JSON: {e}")) +} + +/// Format a JSON value to a TOML pretty string. +fn format_json_to_toml(json: JsonValue) -> Result { + let toml = json_to_toml_value(json); + toml::to_string_pretty(&toml).map_err(|e| fmt_err!("failed to serialize TOML: {e}")) +} + +/// Convert a TOML value to a JSON value. +fn toml_to_json_value(toml: TomlValue) -> JsonValue { + match toml { + TomlValue::String(s) => match s.as_str() { + "null" => JsonValue::Null, + _ => JsonValue::String(s), + }, + TomlValue::Integer(i) => JsonValue::Number(i.into()), + TomlValue::Float(f) => JsonValue::Number(serde_json::Number::from_f64(f).unwrap()), + TomlValue::Boolean(b) => JsonValue::Bool(b), + TomlValue::Array(a) => JsonValue::Array(a.into_iter().map(toml_to_json_value).collect()), + TomlValue::Table(t) => { + JsonValue::Object(t.into_iter().map(|(k, v)| (k, toml_to_json_value(v))).collect()) + } + TomlValue::Datetime(d) => JsonValue::String(d.to_string()), + } +} + +/// Convert a JSON value to a TOML value. +fn json_to_toml_value(json: JsonValue) -> TomlValue { + match json { + JsonValue::String(s) => TomlValue::String(s), + JsonValue::Number(n) => match n.as_i64() { + Some(i) => TomlValue::Integer(i), + None => match n.as_f64() { + Some(f) => TomlValue::Float(f), + None => TomlValue::String(n.to_string()), + }, + }, + JsonValue::Bool(b) => TomlValue::Boolean(b), + JsonValue::Array(a) => TomlValue::Array(a.into_iter().map(json_to_toml_value).collect()), + JsonValue::Object(o) => { + TomlValue::Table(o.into_iter().map(|(k, v)| (k, json_to_toml_value(v))).collect()) + } + JsonValue::Null => TomlValue::String("null".to_string()), + } +} diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs new file mode 100644 index 0000000000000..79299d9fd82b1 --- /dev/null +++ b/crates/cheatcodes/src/utils.rs @@ -0,0 +1,271 @@ +//! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes. + +use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::{aliases::B32, map::HashMap, B64, U256}; +use alloy_sol_types::SolValue; +use foundry_common::ens::namehash; +use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; +use proptest::prelude::Strategy; +use rand::{Rng, RngCore}; + +/// Contains locations of traces ignored via cheatcodes. +/// +/// The way we identify location in traces is by (node_idx, item_idx) tuple where node_idx is an +/// index of a call trace node, and item_idx is a value between 0 and `node.ordering.len()` where i +/// represents point after ith item, and 0 represents the beginning of the node trace. +#[derive(Debug, Default, Clone)] +pub struct IgnoredTraces { + /// Mapping from (start_node_idx, start_item_idx) to (end_node_idx, end_item_idx) representing + /// ranges of trace nodes to ignore. + pub ignored: HashMap<(usize, usize), (usize, usize)>, + /// Keeps track of (start_node_idx, start_item_idx) of the last `vm.pauseTracing` call. + pub last_pause_call: Option<(usize, usize)>, +} + +impl Cheatcode for labelCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { account, newLabel } = self; + state.labels.insert(*account, newLabel.clone()); + Ok(Default::default()) + } +} + +impl Cheatcode for getLabelCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { account } = self; + Ok(match state.labels.get(account) { + Some(label) => label.abi_encode(), + None => format!("unlabeled:{account}").abi_encode(), + }) + } +} + +impl Cheatcode for computeCreateAddressCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { nonce, deployer } = self; + ensure!(*nonce <= U256::from(u64::MAX), "nonce must be less than 2^64 - 1"); + Ok(deployer.create(nonce.to()).abi_encode()) + } +} + +impl Cheatcode for computeCreate2Address_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { salt, initCodeHash, deployer } = self; + Ok(deployer.create2(salt, initCodeHash).abi_encode()) + } +} + +impl Cheatcode for computeCreate2Address_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { salt, initCodeHash } = self; + Ok(DEFAULT_CREATE2_DEPLOYER.create2(salt, initCodeHash).abi_encode()) + } +} + +impl Cheatcode for ensNamehashCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + Ok(namehash(name).abi_encode()) + } +} + +impl Cheatcode for randomUint_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + random_uint(state, None, None) + } +} + +impl Cheatcode for randomUint_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { min, max } = *self; + random_uint(state, None, Some((min, max))) + } +} + +impl Cheatcode for randomUint_2Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { bits } = *self; + random_uint(state, Some(bits), None) + } +} + +impl Cheatcode for randomAddressCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + Ok(DynSolValue::type_strategy(&DynSolType::Address) + .new_tree(state.test_runner()) + .unwrap() + .current() + .abi_encode()) + } +} + +impl Cheatcode for randomInt_0Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + random_int(state, None) + } +} + +impl Cheatcode for randomInt_1Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { bits } = *self; + random_int(state, Some(bits)) + } +} + +impl Cheatcode for randomBoolCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let rand_bool: bool = state.rng().gen(); + Ok(rand_bool.abi_encode()) + } +} + +impl Cheatcode for randomBytesCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { len } = *self; + ensure!( + len <= U256::from(usize::MAX), + format!("bytes length cannot exceed {}", usize::MAX) + ); + let mut bytes = vec![0u8; len.to::()]; + state.rng().fill_bytes(&mut bytes); + Ok(bytes.abi_encode()) + } +} + +impl Cheatcode for randomBytes4Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let rand_u32 = state.rng().next_u32(); + Ok(B32::from(rand_u32).abi_encode()) + } +} + +impl Cheatcode for randomBytes8Call { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let rand_u64 = state.rng().next_u64(); + Ok(B64::from(rand_u64).abi_encode()) + } +} + +impl Cheatcode for pauseTracingCall { + fn apply_full( + &self, + ccx: &mut crate::CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else { + // No tracer -> nothing to pause + return Ok(Default::default()) + }; + + // If paused earlier, ignore the call + if ccx.state.ignored_traces.last_pause_call.is_some() { + return Ok(Default::default()) + } + + let cur_node = &tracer.traces().nodes().last().expect("no trace nodes"); + ccx.state.ignored_traces.last_pause_call = Some((cur_node.idx, cur_node.ordering.len())); + + Ok(Default::default()) + } +} + +impl Cheatcode for resumeTracingCall { + fn apply_full( + &self, + ccx: &mut crate::CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, + ) -> Result { + let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else { + // No tracer -> nothing to unpause + return Ok(Default::default()) + }; + + let Some(start) = ccx.state.ignored_traces.last_pause_call.take() else { + // Nothing to unpause + return Ok(Default::default()) + }; + + let node = &tracer.traces().nodes().last().expect("no trace nodes"); + ccx.state.ignored_traces.ignored.insert(start, (node.idx, node.ordering.len())); + + Ok(Default::default()) + } +} + +impl Cheatcode for setArbitraryStorageCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { target } = self; + ccx.state.arbitrary_storage().mark_arbitrary(target); + + Ok(Default::default()) + } +} + +impl Cheatcode for copyStorageCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { from, to } = self; + + ensure!( + !ccx.state.has_arbitrary_storage(to), + "target address cannot have arbitrary storage" + ); + + if let Ok(from_account) = ccx.load_account(*from) { + let from_storage = from_account.storage.clone(); + if let Ok(mut to_account) = ccx.load_account(*to) { + to_account.storage = from_storage; + if let Some(ref mut arbitrary_storage) = &mut ccx.state.arbitrary_storage { + arbitrary_storage.mark_copy(from, to); + } + } + } + + Ok(Default::default()) + } +} + +/// Helper to generate a random `uint` value (with given bits or bounded if specified) +/// from type strategy. +fn random_uint(state: &mut Cheatcodes, bits: Option, bounds: Option<(U256, U256)>) -> Result { + if let Some(bits) = bits { + // Generate random with specified bits. + ensure!(bits <= U256::from(256), "number of bits cannot exceed 256"); + return Ok(DynSolValue::type_strategy(&DynSolType::Uint(bits.to::())) + .new_tree(state.test_runner()) + .unwrap() + .current() + .abi_encode()) + } + + if let Some((min, max)) = bounds { + ensure!(min <= max, "min must be less than or equal to max"); + // Generate random between range min..=max + let exclusive_modulo = max - min; + let mut random_number: U256 = state.rng().gen(); + if exclusive_modulo != U256::MAX { + let inclusive_modulo = exclusive_modulo + U256::from(1); + random_number %= inclusive_modulo; + } + random_number += min; + return Ok(random_number.abi_encode()) + } + + // Generate random `uint256` value. + Ok(DynSolValue::type_strategy(&DynSolType::Uint(256)) + .new_tree(state.test_runner()) + .unwrap() + .current() + .abi_encode()) +} + +/// Helper to generate a random `int` value (with given bits if specified) from type strategy. +fn random_int(state: &mut Cheatcodes, bits: Option) -> Result { + let no_bits = bits.unwrap_or(U256::from(256)); + ensure!(no_bits <= U256::from(256), "number of bits cannot exceed 256"); + Ok(DynSolValue::type_strategy(&DynSolType::Int(no_bits.to::())) + .new_tree(state.test_runner()) + .unwrap() + .current() + .abi_encode()) +} diff --git a/crates/cheatcodes/src/version.rs b/crates/cheatcodes/src/version.rs new file mode 100644 index 0000000000000..d88fa09df0f76 --- /dev/null +++ b/crates/cheatcodes/src/version.rs @@ -0,0 +1,41 @@ +use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_sol_types::SolValue; +use foundry_common::version::SEMVER_VERSION; +use semver::Version; +use std::cmp::Ordering; + +impl Cheatcode for foundryVersionCmpCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { version } = self; + foundry_version_cmp(version).map(|cmp| (cmp as i8).abi_encode()) + } +} + +impl Cheatcode for foundryVersionAtLeastCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { version } = self; + foundry_version_cmp(version).map(|cmp| cmp.is_ge().abi_encode()) + } +} + +fn foundry_version_cmp(version: &str) -> Result { + version_cmp(SEMVER_VERSION.split('-').next().unwrap(), version) +} + +fn version_cmp(version_a: &str, version_b: &str) -> Result { + let version_a = parse_version(version_a)?; + let version_b = parse_version(version_b)?; + Ok(version_a.cmp(&version_b)) +} + +fn parse_version(version: &str) -> Result { + let version = + Version::parse(version).map_err(|e| fmt_err!("invalid version `{version}`: {e}"))?; + if !version.pre.is_empty() { + return Err(fmt_err!("invalid version `{version}`: pre-release versions are not supported")); + } + if !version.build.is_empty() { + return Err(fmt_err!("invalid version `{version}`: build metadata is not supported")); + } + Ok(version) +} diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 679063eb1b1c9..78acd8faaa62b 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -1,6 +1,9 @@ [package] name = "chisel" -authors = ["clabby ", "asnared "] +authors = [ + "clabby ", + "asnared ", +] description = "Fast, utilitarian, and verbose Solidity REPL" version.workspace = true @@ -10,55 +13,58 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "chisel" path = "bin/main.rs" -[build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } - [dependencies] # forge -foundry-evm.workspace = true -foundry-config.workspace = true +forge-fmt.workspace = true foundry-cli.workspace = true foundry-common.workspace = true -forge-fmt.workspace = true - -# ethers -ethers.workspace = true -ethers-solc = { workspace = true, features = ["project-util", "full"] } +foundry-compilers = { workspace = true, features = ["project-util", "full"] } +foundry-config.workspace = true +foundry-evm.workspace = true -# async -tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.11", default-features = false } +alloy-dyn-abi = { workspace = true, features = ["arbitrary"] } +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-json-abi.workspace = true -# misc clap = { version = "4", features = ["derive", "env", "wrap_help"] } -rustyline = "11" +dirs.workspace = true +eyre.workspace = true +regex.workspace = true +reqwest.workspace = true +revm.workspace = true +rustyline = "15" +semver.workspace = true +serde_json.workspace = true +serde.workspace = true solang-parser.workspace = true -yansi = "0.5" -strum = { version = "0.25", features = ["derive"] } -serde = "1" -serde_json = { version = "1", features = ["raw_value"] } -semver = "1" -bytes = "1" -revm = "3" -eyre = "0.6" -dirs = "5" +solar-parse.workspace = true +strum = { workspace = true, features = ["derive"] } time = { version = "0.3", features = ["formatting"] } -regex = "1" +tokio = { workspace = true, features = ["full"] } +yansi.workspace = true +tracing.workspace = true +walkdir.workspace = true + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] -criterion = { version = "0.5", features = ["async_tokio"] } -serial_test = "2" -once_cell = "1" +serial_test = "3" +tracing-subscriber.workspace = true [features] -default = ["rustls"] -rustls = ["ethers/rustls", "reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] -openssl = ["ethers/openssl", "reqwest/default-tls"] - -[[bench]] -name = "session_source" -harness = false +default = ["jemalloc"] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] diff --git a/crates/chisel/README.md b/crates/chisel/README.md deleted file mode 100644 index 8ea2840aa36a8..0000000000000 --- a/crates/chisel/README.md +++ /dev/null @@ -1,216 +0,0 @@ -# `chisel` - -Chisel is a fast, utilitarian, and verbose Solidity REPL. It is heavily inspired by the incredible work done in [soli](https://github.com/jpopesculian/soli) and [solidity-shell](https://github.com/tintinweb/solidity-shell)! - -![preview](./assets/preview.gif) - -## Why? - -Ever wanted to quickly test a small feature in solidity? - -Perhaps to test how custom errors work, or how to write inline assembly? - -Chisel is a fully-functional Solidity REPL, allowing you to write, execute, and debug Solidity directly in the command line. - -Once you finish testing, Chisel even lets you export your code to a new solidity file! - -In this sense, Chisel even serves as a Foundry script generator. - -## Feature Completion - -[soli](https://github.com/jpopesculian/soli) and [solidity-shell](https://github.com/tintinweb/solidity-shell) both provide a great solidity REPL, achieving: - -- Statement support -- Custom events, errors, functions, imports -- Inspecting variables -- Forking remote chains -- Session caching - -Chisel aims to improve upon existing Solidity REPLs by integrating with foundry as well as offering additional functionality: - -- More verbose variable / state inspection -- Improved error messages -- Foundry-style call traces -- In-depth environment configuration -- ... and many more future features! - -### Migrating from [soli](https://github.com/jpopesculian/soli) or [solidity-shell](https://github.com/tintinweb/solidity-shell) - -Migration from existing Solidity REPLs such as [soli](https://github.com/jpopesculian/soli) or [solidity-shell](https://github.com/tintinweb/solidity-shell) is as -simple as installing Chisel via `foundryup`. For information on features, usage, and configuration, see the [Usage](#usage) section as well as the chisel manpage (`man chisel` or `chisel --help`). - -## Installation - -To install `chisel`, simply run `foundryup`! - -If you do not have `foundryup` installed, reference the Foundry [installation guide](../../README.md#installation). - -## Usage - -### REPL Commands - -```text -⚒️ Chisel help -============= -General - !help | !h - Display all commands - !quit | !q - Quit Chisel - !exec [args] | !e [args] - Execute a shell command and print the output - -Session - !clear | !c - Clear current session source - !source | !so - Display the source code of the current session - !save [id] | !s [id] - Save the current session to cache - !load | !l - Load a previous session ID from cache - !list | !ls - List all cached sessions - !clearcache | !cc - Clear the chisel cache of all stored sessions - !export | !ex - Export the current session source to a script file - !fetch | !fe - Fetch the interface of a verified contract on Etherscan - !edit - Open the current session in an editor - -Environment - !fork | !f - Fork an RPC for the current session. Supply 0 arguments to return to a local network - !traces | !t - Enable / disable traces for the current session - !calldata [data] | !cd [data] - Set calldata (`msg.data`) for the current session (appended after function selector). Clears it if no argument provided. - -Debug - !memdump | !md - Dump the raw memory of the current state - !stackdump | !sd - Dump the raw stack of the current state - !rawstack | !rs - Display the raw value of a variable's stack allocation. For variables that are > 32 bytes in length, this will display their memory pointer. -``` - -### Cache Session - -While chisel sessions are not persistent by default, they can be saved to the cache via the builtin `save` command from within the REPL. - -Sessions can also be named by supplying a single argument to the `save` command, i.e. `!save my_session`. - -```text -$ chisel -➜ uint a = 1; -➜ uint b = a << 0x08; -➜ !save -Saved session to cache with ID = 0. -``` - -### Loading a Previous Session - -Chisel allows you to load a previous session from your history. - -To view your history, you can run `chisel list` or `!list`. This will print a list of your previous sessions, identifiable by their index. - -You can also run `chisel view ` or `!view ` to view the contents of a specific session. - -To load a session, run `chisel load ` or use the `!load ` where `` is a valid session index (eg 2 in the example below). - -```text -$ chisel list -⚒️ Chisel Sessions -"2022-10-27 14:46:29" - chisel-0.json -"2022-10-27 14:46:29" - chisel-1.json -$ chisel view 1 -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.17; - -contract REPL { - event KeccakEvent(bytes32 hash); - - function run() public { - emit KeccakEvent(keccak256(abi.encode("Hello, world!"))); - } -} -$ chisel load 1 -➜ ... -``` - -### Clearing the Cache - -To clear Chisel's cache (stored in `~/.foundry/cache/chisel`), use the `chisel clear-cache` or `!clearcache` command. - -```text -➜ !clearcache -Cleared chisel cache! -``` - -### Toggling Traces - -By default, traces will only be shown if an input causes the call to the REPL contract to revert. To turn traces on -regardless of the call result, use the `!traces` command or pass in a verbosity option of any level (`-v`) to -the chisel binary. - -```text -➜ uint a -➜ contract Test { - function get() external view returns (uint) { - return 256; - } -} -➜ Test t = new Test() -➜ !traces -Successfully enabled traces! -➜ a = t.get() -Traces: - [69808] 0xBd770416a3345F91E4B34576cb804a576fa48EB1::run() - ├─ [36687] → new @0xf4D9599aFd90B5038b18e3B551Bc21a97ed21c37 - │ └─ ← 183 bytes of code - ├─ [315] 0xf4D9599aFd90B5038b18e3B551Bc21a97ed21c37::get() [staticcall] - │ └─ ← 0x0000000000000000000000000000000000000000000000000000000000000100 - └─ ← () - -➜ a -Type: uint -├ Hex: 0x100 -└ Decimal: 256 -``` - -### Forking a Network - -To fork a network within your chisel session, use the `!fork ` command or supply a `--fork-url ` flag -to the chisel binary. The `!fork` command also accepts aliases from the `[rpc_endpoints]` section of your `foundry.toml` -if chisel was launched in the root of a foundry project (ex. `!fork mainnet`), as well as interpolated environment variables -(ex. `!fork https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}`). - -### Fetching an Interface of a Verified Contract - -To fetch an interface of a verified contract on Etherscan, use the `!fetch` / `!f` command. - -> **Note** -> At the moment, only contracts that are deployed and verified on mainnet can be fetched. Support for other -> networks with Etherscan explorers coming soon. - -```text -➜ !fetch 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 IWETH -Added 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2's interface to source as `IWETH` -``` - -### Executing a Shell Command - -Shell commands can be executed within Chisel with the `!exec` / `!e` command. - -```text -➜ !e ls -anvil -binder -Cargo.lock -Cargo.toml -cast -chisel -cli -common -config -CONTRIBUTING.md -Dockerfile -docs -evm -fmt -forge -foundryup -LICENSE-APACHE -LICENSE-MIT -README.md -rustfmt.toml -target -testdata -ui -utils -``` diff --git a/crates/chisel/benches/session_source.rs b/crates/chisel/benches/session_source.rs deleted file mode 100644 index 696c57c9745a0..0000000000000 --- a/crates/chisel/benches/session_source.rs +++ /dev/null @@ -1,94 +0,0 @@ -use chisel::session_source::{SessionSource, SessionSourceConfig}; -use criterion::{criterion_group, Criterion}; -use ethers_solc::Solc; -use foundry_config::Config; -use foundry_evm::executor::opts::EvmOpts; -use once_cell::sync::Lazy; -use std::hint::black_box; -use tokio::runtime::Runtime; - -static SOLC: Lazy = Lazy::new(|| Solc::find_or_install_svm_version("0.8.19").unwrap()); - -/// Benchmark for the `clone_with_new_line` function in [SessionSource] -fn clone_with_new_line(c: &mut Criterion) { - let mut g = c.benchmark_group("session_source"); - - // Grab an empty session source - g.bench_function("clone_with_new_line", |b| { - b.iter(|| { - let session_source = get_empty_session_source(); - let new_line = String::from("uint a = 1"); - black_box(session_source.clone_with_new_line(new_line).unwrap()); - }) - }); -} - -/// Benchmark for the `build` function in [SessionSource] -fn build(c: &mut Criterion) { - let mut g = c.benchmark_group("session_source"); - - g.bench_function("build", |b| { - b.iter(|| { - // Grab an empty session source - let mut session_source = get_empty_session_source(); - black_box(session_source.build().unwrap()) - }) - }); -} - -/// Benchmark for the `execute` function in [SessionSource] -fn execute(c: &mut Criterion) { - let mut g = c.benchmark_group("session_source"); - - g.bench_function("execute", |b| { - b.to_async(rt()).iter(|| async { - // Grab an empty session source - let mut session_source = get_empty_session_source(); - black_box(session_source.execute().await.unwrap()) - }) - }); -} - -/// Benchmark for the `inspect` function in [SessionSource] -fn inspect(c: &mut Criterion) { - let mut g = c.benchmark_group("session_source"); - - g.bench_function("inspect", |b| { - b.to_async(rt()).iter(|| async { - // Grab an empty session source - let mut session_source = get_empty_session_source(); - // Add a uint named "a" with value 1 to the session source - session_source.with_run_code("uint a = 1"); - black_box(session_source.inspect("a").await.unwrap()) - }) - }); -} - -/// Helper function for getting an empty [SessionSource] with default configuration -fn get_empty_session_source() -> SessionSource { - SessionSource::new( - SOLC.clone(), - SessionSourceConfig { - foundry_config: Config::default(), - evm_opts: EvmOpts::default(), - backend: None, - traces: false, - calldata: None, - }, - ) -} - -fn rt() -> Runtime { - Runtime::new().unwrap() -} - -fn main() { - // Install before benches if not present - let _ = Lazy::force(&SOLC); - - session_source_benches(); - - Criterion::default().configure_from_args().final_summary() -} - -criterion_group!(session_source_benches, clone_with_new_line, build, execute, inspect); diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 55de1f0649edb..3af1470ffc8d1 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -7,12 +7,18 @@ use chisel::{ history::chisel_history_file, prelude::{ChiselCommand, ChiselDispatcher, DispatchResult, SolidityHelper}, }; -use clap::Parser; +use clap::{Parser, Subcommand}; +use eyre::Context; use foundry_cli::{ - opts::CoreBuildArgs, + handler, + opts::{BuildOpts, GlobalArgs}, utils::{self, LoadConfig}, }; -use foundry_common::evm::EvmArgs; +use foundry_common::{ + evm::EvmArgs, + fs, + version::{LONG_VERSION, SHORT_VERSION}, +}; use foundry_config::{ figment::{ value::{Dict, Map}, @@ -21,37 +27,56 @@ use foundry_config::{ Config, }; use rustyline::{config::Configurer, error::ReadlineError, Editor}; +use std::path::PathBuf; +use tracing::debug; use yansi::Paint; -// Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(ChiselParser, opts, evm_opts); +#[macro_use] +extern crate foundry_common; -const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +// Loads project's figment and merges the build cli arguments into it +foundry_config::merge_impl_figment_convert!(Chisel, build, evm); /// Fast, utilitarian, and verbose Solidity REPL. #[derive(Debug, Parser)] -#[clap(name = "chisel", version = VERSION_MESSAGE)] -pub struct ChiselParser { +#[command(name = "chisel", version = SHORT_VERSION, long_version = LONG_VERSION)] +pub struct Chisel { + /// Include the global arguments. + #[command(flatten)] + pub global: GlobalArgs, + #[command(subcommand)] - pub sub: Option, + pub cmd: Option, - #[clap(flatten)] - pub opts: CoreBuildArgs, + /// Path to a directory containing Solidity files to import, or path to a single Solidity file. + /// + /// These files will be evaluated before the top-level of the + /// REPL, therefore functioning as a prelude + #[arg(long, help_heading = "REPL options")] + pub prelude: Option, - #[clap(flatten)] - pub evm_opts: EvmArgs, + /// Disable the default `Vm` import. + #[arg(long, help_heading = "REPL options", long_help = format!( + "Disable the default `Vm` import.\n\n\ + The import is disabled by default if the Solc version is less than {}.", + chisel::session_source::MIN_VM_VERSION + ))] + pub no_vm: bool, + + #[command(flatten)] + pub build: BuildOpts, + + #[command(flatten)] + pub evm: EvmArgs, } /// Chisel binary subcommands -#[derive(clap::Subcommand, Debug)] -pub enum ChiselParserSub { +#[derive(Debug, Subcommand)] +pub enum ChiselSubcommand { /// List all cached sessions List, @@ -69,20 +94,33 @@ pub enum ChiselParserSub { /// Clear all cached chisel sessions from the cache directory ClearCache, + + /// Simple evaluation of a command without entering the REPL + Eval { + /// The command to be evaluated. + command: String, + }, } -#[tokio::main] -async fn main() -> eyre::Result<()> { - #[cfg(windows)] - if !Paint::enable_windows_ascii() { - Paint::disable() +fn main() { + if let Err(err) = run() { + let _ = foundry_common::sh_err!("{err:?}"); + std::process::exit(1); } +} +fn run() -> eyre::Result<()> { + handler::install(); + utils::subscriber(); utils::load_dotenv(); - // Parse command args - let args = ChiselParser::parse(); + let args = Chisel::parse(); + args.global.init()?; + main_args(args) +} +#[tokio::main] +async fn main_args(args: Chisel) -> eyre::Result<()> { // Keeps track of whether or not an interrupt was the last input let mut interrupt = false; @@ -94,54 +132,62 @@ async fn main() -> eyre::Result<()> { // Enable traces if any level of verbosity was passed traces: config.verbosity > 0, foundry_config: config, + no_vm: args.no_vm, evm_opts, backend: None, calldata: None, })?; + // Execute prelude Solidity source files + evaluate_prelude(&mut dispatcher, args.prelude).await?; + // Check for chisel subcommands - match &args.sub { - Some(ChiselParserSub::List) => { + match &args.cmd { + Some(ChiselSubcommand::List) => { let sessions = dispatcher.dispatch_command(ChiselCommand::ListSessions, &[]).await; match sessions { DispatchResult::CommandSuccess(Some(session_list)) => { - println!("{session_list}"); + sh_println!("{session_list}")?; } - DispatchResult::CommandFailed(e) => eprintln!("{e}"), + DispatchResult::CommandFailed(e) => sh_err!("{e}")?, _ => panic!("Unexpected result: Please report this bug."), } return Ok(()) } - Some(ChiselParserSub::Load { id }) | Some(ChiselParserSub::View { id }) => { + Some(ChiselSubcommand::Load { id }) | Some(ChiselSubcommand::View { id }) => { // For both of these subcommands, we need to attempt to load the session from cache match dispatcher.dispatch_command(ChiselCommand::Load, &[id]).await { DispatchResult::CommandSuccess(_) => { /* Continue */ } DispatchResult::CommandFailed(e) => { - eprintln!("{e}"); + sh_err!("{e}")?; return Ok(()) } _ => panic!("Unexpected result! Please report this bug."), } // If the subcommand was `view`, print the source and exit. - if matches!(args.sub, Some(ChiselParserSub::View { .. })) { + if matches!(args.cmd, Some(ChiselSubcommand::View { .. })) { match dispatcher.dispatch_command(ChiselCommand::Source, &[]).await { DispatchResult::CommandSuccess(Some(source)) => { - println!("{source}"); + sh_println!("{source}")?; } _ => panic!("Unexpected result! Please report this bug."), } return Ok(()) } } - Some(ChiselParserSub::ClearCache) => { + Some(ChiselSubcommand::ClearCache) => { match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await { - DispatchResult::CommandSuccess(Some(msg)) => println!("{}", Paint::green(msg)), - DispatchResult::CommandFailed(e) => eprintln!("{e}"), + DispatchResult::CommandSuccess(Some(msg)) => sh_println!("{}", msg.green())?, + DispatchResult::CommandFailed(e) => sh_err!("{e}")?, _ => panic!("Unexpected result! Please report this bug."), } return Ok(()) } + Some(ChiselSubcommand::Eval { command }) => { + dispatch_repl_line(&mut dispatcher, command).await?; + return Ok(()) + } None => { /* No chisel subcommand present; Continue */ } } @@ -158,14 +204,13 @@ async fn main() -> eyre::Result<()> { } // Print welcome header - println!("Welcome to Chisel! Type `{}` to show available commands.", Paint::green("!help")); + sh_println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green())?; // Begin Rustyline loop loop { // Get the prompt from the dispatcher // Variable based on status of the last entry let prompt = dispatcher.get_prompt(); - rl.helper_mut().unwrap().set_errored(dispatcher.errored); // Read the next line let next_string = rl.readline(prompt.as_ref()); @@ -173,35 +218,25 @@ async fn main() -> eyre::Result<()> { // Try to read the string match next_string { Ok(line) => { + debug!("dispatching next line: {line}"); // Clear interrupt flag interrupt = false; // Dispatch and match results - match dispatcher.dispatch(&line).await { - DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => if let Some(msg) = msg { - println!("{}", Paint::green(msg)); - }, - DispatchResult::UnrecognizedCommand(e) => eprintln!("{e}"), - DispatchResult::SolangParserFailed(e) => { - eprintln!("{}", Paint::red("Compilation error")); - eprintln!("{}", Paint::red(format!("{e:?}"))); - } - DispatchResult::FileIoError(e) => eprintln!("{}", Paint::red(format!("⚒️ Chisel File IO Error - {e}"))), - DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => eprintln!("{}", Paint::red(msg)), - DispatchResult::Failure(None) => eprintln!("{}\nPlease Report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose", Paint::red("⚒️ Unknown Chisel Error ⚒️")), - } + let errored = dispatch_repl_line(&mut dispatcher, &line).await?; + rl.helper_mut().unwrap().set_errored(errored); } Err(ReadlineError::Interrupted) => { if interrupt { break } else { - println!("(To exit, press Ctrl+C again)"); + sh_println!("(To exit, press Ctrl+C again)")?; interrupt = true; } } Err(ReadlineError::Eof) => break, Err(err) => { - println!("Error: {err:?}"); + sh_err!("{err:?}")?; break } } @@ -215,7 +250,7 @@ async fn main() -> eyre::Result<()> { } /// [Provider] impl -impl Provider for ChiselParser { +impl Provider for Chisel { fn metadata(&self) -> Metadata { Metadata::named("Script Args Provider") } @@ -224,3 +259,71 @@ impl Provider for ChiselParser { Ok(Map::from([(Config::selected_profile(), Dict::default())])) } } + +/// Evaluate a single Solidity line. +async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> eyre::Result { + let r = dispatcher.dispatch(line).await; + match &r { + DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => { + debug!(%line, ?msg, "dispatch success"); + if let Some(msg) = msg { + sh_println!("{}", msg.green())?; + } + }, + DispatchResult::UnrecognizedCommand(e) => sh_err!("{e}")?, + DispatchResult::SolangParserFailed(e) => { + sh_err!("{}", "Compilation error".red())?; + sh_eprintln!("{}", format!("{e:?}").red())?; + } + DispatchResult::FileIoError(e) => sh_err!("{}", format!("File IO - {e}").red())?, + DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => sh_err!("{}", msg.red())?, + DispatchResult::Failure(None) => sh_err!("Please report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose")?, + } + Ok(r.is_error()) +} + +/// Evaluate multiple Solidity source files contained within a +/// Chisel prelude directory. +async fn evaluate_prelude( + dispatcher: &mut ChiselDispatcher, + maybe_prelude: Option, +) -> eyre::Result<()> { + let Some(prelude_dir) = maybe_prelude else { return Ok(()) }; + if prelude_dir.is_file() { + sh_println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display())?; + load_prelude_file(dispatcher, prelude_dir).await?; + sh_println!("{}\n", "Prelude source file loaded successfully!".green())?; + } else { + let prelude_sources = fs::files_with_ext(&prelude_dir, "sol"); + let mut print_success_msg = false; + for source_file in prelude_sources { + print_success_msg = true; + sh_println!("{} {}", "Loading prelude source file:".yellow(), source_file.display())?; + load_prelude_file(dispatcher, source_file).await?; + } + + if print_success_msg { + sh_println!("{}\n", "All prelude source files loaded successfully!".green())?; + } + } + Ok(()) +} + +/// Loads a single Solidity file into the prelude. +async fn load_prelude_file(dispatcher: &mut ChiselDispatcher, file: PathBuf) -> eyre::Result<()> { + let prelude = fs::read_to_string(file) + .wrap_err("Could not load source file. Are you sure this path is correct?")?; + dispatch_repl_line(dispatcher, &prelude).await?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Chisel::command().debug_assert(); + } +} diff --git a/crates/chisel/build.rs b/crates/chisel/build.rs deleted file mode 100644 index c2f550fb6f829..0000000000000 --- a/crates/chisel/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/chisel/src/cmd.rs b/crates/chisel/src/cmd.rs index 99e4086b3311a..c13272bc90e2b 100644 --- a/crates/chisel/src/cmd.rs +++ b/crates/chisel/src/cmd.rs @@ -19,10 +19,10 @@ pub enum ChiselCommand { /// Print the generated source contract Source, /// Save the current session to the cache - /// Takes: [session-id] + /// Takes: `` Save, /// Load a previous session from cache - /// Takes: + /// Takes: `` /// /// WARNING: This will overwrite the current session (though the current session will be /// optimistically cached) @@ -45,7 +45,7 @@ pub enum ChiselCommand { /// Export the current REPL session source to a Script file Export, /// Fetch an interface of a verified contract on Etherscan - /// Takes: + /// Takes: ` ` Fetch, /// Executes a shell command Exec, @@ -61,24 +61,24 @@ impl FromStr for ChiselCommand { fn from_str(s: &str) -> Result { match s.to_lowercase().as_ref() { - "help" | "h" => Ok(ChiselCommand::Help), - "quit" | "q" => Ok(ChiselCommand::Quit), - "clear" | "c" => Ok(ChiselCommand::Clear), - "source" | "so" => Ok(ChiselCommand::Source), - "save" | "s" => Ok(ChiselCommand::Save), - "list" | "ls" => Ok(ChiselCommand::ListSessions), - "load" | "l" => Ok(ChiselCommand::Load), - "clearcache" | "cc" => Ok(ChiselCommand::ClearCache), - "fork" | "f" => Ok(ChiselCommand::Fork), - "traces" | "t" => Ok(ChiselCommand::Traces), - "calldata" | "cd" => Ok(ChiselCommand::Calldata), - "memdump" | "md" => Ok(ChiselCommand::MemDump), - "stackdump" | "sd" => Ok(ChiselCommand::StackDump), - "export" | "ex" => Ok(ChiselCommand::Export), - "fetch" | "fe" => Ok(ChiselCommand::Fetch), - "exec" | "e" => Ok(ChiselCommand::Exec), - "rawstack" | "rs" => Ok(ChiselCommand::RawStack), - "edit" => Ok(ChiselCommand::Edit), + "help" | "h" => Ok(Self::Help), + "quit" | "q" => Ok(Self::Quit), + "clear" | "c" => Ok(Self::Clear), + "source" | "so" => Ok(Self::Source), + "save" | "s" => Ok(Self::Save), + "list" | "ls" => Ok(Self::ListSessions), + "load" | "l" => Ok(Self::Load), + "clearcache" | "cc" => Ok(Self::ClearCache), + "fork" | "f" => Ok(Self::Fork), + "traces" | "t" => Ok(Self::Traces), + "calldata" | "cd" => Ok(Self::Calldata), + "memdump" | "md" => Ok(Self::MemDump), + "stackdump" | "sd" => Ok(Self::StackDump), + "export" | "ex" => Ok(Self::Export), + "fetch" | "fe" => Ok(Self::Fetch), + "exec" | "e" => Ok(Self::Exec), + "rawstack" | "rs" => Ok(Self::RawStack), + "edit" => Ok(Self::Edit), _ => Err(ChiselDispatcher::make_error(format!( "Unknown command \"{s}\"! See available commands with `!help`.", )) @@ -103,10 +103,10 @@ pub enum CmdCategory { impl core::fmt::Display for CmdCategory { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let string = match self { - CmdCategory::General => "General", - CmdCategory::Session => "Session", - CmdCategory::Env => "Environment", - CmdCategory::Debug => "Debug", + Self::General => "General", + Self::Session => "Session", + Self::Env => "Environment", + Self::Debug => "Debug", }; f.write_str(string) } diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 1115514749b90..a445e99e744f7 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -3,49 +3,64 @@ //! This module contains the `ChiselDispatcher` struct, which handles the dispatching //! of both builtin commands and Solidity snippets. -use crate::prelude::{ - ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, SessionSourceConfig, - SolidityHelper, +use crate::{ + prelude::{ + ChiselCommand, ChiselResult, ChiselSession, CmdCategory, CmdDescriptor, + SessionSourceConfig, SolidityHelper, + }, + session_source::SessionSource, }; -use ethers::{abi::ParamType, contract::Lazy, types::Address, utils::hex}; +use alloy_json_abi::{InternalType, JsonAbi}; +use alloy_primitives::{hex, Address}; use forge_fmt::FormatterConfig; -use foundry_config::{Config, RpcEndpoint}; +use foundry_config::{Config, RpcEndpointUrl}; use foundry_evm::{ decode::decode_console_logs, - trace::{ - identifier::{EtherscanIdentifier, SignaturesIdentifier}, - CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + traces::{ + decode_trace_arena, + identifier::{SignaturesIdentifier, TraceIdentifiers}, + render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, }, }; use regex::Regex; use reqwest::Url; use serde::{Deserialize, Serialize}; use solang_parser::diagnostics::Diagnostic; -use std::{borrow::Cow, error::Error, io::Write, path::PathBuf, process::Command}; +use std::{ + borrow::Cow, + error::Error, + io::Write, + path::{Path, PathBuf}, + process::Command, + sync::LazyLock, +}; use strum::IntoEnumIterator; +use tracing::debug; use yansi::Paint; -/// Prompt arrow character -pub static PROMPT_ARROW: char = '➜'; -static DEFAULT_PROMPT: &str = "➜ "; +/// Prompt arrow character. +pub const PROMPT_ARROW: char = '➜'; +/// Prompt arrow string. +pub const PROMPT_ARROW_STR: &str = "➜"; +const DEFAULT_PROMPT: &str = "➜ "; /// Command leader character -pub static COMMAND_LEADER: char = '!'; +pub const COMMAND_LEADER: char = '!'; /// Chisel character -pub static CHISEL_CHAR: &str = "⚒️"; +pub const CHISEL_CHAR: &str = "⚒️"; /// Matches Solidity comments -static COMMENT_RE: Lazy = - Lazy::new(|| Regex::new(r"^\s*(?://.*\s*$)|(/*[\s\S]*?\*/\s*$)").unwrap()); +static COMMENT_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^\s*(?://.*\s*$)|(/*[\s\S]*?\*/\s*$)").unwrap()); -/// Matches Ethereum addresses -static ADDRESS_RE: Lazy = Lazy::new(|| Regex::new(r"0x[a-fA-F0-9]{40}").unwrap()); +/// Matches Ethereum addresses that are not strings +static ADDRESS_RE: LazyLock = LazyLock::new(|| { + Regex::new(r#"(?m)(([^"']\s*)|^)(?P
0x[a-fA-F0-9]{40})((\s*[^"'\w])|$)"#).unwrap() +}); /// Chisel input dispatcher #[derive(Debug)] pub struct ChiselDispatcher { - /// The status of the previous dispatch - pub errored: bool, /// A Chisel Session pub session: ChiselSession, } @@ -69,6 +84,20 @@ pub enum DispatchResult { FileIoError(Box), } +impl DispatchResult { + /// Returns `true` if the result is an error. + pub fn is_error(&self) -> bool { + matches!( + self, + Self::Failure(_) | + Self::CommandFailed(_) | + Self::UnrecognizedCommand(_) | + Self::SolangParserFailed(_) | + Self::FileIoError(_) + ) + } +} + /// A response from the Etherscan API's `getabi` action #[derive(Debug, Serialize, Deserialize)] pub struct EtherscanABIResponse { @@ -87,17 +116,7 @@ pub struct EtherscanABIResponse { macro_rules! format_param { ($param:expr) => {{ let param = $param; - format!( - "{}{}", - param.kind, - if param.kind.is_dynamic() || - matches!(param.kind, ParamType::FixedArray(_, _) | ParamType::Tuple(_)) - { - " memory" - } else { - "" - } - ) + format!("{}{}", param.ty, if param.is_complex_type() { " memory" } else { "" }) }}; } @@ -107,7 +126,7 @@ pub fn format_source(source: &str, config: FormatterConfig) -> eyre::Result { let mut formatted_source = String::default(); - if forge_fmt::format(&mut formatted_source, parsed, config).is_err() { + if forge_fmt::format_to(&mut formatted_source, parsed, config).is_err() { eyre::bail!("Could not format source!"); } @@ -120,7 +139,29 @@ pub fn format_source(source: &str, config: FormatterConfig) -> eyre::Result eyre::Result { - ChiselSession::new(config).map(|session| Self { errored: false, session }) + ChiselSession::new(config).map(|session| Self { session }) + } + + /// Returns the optional ID of the current session. + pub fn id(&self) -> Option<&str> { + self.session.id.as_deref() + } + + /// Returns the [`SessionSource`]. + pub fn source(&self) -> &SessionSource { + &self.session.session_source + } + + /// Returns the [`SessionSource`]. + pub fn source_mut(&mut self) -> &mut SessionSource { + &mut self.session.session_source + } + + fn format_source(&self) -> eyre::Result { + format_source( + &self.source().to_repl_source(), + self.source().config.foundry_config.fmt.clone(), + ) } /// Returns the prompt based on the current status of the Dispatcher @@ -157,7 +198,7 @@ impl ChiselDispatcher { ChiselCommand::iter().map(CmdDescriptor::from).collect::>(); DispatchResult::CommandSuccess(Some(format!( "{}\n{}", - Paint::cyan(format!("{CHISEL_CHAR} Chisel help\n=============")), + format!("{CHISEL_CHAR} Chisel help\n=============").cyan(), CmdCategory::iter() .map(|cat| { // Get commands in the current category @@ -171,13 +212,13 @@ impl ChiselDispatcher { // Format the help menu for the current category format!( "{}\n{}\n", - Paint::magenta(cat), + cat.magenta(), cat_cmds .iter() .map(|(cmds, desc, _)| format!( "\t{} - {}", cmds.iter() - .map(|cmd| format!("!{}", Paint::green(cmd))) + .map(|cmd| format!("!{}", cmd.green())) .collect::>() .join(" | "), desc @@ -195,18 +236,10 @@ impl ChiselDispatcher { std::process::exit(0); } ChiselCommand::Clear => { - if let Some(session_source) = self.session.session_source.as_mut() { - // Drain all source sections - session_source.drain_run(); - session_source.drain_global_code(); - session_source.drain_top_level_code(); - - DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) - } else { - DispatchResult::CommandFailed( - Paint::red("Session source not present!").to_string(), - ) - } + self.source_mut().drain_run(); + self.source_mut().drain_global_code(); + self.source_mut().drain_top_level_code(); + DispatchResult::CommandSuccess(Some(String::from("Cleared session!"))) } ChiselCommand::Save => { if args.len() <= 1 { @@ -239,15 +272,14 @@ impl ChiselDispatcher { // Use args as the name let name = args[0]; // Try to save the current session before loading another - if let Some(session_source) = &self.session.session_source { - // Don't save an empty session - if !session_source.run_code.is_empty() { - if let Err(e) = self.session.write() { - return DispatchResult::FileIoError(e.into()) - } - println!("{}", Paint::green("Saved current session!")); + // Don't save an empty session + if !self.source().run_code.is_empty() { + if let Err(e) = self.session.write() { + return DispatchResult::FileIoError(e.into()) } + let _ = sh_println!("{}", "Saved current session!".green()); } + // Parse the arguments let new_session = match name { "latest" => ChiselSession::latest(), @@ -261,7 +293,7 @@ impl ChiselDispatcher { // SAFETY // Should never panic due to the checks performed when the session was created // in the first place. - new_session.session_source.as_mut().unwrap().build().unwrap(); + new_session.session_source.build().unwrap(); self.session = new_session; DispatchResult::CommandSuccess(Some(format!( @@ -275,11 +307,11 @@ impl ChiselDispatcher { ChiselCommand::ListSessions => match ChiselSession::list_sessions() { Ok(sessions) => DispatchResult::CommandSuccess(Some(format!( "{}\n{}", - Paint::cyan(format!("{CHISEL_CHAR} Chisel Sessions")), + format!("{CHISEL_CHAR} Chisel Sessions").cyan(), sessions .iter() .map(|(time, name)| { - format!("{} - {}", Paint::blue(format!("{time:?}")), name) + format!("{} - {}", format!("{time:?}").blue(), name) }) .collect::>() .join("\n") @@ -288,23 +320,14 @@ impl ChiselDispatcher { "No sessions found. Use the `!save` command to save a session.", )), }, - ChiselCommand::Source => { - if let Some(session_source) = self.session.session_source.as_ref() { - match format_source( - &session_source.to_repl_source(), - session_source.config.foundry_config.fmt.clone(), - ) { - Ok(formatted_source) => DispatchResult::CommandSuccess(Some( - SolidityHelper::highlight(&formatted_source).into_owned(), - )), - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), - } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + ChiselCommand::Source => match self.format_source() { + Ok(formatted_source) => DispatchResult::CommandSuccess(Some( + SolidityHelper::new().highlight(&formatted_source).into_owned(), + )), + Err(_) => { + DispatchResult::CommandFailed(String::from("Failed to format session source")) } - } + }, ChiselCommand::ClearCache => match ChiselSession::clear_cache() { Ok(_) => { self.session.id = None; @@ -313,194 +336,157 @@ impl ChiselDispatcher { Err(_) => DispatchResult::CommandFailed(Self::make_error("Failed to clear cache!")), }, ChiselCommand::Fork => { - if let Some(session_source) = self.session.session_source.as_mut() { - if args.is_empty() || args[0].trim().is_empty() { - session_source.config.evm_opts.fork_url = None; - return DispatchResult::CommandSuccess(Some( - "Now using local environment.".to_string(), - )) - } - if args.len() != 1 { - return DispatchResult::CommandFailed(Self::make_error( - "Must supply a session ID as the argument.", - )) + if args.is_empty() || args[0].trim().is_empty() { + self.source_mut().config.evm_opts.fork_url = None; + return DispatchResult::CommandSuccess(Some( + "Now using local environment.".to_string(), + )) + } + if args.len() != 1 { + return DispatchResult::CommandFailed(Self::make_error( + "Must supply a session ID as the argument.", + )) + } + let arg = *args.first().unwrap(); + + // If the argument is an RPC alias designated in the + // `[rpc_endpoints]` section of the `foundry.toml` within + // the pwd, use the URL matched to the key. + let endpoint = if let Some(endpoint) = + self.source_mut().config.foundry_config.rpc_endpoints.get(arg) + { + endpoint.clone() + } else { + RpcEndpointUrl::Env(arg.to_string()).into() + }; + let fork_url = match endpoint.resolve().url() { + Ok(fork_url) => fork_url, + Err(e) => { + return DispatchResult::CommandFailed(Self::make_error(format!( + "\"{}\" ENV Variable not set!", + e.var + ))) } - let arg = *args.first().unwrap(); - - // If the argument is an RPC alias designated in the - // `[rpc_endpoints]` section of the `foundry.toml` within - // the pwd, use the URL matched to the key. - let endpoint = if let Some(endpoint) = - session_source.config.foundry_config.rpc_endpoints.get(arg) - { - endpoint.clone() - } else { - RpcEndpoint::Env(arg.to_string()) - }; - let fork_url = match endpoint.resolve() { - Ok(fork_url) => fork_url, - Err(e) => { - return DispatchResult::CommandFailed(Self::make_error(format!( - "\"{}\" ENV Variable not set!", - e.var - ))) - } - }; + }; - // Check validity of URL - if Url::parse(&fork_url).is_err() { - return DispatchResult::CommandFailed(Self::make_error("Invalid fork URL!")) - } + // Check validity of URL + if Url::parse(&fork_url).is_err() { + return DispatchResult::CommandFailed(Self::make_error("Invalid fork URL!")) + } - // Create success message before moving the fork_url - let success_msg = format!("Set fork URL to {}", Paint::yellow(&fork_url)); + // Create success message before moving the fork_url + let success_msg = format!("Set fork URL to {}", &fork_url.yellow()); - // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] - // field - session_source.config.evm_opts.fork_url = Some(fork_url); + // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] + // field + self.source_mut().config.evm_opts.fork_url = Some(fork_url); - // Clear the backend so that it is re-instantiated with the new fork - // upon the next execution of the session source. - session_source.config.backend = None; + // Clear the backend so that it is re-instantiated with the new fork + // upon the next execution of the session source. + self.source_mut().config.backend = None; - DispatchResult::CommandSuccess(Some(success_msg)) - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) - } + DispatchResult::CommandSuccess(Some(success_msg)) } ChiselCommand::Traces => { - if let Some(session_source) = self.session.session_source.as_mut() { - session_source.config.traces = !session_source.config.traces; - DispatchResult::CommandSuccess(Some(format!( - "{} traces!", - if session_source.config.traces { "Enabled" } else { "Disabled" } - ))) - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) - } + self.source_mut().config.traces = !self.source_mut().config.traces; + DispatchResult::CommandSuccess(Some(format!( + "{} traces!", + if self.source_mut().config.traces { "Enabled" } else { "Disabled" } + ))) } ChiselCommand::Calldata => { - if let Some(session_source) = self.session.session_source.as_mut() { - // remove empty space, double quotes, and 0x prefix - let arg = args - .first() - .map(|s| { - s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'') - }) - .map(|s| s.strip_prefix("0x").unwrap_or(s)) - .unwrap_or(""); - - if arg.is_empty() { - session_source.config.calldata = None; - return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) - } + // remove empty space, double quotes, and 0x prefix + let arg = args + .first() + .map(|s| s.trim_matches(|c: char| c.is_whitespace() || c == '"' || c == '\'')) + .map(|s| s.strip_prefix("0x").unwrap_or(s)) + .unwrap_or(""); + + if arg.is_empty() { + self.source_mut().config.calldata = None; + return DispatchResult::CommandSuccess(Some("Calldata cleared.".to_string())) + } - let calldata = hex::decode(arg); - match calldata { - Ok(calldata) => { - session_source.config.calldata = Some(calldata); - DispatchResult::CommandSuccess(Some(format!( - "Set calldata to '{}'", - Paint::yellow(arg) - ))) - } - Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( - "Invalid calldata: {}", - e - ))), + let calldata = hex::decode(arg); + match calldata { + Ok(calldata) => { + self.source_mut().config.calldata = Some(calldata); + DispatchResult::CommandSuccess(Some(format!( + "Set calldata to '{}'", + arg.yellow() + ))) } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( + "Invalid calldata: {e}" + ))), } } ChiselCommand::MemDump | ChiselCommand::StackDump => { - if let Some(session_source) = self.session.session_source.as_mut() { - match session_source.execute().await { - Ok((_, res)) => { - if let Some((stack, mem, _)) = res.state.as_ref() { - if matches!(cmd, ChiselCommand::MemDump) { - // Print memory by word - (0..mem.len()).step_by(32).for_each(|i| { - println!( - "{}: {}", - Paint::yellow(format!( - "[0x{:02x}:0x{:02x}]", - i, - i + 32 - )), - Paint::cyan(format!( - "0x{}", - hex::encode(&mem.data()[i..i + 32]) - )) - ); - }); - } else { - // Print all stack items - (0..stack.len()).rev().for_each(|i| { - println!( - "{}: {}", - Paint::yellow(format!("[{}]", stack.len() - i - 1)), - Paint::cyan(format!("0x{:02x}", stack.data()[i])) - ); - }); - } - DispatchResult::CommandSuccess(None) + match self.source_mut().execute().await { + Ok((_, res)) => { + if let Some((stack, mem, _)) = res.state.as_ref() { + if matches!(cmd, ChiselCommand::MemDump) { + // Print memory by word + (0..mem.len()).step_by(32).for_each(|i| { + let _ = sh_println!( + "{}: {}", + format!("[0x{:02x}:0x{:02x}]", i, i + 32).yellow(), + hex::encode_prefixed(&mem[i..i + 32]).cyan() + ); + }); } else { - DispatchResult::CommandFailed(Self::make_error( - "Run function is empty.", - )) + // Print all stack items + (0..stack.len()).rev().for_each(|i| { + let _ = sh_println!( + "{}: {}", + format!("[{}]", stack.len() - i - 1).yellow(), + format!("0x{:02x}", stack[i]).cyan() + ); + }); } + DispatchResult::CommandSuccess(None) + } else { + DispatchResult::CommandFailed(Self::make_error( + "Run function is empty.", + )) } - Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(e) => DispatchResult::CommandFailed(Self::make_error(e.to_string())), } } ChiselCommand::Export => { // Check if the current session inherits `Script.sol` before exporting - if let Some(session_source) = self.session.session_source.as_ref() { - // Check if the pwd is a foundry project - if PathBuf::from("foundry.toml").exists() { - // Create "script" dir if it does not already exist. - if !PathBuf::from("script").exists() { - if let Err(e) = std::fs::create_dir_all("script") { - return DispatchResult::CommandFailed(Self::make_error( - e.to_string(), - )) - } - } - match format_source( - &session_source.to_script_source(), - session_source.config.foundry_config.fmt.clone(), - ) { - Ok(formatted_source) => { - // Write session source to `script/REPL.s.sol` - if let Err(e) = std::fs::write( - PathBuf::from("script/REPL.s.sol"), - formatted_source, - ) { - return DispatchResult::CommandFailed(Self::make_error( - e.to_string(), - )) - } + // Check if the pwd is a foundry project + if !Path::new("foundry.toml").exists() { + return DispatchResult::CommandFailed(Self::make_error( + "Must be in a foundry project to export source to script.", + )); + } - DispatchResult::CommandSuccess(Some(String::from( - "Exported session source to script/REPL.s.sol!", - ))) - } - Err(_) => DispatchResult::CommandFailed(String::from( - "Failed to format session source", - )), + // Create "script" dir if it does not already exist. + if !Path::new("script").exists() { + if let Err(e) = std::fs::create_dir_all("script") { + return DispatchResult::CommandFailed(Self::make_error(e.to_string())) + } + } + + match self.format_source() { + Ok(formatted_source) => { + // Write session source to `script/REPL.s.sol` + if let Err(e) = + std::fs::write(PathBuf::from("script/REPL.s.sol"), formatted_source) + { + return DispatchResult::CommandFailed(Self::make_error(e.to_string())) } - } else { - DispatchResult::CommandFailed(Self::make_error( - "Must be in a foundry project to export source to script.", - )) + + DispatchResult::CommandSuccess(Some(String::from( + "Exported session source to script/REPL.s.sol!", + ))) } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) + Err(_) => DispatchResult::CommandFailed(String::from( + "Failed to format session source", + )), } } ChiselCommand::Fetch => { @@ -513,15 +499,8 @@ impl ChiselDispatcher { let request_url = format!( "https://api.etherscan.io/api?module=contract&action=getabi&address={}{}", args[0], - if let Some(api_key) = self - .session - .session_source - .as_ref() - .unwrap() - .config - .foundry_config - .etherscan_api_key - .as_ref() + if let Some(api_key) = + self.source().config.foundry_config.etherscan_api_key.as_ref() { format!("&apikey={api_key}") } else { @@ -537,7 +516,8 @@ impl ChiselDispatcher { let json = response.json::().await.unwrap(); if json.status == "1" && json.result.is_some() { let abi = json.result.unwrap(); - if let Ok(abi) = ethers::abi::Abi::load(abi.as_bytes()) { + let abi: serde_json::Result = serde_json::from_str(&abi); + if let Ok(abi) = abi { let mut interface = format!( "// Interface of {}\ninterface {} {{\n", args[0], args[1] @@ -550,7 +530,22 @@ impl ChiselDispatcher { err.name, err.inputs .iter() - .map(|input| format_param!(input)) + .map(|input| { + let mut param_type = &input.ty; + // If complex type then add the name of custom type. + // see . + if input.is_complex_type() { + if let Some( + InternalType::Enum { contract: _, ty } | + InternalType::Struct { contract: _, ty } | + InternalType::Other { contract: _, ty }, + ) = &input.internal_type + { + param_type = ty; + } + } + format!("{} {}", param_type, input.name) + }) .collect::>() .join(",") )); @@ -564,7 +559,7 @@ impl ChiselDispatcher { .inputs .iter() .map(|input| { - let mut formatted = format!("{}", input.kind); + let mut formatted = input.ty.to_string(); if input.indexed { formatted.push_str(" indexed"); } @@ -585,9 +580,9 @@ impl ChiselDispatcher { .collect::>() .join(","), match func.state_mutability { - ethers::abi::StateMutability::Pure => " pure", - ethers::abi::StateMutability::View => " view", - ethers::abi::StateMutability::Payable => " payable", + alloy_json_abi::StateMutability::Pure => " pure", + alloy_json_abi::StateMutability::View => " view", + alloy_json_abi::StateMutability::Payable => " payable", _ => "", }, if func.outputs.is_empty() { @@ -610,11 +605,7 @@ impl ChiselDispatcher { // Add the interface to the source outright - no need to verify // syntax via compilation and/or // parsing. - self.session - .session_source - .as_mut() - .unwrap() - .with_global_code(&interface); + self.source_mut().with_global_code(&interface); DispatchResult::CommandSuccess(Some(format!( "Added {}'s interface to source as `{}`", @@ -661,97 +652,93 @@ impl ChiselDispatcher { } } ChiselCommand::Edit => { - if let Some(session_source) = self.session.session_source.as_mut() { - // create a temp file with the content of the run code - let mut temp_file_path = std::env::temp_dir(); - temp_file_path.push("chisel-tmp.sol"); - let result = std::fs::File::create(&temp_file_path) - .map(|mut file| file.write_all(session_source.run_code.as_bytes())); - if let Err(e) = result { - return DispatchResult::CommandFailed(format!( - "Could not write to a temporary file: {e}" - )) - } + // create a temp file with the content of the run code + let mut temp_file_path = std::env::temp_dir(); + temp_file_path.push("chisel-tmp.sol"); + let result = std::fs::File::create(&temp_file_path) + .map(|mut file| file.write_all(self.source().run_code.as_bytes())); + if let Err(e) = result { + return DispatchResult::CommandFailed(format!( + "Could not write to a temporary file: {e}" + )) + } - // open the temp file with the editor - let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); - let mut cmd = Command::new(editor); - cmd.arg(&temp_file_path); - - match cmd.status() { - Ok(status) => { - if !status.success() { - if let Some(status_code) = status.code() { - return DispatchResult::CommandFailed(format!( - "Editor exited with status {status_code}" - )) - } else { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } + // open the temp file with the editor + let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vim".to_string()); + let mut cmd = Command::new(editor); + cmd.arg(&temp_file_path); + + match cmd.status() { + Ok(status) => { + if !status.success() { + if let Some(status_code) = status.code() { + return DispatchResult::CommandFailed(format!( + "Editor exited with status {status_code}" + )) + } else { + return DispatchResult::CommandFailed( + "Editor exited without a status code".to_string(), + ) } } - Err(_) => { - return DispatchResult::CommandFailed( - "Editor exited without a status code".to_string(), - ) - } } - - let mut new_session_source = session_source.clone(); - if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { - new_session_source.drain_run(); - new_session_source.with_run_code(&edited_code); - } else { + Err(_) => { return DispatchResult::CommandFailed( - "Could not read the edited file".to_string(), + "Editor exited without a status code".to_string(), ) } + } - // if the editor exited successfully, try to compile the new code - match new_session_source.execute().await { - Ok((_, mut res)) => { - let failed = !res.success; - if new_session_source.config.traces || failed { - if let Ok(decoder) = - Self::decode_traces(&new_session_source.config, &mut res) - { - if let Err(e) = Self::show_traces(&decoder, &mut res).await { - self.errored = true; - return DispatchResult::CommandFailed(e.to_string()) - }; - - // Show console logs, if there are any - let decoded_logs = decode_console_logs(&res.logs); - if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); - for log in decoded_logs { - println!(" {log}"); - } + let mut new_session_source = self.source().clone(); + if let Ok(edited_code) = std::fs::read_to_string(temp_file_path) { + new_session_source.drain_run(); + new_session_source.with_run_code(&edited_code); + } else { + return DispatchResult::CommandFailed( + "Could not read the edited file".to_string(), + ) + } + + // if the editor exited successfully, try to compile the new code + match new_session_source.execute().await { + Ok((_, mut res)) => { + let failed = !res.success; + if new_session_source.config.traces || failed { + if let Ok(decoder) = + Self::decode_traces(&new_session_source.config, &mut res).await + { + if let Err(e) = Self::show_traces(&decoder, &mut res).await { + return DispatchResult::CommandFailed(e.to_string()) + }; + + // Show console logs, if there are any + let decoded_logs = decode_console_logs(&res.logs); + if !decoded_logs.is_empty() { + let _ = sh_println!("{}", "Logs:".green()); + for log in decoded_logs { + let _ = sh_println!(" {log}"); } } + } + if failed { // If the contract execution failed, continue on without // updating the source. - self.errored = true; - DispatchResult::CommandFailed(Self::make_error( + return DispatchResult::CommandFailed(Self::make_error( "Failed to execute edited contract!", - )) - } else { - // the code could be compiled, save it - *session_source = new_session_source; - DispatchResult::CommandSuccess(Some(String::from( - "Successfully edited `run()` function's body!", - ))) + )); } } - Err(_) => DispatchResult::CommandFailed( - "The code could not be compiled".to_string(), - ), + + // the code could be compiled, save it + *self.source_mut() = new_session_source; + DispatchResult::CommandSuccess(Some(String::from( + "Successfully edited `run()` function's body!", + ))) + } + Err(_) => { + DispatchResult::CommandFailed("The code could not be compiled".to_string()) } - } else { - DispatchResult::CommandFailed(Self::make_error("Session not present.")) } } ChiselCommand::RawStack => { @@ -768,14 +755,7 @@ impl ChiselDispatcher { let to_inspect = args.first().unwrap(); // Get a mutable reference to the session source - let source = match self.session.session_source.as_mut() { - Some(session_source) => session_source, - _ => { - return DispatchResult::CommandFailed( - "Session source not present".to_string(), - ) - } - }; + let source = self.source_mut(); // Copy the variable's stack contents into a bytes32 variable without updating // the current session source. @@ -804,33 +784,21 @@ impl ChiselDispatcher { let raw_cmd = &split[0][1..]; return match raw_cmd.parse::() { - Ok(cmd) => { - let command_dispatch = self.dispatch_command(cmd, &split[1..]).await; - self.errored = !matches!(command_dispatch, DispatchResult::CommandSuccess(_)); - command_dispatch - } - Err(e) => { - self.errored = true; - DispatchResult::UnrecognizedCommand(e) - } + Ok(cmd) => self.dispatch_command(cmd, &split[1..]).await, + Err(e) => DispatchResult::UnrecognizedCommand(e), } } if input.trim().is_empty() { + debug!("empty dispatch input"); return DispatchResult::Success(None) } // Get a mutable reference to the session source - let source = match self.session.session_source.as_mut().ok_or(DispatchResult::Failure(None)) - { - Ok(project) => project, - Err(e) => { - self.errored = true; - return e - } - }; + let source = self.source_mut(); // If the input is a comment, add it to the run code so we avoid running with empty input if COMMENT_RE.is_match(input) { + debug!(%input, "matched comment"); source.with_run_code(input); return DispatchResult::Success(None) } @@ -838,13 +806,14 @@ impl ChiselDispatcher { // If there is an address (or multiple addresses) in the input, ensure that they are // encoded with a valid checksum per EIP-55. let mut heap_input = input.to_string(); - ADDRESS_RE.find_iter(input).for_each(|m| { + ADDRESS_RE.captures_iter(input).for_each(|m| { // Convert the match to a string slice - let match_str = m.as_str(); + let match_str = m.name("address").expect("exists").as_str(); + // We can always safely unwrap here due to the regex matching. let addr: Address = match_str.parse().expect("Valid address regex"); // Replace all occurrences of the address with a checksummed version - heap_input = heap_input.replace(match_str, ðers::utils::to_checksum(&addr, None)); + heap_input = heap_input.replace(match_str, &addr.to_string()); }); // Replace the old input with the formatted input. input = &heap_input; @@ -853,7 +822,6 @@ impl ChiselDispatcher { let (mut new_source, do_execute) = match source.clone_with_new_line(input.to_string()) { Ok(new) => new, Err(e) => { - self.errored = true; return DispatchResult::CommandFailed(Self::make_error(format!( "Failed to parse input! {e}" ))) @@ -864,16 +832,18 @@ impl ChiselDispatcher { // Should change up how this works. match source.inspect(input).await { // Continue and print - Ok((true, Some(res))) => println!("{res}"), + Ok((true, Some(res))) => { + let _ = sh_println!("{res}"); + } Ok((true, None)) => {} // Return successfully - Ok((false, res)) => return DispatchResult::Success(res), + Ok((false, res)) => { + debug!(%input, ?res, "inspect success"); + return DispatchResult::Success(res) + } // Return with the error - Err(e) => { - self.errored = true; - return DispatchResult::CommandFailed(Self::make_error(e)) - } + Err(e) => return DispatchResult::CommandFailed(Self::make_error(e)), } if do_execute { @@ -884,25 +854,24 @@ impl ChiselDispatcher { // If traces are enabled or there was an error in execution, show the execution // traces. if new_source.config.traces || failed { - if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res) { + if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res).await + { if let Err(e) = Self::show_traces(&decoder, &mut res).await { - self.errored = true; return DispatchResult::CommandFailed(e.to_string()) }; // Show console logs, if there are any let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + let _ = sh_println!("{}", "Logs:".green()); for log in decoded_logs { - println!(" {log}"); + let _ = sh_println!(" {log}"); } } // If the contract execution failed, continue on without adding the new // line to the source. if failed { - self.errored = true; return DispatchResult::Failure(Some(Self::make_error( "Failed to execute REPL contract!", ))) @@ -911,28 +880,20 @@ impl ChiselDispatcher { } // Replace the old session source with the new version - self.session.session_source = Some(new_source); - // Clear any outstanding errors - self.errored = false; + *self.source_mut() = new_source; DispatchResult::Success(None) } - Err(e) => { - self.errored = true; - DispatchResult::Failure(Some(e.to_string())) - } + Err(e) => DispatchResult::Failure(Some(e.to_string())), } } else { match new_source.build() { - Ok(_) => { - self.session.session_source = Some(new_source); - self.errored = false; + Ok(out) => { + debug!(%input, ?out, "skipped execute and rebuild source"); + *self.source_mut() = new_source; DispatchResult::Success(None) } - Err(e) => { - self.errored = true; - DispatchResult::Failure(Some(e.to_string())) - } + Err(e) => DispatchResult::Failure(Some(e.to_string())), } } } @@ -948,27 +909,27 @@ impl ChiselDispatcher { /// ### Returns /// /// Optionally, a [CallTraceDecoder] - pub fn decode_traces( + pub async fn decode_traces( session_config: &SessionSourceConfig, result: &mut ChiselResult, // known_contracts: &ContractsByArtifact, ) -> eyre::Result { - let mut etherscan_identifier = EtherscanIdentifier::new( + let mut decoder = CallTraceDecoderBuilder::new() + .with_labels(result.labeled_addresses.clone()) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + session_config.foundry_config.offline, + )?) + .build(); + + let mut identifier = TraceIdentifiers::new().with_etherscan( &session_config.foundry_config, - session_config.evm_opts.get_remote_chain_id(), + session_config.evm_opts.get_remote_chain_id().await, )?; - - let mut decoder = - CallTraceDecoderBuilder::new().with_labels(result.labeled_addresses.clone()).build(); - - decoder.add_signature_identifier(SignaturesIdentifier::new( - Config::foundry_cache_dir(), - session_config.foundry_config.offline, - )?); - - for (_, trace) in &mut result.traces { - // decoder.identify(trace, &mut local_identifier); - decoder.identify(trace, &mut etherscan_identifier); + if !identifier.is_empty() { + for (_, trace) in &mut result.traces { + decoder.identify(trace, &mut identifier); + } } Ok(decoder) } @@ -991,19 +952,19 @@ impl ChiselDispatcher { eyre::bail!("Unexpected error: No traces gathered. Please report this as a bug: https://github.com/foundry-rs/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); } - println!("{}", Paint::green("Traces:")); + sh_println!("{}", "Traces:".green())?; for (kind, trace) in &mut result.traces { // Display all Setup + Execution traces. if matches!(kind, TraceKind::Setup | TraceKind::Execution) { - decoder.decode(trace).await; - println!("{trace}"); + decode_trace_arena(trace, decoder).await?; + sh_println!("{}", render_trace_arena(trace))?; } } Ok(()) } - /// Format a type that implements [fmt::Display] as a chisel error string. + /// Format a type that implements [std::fmt::Display] as a chisel error string. /// /// ### Takes /// @@ -1013,7 +974,7 @@ impl ChiselDispatcher { /// /// A formatted error [String]. pub fn make_error(msg: T) -> String { - format!("{} {}", Paint::red(format!("{CHISEL_CHAR} Chisel Error:")), Paint::red(msg)) + format!("{}", msg.red()) } } @@ -1031,4 +992,16 @@ mod tests { assert!(COMMENT_RE.is_match(" \t\n /* block \n \t comment */\n")); assert!(!COMMENT_RE.is_match("/* block \n \t comment */\nwith \tother")); } + + #[test] + fn test_address_regex() { + assert!(ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4")); + assert!(ADDRESS_RE.is_match(" 0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4 ")); + assert!(ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4,")); + assert!(ADDRESS_RE.is_match("(0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4)")); + assert!(!ADDRESS_RE.is_match("0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4aaa")); + assert!(!ADDRESS_RE.is_match("'0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + assert!(!ADDRESS_RE.is_match("' 0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + assert!(!ADDRESS_RE.is_match("'0xe5f3aF50FE5d0bF402a3C6F55ccC47d4307922d4'")); + } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index dc536d9801f42..1315cb779ba09 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -5,23 +5,22 @@ use crate::prelude::{ ChiselDispatcher, ChiselResult, ChiselRunner, IntermediateOutput, SessionSource, SolidityHelper, }; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_json_abi::EventParam; +use alloy_primitives::{hex, Address, B256, U256}; use core::fmt::Debug; -use ethers::{ - abi::{ethabi, ParamType, Token}, - types::{Address, I256, U256}, - utils::hex, -}; -use ethers_solc::Artifact; use eyre::{Result, WrapErr}; +use foundry_compilers::Artifact; use foundry_evm::{ - decode::decode_console_logs, - executor::{inspector::CheatsConfig, Backend, ExecutorBuilder}, - utils::ru256_to_u256, + backend::Backend, decode::decode_console_logs, executors::ExecutorBuilder, + inspectors::CheatsConfig, traces::TraceMode, }; use solang_parser::pt::{self, CodeLocation}; +use std::str::FromStr; +use tracing::debug; use yansi::Paint; -const USIZE_MAX_AS_U256: U256 = U256([usize::MAX as u64, 0, 0, 0]); +const USIZE_MAX_AS_U256: U256 = U256::from_limbs([usize::MAX as u64, 0, 0, 0]); /// Executor implementation for [SessionSource] impl SessionSource { @@ -31,6 +30,8 @@ impl SessionSource { /// /// Optionally, a tuple containing the [Address] of the deployed REPL contract as well as /// the [ChiselResult]. + /// + /// Returns an error if compilation fails. pub async fn execute(&mut self) -> Result<(Address, ChiselResult)> { // Recompile the project and ensure no errors occurred. let compiled = self.build()?; @@ -48,6 +49,22 @@ impl SessionSource { // Fetch the run function's body statement let run_func_statements = compiled.intermediate.run_func_body()?; + // Record loc of first yul block return statement (if any). + // This is used to decide which is the final statement within the `run()` method. + // see . + let last_yul_return = run_func_statements.iter().find_map(|statement| { + if let pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } = statement { + if let Some(statement) = block.statements.last() { + if let pt::YulStatement::FunctionCall(yul_call) = statement { + if yul_call.id.name == "return" { + return Some(statement.loc()) + } + } + } + } + None + }); + // Find the last statement within the "run()" method and get the program // counter via the source map. if let Some(final_statement) = run_func_statements.last() { @@ -57,9 +74,13 @@ impl SessionSource { // // There is some code duplication within the arms due to the difference between // the [pt::Statement] type and the [pt::YulStatement] types. - let source_loc = match final_statement { + let mut source_loc = match final_statement { pt::Statement::Assembly { loc: _, dialect: _, flags: _, block } => { - if let Some(statement) = block.statements.last() { + // Select last non variable declaration statement, see . + let last_statement = block.statements.iter().rev().find(|statement| { + !matches!(statement, pt::YulStatement::VariableDeclaration(_, _, _)) + }); + if let Some(statement) = last_statement { statement.loc() } else { // In the case where the block is empty, attempt to grab the statement @@ -87,18 +108,25 @@ impl SessionSource { _ => final_statement.loc(), }; + // Consider yul return statement as final statement (if it's loc is lower) . + if let Some(yul_return) = last_yul_return { + if yul_return.end() < source_loc.start() { + source_loc = yul_return; + } + } + // Map the source location of the final statement of the `run()` function to its // corresponding runtime program counter let final_pc = { - let offset = source_loc.start(); - let length = source_loc.end() - source_loc.start(); + let offset = source_loc.start() as u32; + let length = (source_loc.end() - source_loc.start()) as u32; contract .get_source_map_deployed() .unwrap() .unwrap() .into_iter() .zip(InstructionIter::new(&deployed_bytecode)) - .filter(|(s, _)| s.offset == offset && s.length == length) + .filter(|(s, _)| s.offset() == offset && s.length() == length) .map(|(_, i)| i.pc) .max() .unwrap_or_default() @@ -111,7 +139,7 @@ impl SessionSource { runner.run(bytecode.into_owned()) } else { // Return a default result if no statements are present. - Ok((Address::zero(), ChiselResult::default())) + Ok((Address::ZERO, ChiselResult::default())) } } else { eyre::bail!("Failed to find REPL contract!") @@ -126,35 +154,41 @@ impl SessionSource { /// /// ### Returns /// - /// If the input is valid `Ok((formatted_output, continue))` where: + /// If the input is valid `Ok((continue, formatted_output))` where: /// - `continue` is true if the input should be appended to the source - /// - `formatted_output` is the formatted value + /// - `formatted_output` is the formatted value, if any pub async fn inspect(&self, input: &str) -> Result<(bool, Option)> { let line = format!("bytes memory inspectoor = abi.encode({input});"); - let mut source = match self.clone_with_new_line(line) { + let mut source = match self.clone_with_new_line(line.clone()) { Ok((source, _)) => source, - Err(_) => return Ok((true, None)), + Err(err) => { + debug!(%err, "failed to build new source"); + return Ok((true, None)) + } }; let mut source_without_inspector = self.clone(); // Events and tuples fails compilation due to it not being able to be encoded in // `inspectoor`. If that happens, try executing without the inspector. - let (mut res, has_inspector) = match source.execute().await { - Ok((_, res)) => (res, true), - Err(e) => match source_without_inspector.execute().await { - Ok((_, res)) => (res, false), - Err(_) => { - if self.config.foundry_config.verbosity >= 3 { - eprintln!("Could not inspect: {e}"); + let (mut res, err) = match source.execute().await { + Ok((_, res)) => (res, None), + Err(err) => { + debug!(?err, %input, "execution failed"); + match source_without_inspector.execute().await { + Ok((_, res)) => (res, Some(err)), + Err(_) => { + if self.config.foundry_config.verbosity >= 3 { + sh_err!("Could not inspect: {err}")?; + } + return Ok((true, None)) } - return Ok((true, None)) } - }, + } }; // If abi-encoding the input failed, check whether it is an event - if !has_inspector { + if let Some(err) = err { let generated_output = source_without_inspector .generated_output .as_ref() @@ -171,19 +205,25 @@ impl SessionSource { return Ok((false, Some(formatted))) } + // we were unable to check the event + if self.config.foundry_config.verbosity >= 3 { + sh_err!("Failed eval: {err}")?; + } + + debug!(%err, %input, "failed abi encode input"); return Ok((false, None)) } let Some((stack, memory, _)) = &res.state else { // Show traces and logs, if there are any, and return an error - if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res) { + if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res).await { ChiselDispatcher::show_traces(&decoder, &mut res).await?; } let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + sh_println!("{}", "Logs:".green())?; for log in decoded_logs { - println!(" {log}"); + sh_println!(" {log}")?; } } @@ -223,15 +263,14 @@ impl SessionSource { // the file compiled correctly, thus the last stack item must be the memory offset of // the `bytes memory inspectoor` value - let mut offset = ru256_to_u256(*stack.data().last().unwrap()).as_usize(); - let mem = memory.data(); - let len = U256::from(&mem[offset..offset + 32]).as_usize(); + let mut offset = stack.last().unwrap().to::(); + let mem_offset = &memory[offset..offset + 32]; + let len = U256::try_from_be_slice(mem_offset).unwrap().to::(); offset += 32; - let data = &mem[offset..offset + len]; - let mut tokens = - ethabi::decode(&[ty], data).wrap_err("Could not decode inspected values")?; + let data = &memory[offset..offset + len]; // `tokens` is guaranteed to have the same length as the provided types - let token = tokens.pop().unwrap(); + let token = + DynSolType::abi_decode(&ty, data).wrap_err("Could not decode inspected values")?; Ok((should_continue(contract_expr), Some(format_token(token)))) } @@ -267,7 +306,7 @@ impl SessionSource { /// /// ### Takes /// - /// The final statement's program counter for the [ChiselInspector] + /// The final statement's program counter for the ChiselInspector /// /// ### Returns /// @@ -280,126 +319,147 @@ impl SessionSource { let backend = match self.config.backend.take() { Some(backend) => backend, None => { - let backend = Backend::spawn( - self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()), - ) - .await; + let fork = self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()); + let backend = Backend::spawn(fork); self.config.backend = Some(backend.clone()); backend } }; // Build a new executor - let executor = ExecutorBuilder::default() - .with_config(env) - .with_chisel_state(final_pc) - .set_tracing(true) - .with_spec(foundry_evm::utils::evm_spec(&self.config.foundry_config.evm_version)) - .with_gas_limit(self.config.evm_opts.gas_limit()) - .with_cheatcodes(CheatsConfig::new(&self.config.foundry_config, &self.config.evm_opts)) - .build(backend); + let executor = ExecutorBuilder::new() + .inspectors(|stack| { + stack.chisel_state(final_pc).trace_mode(TraceMode::Call).cheatcodes( + CheatsConfig::new( + &self.config.foundry_config, + self.config.evm_opts.clone(), + None, + None, + ) + .into(), + ) + }) + .gas_limit(self.config.evm_opts.gas_limit()) + .spec_id(self.config.foundry_config.evm_spec_id()) + .legacy_assertions(self.config.foundry_config.legacy_assertions) + .build(env, backend); // Create a [ChiselRunner] with a default balance of [U256::MAX] and // the sender [Address::zero]. - ChiselRunner::new(executor, U256::MAX, Address::zero(), self.config.calldata.clone()) + ChiselRunner::new(executor, U256::MAX, Address::ZERO, self.config.calldata.clone()) } } -/// Formats a [Token] into an inspection message -/// -/// ### Takes -/// -/// An owned [Token] -/// -/// ### Returns -/// -/// A formatted [Token] for use in inspection output. -/// -/// TODO: Verbosity option -fn format_token(token: Token) -> String { +/// Formats a value into an inspection message +// TODO: Verbosity option +fn format_token(token: DynSolValue) -> String { match token { - Token::Address(a) => { - format!("Type: {}\n└ Data: {}", Paint::red("address"), Paint::cyan(format!("0x{a:x}"))) + DynSolValue::Address(a) => { + format!("Type: {}\n└ Data: {}", "address".red(), a.cyan()) } - Token::FixedBytes(b) => { + DynSolValue::FixedBytes(b, byte_len) => { format!( "Type: {}\n└ Data: {}", - Paint::red(format!("bytes{}", b.len())), - Paint::cyan(format!("0x{}", hex::encode(b))) + format!("bytes{byte_len}").red(), + hex::encode_prefixed(b).cyan() ) } - Token::Int(i) => { + DynSolValue::Int(i, bit_len) => { format!( - "Type: {}\n├ Hex: {}\n└ Decimal: {}", - Paint::red("int"), - Paint::cyan(format!("0x{i:x}")), - Paint::cyan(I256::from_raw(i)) + "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", + format!("int{bit_len}").red(), + format!( + "0x{}", + format!("{i:x}") + .char_indices() + .skip(64 - bit_len / 4) + .take(bit_len / 4) + .map(|(_, c)| c) + .collect::() + ) + .cyan(), + hex::encode_prefixed(B256::from(i)).cyan(), + i.cyan() ) } - Token::Uint(i) => { + DynSolValue::Uint(i, bit_len) => { format!( - "Type: {}\n├ Hex: {}\n└ Decimal: {}", - Paint::red("uint"), - Paint::cyan(format!("0x{i:x}")), - Paint::cyan(i) + "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", + format!("uint{bit_len}").red(), + format!( + "0x{}", + format!("{i:x}") + .char_indices() + .skip(64 - bit_len / 4) + .take(bit_len / 4) + .map(|(_, c)| c) + .collect::() + ) + .cyan(), + hex::encode_prefixed(B256::from(i)).cyan(), + i.cyan() ) } - Token::Bool(b) => { - format!("Type: {}\n└ Value: {}", Paint::red("bool"), Paint::cyan(b)) + DynSolValue::Bool(b) => { + format!("Type: {}\n└ Value: {}", "bool".red(), b.cyan()) } - Token::String(_) | Token::Bytes(_) => { - let hex = hex::encode(ethers::abi::encode(&[token.clone()])); - let s = token.into_string(); + DynSolValue::String(_) | DynSolValue::Bytes(_) => { + let hex = hex::encode(token.abi_encode()); + let s = token.as_str(); format!( "Type: {}\n{}├ Hex (Memory):\n├─ Length ({}): {}\n├─ Contents ({}): {}\n├ Hex (Tuple Encoded):\n├─ Pointer ({}): {}\n├─ Length ({}): {}\n└─ Contents ({}): {}", - Paint::red(if s.is_some() { "string" } else { "dynamic bytes" }), + if s.is_some() { "string" } else { "dynamic bytes" }.red(), if let Some(s) = s { - format!("├ UTF-8: {}\n", Paint::cyan(s)) + format!("├ UTF-8: {}\n", s.cyan()) } else { String::default() }, - Paint::yellow("[0x00:0x20]"), - Paint::cyan(format!("0x{}", &hex[64..128])), - Paint::yellow("[0x20:..]"), - Paint::cyan(format!("0x{}", &hex[128..])), - Paint::yellow("[0x00:0x20]"), - Paint::cyan(format!("0x{}", &hex[..64])), - Paint::yellow("[0x20:0x40]"), - Paint::cyan(format!("0x{}", &hex[64..128])), - Paint::yellow("[0x40:..]"), - Paint::cyan(format!("0x{}", &hex[128..])), + "[0x00:0x20]".yellow(), + format!("0x{}", &hex[64..128]).cyan(), + "[0x20:..]".yellow(), + format!("0x{}", &hex[128..]).cyan(), + "[0x00:0x20]".yellow(), + format!("0x{}", &hex[..64]).cyan(), + "[0x20:0x40]".yellow(), + format!("0x{}", &hex[64..128]).cyan(), + "[0x40:..]".yellow(), + format!("0x{}", &hex[128..]).cyan(), ) } - Token::FixedArray(tokens) | Token::Array(tokens) => { + DynSolValue::FixedArray(tokens) | DynSolValue::Array(tokens) => { let mut out = format!( "{}({}) = {}", - Paint::red("array"), - Paint::yellow(format!("{}", tokens.len())), - Paint::red('[') + "array".red(), + format!("{}", tokens.len()).yellow(), + '['.red() ); for token in tokens { out.push_str("\n ├ "); out.push_str(&format_token(token).replace('\n', "\n ")); out.push('\n'); } - out.push_str(&Paint::red(']').to_string()); + out.push_str(&']'.red().to_string()); out } - Token::Tuple(tokens) => { - let mut out = format!( - "{}({}) = {}", - Paint::red("tuple"), - Paint::yellow(tokens.iter().map(ToString::to_string).collect::>().join(",")), - Paint::red('(') - ); + DynSolValue::Tuple(tokens) => { + let displayed_types = tokens + .iter() + .map(|t| t.sol_type_name().unwrap_or_default()) + .collect::>() + .join(", "); + let mut out = + format!("{}({}) = {}", "tuple".red(), displayed_types.yellow(), '('.red()); for token in tokens { out.push_str("\n ├ "); out.push_str(&format_token(token).replace('\n', "\n ")); out.push('\n'); } - out.push_str(&Paint::red(')').to_string()); + out.push_str(&')'.red().to_string()); out } + _ => { + unimplemented!() + } } } @@ -428,15 +488,22 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result>>()?; - let event = ethabi::Event { name: event_name, inputs, anonymous: event_definition.anonymous }; + let event = + alloy_json_abi::Event { name: event_name, inputs, anonymous: event_definition.anonymous }; Ok(format!( - "Type: {}\n├ Name: {}\n└ Signature: {:?}", - Paint::red("event"), - SolidityHelper::highlight(&format!( + "Type: {}\n├ Name: {}\n├ Signature: {:?}\n└ Selector: {:?}", + "event".red(), + SolidityHelper::new().highlight(&format!( "{}({})", &event.name, &event @@ -444,7 +511,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result Result>() .join(", ") )), - Paint::cyan(event.signature()), + event.signature().cyan(), + event.selector().cyan(), )) } @@ -464,10 +532,10 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result), @@ -518,7 +586,7 @@ impl Type { if n > USIZE_MAX_AS_U256 { None } else { - Some(n.as_usize()) + Some(n.to::()) } }); match expr.as_ref() { @@ -585,19 +653,18 @@ impl Type { } // address - pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(ParamType::Address)), + pt::Expression::AddressLiteral(_, _) => Some(Self::Builtin(DynSolType::Address)), pt::Expression::HexNumberLiteral(_, s, _) => { - match s.parse() { + match s.parse::
() { Ok(addr) => { - let checksummed = ethers::utils::to_checksum(&addr, None); - if *s == checksummed { - Some(Self::Builtin(ParamType::Address)) + if *s == addr.to_checksum(None) { + Some(Self::Builtin(DynSolType::Address)) } else { - Some(Self::Builtin(ParamType::Uint(256))) + Some(Self::Builtin(DynSolType::Uint(256))) } }, _ => { - Some(Self::Builtin(ParamType::Uint(256))) + Some(Self::Builtin(DynSolType::Uint(256))) } } } @@ -614,13 +681,13 @@ impl Type { pt::Expression::Multiply(_, lhs, rhs) | pt::Expression::Divide(_, lhs, rhs) => { match (Self::ethabi(lhs, None), Self::ethabi(rhs, None)) { - (Some(ParamType::Int(_)), Some(ParamType::Int(_))) | - (Some(ParamType::Int(_)), Some(ParamType::Uint(_))) | - (Some(ParamType::Uint(_)), Some(ParamType::Int(_))) => { - Some(Self::Builtin(ParamType::Int(256))) + (Some(DynSolType::Int(_)), Some(DynSolType::Int(_))) | + (Some(DynSolType::Int(_)), Some(DynSolType::Uint(_))) | + (Some(DynSolType::Uint(_)), Some(DynSolType::Int(_))) => { + Some(Self::Builtin(DynSolType::Int(256))) } _ => { - Some(Self::Builtin(ParamType::Uint(256))) + Some(Self::Builtin(DynSolType::Uint(256))) } } } @@ -633,11 +700,11 @@ impl Type { pt::Expression::BitwiseXor(_, _, _) | pt::Expression::ShiftRight(_, _, _) | pt::Expression::ShiftLeft(_, _, _) | - pt::Expression::NumberLiteral(_, _, _, _) => Some(Self::Builtin(ParamType::Uint(256))), + pt::Expression::NumberLiteral(_, _, _, _) => Some(Self::Builtin(DynSolType::Uint(256))), // TODO: Rational numbers pt::Expression::RationalNumberLiteral(_, _, _, _, _) => { - Some(Self::Builtin(ParamType::Uint(256))) + Some(Self::Builtin(DynSolType::Uint(256))) } // bool @@ -650,13 +717,13 @@ impl Type { pt::Expression::LessEqual(_, _, _) | pt::Expression::More(_, _, _) | pt::Expression::MoreEqual(_, _, _) | - pt::Expression::Not(_, _) => Some(Self::Builtin(ParamType::Bool)), + pt::Expression::Not(_, _) => Some(Self::Builtin(DynSolType::Bool)), // string - pt::Expression::StringLiteral(_) => Some(Self::Builtin(ParamType::String)), + pt::Expression::StringLiteral(_) => Some(Self::Builtin(DynSolType::String)), // bytes - pt::Expression::HexLiteral(_) => Some(Self::Builtin(ParamType::Bytes)), + pt::Expression::HexLiteral(_) => Some(Self::Builtin(DynSolType::Bytes)), // function pt::Expression::FunctionCall(_, name, args) => { @@ -689,14 +756,14 @@ impl Type { fn from_type(ty: &pt::Type) -> Option { let ty = match ty { pt::Type::Address | pt::Type::AddressPayable | pt::Type::Payable => { - Self::Builtin(ParamType::Address) + Self::Builtin(DynSolType::Address) } - pt::Type::Bool => Self::Builtin(ParamType::Bool), - pt::Type::String => Self::Builtin(ParamType::String), - pt::Type::Int(size) => Self::Builtin(ParamType::Int(*size as usize)), - pt::Type::Uint(size) => Self::Builtin(ParamType::Uint(*size as usize)), - pt::Type::Bytes(size) => Self::Builtin(ParamType::FixedBytes(*size as usize)), - pt::Type::DynamicBytes => Self::Builtin(ParamType::Bytes), + pt::Type::Bool => Self::Builtin(DynSolType::Bool), + pt::Type::String => Self::Builtin(DynSolType::String), + pt::Type::Int(size) => Self::Builtin(DynSolType::Int(*size as usize)), + pt::Type::Uint(size) => Self::Builtin(DynSolType::Uint(*size as usize)), + pt::Type::Bytes(size) => Self::Builtin(DynSolType::FixedBytes(*size as usize)), + pt::Type::DynamicBytes => Self::Builtin(DynSolType::Bytes), pt::Type::Mapping { value, .. } => Self::from_expression(value)?, pt::Type::Function { params, returns, .. } => { let params = map_parameters(params); @@ -705,7 +772,7 @@ impl Type { .map(|(returns, _)| map_parameters(returns)) .unwrap_or_default(); Self::Function( - Box::new(Type::Custom(vec!["__fn_type__".to_string()])), + Box::new(Self::Custom(vec!["__fn_type__".to_string()])), params, returns, ) @@ -717,6 +784,8 @@ impl Type { } /// Handle special expressions like [global variables](https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables) + /// + /// See: fn map_special(self) -> Self { if !matches!(self, Self::Function(_, _, _) | Self::Access(_, _) | Self::Custom(_)) { return self @@ -739,8 +808,8 @@ impl Type { // Array / bytes members let ty = Self::Builtin(ty); match access.as_str() { - "length" if ty.is_dynamic() || ty.is_array() => { - return Self::Builtin(ParamType::Uint(256)) + "length" if ty.is_dynamic() || ty.is_array() || ty.is_fixed_bytes() => { + return Self::Builtin(DynSolType::Uint(256)) } "pop" if ty.is_dynamic_array() => return ty, _ => {} @@ -755,31 +824,32 @@ impl Type { match len { 0 => unreachable!(), 1 => match name { - "gasleft" | "addmod" | "mulmod" => Some(ParamType::Uint(256)), - "keccak256" | "sha256" | "blockhash" => Some(ParamType::FixedBytes(32)), - "ripemd160" => Some(ParamType::FixedBytes(20)), - "ecrecover" => Some(ParamType::Address), + "gasleft" | "addmod" | "mulmod" => Some(DynSolType::Uint(256)), + "keccak256" | "sha256" | "blockhash" => Some(DynSolType::FixedBytes(32)), + "ripemd160" => Some(DynSolType::FixedBytes(20)), + "ecrecover" => Some(DynSolType::Address), _ => None, }, 2 => { let access = types.first().unwrap().as_str(); match name { "block" => match access { - "coinbase" => Some(ParamType::Address), - "basefee" | "chainid" | "difficulty" | "gaslimit" | "number" | - "timestamp" => Some(ParamType::Uint(256)), + "coinbase" => Some(DynSolType::Address), + "timestamp" | "difficulty" | "prevrandao" | "number" | "gaslimit" | + "chainid" | "basefee" | "blobbasefee" => Some(DynSolType::Uint(256)), _ => None, }, "msg" => match access { - "data" => Some(ParamType::Bytes), - "sender" => Some(ParamType::Address), - "sig" => Some(ParamType::FixedBytes(4)), - "value" => Some(ParamType::Uint(256)), + "sender" => Some(DynSolType::Address), + "gas" => Some(DynSolType::Uint(256)), + "value" => Some(DynSolType::Uint(256)), + "data" => Some(DynSolType::Bytes), + "sig" => Some(DynSolType::FixedBytes(4)), _ => None, }, "tx" => match access { - "gasprice" => Some(ParamType::Uint(256)), - "origin" => Some(ParamType::Address), + "origin" => Some(DynSolType::Address), + "gasprice" => Some(DynSolType::Uint(256)), _ => None, }, "abi" => match access { @@ -799,32 +869,33 @@ impl Type { None => None, } } - s if s.starts_with("encode") => Some(ParamType::Bytes), + s if s.starts_with("encode") => Some(DynSolType::Bytes), _ => None, }, "address" => match access { - "balance" => Some(ParamType::Uint(256)), - "code" => Some(ParamType::Bytes), - "codehash" => Some(ParamType::FixedBytes(32)), - "send" => Some(ParamType::Bool), + "balance" => Some(DynSolType::Uint(256)), + "code" => Some(DynSolType::Bytes), + "codehash" => Some(DynSolType::FixedBytes(32)), + "send" => Some(DynSolType::Bool), _ => None, }, "type" => match access { - "name" => Some(ParamType::String), - "creationCode" | "runtimeCode" => Some(ParamType::Bytes), - "interfaceId" => Some(ParamType::FixedBytes(4)), - "min" | "max" => { - let arg = args.unwrap().pop().flatten().unwrap(); - Some(arg.into_builtin().unwrap()) - } + "name" => Some(DynSolType::String), + "creationCode" | "runtimeCode" => Some(DynSolType::Bytes), + "interfaceId" => Some(DynSolType::FixedBytes(4)), + "min" | "max" => Some( + // Either a builtin or an enum + (|| args?.pop()??.into_builtin())() + .unwrap_or(DynSolType::Uint(256)), + ), _ => None, }, "string" => match access { - "concat" => Some(ParamType::String), + "concat" => Some(DynSolType::String), _ => None, }, "bytes" => match access { - "concat" => Some(ParamType::Bytes), + "concat" => Some(DynSolType::Bytes), _ => None, }, _ => None, @@ -846,7 +917,7 @@ impl Type { /// Recurses over itself, appending all the idents and function arguments in the order that they /// are found - fn recurse(&self, types: &mut Vec, args: &mut Option>>) { + fn recurse(&self, types: &mut Vec, args: &mut Option>>) { match self { Self::Builtin(ty) => types.push(ty.to_string()), Self::Custom(tys) => types.extend(tys.clone()), @@ -874,14 +945,14 @@ impl Type { /// /// ### Returns /// - /// If successful, an `Ok(Some(ParamType))` variant. + /// If successful, an `Ok(Some(DynSolType))` variant. /// If gracefully failed, an `Ok(None)` variant. /// If failed, an `Err(e)` variant. fn infer_custom_type( intermediate: &IntermediateOutput, custom_type: &mut Vec, contract_name: Option, - ) -> Result> { + ) -> Result> { if let Some("this") | Some("super") = custom_type.last().map(String::as_str) { custom_type.pop(); } @@ -952,7 +1023,7 @@ impl Type { .ok_or_else(|| eyre::eyre!("Struct `{cur_type}` has invalid fields")) }) .collect::>>()?; - Ok(Some(ParamType::Tuple(inner_types))) + Ok(Some(DynSolType::Tuple(inner_types))) } else { eyre::bail!("Could not find any definition in contract \"{contract_name}\" for type: {custom_type:?}") } @@ -990,7 +1061,7 @@ impl Type { expr: &pt::Expression, intermediate: Option<&IntermediateOutput>, custom_type: &mut Vec, - ) -> Result> { + ) -> Result> { // Resolve local (in `run` function) or global (in the `REPL` or other contract) variable let res = match &expr { // Custom variable handling @@ -1008,7 +1079,7 @@ impl Type { Self::infer_custom_type(intermediate, custom_type, Some(name.clone())) } else { // We have no types left to recurse: return the address of the contract. - Ok(Some(ParamType::Address)) + Ok(Some(DynSolType::Address)) } } else { Err(eyre::eyre!("Could not infer variable type")) @@ -1035,28 +1106,28 @@ impl Type { } } - /// Attempt to convert this type into a [ParamType] + /// Attempt to convert this type into a [DynSolType] /// /// ### Takes /// An immutable reference to an [IntermediateOutput] /// /// ### Returns - /// Optionally, a [ParamType] - fn try_as_ethabi(self, intermediate: Option<&IntermediateOutput>) -> Option { + /// Optionally, a [DynSolType] + fn try_as_ethabi(self, intermediate: Option<&IntermediateOutput>) -> Option { match self { Self::Builtin(ty) => Some(ty), - Self::Tuple(types) => Some(ParamType::Tuple(types_to_parameters(types, intermediate))), + Self::Tuple(types) => Some(DynSolType::Tuple(types_to_parameters(types, intermediate))), Self::Array(inner) => match *inner { ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), - _ => { - inner.try_as_ethabi(intermediate).map(|inner| ParamType::Array(Box::new(inner))) - } + _ => inner + .try_as_ethabi(intermediate) + .map(|inner| DynSolType::Array(Box::new(inner))), }, Self::FixedArray(inner, size) => match *inner { ty @ Self::Custom(_) => ty.try_as_ethabi(intermediate), _ => inner .try_as_ethabi(intermediate) - .map(|inner| ParamType::FixedArray(Box::new(inner), size)), + .map(|inner| DynSolType::FixedArray(Box::new(inner), size)), }, ty @ Self::ArrayIndex(_, _) => ty.into_array_index(intermediate), Self::Function(ty, _, _) => ty.try_as_ethabi(intermediate), @@ -1075,7 +1146,7 @@ impl Type { fn ethabi( expr: &pt::Expression, intermediate: Option<&IntermediateOutput>, - ) -> Option { + ) -> Option { Self::from_expression(expr) .map(Self::map_special) .and_then(|ty| ty.try_as_ethabi(intermediate)) @@ -1085,7 +1156,7 @@ impl Type { fn get_function_return_type<'a>( contract_expr: Option<&'a pt::Expression>, intermediate: &IntermediateOutput, - ) -> Option<(&'a pt::Expression, ParamType)> { + ) -> Option<(&'a pt::Expression, DynSolType)> { let function_call = match contract_expr? { pt::Expression::FunctionCall(_, function_call, _) => function_call, _ => return None, @@ -1113,38 +1184,38 @@ impl Type { .function_definitions .get(&function_name.name)?; let return_parameter = contract.as_ref().returns.first()?.to_owned().1?; - Type::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) + Self::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) } - /// Inverts Int to Uint and viceversa. + /// Inverts Int to Uint and vice-versa. fn invert_int(self) -> Self { match self { - Self::Builtin(ParamType::Uint(n)) => Self::Builtin(ParamType::Int(n)), - Self::Builtin(ParamType::Int(n)) => Self::Builtin(ParamType::Uint(n)), + Self::Builtin(DynSolType::Uint(n)) => Self::Builtin(DynSolType::Int(n)), + Self::Builtin(DynSolType::Int(n)) => Self::Builtin(DynSolType::Uint(n)), x => x, } } - /// Returns the `ParamType` contained by `Type::Builtin` + /// Returns the `DynSolType` contained by `Type::Builtin` #[inline] - fn into_builtin(self) -> Option { + fn into_builtin(self) -> Option { match self { Self::Builtin(ty) => Some(ty), _ => None, } } - /// Returns the resulting `ParamType` of indexing self - fn into_array_index(self, intermediate: Option<&IntermediateOutput>) -> Option { + /// Returns the resulting `DynSolType` of indexing self + fn into_array_index(self, intermediate: Option<&IntermediateOutput>) -> Option { match self { Self::Array(inner) | Self::FixedArray(inner, _) | Self::ArrayIndex(inner, _) => { match inner.try_as_ethabi(intermediate) { - Some(ParamType::Array(inner)) | Some(ParamType::FixedArray(inner, _)) => { + Some(DynSolType::Array(inner)) | Some(DynSolType::FixedArray(inner, _)) => { Some(*inner) } - Some(ParamType::Bytes) | Some(ParamType::String) => { - Some(ParamType::FixedBytes(1)) - } + Some(DynSolType::Bytes) | + Some(DynSolType::String) | + Some(DynSolType::FixedBytes(_)) => Some(DynSolType::FixedBytes(1)), ty => ty, } } @@ -1156,7 +1227,9 @@ impl Type { #[inline] fn is_dynamic(&self) -> bool { match self { - Self::Builtin(ty) => ty.is_dynamic(), + // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are + // not dynamic, nor are tuples of non-dynamic types. + Self::Builtin(DynSolType::Bytes | DynSolType::String | DynSolType::Array(_)) => true, Self::Array(_) => true, _ => false, } @@ -1169,15 +1242,19 @@ impl Type { self, Self::Array(_) | Self::FixedArray(_, _) | - Self::Builtin(ParamType::Array(_)) | - Self::Builtin(ParamType::FixedArray(_, _)) + Self::Builtin(DynSolType::Array(_)) | + Self::Builtin(DynSolType::FixedArray(_, _)) ) } /// Returns whether this type is a dynamic array (can call push, pop) #[inline] fn is_dynamic_array(&self) -> bool { - matches!(self, Self::Array(_) | Self::Builtin(ParamType::Array(_))) + matches!(self, Self::Array(_) | Self::Builtin(DynSolType::Array(_))) + } + + fn is_fixed_bytes(&self) -> bool { + matches!(self, Self::Builtin(DynSolType::FixedBytes(_))) } } @@ -1185,7 +1262,7 @@ impl Type { /// /// Ref: #[inline] -fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option { +fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option { if !matches!(func.ty, pt::FunctionTy::Function) { return None } @@ -1197,8 +1274,8 @@ fn func_members(func: &pt::FunctionDefinition, custom_type: &[String]) -> Option match vis { Some(pt::Visibility::External(_)) | Some(pt::Visibility::Public(_)) => { match custom_type.first().unwrap().as_str() { - "address" => Some(ParamType::Address), - "selector" => Some(ParamType::FixedBytes(4)), + "address" => Some(DynSolType::Address), + "selector" => Some(DynSolType::FixedBytes(4)), _ => None, } } @@ -1253,14 +1330,14 @@ fn map_parameters(params: &[(pt::Loc, Option)]) -> Vec>, intermediate: Option<&IntermediateOutput>, -) -> Vec { +) -> Vec { types.into_iter().filter_map(|ty| ty.and_then(|ty| ty.try_as_ethabi(intermediate))).collect() } fn parse_number_literal(expr: &pt::Expression) -> Option { match expr { pt::Expression::NumberLiteral(_, num, exp, unit) => { - let num = U256::from_dec_str(num).unwrap_or(U256::zero()); + let num = U256::from_str(num).unwrap_or(U256::ZERO); let exp = exp.parse().unwrap_or(0u32); if exp > 77 { None @@ -1294,13 +1371,13 @@ fn unit_multiplier(unit: &Option) -> Result { "ether" => 10_usize.pow(18), other => eyre::bail!("unknown unit: {other}"), }; - Ok(mul.into()) + Ok(U256::from(mul)) } else { - Ok(U256::one()) + Ok(U256::from(1)) } } -#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Instruction { pub pc: usize, pub opcode: u8, @@ -1319,7 +1396,7 @@ impl<'a> InstructionIter<'a> { } } -impl<'a> Iterator for InstructionIter<'a> { +impl Iterator for InstructionIter<'_> { type Item = Instruction; fn next(&mut self) -> Option { let pc = self.offset; @@ -1341,20 +1418,20 @@ impl<'a> Iterator for InstructionIter<'a> { #[cfg(test)] mod tests { use super::*; - use ethers_solc::{error::SolcError, Solc}; - use once_cell::sync::Lazy; + use foundry_compilers::{error::SolcError, solc::Solc}; + use semver::Version; use std::sync::Mutex; #[test] fn test_const() { - assert_eq!(USIZE_MAX_AS_U256.low_u64(), usize::MAX as u64); - assert_eq!(USIZE_MAX_AS_U256.as_u64(), usize::MAX as u64); + assert_eq!(USIZE_MAX_AS_U256.to::(), usize::MAX as u64); + assert_eq!(USIZE_MAX_AS_U256.to::(), usize::MAX as u64); } #[test] fn test_expressions() { - static EXPRESSIONS: &[(&str, ParamType)] = { - use ParamType::*; + static EXPRESSIONS: &[(&str, DynSolType)] = { + use DynSolType::*; &[ // units // uint @@ -1439,13 +1516,13 @@ mod tests { let source = &mut source(); - let array_expressions: &[(&str, ParamType)] = &[ - ("[1, 2, 3]", fixed_array(ParamType::Uint(256), 3)), - ("[uint8(1), 2, 3]", fixed_array(ParamType::Uint(8), 3)), - ("[int8(1), 2, 3]", fixed_array(ParamType::Int(8), 3)), - ("new uint256[](3)", array(ParamType::Uint(256))), - ("uint256[] memory a = new uint256[](3);\na[0]", ParamType::Uint(256)), - ("uint256[] memory a = new uint256[](3);\na[0:3]", array(ParamType::Uint(256))), + let array_expressions: &[(&str, DynSolType)] = &[ + ("[1, 2, 3]", fixed_array(DynSolType::Uint(256), 3)), + ("[uint8(1), 2, 3]", fixed_array(DynSolType::Uint(8), 3)), + ("[int8(1), 2, 3]", fixed_array(DynSolType::Int(8), 3)), + ("new uint256[](3)", array(DynSolType::Uint(256))), + ("uint256[] memory a = new uint256[](3);\na[0]", DynSolType::Uint(256)), + ("uint256[] memory a = new uint256[](3);\na[0:3]", array(DynSolType::Uint(256))), ]; generic_type_test(source, array_expressions); generic_type_test(source, EXPRESSIONS); @@ -1453,8 +1530,8 @@ mod tests { #[test] fn test_types() { - static TYPES: &[(&str, ParamType)] = { - use ParamType::*; + static TYPES: &[(&str, DynSolType)] = { + use DynSolType::*; &[ // bool ("bool", Bool), @@ -1498,17 +1575,17 @@ mod tests { ] }; - let mut types: Vec<(String, ParamType)> = Vec::with_capacity(96 + 32 + 100); + let mut types: Vec<(String, DynSolType)> = Vec::with_capacity(96 + 32 + 100); for (n, b) in (8..=256).step_by(8).zip(1..=32) { - types.push((format!("uint{n}(0)"), ParamType::Uint(n))); - types.push((format!("int{n}(0)"), ParamType::Int(n))); - types.push((format!("bytes{b}(0x00)"), ParamType::FixedBytes(b))); + types.push((format!("uint{n}(0)"), DynSolType::Uint(n))); + types.push((format!("int{n}(0)"), DynSolType::Int(n))); + types.push((format!("bytes{b}(0x00)"), DynSolType::FixedBytes(b))); } for n in 0..=32 { types.push(( format!("uint256[{n}]"), - ParamType::FixedArray(Box::new(ParamType::Uint(256)), n), + DynSolType::FixedArray(Box::new(DynSolType::Uint(256)), n), )); } @@ -1518,9 +1595,11 @@ mod tests { #[test] fn test_global_vars() { + init_tracing(); + // https://docs.soliditylang.org/en/latest/cheatsheet.html#global-variables let global_variables = { - use ParamType::*; + use DynSolType::*; &[ // abi ("abi.decode(bytes, (uint8[13]))", Tuple(vec![FixedArray(Box::new(Uint(8)), 13)])), @@ -1597,9 +1676,13 @@ mod tests { ("type(C).runtimeCode", Bytes), ("type(I).interfaceId", FixedBytes(4)), ("type(uint256).min", Uint(256)), + ("type(int128).min", Int(128)), ("type(int256).min", Int(256)), ("type(uint256).max", Uint(256)), + ("type(int128).max", Int(128)), ("type(int256).max", Int(256)), + ("type(Enum1).min", Uint(256)), + ("type(Enum1).max", Uint(256)), // function ("this.run.address", Address), ("this.run.selector", FixedBytes(4)), @@ -1612,39 +1695,45 @@ mod tests { #[track_caller] fn source() -> SessionSource { // synchronize solc install - static PRE_INSTALL_SOLC_LOCK: Lazy> = Lazy::new(|| Mutex::new(false)); + static PRE_INSTALL_SOLC_LOCK: Mutex = Mutex::new(false); // on some CI targets installing results in weird malformed solc files, we try installing it // multiple times + let version = "0.8.20"; for _ in 0..3 { let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock().unwrap(); if !*is_preinstalled { - let solc = - Solc::find_or_install_svm_version("0.8.19").and_then(|solc| solc.version()); - if solc.is_err() { - // try reinstalling - let solc = Solc::blocking_install(&"0.8.19".parse().unwrap()); - if solc.map_err(SolcError::from).and_then(|solc| solc.version()).is_ok() { - *is_preinstalled = true; + let solc = Solc::find_or_install(&version.parse().unwrap()) + .map(|solc| (solc.version.clone(), solc)); + match solc { + Ok((v, solc)) => { + // successfully installed + let _ = sh_println!("found installed Solc v{v} @ {}", solc.solc.display()); break } - } else { - // successfully installed - break + Err(e) => { + // try reinstalling + let _ = sh_err!("error while trying to re-install Solc v{version}: {e}"); + let solc = Solc::blocking_install(&version.parse().unwrap()); + if solc.map_err(SolcError::from).is_ok() { + *is_preinstalled = true; + break + } + } } } } - let solc = Solc::find_or_install_svm_version("0.8.19").expect("could not install solc"); + let solc = Solc::find_or_install(&Version::new(0, 8, 19)).expect("could not install solc"); SessionSource::new(solc, Default::default()) } - fn array(ty: ParamType) -> ParamType { - ParamType::Array(Box::new(ty)) + fn array(ty: DynSolType) -> DynSolType { + DynSolType::Array(Box::new(ty)) } - fn fixed_array(ty: ParamType, len: usize) -> ParamType { - ParamType::FixedArray(Box::new(ty), len) + fn fixed_array(ty: DynSolType, len: usize) -> DynSolType { + DynSolType::FixedArray(Box::new(ty), len) } fn parse(s: &mut SessionSource, input: &str, clear: bool) -> IntermediateOutput { @@ -1654,14 +1743,16 @@ mod tests { s.drain_global_code(); } - let input = input.trim_end().trim_end_matches(';').to_string() + ";"; + *s = s.clone_with_new_line("enum Enum1 { A }".into()).unwrap().0; + + let input = format!("{};", input.trim_end().trim_end_matches(';')); let (mut _s, _) = s.clone_with_new_line(input).unwrap(); *s = _s.clone(); let s = &mut _s; if let Err(e) = s.parse() { for err in e { - eprintln!("{} @ {}:{}", err.message, err.loc.start(), err.loc.end()); + let _ = sh_eprintln!("{}:{}: {}", err.loc.start(), err.loc.end(), err.message); } let source = s.to_repl_source(); panic!("could not parse input:\n{source}") @@ -1687,16 +1778,15 @@ mod tests { (Type::from_expression(&expr).map(Type::map_special), intermediate) } - fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { + fn get_type_ethabi(s: &mut SessionSource, input: &str, clear: bool) -> Option { let (ty, intermediate) = get_type(s, input, clear); ty.and_then(|ty| ty.try_as_ethabi(Some(&intermediate))) } - #[track_caller] fn generic_type_test<'a, T, I>(s: &mut SessionSource, input: I) where T: AsRef + std::fmt::Display + 'a, - I: IntoIterator + 'a, + I: IntoIterator + 'a, { for (input, expected) in input.into_iter() { let input = input.as_ref(); @@ -1704,4 +1794,10 @@ mod tests { assert_eq!(ty.as_ref(), Some(expected), "\n{input}"); } } + + fn init_tracing() { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init(); + } } diff --git a/crates/chisel/src/lib.rs b/crates/chisel/src/lib.rs index c5668909ff750..5eeedada69bbf 100644 --- a/crates/chisel/src/lib.rs +++ b/crates/chisel/src/lib.rs @@ -1,36 +1,21 @@ -#![doc = include_str!("../README.md")] -#![warn(missing_docs)] -#![warn(unused_extern_crates)] -#![forbid(unsafe_code)] -#![forbid(where_clauses_object_safety)] +//! Chisel is a fast, utilitarian, and verbose Solidity REPL. -/// REPL input dispatcher module -pub mod dispatcher; +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -/// Builtin Chisel commands -pub mod cmd; +#[macro_use] +extern crate foundry_common; +pub mod cmd; +pub mod dispatcher; +pub mod executor; pub mod history; - -/// Chisel Environment Module +pub mod runner; pub mod session; - -/// Chisel Session Source wrapper pub mod session_source; - -/// REPL contract runner -pub mod runner; - -/// REPL contract executor -pub mod executor; - -/// A Solidity Helper module for rustyline pub mod solidity_helper; -/// Prelude of all chisel modules pub mod prelude { pub use crate::{ - cmd::*, dispatcher::*, executor::*, runner::*, session::*, session_source::*, - solidity_helper::*, + cmd::*, dispatcher::*, runner::*, session::*, session_source::*, solidity_helper::*, }; } diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index 45aa46f93342f..72b083e1fb882 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -3,17 +3,13 @@ //! This module contains the `ChiselRunner` struct, which assists with deploying //! and calling the REPL contract on a in-memory REVM instance. -use ethers::{ - prelude::{types::U256, Address}, - types::{Bytes, Log}, -}; +use alloy_primitives::{map::AddressHashMap, Address, Bytes, Log, U256}; use eyre::Result; use foundry_evm::{ - executor::{DeployResult, Executor, RawCallResult}, - trace::{CallTraceArena, TraceKind}, + executors::{DeployResult, Executor, RawCallResult}, + traces::{TraceKind, Traces}, }; use revm::interpreter::{return_ok, InstructionResult}; -use std::collections::BTreeMap; /// The function selector of the REPL contract's entrypoint, the `run()` function. static RUN_SELECTOR: [u8; 4] = [0xc0, 0x40, 0x62, 0x26]; @@ -42,21 +38,17 @@ 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 - pub labeled_addresses: BTreeMap, + pub labeled_addresses: AddressHashMap, /// Return data - pub returned: bytes::Bytes, + pub returned: Bytes, /// Called address pub address: Option
, /// EVM State at the final instruction of the `run()` function - pub state: Option<( - revm::interpreter::Stack, - revm::interpreter::Memory, - revm::interpreter::InstructionResult, - )>, + pub state: Option<(Vec, Vec, InstructionResult)>, } /// ChiselRunner implementation @@ -98,7 +90,7 @@ impl ChiselRunner { // We don't care about deployment traces / logs here let DeployResult { address, .. } = self .executor - .deploy(self.sender, bytecode.0, 0.into(), None) + .deploy(self.sender, bytecode, U256::ZERO, None) .map_err(|err| eyre::eyre!("Failed to deploy REPL contract:\n{}", err))?; // Reset the sender's balance to the initial balance for calls. @@ -111,19 +103,19 @@ impl ChiselRunner { } // Call the "run()" function of the REPL contract - let call_res = self.call(self.sender, address, Bytes::from(calldata), 0.into(), true); + let call_res = self.call(self.sender, address, Bytes::from(calldata), U256::from(0), true); call_res.map(|res| (address, res)) } - /// Executes the call + /// Executes the call. /// /// This will commit the changes if `commit` is true. /// /// This will return _estimated_ gas instead of the precise gas the call would consume, so it /// can be used as `gas_limit`. /// - /// Taken from [Forge's Script Runner](https://github.com/foundry-rs/foundry/blob/master/cli/src/cmd/forge/script/runner.rs) + /// Taken from Forge's script runner. fn call( &mut self, from: Address, @@ -133,7 +125,7 @@ impl ChiselRunner { commit: bool, ) -> eyre::Result { let fs_commit_changed = - if let Some(ref mut cheatcodes) = self.executor.inspector_config_mut().cheatcodes { + 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 @@ -141,11 +133,11 @@ impl ChiselRunner { false }; - let mut res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; + 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_mut().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 @@ -157,11 +149,11 @@ impl ChiselRunner { while (highest_gas_limit - lowest_gas_limit) > 1 { let mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; self.executor.env_mut().tx.gas_limit = mid_gas_limit; - let res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; + let res = self.executor.call_raw(from, to, calldata.clone(), value)?; match res.exit_reason { InstructionResult::Revert | InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { + InstructionResult::OutOfFunds => { lowest_gas_limit = mid_gas_limit; } _ => { @@ -175,7 +167,7 @@ impl ChiselRunner { { // update the gas gas_used = highest_gas_limit; - break + break; } last_highest_gas_limit = highest_gas_limit; } @@ -188,16 +180,16 @@ impl ChiselRunner { // if we changed `fs_commit` during gas limit search, re-execute the call with original // value if fs_commit_changed { - if let Some(ref mut cheatcodes) = self.executor.inspector_config_mut().cheatcodes { + if let Some(cheatcodes) = &mut self.executor.inspector_mut().cheatcodes { cheatcodes.fs_commit = !cheatcodes.fs_commit; } - res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; + res = self.executor.call_raw(from, to, calldata.clone(), value)?; } if commit { // if explicitly requested we can now commit the call - res = self.executor.call_raw_committing(from, to, calldata.0, value)?; + res = self.executor.transact_raw(from, to, calldata, value)?; } let RawCallResult { result, reverted, logs, traces, labels, chisel_state, .. } = res; @@ -210,8 +202,7 @@ impl ChiselRunner { traces: traces .map(|traces| { // Manually adjust gas for the trace to add back the stipend/real used gas - // TODO: For chisel, we may not want to perform this adjustment. - // traces.arena[0].trace.gas_cost = gas_used; + vec![(TraceKind::Execution, traces)] }) .unwrap_or_default(), diff --git a/crates/chisel/src/session.rs b/crates/chisel/src/session.rs index d0e49a7c2a5ec..2f293c1cd9172 100644 --- a/crates/chisel/src/session.rs +++ b/crates/chisel/src/session.rs @@ -4,9 +4,7 @@ //! wrapper for a serializable REPL session. use crate::prelude::{SessionSource, SessionSourceConfig}; - use eyre::Result; - use serde::{Deserialize, Serialize}; use std::path::Path; use time::{format_description, OffsetDateTime}; @@ -15,7 +13,7 @@ use time::{format_description, OffsetDateTime}; #[derive(Debug, Serialize, Deserialize)] pub struct ChiselSession { /// The `SessionSource` object that houses the REPL session. - pub session_source: Option, + pub session_source: SessionSource, /// The current session's identifier pub id: Option, } @@ -33,9 +31,8 @@ impl ChiselSession { /// A new instance of [ChiselSession] pub fn new(config: SessionSourceConfig) -> Result { let solc = config.solc()?; - // Return initialized ChiselSession with set solc version - Ok(Self { session_source: Some(SessionSource::new(solc, config)), id: None }) + Ok(Self { session_source: SessionSource::new(solc, config), id: None }) } /// Render the full source code for the current session. @@ -49,11 +46,7 @@ impl ChiselSession { /// This function will not panic, but will return a blank string if the /// session's [SessionSource] is None. pub fn contract_source(&self) -> String { - if let Some(source) = &self.session_source { - source.to_repl_source() - } else { - String::default() - } + self.session_source.to_repl_source() } /// Clears the cache directory @@ -147,9 +140,11 @@ impl ChiselSession { /// /// Optionally, the directory of the chisel cache. pub fn cache_dir() -> Result { - let home_dir = dirs::home_dir().ok_or(eyre::eyre!("Failed to grab home directory"))?; - let home_dir_str = - home_dir.to_str().ok_or(eyre::eyre!("Failed to convert home directory to string"))?; + let home_dir = + dirs::home_dir().ok_or_else(|| eyre::eyre!("Failed to grab home directory"))?; + let home_dir_str = home_dir + .to_str() + .ok_or_else(|| eyre::eyre!("Failed to convert home directory to string"))?; Ok(format!("{home_dir_str}/.foundry/cache/chisel/")) } @@ -210,9 +205,9 @@ impl ChiselSession { /// /// Optionally, an owned instance of the loaded chisel session. pub fn load(id: &str) -> Result { - let cache_dir = ChiselSession::cache_dir()?; + let cache_dir = Self::cache_dir()?; let contents = std::fs::read_to_string(Path::new(&format!("{cache_dir}chisel-{id}.json")))?; - let chisel_env: ChiselSession = serde_json::from_str(&contents)?; + let chisel_env: Self = serde_json::from_str(&contents)?; Ok(chisel_env) } @@ -224,14 +219,18 @@ impl ChiselSession { pub fn latest_cached_session() -> Result { let cache_dir = Self::cache_dir()?; let mut entries = std::fs::read_dir(cache_dir)?; - let mut latest = entries.next().ok_or(eyre::eyre!("No entries found!"))??; + let mut latest = entries.next().ok_or_else(|| eyre::eyre!("No entries found!"))??; for entry in entries { let entry = entry?; if entry.metadata()?.modified()? > latest.metadata()?.modified()? { latest = entry; } } - Ok(latest.path().to_str().ok_or(eyre::eyre!("Failed to get session path!"))?.to_string()) + Ok(latest + .path() + .to_str() + .ok_or_else(|| eyre::eyre!("Failed to get session path!"))? + .to_string()) } /// Loads the latest ChiselSession from the cache file @@ -242,13 +241,13 @@ impl ChiselSession { pub fn latest() -> Result { let last_session = Self::latest_cached_session()?; let last_session_contents = std::fs::read_to_string(Path::new(&last_session))?; - let chisel_env: ChiselSession = serde_json::from_str(&last_session_contents)?; + let chisel_env: Self = serde_json::from_str(&last_session_contents)?; Ok(chisel_env) } } /// Generic helper function that attempts to convert a type that has -/// an [Into] implementation into a formatted date string. +/// an [`Into`] implementation into a formatted date string. fn systemtime_strftime(dt: T, format: &str) -> Result where T: Into, diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index c7bea2b5fd567..f6e36395c507e 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -4,25 +4,30 @@ //! the REPL contract's source code. It provides simple compilation, parsing, and //! execution helpers. -use ethers_solc::{ - artifacts::{Source, Sources}, - CompilerInput, CompilerOutput, EvmVersion, Solc, -}; +use alloy_primitives::map::HashMap; use eyre::Result; use forge_fmt::solang_ext::SafeUnwrap; +use foundry_compilers::{ + artifacts::{CompilerOutput, Settings, SolcInput, Source, Sources}, + compilers::solc::Solc, +}; use foundry_config::{Config, SolcReq}; -use foundry_evm::executor::{opts::EvmOpts, Backend}; +use foundry_evm::{backend::Backend, opts::EvmOpts}; use semver::Version; use serde::{Deserialize, Serialize}; -use solang_parser::pt; -use std::{collections::HashMap, fs, path::PathBuf}; +use solang_parser::{diagnostics::Diagnostic, pt}; +use std::{fs, path::PathBuf}; +use walkdir::WalkDir; use yansi::Paint; +/// The minimum Solidity version of the `Vm` interface. +pub const MIN_VM_VERSION: Version = Version::new(0, 6, 2); + /// Solidity source for the `Vm` interface in [forge-std](https://github.com/foundry-rs/forge-std) static VM_SOURCE: &str = include_str!("../../../testdata/cheats/Vm.sol"); /// Intermediate output for the compiled [SessionSource] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct IntermediateOutput { /// All expressions within the REPL contract's run function and top level scope. #[serde(skip)] @@ -34,7 +39,7 @@ pub struct IntermediateOutput { /// A refined intermediate parse tree for a contract that enables easy lookups /// of definitions. -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct IntermediateContract { /// All function definitions within the contract #[serde(skip)] @@ -54,7 +59,7 @@ pub struct IntermediateContract { type IntermediateContracts = HashMap; /// Full compilation output for the [SessionSource] -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GeneratedOutput { /// The [IntermediateOutput] component pub intermediate: IntermediateOutput, @@ -63,12 +68,14 @@ pub struct GeneratedOutput { } /// Configuration for the [SessionSource] -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SessionSourceConfig { /// Foundry configuration pub foundry_config: Config, /// EVM Options pub evm_opts: EvmOpts, + /// Disable the default `Vm` import. + pub no_vm: bool, #[serde(skip)] /// In-memory REVM db for the session's runner. pub backend: Option, @@ -89,52 +96,33 @@ impl SessionSourceConfig { let solc_req = if let Some(solc_req) = self.foundry_config.solc.clone() { solc_req } else if let Some(version) = Solc::installed_versions().into_iter().max() { - SolcReq::Version(version.into()) + SolcReq::Version(version) } else { if !self.foundry_config.offline { - print!("{}", Paint::green("No solidity versions installed! ")); + sh_print!("{}", "No solidity versions installed! ".green())?; } // use default - SolcReq::Version("0.8.19".parse().unwrap()) + SolcReq::Version(Version::new(0, 8, 19)) }; match solc_req { SolcReq::Version(version) => { - // We now need to verify if the solc version provided is supported by the evm - // version set. If not, we bail and ask the user to provide a newer version. - // 1. Do we need solc 0.8.18 or higher? - let evm_version = self.foundry_config.evm_version; - let needs_post_merge_solc = evm_version >= EvmVersion::Paris; - // 2. Check if the version provided is less than 0.8.18 and bail, - // or leave it as-is if we don't need a post merge solc version or the version we - // have is good enough. - let v = if needs_post_merge_solc && version < Version::new(0, 8, 18) { - eyre::bail!("solc {version} is not supported by the set evm version: {evm_version}. Please install and use a version of solc higher or equal to 0.8.18. -You can also set the solc version in your foundry.toml.") + let solc = if let Some(solc) = Solc::find_svm_installed_version(&version)? { + solc } else { - version.to_string() - }; - - let mut solc = Solc::find_svm_installed_version(&v)?; - - if solc.is_none() { if self.foundry_config.offline { eyre::bail!("can't install missing solc {version} in offline mode") } - println!( - "{}", - Paint::green(format!("Installing solidity version {version}...")) - ); - Solc::blocking_install(&version)?; - solc = Solc::find_svm_installed_version(&v)?; - } - solc.ok_or_else(|| eyre::eyre!("Failed to install {version}")) + sh_println!("{}", format!("Installing solidity version {version}...").green())?; + Solc::blocking_install(&version)? + }; + Ok(solc) } SolcReq::Local(solc) => { if !solc.is_file() { eyre::bail!("`solc` {} does not exist", solc.display()); } - Ok(Solc::new(solc)) + Ok(Solc::new(solc)?) } } } @@ -143,7 +131,7 @@ You can also set the solc version in your foundry.toml.") /// REPL Session Source wrapper /// /// Heavily based on soli's [`ConstructedSource`](https://github.com/jpopesculian/soli/blob/master/src/main.rs#L166) -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SessionSource { /// The file name pub file_name: PathBuf, @@ -184,8 +172,11 @@ impl SessionSource { /// /// A new instance of [SessionSource] #[track_caller] - pub fn new(solc: Solc, config: SessionSourceConfig) -> Self { - debug_assert!(solc.version().is_ok(), "{:?}", solc.version()); + pub fn new(solc: Solc, mut config: SessionSourceConfig) -> Self { + if solc.version < MIN_VM_VERSION && !config.no_vm { + tracing::info!(version=%solc.version, minimum=%MIN_VM_VERSION, "Disabling VM injection"); + config.no_vm = true; + } Self { file_name: PathBuf::from("ReplContract.sol".to_string()), @@ -225,7 +216,7 @@ impl SessionSource { /// /// Optionally, a shallow-cloned [SessionSource] with the passed content appended to the /// source code. - pub fn clone_with_new_line(&self, mut content: String) -> Result<(SessionSource, bool)> { + pub fn clone_with_new_line(&self, mut content: String) -> Result<(Self, bool)> { let new_source = self.shallow_clone(); if let Some(parsed) = parse_fragment(new_source.solc, new_source.config, &content) .or_else(|| { @@ -284,59 +275,58 @@ impl SessionSource { /// Clears global code from the source pub fn drain_global_code(&mut self) -> &mut Self { - self.global_code.clear(); + String::clear(&mut self.global_code); self.generated_output = None; self } /// Clears top-level code from the source pub fn drain_top_level_code(&mut self) -> &mut Self { - self.top_level_code.clear(); + String::clear(&mut self.top_level_code); self.generated_output = None; self } /// Clears the "run()" function's code pub fn drain_run(&mut self) -> &mut Self { - self.run_code.clear(); + String::clear(&mut self.run_code); self.generated_output = None; self } - /// Generates and ethers_solc::CompilerInput from the source + /// Generates and [`SolcInput`] from the source. /// /// ### Returns /// - /// A [CompilerInput] object containing forge-std's `Vm` interface as well as the REPL contract + /// A [`SolcInput`] object containing forge-std's `Vm` interface as well as the REPL contract /// source. - pub fn compiler_input(&self) -> CompilerInput { + pub fn compiler_input(&self) -> SolcInput { let mut sources = Sources::new(); sources.insert(self.file_name.clone(), Source::new(self.to_repl_source())); + let remappings = self.config.foundry_config.get_all_remappings().collect::>(); + // Include Vm.sol if forge-std remapping is not available - if !self - .config - .foundry_config - .get_all_remappings() - .into_iter() - .any(|r| r.name.starts_with("forge-std")) - { - sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE.to_owned())); + if !self.config.no_vm && !remappings.iter().any(|r| r.name.starts_with("forge-std")) { + sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE)); } + let settings = Settings { + remappings, + evm_version: self + .config + .foundry_config + .evm_version + .normalize_version_solc(&self.solc.version), + ..Default::default() + }; + // we only care about the solidity source, so we can safely unwrap - let mut compiler_input = CompilerInput::with_sources(sources) + SolcInput::resolve_and_build(sources, settings) .into_iter() .next() - .expect("Solidity source not found"); - - // get all remappings from the config - compiler_input.settings.remappings = self.config.foundry_config.get_all_remappings(); - - // We also need to enforce the EVM version that the user has specified. - compiler_input.settings.evm_version = Some(self.config.foundry_config.evm_version); - - compiler_input + .map(|i| i.sanitized(&self.solc.version)) + .expect("Solidity source not found") } /// Compiles the source using [solang_parser] @@ -355,7 +345,7 @@ impl SessionSource { /// /// Optionally, a map of contract names to a vec of [IntermediateContract]s. pub fn generate_intermediate_contracts(&self) -> Result> { - let mut res_map = HashMap::new(); + let mut res_map = HashMap::default(); let parsed_map = self.compiler_input().sources; for source in parsed_map.values() { Self::get_intermediate_contract(&source.content, &mut res_map); @@ -371,7 +361,7 @@ impl SessionSource { // Construct variable definitions let variable_definitions = intermediate_contracts .get("REPL") - .ok_or(eyre::eyre!("Could not find intermediate REPL contract!"))? + .ok_or_else(|| eyre::eyre!("Could not find intermediate REPL contract!"))? .variable_definitions .clone() .into_iter() @@ -444,25 +434,28 @@ impl SessionSource { /// /// The [SessionSource] represented as a Forge Script contract. pub fn to_script_source(&self) -> String { - let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Version { major, minor, patch, .. } = self.solc.version; + let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; + + let script_import = + if !config.no_vm { "import {Script} from \"forge-std/Script.sol\";\n" } else { "" }; + format!( r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^{major}.{minor}.{patch}; -import {{Script}} from "forge-std/Script.sol"; -{} +{script_import} +{global_code} -contract {} is Script {{ - {} - +contract {contract_name} is Script {{ + {top_level_code} + /// @notice Script entry point function run() public {{ - {} + {run_code} }} -}} - "#, - self.global_code, self.contract_name, self.top_level_code, self.run_code, +}}"#, ) } @@ -472,26 +465,46 @@ contract {} is Script {{ /// /// The [SessionSource] represented as a REPL contract. pub fn to_repl_source(&self) -> String { - let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Version { major, minor, patch, .. } = self.solc.version; + let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; + let (mut vm_import, mut vm_constant) = (String::new(), String::new()); + if !config.no_vm { + // Check if there's any `forge-std` remapping and determine proper path to it by + // searching remapping path. + if let Some(remapping) = config + .foundry_config + .remappings + .iter() + .find(|remapping| remapping.name == "forge-std/") + { + if let Some(vm_path) = WalkDir::new(&remapping.path.path) + .into_iter() + .filter_map(|e| e.ok()) + .find(|e| e.file_name() == "Vm.sol") + { + vm_import = format!("import {{Vm}} from \"{}\";\n", vm_path.path().display()); + vm_constant = "Vm internal constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n".to_string(); + } + } + } + format!( r#" // SPDX-License-Identifier: UNLICENSED pragma solidity ^{major}.{minor}.{patch}; -import {{Vm}} from "forge-std/Vm.sol"; -{} +{vm_import} +{global_code} -contract {} {{ - Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); - {} +contract {contract_name} {{ + {vm_constant} + {top_level_code} /// @notice REPL contract entry point function run() public {{ - {} + {run_code} }} -}} - "#, - self.global_code, self.contract_name, self.top_level_code, self.run_code, +}}"#, ) } @@ -513,7 +526,11 @@ contract {} {{ pt::Import::Plain(s, _) | pt::Import::Rename(s, _, _) | pt::Import::GlobalSymbol(s, _, _) => { - let path = PathBuf::from(s.string); + let s = match s { + pt::ImportPath::Filename(s) => s.string, + pt::ImportPath::Path(p) => p.to_string(), + }; + let path = PathBuf::from(s); match fs::read_to_string(path) { Ok(source) => { @@ -604,13 +621,13 @@ impl IntermediateOutput { match self .intermediate_contracts .get("REPL") - .ok_or(eyre::eyre!("Could not find REPL intermediate contract!"))? + .ok_or_else(|| eyre::eyre!("Could not find REPL intermediate contract!"))? .function_definitions .get("run") - .ok_or(eyre::eyre!("Could not find run function definition in REPL contract!"))? + .ok_or_else(|| eyre::eyre!("Could not find run function definition in REPL contract!"))? .body .as_ref() - .ok_or(eyre::eyre!("Could not find run function body!"))? + .ok_or_else(|| eyre::eyre!("Could not find run function body!"))? { pt::Statement::Block { statements, .. } => Ok(statements), _ => eyre::bail!("Could not find statements within run function body!"), @@ -641,15 +658,28 @@ pub fn parse_fragment( ) -> Option { let mut base = SessionSource::new(solc, config); - if base.clone().with_run_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Function) + match base.clone().with_run_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Function), + Err(e) => debug_errors(&e), } - if base.clone().with_top_level_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Contract) + match base.clone().with_top_level_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Contract), + Err(e) => debug_errors(&e), } - if base.with_global_code(buffer).parse().is_ok() { - return Some(ParseTreeFragment::Source) + match base.with_global_code(buffer).parse() { + Ok(_) => return Some(ParseTreeFragment::Source), + Err(e) => debug_errors(&e), } None } + +fn debug_errors(errors: &[Diagnostic]) { + if !tracing::enabled!(tracing::Level::DEBUG) { + return; + } + + for error in errors { + tracing::debug!("error: {}", error.message); + } +} diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index 42641a1f0a8d2..c9df0c357214f 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -5,24 +5,22 @@ use crate::{ dispatcher::PROMPT_ARROW, - prelude::{ChiselCommand, COMMAND_LEADER}, + prelude::{ChiselCommand, COMMAND_LEADER, PROMPT_ARROW_STR}, }; use rustyline::{ completion::Completer, - highlight::Highlighter, + highlight::{CmdKind, Highlighter}, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, }; -use solang_parser::{ - lexer::{Lexer, Token}, - pt, +use solar_parse::{ + interface::{Session, SessionGlobals}, + token::{Token, TokenKind}, + Lexer, }; -use std::{borrow::Cow, str::FromStr}; -use yansi::{Color, Paint, Style}; - -/// The default pre-allocation for solang parsed comments -const DEFAULT_COMMENTS: usize = 5; +use std::{borrow::Cow, ops::Range, str::FromStr}; +use yansi::{Color, Style}; /// The maximum length of an ANSI prefix + suffix characters using [SolidityHelper]. /// @@ -33,20 +31,35 @@ const DEFAULT_COMMENTS: usize = 5; /// * 4 - suffix: `\x1B[0m` const MAX_ANSI_LEN: usize = 9; -/// `(start, style, end)` -pub type SpannedStyle = (usize, Style, usize); - /// A rustyline helper for Solidity code -#[derive(Clone, Debug, Default)] pub struct SolidityHelper { - /// Whether the dispatcher has errored. - pub errored: bool, + errored: bool, + + do_paint: bool, + sess: Session, + globals: SessionGlobals, +} + +impl Default for SolidityHelper { + fn default() -> Self { + Self::new() + } } impl SolidityHelper { /// Create a new SolidityHelper. pub fn new() -> Self { - Self::default() + Self { + errored: false, + do_paint: yansi::is_enabled(), + sess: Session::builder().with_silent_emitter(None).build(), + globals: SessionGlobals::new(), + } + } + + /// Returns whether the helper is in an errored state. + pub fn errored(&self) -> bool { + self.errored } /// Set the errored field. @@ -55,54 +68,9 @@ impl SolidityHelper { self } - /// Get styles for a solidity source string - pub fn get_styles(input: &str) -> Vec { - let mut comments = Vec::with_capacity(DEFAULT_COMMENTS); - let mut errors = Vec::with_capacity(5); - let mut out = Lexer::new(input, 0, &mut comments, &mut errors) - .map(|(start, token, end)| (start, token.style(), end)) - .collect::>(); - - // highlight comments too - let comments_iter = comments.into_iter().map(|comment| { - let loc = match comment { - pt::Comment::Line(loc, _) | - pt::Comment::Block(loc, _) | - pt::Comment::DocLine(loc, _) | - pt::Comment::DocBlock(loc, _) => loc, - }; - (loc.start(), Style::default().dimmed(), loc.end()) - }); - out.extend(comments_iter); - - out - } - - /// Get contiguous styles for a solidity source string - pub fn get_contiguous_styles(input: &str) -> Vec { - let mut styles = Self::get_styles(input); - styles.sort_unstable_by_key(|(start, _, _)| *start); - - let len = input.len(); - // len / 4 is just a random average of whitespaces in the input - let mut out = Vec::with_capacity(styles.len() + len / 4 + 1); - let mut index = 0; - for (start, style, end) in styles { - if index < start { - out.push((index, Style::default(), start)); - } - out.push((start, style, end)); - index = end; - } - if index < len { - out.push((index, Style::default(), len)); - } - out - } - - /// Highlights a solidity source string - pub fn highlight(input: &str) -> Cow { - if !Paint::is_enabled() { + /// Highlights a Solidity source string. + pub fn highlight<'a>(&self, input: &'a str) -> Cow<'a, str> { + if !self.do_paint() { return Cow::Borrowed(input) } @@ -119,7 +87,7 @@ impl SolidityHelper { // cmd out.push(COMMAND_LEADER); let cmd_res = ChiselCommand::from_str(cmd); - let style = Style::new(if cmd_res.is_ok() { Color::Green } else { Color::Red }); + let style = (if cmd_res.is_ok() { Color::Green } else { Color::Red }).foreground(); Self::paint_unchecked(cmd, style, &mut out); // rest @@ -133,52 +101,53 @@ impl SolidityHelper { Cow::Owned(out) } else { - let styles = Self::get_contiguous_styles(input); - let len = styles.len(); - if len == 0 { - Cow::Borrowed(input) - } else { - let mut out = String::with_capacity(input.len() + MAX_ANSI_LEN * len); - for (start, style, end) in styles { - Self::paint_unchecked(&input[start..end], style, &mut out); + let mut out = String::with_capacity(input.len() * 2); + self.with_contiguous_styles(input, |style, range| { + Self::paint_unchecked(&input[range], style, &mut out); + }); + Cow::Owned(out) + } + } + + /// Returns a list of styles and the ranges they should be applied to. + /// + /// Covers the entire source string, including any whitespace. + fn with_contiguous_styles(&self, input: &str, mut f: impl FnMut(Style, Range)) { + self.enter(|sess| { + let len = input.len(); + let mut index = 0; + for token in Lexer::new(sess, input) { + let range = token.span.lo().to_usize()..token.span.hi().to_usize(); + let style = token_style(&token); + if index < range.start { + f(Style::default(), index..range.start); } - Cow::Owned(out) + index = range.end; + f(style, range); } - } + if index < len { + f(Style::default(), index..len); + } + }); } /// Validate that a source snippet is closed (i.e., all braces and parenthesis are matched). - fn validate_closed(input: &str) -> ValidationResult { - let mut bracket_depth = 0usize; - let mut paren_depth = 0usize; - let mut brace_depth = 0usize; - let mut comments = Vec::with_capacity(DEFAULT_COMMENTS); - // returns on any encountered error, so allocate for just one - let mut errors = Vec::with_capacity(1); - for (_, token, _) in Lexer::new(input, 0, &mut comments, &mut errors) { - match token { - Token::OpenBracket => { - bracket_depth += 1; - } - Token::OpenCurlyBrace => { - brace_depth += 1; + fn validate_closed(&self, input: &str) -> ValidationResult { + let mut depth = [0usize; 3]; + self.enter(|sess| { + for token in Lexer::new(sess, input) { + match token.kind { + TokenKind::OpenDelim(delim) => { + depth[delim as usize] += 1; + } + TokenKind::CloseDelim(delim) => { + depth[delim as usize] = depth[delim as usize].saturating_sub(1); + } + _ => {} } - Token::OpenParenthesis => { - paren_depth += 1; - } - Token::CloseBracket => { - bracket_depth = bracket_depth.saturating_sub(1); - } - Token::CloseCurlyBrace => { - brace_depth = brace_depth.saturating_sub(1); - } - Token::CloseParenthesis => { - paren_depth = paren_depth.saturating_sub(1); - } - _ => {} } - } - if (bracket_depth | brace_depth | paren_depth) == 0 { + }); + if depth == [0; 3] { ValidationResult::Valid(None) } else { ValidationResult::Incomplete @@ -186,8 +155,7 @@ impl SolidityHelper { } /// Formats `input` with `style` into `out`, without checking `style.wrapping` or - /// `Paint::is_enabled` - #[inline] + /// `self.do_paint`. fn paint_unchecked(string: &str, style: Style, out: &mut String) { if style == Style::default() { out.push_str(string); @@ -198,20 +166,29 @@ impl SolidityHelper { } } - #[inline] fn paint_unchecked_owned(string: &str, style: Style) -> String { let mut out = String::with_capacity(MAX_ANSI_LEN + string.len()); Self::paint_unchecked(string, style, &mut out); out } + + /// Returns whether to color the output. + fn do_paint(&self) -> bool { + self.do_paint + } + + /// Enters the session. + fn enter(&self, f: impl FnOnce(&Session)) { + self.globals.set(|| self.sess.enter(|| f(&self.sess))); + } } impl Highlighter for SolidityHelper { fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { - Self::highlight(line) + self.highlight(line) } - fn highlight_char(&self, line: &str, pos: usize) -> bool { + fn highlight_char(&self, line: &str, pos: usize, _kind: CmdKind) -> bool { pos == line.len() } @@ -220,7 +197,7 @@ impl Highlighter for SolidityHelper { prompt: &'p str, _default: bool, ) -> Cow<'b, str> { - if !Paint::is_enabled() { + if !self.do_paint() { return Cow::Borrowed(prompt) } @@ -231,20 +208,17 @@ impl Highlighter for SolidityHelper { let id_end = prompt.find(')').unwrap(); let id_span = 5..id_end; let id = &prompt[id_span.clone()]; - out.replace_range(id_span, &Self::paint_unchecked_owned(id, Color::Yellow.style())); - out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.style())); + out.replace_range( + id_span, + &Self::paint_unchecked_owned(id, Color::Yellow.foreground()), + ); + out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.foreground())); } if let Some(i) = out.find(PROMPT_ARROW) { - let style = if self.errored { Color::Red.style() } else { Color::Green.style() }; - - let mut arrow = String::with_capacity(MAX_ANSI_LEN + 4); - - let _ = style.fmt_prefix(&mut arrow); - arrow.push(PROMPT_ARROW); - let _ = style.fmt_suffix(&mut arrow); - - out.replace_range(i..=i + 2, &arrow); + let style = + if self.errored { Color::Red.foreground() } else { Color::Green.foreground() }; + out.replace_range(i..=i + 2, &Self::paint_unchecked_owned(PROMPT_ARROW_STR, style)); } Cow::Owned(out) @@ -252,8 +226,8 @@ impl Highlighter for SolidityHelper { } impl Validator for SolidityHelper { - fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result { - Ok(Self::validate_closed(ctx.input())) + fn validate(&self, ctx: &mut ValidationContext<'_>) -> rustyline::Result { + Ok(self.validate_closed(ctx.input())) } } @@ -267,44 +241,32 @@ impl Hinter for SolidityHelper { impl Helper for SolidityHelper {} -/// Trait that assigns a color to a Token kind -pub trait TokenStyle { - /// Returns the style with which the token should be decorated with. - fn style(&self) -> Style; -} +#[allow(non_upper_case_globals)] +#[deny(unreachable_patterns)] +fn token_style(token: &Token) -> Style { + use solar_parse::{ + interface::kw::*, + token::{TokenKind::*, TokenLitKind::*}, + }; -/// [TokenStyle] implementation for [Token] -impl<'a> TokenStyle for Token<'a> { - fn style(&self) -> Style { - use Token::*; - match self { - StringLiteral(_, _) => Color::Green.style(), - - AddressLiteral(_) | - HexLiteral(_) | - Number(_, _) | - RationalNumber(_, _, _) | - HexNumber(_) | - True | - False => Color::Yellow.style(), + match token.kind { + Literal(Str | HexStr | UnicodeStr, _) => Color::Green.foreground(), + Literal(..) => Color::Yellow.foreground(), + Ident( Memory | Storage | Calldata | Public | Private | Internal | External | Constant | Pure | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | - Modifier | Immutable | Unchecked => Color::Cyan.style(), + Modifier | Immutable | Unchecked, + ) => Color::Cyan.foreground(), - Contract | Library | Interface | Function | Pragma | Import | Struct | Event | - Enum | Type | Constructor | As | Is | Using | New | Delete | Do | Continue | - Break | Throw | Emit | Return | Returns | Revert | For | While | If | Else | Try | - Catch | Assembly | Let | Leave | Switch | Case | Default | YulArrow | Arrow => { - Color::Magenta.style() - } + Ident(s) if s.is_elementary_type() => Color::Blue.foreground(), + Ident(Mapping) => Color::Blue.foreground(), - Uint(_) | Int(_) | Bytes(_) | Byte | DynamicBytes | Bool | Address | String | - Mapping => Color::Blue.style(), + Ident(s) if s.is_used_keyword() || s.is_yul_keyword() => Color::Magenta.foreground(), + Arrow | FatArrow => Color::Magenta.foreground(), - Identifier(_) => Style::default(), + Comment(..) => Color::Primary.dim(), - _ => Style::default(), - } + _ => Color::Primary.foreground(), } } diff --git a/crates/chisel/tests/cache.rs b/crates/chisel/tests/cache.rs index 9ff4e325719fb..7016bce09c76e 100644 --- a/crates/chisel/tests/cache.rs +++ b/crates/chisel/tests/cache.rs @@ -1,7 +1,6 @@ use chisel::session::ChiselSession; -use ethers_solc::EvmVersion; +use foundry_compilers::artifacts::EvmVersion; use foundry_config::Config; -use foundry_evm::executor::opts::EvmOpts; use serial_test::serial; use std::path::Path; @@ -43,12 +42,9 @@ fn test_write_session() { // Create a new session let mut env = ChiselSession::new(chisel::session_source::SessionSourceConfig { foundry_config, - evm_opts: EvmOpts::default(), - backend: None, - traces: false, - calldata: None, + ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {e}")); // Write the session let cached_session_name = env.write().unwrap(); @@ -76,7 +72,7 @@ fn test_write_session_with_name() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.id = Some(String::from("test")); // Write the session @@ -126,7 +122,7 @@ fn test_list_sessions() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); @@ -153,7 +149,7 @@ fn test_load_cache() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); // Load the session @@ -163,10 +159,7 @@ fn test_load_cache() { assert!(new_env.is_ok()); let new_env = new_env.unwrap(); assert_eq!(new_env.id.unwrap(), String::from("0")); - assert_eq!( - new_env.session_source.unwrap().to_repl_source(), - env.session_source.unwrap().to_repl_source() - ); + assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); } #[test] @@ -184,7 +177,7 @@ fn test_write_same_session_multiple_times() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); env.write().unwrap(); env.write().unwrap(); @@ -207,7 +200,7 @@ fn test_load_latest_cache() { foundry_config: foundry_config.clone(), ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); let wait_time = std::time::Duration::from_millis(100); @@ -217,7 +210,7 @@ fn test_load_latest_cache() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env2.write().unwrap(); // Load the latest session @@ -225,8 +218,5 @@ fn test_load_latest_cache() { // Validate the session assert_eq!(new_env.id.unwrap(), "1"); - assert_eq!( - new_env.session_source.unwrap().to_repl_source(), - env.session_source.unwrap().to_repl_source() - ); + assert_eq!(new_env.session_source.to_repl_source(), env.session_source.to_repl_source()); } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 07734dd122ac6..7fe67041e3f1b 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -9,45 +9,49 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -# foundry internal -foundry-config.workspace = true +forge-fmt.workspace = true foundry-common.workspace = true +foundry-config.workspace = true +foundry-debugger.workspace = true foundry-evm.workspace = true -ui.workspace = true +foundry-wallets.workspace = true -# aws -rusoto_core = { version = "0.48", default-features = false } -rusoto_kms = { version = "0.48", default-features = false } +foundry-compilers = { workspace = true, features = ["full"] } -# eth -ethers = { workspace = true, features = ["aws", "ledger", "trezor"] } +alloy-eips.workspace = true +alloy-dyn-abi.workspace = true +alloy-json-abi.workspace = true +alloy-primitives.workspace = true +alloy-provider.workspace = true +alloy-rlp.workspace = true +alloy-chains.workspace = true -async-trait = "0.1" clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -color-eyre = "0.6" +color-eyre.workspace = true dotenvy = "0.15" -eyre = "0.6" -hex = { workspace = true, features = ["serde"] } +eyre.workspace = true +futures.workspace = true indicatif = "0.17" itertools.workspace = true -once_cell = "1" -regex = { version = "1", default-features = false } -rpassword = "7" -serde = { version = "1", features = ["derive"] } -strsim = "0.10" -strum = { version = "0.25", features = ["derive"] } -thiserror = "1" -tokio = { version = "1", features = ["macros"] } -tracing = "0.1" -tracing-error = "0.2" -tracing-subscriber = { version = "0.3", features = ["registry", "env-filter", "fmt"] } -yansi = "0.5" +rayon.workspace = true +regex = { workspace = true, default-features = false } +serde_json.workspace = true +serde.workspace = true +strsim = "0.11" +strum = { workspace = true, features = ["derive"] } +tokio = { workspace = true, features = ["macros"] } +tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] } +tracing.workspace = true +yansi.workspace = true + +tracing-tracy = { version = "0.11", optional = true } [dev-dependencies] -tempfile = "3.7" +tempfile.workspace = true [features] -default = ["rustls"] -rustls = ["ethers/rustls", "rusoto_core/rustls"] -openssl = ["ethers/openssl"] +tracy = ["dep:tracing-tracy"] diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index c1ee8fe29a95f..147d2461f407f 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -1,31 +1,55 @@ -use eyre::{EyreHandler, Result}; -use std::error::Error; -use tracing::error; -use yansi::Paint; +use eyre::EyreHandler; +use itertools::Itertools; +use std::{error::Error, fmt}; -/// A custom context type for Foundry specific error reporting via `eyre` -#[derive(Debug)] -pub struct Handler; +/// A custom context type for Foundry specific error reporting via `eyre`. +pub struct Handler { + debug_handler: Option>, +} + +impl Default for Handler { + fn default() -> Self { + Self::new() + } +} + +impl Handler { + /// Create a new instance of the `Handler`. + pub fn new() -> Self { + Self { debug_handler: None } + } + + /// Override the debug handler with a custom one. + pub fn debug_handler(mut self, debug_handler: Option>) -> Self { + self.debug_handler = debug_handler; + self + } +} impl EyreHandler for Handler { - fn debug( - &self, - error: &(dyn Error + 'static), - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { + fn display(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { + use fmt::Display; + foundry_common::errors::dedup_chain(error).into_iter().format("; ").fmt(f) + } + + fn debug(&self, error: &(dyn Error + 'static), f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(debug_handler) = &self.debug_handler { + return debug_handler.debug(error, f); + } + if f.alternate() { - return core::fmt::Debug::fmt(error, f) + return fmt::Debug::fmt(error, f) } - writeln!(f)?; - write!(f, "{}", Paint::red(error))?; + let errors = foundry_common::errors::dedup_chain(error); - if let Some(cause) = error.source() { - write!(f, "\n\nContext:")?; + let (error, sources) = errors.split_first().unwrap(); + write!(f, "{error}")?; - let multiple = cause.source().is_some(); - let errors = std::iter::successors(Some(cause), |e| (*e).source()); + if !sources.is_empty() { + write!(f, "\n\nContext:")?; - for (n, error) in errors.enumerate() { + let multiple = sources.len() > 1; + for (n, error) in sources.iter().enumerate() { writeln!(f)?; if multiple { write!(f, "- Error #{n}: {error}")?; @@ -37,9 +61,15 @@ impl EyreHandler for Handler { Ok(()) } + + fn track_caller(&mut self, location: &'static std::panic::Location<'static>) { + if let Some(debug_handler) = &mut self.debug_handler { + debug_handler.track_caller(location); + } + } } -/// Installs the Foundry eyre hook as the global error report hook. +/// Installs the Foundry [`eyre`] and [`panic`](mod@std::panic) hooks as the global ones. /// /// # Details /// @@ -48,28 +78,21 @@ impl EyreHandler for Handler { /// verbose debug-centric handler is installed. /// /// Panics are always caught by the more debug-centric handler. -#[cfg_attr(windows, inline(never))] -pub fn install() -> Result<()> { - let debug_enabled = std::env::var("FOUNDRY_DEBUG").is_ok(); - - if debug_enabled { - color_eyre::install()?; - } else { - let (panic_hook, _) = color_eyre::config::HookBuilder::default() - .panic_section( - "This is a bug. Consider reporting it at https://github.com/foundry-rs/foundry", - ) - .into_hooks(); - panic_hook.install(); - // see - if cfg!(windows) { - if let Err(err) = eyre::set_hook(Box::new(move |_| Box::new(Handler))) { - error!(?err, "failed to install panic hook"); - } - } else { - eyre::set_hook(Box::new(move |_| Box::new(Handler)))?; - } +pub fn install() { + if std::env::var_os("RUST_BACKTRACE").is_none() { + std::env::set_var("RUST_BACKTRACE", "1"); } - Ok(()) + let panic_section = + "This is a bug. Consider reporting it at https://github.com/foundry-rs/foundry"; + let (panic_hook, debug_hook) = + color_eyre::config::HookBuilder::default().panic_section(panic_section).into_hooks(); + panic_hook.install(); + let debug_hook = debug_hook.into_eyre_hook(); + let debug = std::env::var_os("FOUNDRY_DEBUG").is_some(); + if let Err(e) = eyre::set_hook(Box::new(move |e| { + Box::new(Handler::new().debug_handler(debug.then(|| debug_hook(e)))) + })) { + debug!("failed to install eyre error hook: {e}"); + } } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 39f4e95765dd2..9c1fb848edaa9 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,6 +1,16 @@ -#![warn(unused_crate_dependencies)] +//! # foundry-cli +//! +//! Common CLI utilities. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; pub mod handler; pub mod opts; -pub mod stdin; pub mod utils; diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index a9fbffabb16b3..b9de478c3f237 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -1,10 +1,13 @@ -use super::ProjectPathsArgs; -use crate::{opts::CompilerArgs, utils::LoadConfig}; +use super::ProjectPathOpts; +use crate::{opts::CompilerOpts, utils::LoadConfig}; use clap::{Parser, ValueHint}; -use ethers::solc::{ - artifacts::RevertStrings, remappings::Remapping, utils::canonicalized, Project, -}; use eyre::Result; +use foundry_compilers::{ + artifacts::{remappings::Remapping, RevertStrings}, + compilers::multi::MultiCompiler, + utils::canonicalized, + Project, +}; use foundry_config::{ figment, figment::{ @@ -12,61 +15,78 @@ use foundry_config::{ value::{Dict, Map, Value}, Figment, Metadata, Profile, Provider, }, - providers::remappings::Remappings, - Config, + filter::SkipBuildFilter, + Config, Remappings, }; use serde::Serialize; use std::path::PathBuf; -#[derive(Debug, Clone, Parser, Serialize, Default)] -#[clap(next_help_heading = "Build options")] -pub struct CoreBuildArgs { +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Build options")] +pub struct BuildOpts { /// Clear the cache and artifacts folder and recompile. - #[clap(long, help_heading = "Cache options")] + #[arg(long, help_heading = "Cache options")] #[serde(skip)] pub force: bool, + /// Disable the cache. + #[arg(long)] + #[serde(skip)] + pub no_cache: bool, + /// Set pre-linked libraries. - #[clap(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] + #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, /// Ignore solc warnings by error code. - #[clap(long, help_heading = "Compiler options", value_name = "ERROR_CODES")] + #[arg(long, help_heading = "Compiler options", value_name = "ERROR_CODES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub ignored_error_codes: Vec, /// Warnings will trigger a compiler error - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub deny_warnings: bool, /// Do not auto-detect the `solc` version. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub no_auto_detect: bool, /// Specify the solc version, or a path to a local solc, to build with. /// /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`. - #[clap(long = "use", help_heading = "Compiler options", value_name = "SOLC_VERSION")] + #[arg( + long = "use", + alias = "compiler-version", + help_heading = "Compiler options", + value_name = "SOLC_VERSION" + )] #[serde(skip)] pub use_solc: Option, /// Do not access the network. /// /// Missing solc versions will not be installed. - #[clap(help_heading = "Compiler options", long)] + #[arg(help_heading = "Compiler options", long)] #[serde(skip)] pub offline: bool, /// Use the Yul intermediate representation compilation pipeline. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub via_ir: bool, + /// Do not append any metadata to the bytecode. + /// + /// This is equivalent to setting `bytecode_hash` to `none` and `cbor_metadata` to `false`. + #[arg(long, help_heading = "Compiler options")] + #[serde(skip)] + pub no_metadata: bool, + /// The path to the contract artifacts folder. - #[clap( + #[arg( long = "out", short, help_heading = "Project options", @@ -80,22 +100,17 @@ pub struct CoreBuildArgs { /// /// Possible values are "default", "strip" (remove), /// "debug" (Solidity-generated revert strings) and "verboseDebug" - #[clap(long, help_heading = "Project options", value_name = "REVERT")] + #[arg(long, help_heading = "Project options", value_name = "REVERT")] #[serde(skip)] pub revert_strings: Option, - /// Don't print anything on startup. - #[clap(long, help_heading = "Compiler options")] - #[serde(skip)] - pub silent: bool, - /// Generate build info files. - #[clap(long, help_heading = "Project options")] + #[arg(long, help_heading = "Project options")] #[serde(skip)] pub build_info: bool, /// Output path to directory that build info files will be written to. - #[clap( + #[arg( long, help_heading = "Project options", value_hint = ValueHint::DirPath, @@ -105,23 +120,39 @@ pub struct CoreBuildArgs { #[serde(skip_serializing_if = "Option::is_none")] pub build_info_path: Option, - #[clap(flatten)] + /// Use EOF-enabled solc binary. Enables via-ir and sets EVM version to Prague. Requires Docker + /// to be installed. + /// + /// Note that this is a temporary solution until the EOF support is merged into the main solc + /// release. + #[arg(long)] + #[serde(skip)] + pub eof: bool, + + /// Skip building files whose names contain the given filter. + /// + /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. + #[arg(long, num_args(1..))] + #[serde(skip)] + pub skip: Option>, + + #[command(flatten)] #[serde(flatten)] - pub compiler: CompilerArgs, + pub compiler: CompilerOpts, - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] - pub project_paths: ProjectPathsArgs, + pub project_paths: ProjectPathOpts, } -impl CoreBuildArgs { +impl BuildOpts { /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see - /// [`utils::find_project_root_path`] and merges the cli `BuildArgs` into it before returning - /// [`foundry_config::Config::project()`] - pub fn project(&self) -> Result { - let config = self.try_load_config_emit_warnings()?; + /// `find_project_root` and merges the cli `BuildArgs` into it before returning + /// [`foundry_config::Config::project()`]). + pub fn project(&self) -> Result> { + let config = self.load_config()?; Ok(config.project()?) } @@ -133,9 +164,9 @@ impl CoreBuildArgs { } // Loads project's figment and merges the build cli arguments into it -impl<'a> From<&'a CoreBuildArgs> for Figment { - fn from(args: &'a CoreBuildArgs) -> Self { - let figment = if let Some(ref config_path) = args.project_paths.config_path { +impl<'a> From<&'a BuildOpts> for Figment { + fn from(args: &'a BuildOpts) -> Self { + let mut figment = if let Some(ref config_path) = args.project_paths.config_path { if !config_path.exists() { panic!("error: config-path `{}` does not exist", config_path.display()) } @@ -149,27 +180,23 @@ impl<'a> From<&'a CoreBuildArgs> for Figment { }; // remappings should stack - let mut remappings = Remappings::new_with_remappings(args.project_paths.get_remappings()); + let mut remappings = Remappings::new_with_remappings(args.project_paths.get_remappings()) + .with_figment(&figment); remappings .extend(figment.extract_inner::>("remappings").unwrap_or_default()); - figment.merge(("remappings", remappings.into_inner())).merge(args) - } -} + figment = figment.merge(("remappings", remappings.into_inner())).merge(args); -impl<'a> From<&'a CoreBuildArgs> for Config { - fn from(args: &'a CoreBuildArgs) -> Self { - let figment: Figment = args.into(); - let mut config = Config::from_provider(figment).sanitized(); - // if `--config-path` is set we need to adjust the config's root path to the actual root - // path for the project, otherwise it will the parent dir of the `--config-path` - if args.project_paths.config_path.is_some() { - config.__root = args.project_paths.project_root().into(); - } - config + if let Some(skip) = &args.skip { + let mut skip = skip.iter().map(|s| s.file_pattern().to_string()).collect::>(); + skip.extend(figment.extract_inner::>("skip").unwrap_or_default()); + figment = figment.merge(("skip", skip)); + }; + + figment } } -impl Provider for CoreBuildArgs { +impl Provider for BuildOpts { fn metadata(&self) -> Metadata { Metadata::named("Core Build Args Provider") } @@ -199,16 +226,30 @@ impl Provider for CoreBuildArgs { dict.insert("via_ir".to_string(), true.into()); } + if self.no_metadata { + dict.insert("bytecode_hash".to_string(), "none".into()); + dict.insert("cbor_metadata".to_string(), false.into()); + } + if self.force { dict.insert("force".to_string(), self.force.into()); } + // we need to ensure no_cache set accordingly + if self.no_cache { + dict.insert("cache".to_string(), false.into()); + } + if self.build_info { dict.insert("build_info".to_string(), self.build_info.into()); } - if self.compiler.optimize { - dict.insert("optimizer".to_string(), self.compiler.optimize.into()); + if self.compiler.ast { + dict.insert("ast".to_string(), true.into()); + } + + if let Some(optimize) = self.compiler.optimize { + dict.insert("optimizer".to_string(), optimize.into()); } if !self.compiler.extra_output.is_empty() { @@ -227,6 +268,10 @@ impl Provider for CoreBuildArgs { dict.insert("revert_strings".to_string(), revert.to_string().into()); } + if self.eof { + dict.insert("eof".to_string(), true.into()); + } + Ok(Map::from([(Config::selected_profile(), dict)])) } } diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index 0014f5bfb61f0..55c61dcbbedd7 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -1,32 +1,41 @@ use clap::Parser; -use ethers::solc::{artifacts::output_selection::ContractOutputSelection, EvmVersion}; +use foundry_compilers::artifacts::{output_selection::ContractOutputSelection, EvmVersion}; use serde::Serialize; mod core; -pub use self::core::CoreBuildArgs; +pub use self::core::BuildOpts; mod paths; -pub use self::paths::ProjectPathsArgs; +pub use self::paths::ProjectPathOpts; // A set of solc compiler settings that can be set via command line arguments, which are intended // to be merged into an existing `foundry_config::Config`. // // See also `BuildArgs`. -#[derive(Default, Debug, Clone, Parser, Serialize)] -#[clap(next_help_heading = "Compiler options")] -pub struct CompilerArgs { +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Compiler options")] +pub struct CompilerOpts { + /// Includes the AST as JSON in the compiler output. + #[arg(long, help_heading = "Compiler options")] + #[serde(skip)] + pub ast: bool, + /// The target EVM version. - #[clap(long, value_name = "VERSION")] + #[arg(long, value_name = "VERSION")] #[serde(skip_serializing_if = "Option::is_none")] pub evm_version: Option, /// Activate the Solidity optimizer. - #[clap(long)] + #[arg(long, default_missing_value="true", num_args = 0..=1)] #[serde(skip)] - pub optimize: bool, + pub optimize: Option, - /// The number of optimizer runs. - #[clap(long, value_name = "RUNS")] + /// The number of runs specifies roughly how often each opcode of the deployed code will be + /// executed across the life-time of the contract. This means it is a trade-off parameter + /// between code size (deploy cost) and code execution cost (cost after deployment). + /// An `optimizer_runs` parameter of `1` will produce short but expensive code. In contrast, a + /// larger `optimizer_runs` parameter will produce longer but more gas efficient code. + #[arg(long, value_name = "RUNS")] #[serde(skip_serializing_if = "Option::is_none")] pub optimizer_runs: Option, @@ -34,15 +43,15 @@ pub struct CompilerArgs { /// /// Example keys: evm.assembly, ewasm, ir, irOptimized, metadata /// - /// For a full description, see https://docs.soliditylang.org/en/v0.8.13/using-the-compiler.html#input-description - #[clap(long, num_args(1..), value_name = "SELECTOR")] + /// For a full description, see + #[arg(long, num_args(1..), value_name = "SELECTOR")] #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output: Vec, /// Extra output to write to separate files. /// /// Valid values: metadata, ir, irOptimized, ewasm, evm.assembly - #[clap(long, num_args(1..), value_name = "SELECTOR")] + #[arg(long, num_args(1..), value_name = "SELECTOR")] #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output_files: Vec, } @@ -53,15 +62,15 @@ mod tests { #[test] fn can_parse_evm_version() { - let args: CompilerArgs = - CompilerArgs::parse_from(["foundry-cli", "--evm-version", "london"]); + let args: CompilerOpts = + CompilerOpts::parse_from(["foundry-cli", "--evm-version", "london"]); assert_eq!(args.evm_version, Some(EvmVersion::London)); } #[test] fn can_parse_extra_output() { - let args: CompilerArgs = - CompilerArgs::parse_from(["foundry-cli", "--extra-output", "metadata", "ir-optimized"]); + let args: CompilerOpts = + CompilerOpts::parse_from(["foundry-cli", "--extra-output", "metadata", "ir-optimized"]); assert_eq!( args.extra_output, vec![ContractOutputSelection::Metadata, ContractOutputSelection::IrOptimized] @@ -70,7 +79,7 @@ mod tests { #[test] fn can_parse_extra_output_files() { - let args: CompilerArgs = CompilerArgs::parse_from([ + let args: CompilerOpts = CompilerOpts::parse_from([ "foundry-cli", "--extra-output-files", "metadata", diff --git a/crates/cli/src/opts/build/paths.rs b/crates/cli/src/opts/build/paths.rs index b067f39517ed2..263e03c14881a 100644 --- a/crates/cli/src/opts/build/paths.rs +++ b/crates/cli/src/opts/build/paths.rs @@ -1,6 +1,6 @@ use clap::{Parser, ValueHint}; -use ethers::solc::remappings::Remapping; use eyre::Result; +use foundry_compilers::artifacts::remappings::Remapping; use foundry_config::{ figment, figment::{ @@ -8,67 +8,74 @@ use foundry_config::{ value::{Dict, Map, Value}, Metadata, Profile, Provider, }, - find_project_root_path, remappings_from_env_var, Config, + find_project_root, remappings_from_env_var, Config, }; use serde::Serialize; use std::path::PathBuf; /// Common arguments for a project's paths. -#[derive(Debug, Clone, Parser, Serialize, Default)] -#[clap(next_help_heading = "Project options")] -pub struct ProjectPathsArgs { +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Project options")] +pub struct ProjectPathOpts { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(skip)] pub root: Option, /// The contracts source directory. - #[clap(long, short = 'C', value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, short = 'C', value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(rename = "src", skip_serializing_if = "Option::is_none")] pub contracts: Option, /// The project's remappings. - #[clap(long, short = 'R')] + #[arg(long, short = 'R')] #[serde(skip)] pub remappings: Vec, /// The project's remappings from the environment. - #[clap(long, value_name = "ENV")] + #[arg(long, value_name = "ENV")] #[serde(skip)] pub remappings_env: Option, /// The path to the compiler cache. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(skip_serializing_if = "Option::is_none")] pub cache_path: Option, /// The path to the library folder. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(rename = "libs", skip_serializing_if = "Vec::is_empty")] pub lib_paths: Vec, /// Use the Hardhat-style project layout. /// /// This is the same as using: `--contracts contracts --lib-paths node_modules`. - #[clap(long, conflicts_with = "contracts", visible_alias = "hh")] + #[arg(long, conflicts_with = "contracts", visible_alias = "hh")] #[serde(skip)] pub hardhat: bool, /// Path to the config file. - #[clap(long, value_hint = ValueHint::FilePath, value_name = "FILE")] + #[arg(long, value_hint = ValueHint::FilePath, value_name = "FILE")] #[serde(skip)] pub config_path: Option, } -impl ProjectPathsArgs { - /// Returns the root directory to use for configuring the [Project] +impl ProjectPathOpts { + /// Returns the root directory to use for configuring the project. /// - /// This will be the `--root` argument if provided, otherwise see [find_project_root_path()] + /// This will be the `--root` argument if provided, otherwise see [`find_project_root`]. + /// + /// # Panics + /// + /// Panics if the project root directory cannot be found. See [`find_project_root`]. + #[track_caller] pub fn project_root(&self) -> PathBuf { - self.root.clone().unwrap_or_else(|| find_project_root_path(None).unwrap()) + self.root + .clone() + .unwrap_or_else(|| find_project_root(None).expect("could not determine project root")) } /// Returns the remappings to add to the config @@ -83,10 +90,10 @@ impl ProjectPathsArgs { } } -foundry_config::impl_figment_convert!(ProjectPathsArgs); +foundry_config::impl_figment_convert!(ProjectPathOpts); // Make this args a `figment::Provider` so that it can be merged into the `Config` -impl Provider for ProjectPathsArgs { +impl Provider for ProjectPathOpts { fn metadata(&self) -> Metadata { Metadata::named("Project Paths Args Provider") } diff --git a/crates/cli/src/opts/chain.rs b/crates/cli/src/opts/chain.rs index 1649c6fa586b2..2762481e08e86 100644 --- a/crates/cli/src/opts/chain.rs +++ b/crates/cli/src/opts/chain.rs @@ -1,7 +1,6 @@ use clap::builder::{PossibleValuesParser, TypedValueParser}; -use ethers::types::Chain as NamedChain; use eyre::Result; -use foundry_config::Chain; +use foundry_config::{Chain, NamedChain}; use std::ffi::OsStr; use strum::VariantNames; @@ -31,7 +30,7 @@ impl TypedValueParser for ChainValueParser { let s = value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?; if let Ok(id) = s.parse() { - Ok(Chain::Id(id)) + Ok(Chain::from_id(id)) } else { // NamedChain::VARIANTS is a subset of all possible variants, since there are aliases: // mumbai instead of polygon-mumbai etc @@ -40,7 +39,7 @@ impl TypedValueParser for ChainValueParser { // the error to the user s.parse() .map_err(|_| self.inner.parse_ref(cmd, arg, value).unwrap_err()) - .map(Chain::Named) + .map(Chain::from_named) } } } diff --git a/crates/cli/src/opts/dependency.rs b/crates/cli/src/opts/dependency.rs index ee6b0ee99021f..c3d802dbe208d 100644 --- a/crates/cli/src/opts/dependency.rs +++ b/crates/cli/src/opts/dependency.rs @@ -1,18 +1,20 @@ //! CLI dependency parsing use eyre::Result; -use once_cell::sync::Lazy; use regex::Regex; -use std::str::FromStr; +use std::{str::FromStr, sync::LazyLock}; -static GH_REPO_REGEX: Lazy = Lazy::new(|| Regex::new(r"[\w-]+/[\w.-]+").unwrap()); +static GH_REPO_REGEX: LazyLock = LazyLock::new(|| Regex::new(r"[\w-]+/[\w.-]+").unwrap()); /// Git repo prefix regex -pub static GH_REPO_PREFIX_REGEX: Lazy = Lazy::new(|| { - Regex::new(r"((git@)|(git\+https://)|(https://)|(org-([A-Za-z0-9-])+@))?(?P[A-Za-z0-9-]+)\.(?P[A-Za-z0-9-]+)(/|:)") +pub static GH_REPO_PREFIX_REGEX: LazyLock = LazyLock::new(|| { + Regex::new(r"((git@)|(git\+https://)|(https://)|https://(?P[^@]+)@|(org-([A-Za-z0-9-])+@))?(?P[A-Za-z0-9-]+)\.(?P[A-Za-z0-9-]+)(/|:)") .unwrap() }); +static VERSION_PREFIX_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r#"@(tag|branch|rev)="#).unwrap()); + const GITHUB: &str = "github.com"; const VERSION_SEPARATOR: char = '@'; const ALIAS_SEPARATOR: char = '='; @@ -20,7 +22,8 @@ const ALIAS_SEPARATOR: char = '='; /// Commonly used aliases for solidity repos, /// /// These will be autocorrected when used in place of the `org` -const COMMON_ORG_ALIASES: &[(&str, &str); 1] = &[("@openzeppelin", "openzeppelin")]; +const COMMON_ORG_ALIASES: &[(&str, &str); 2] = + &[("@openzeppelin", "openzeppelin"), ("@aave", "aave")]; /// A git dependency which will be installed as a submodule /// @@ -34,7 +37,7 @@ const COMMON_ORG_ALIASES: &[(&str, &str); 1] = &[("@openzeppelin", "openzeppelin /// /// Non Github URLs must be provided with an https:// prefix. /// Adding dependencies as local paths is not supported yet. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Dependency { /// The name of the dependency pub name: String, @@ -49,6 +52,12 @@ pub struct Dependency { impl FromStr for Dependency { type Err = eyre::Error; fn from_str(dependency: &str) -> Result { + // Handle dependency exact ref type (`@tag=`, `@branch=` or `@rev=`)`. + // Only extract version for first tag/branch/commit specified. + let url_and_version: Vec<&str> = VERSION_PREFIX_REGEX.split(dependency).collect(); + let dependency = url_and_version[0]; + let mut tag_or_branch = url_and_version.get(1).map(|version| version.to_string()); + // everything before "=" should be considered the alias let (mut alias, dependency) = if let Some(split) = dependency.split_once(ALIAS_SEPARATOR) { (Some(String::from(split.0)), split.1.to_string()) @@ -72,7 +81,15 @@ impl FromStr for Dependency { let brand = captures.name("brand").unwrap().as_str(); let tld = captures.name("tld").unwrap().as_str(); let project = GH_REPO_PREFIX_REGEX.replace(dependency, ""); - Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git"))) + if let Some(token) = captures.name("token") { + Some(format!( + "https://{}@{brand}.{tld}/{}", + token.as_str(), + project.trim_end_matches(".git") + )) + } else { + Some(format!("https://{brand}.{tld}/{}", project.trim_end_matches(".git"))) + } } else { // If we don't have a URL and we don't have a valid // GitHub repository name, then we assume this is the alias. @@ -96,30 +113,31 @@ impl FromStr for Dependency { // `tag` does not contain a slash let mut split = url_with_version.rsplit(VERSION_SEPARATOR); - let mut tag = None; let mut url = url_with_version.as_str(); - let maybe_tag = split.next().unwrap(); - if let Some(actual_url) = split.next() { - if !maybe_tag.contains('/') { - tag = Some(maybe_tag.to_string()); - url = actual_url; + if tag_or_branch.is_none() { + let maybe_tag_or_branch = split.next().unwrap(); + if let Some(actual_url) = split.next() { + if !maybe_tag_or_branch.contains('/') { + tag_or_branch = Some(maybe_tag_or_branch.to_string()); + url = actual_url; + } } } let url = url.to_string(); let name = url .split('/') - .last() + .next_back() .ok_or_else(|| eyre::eyre!("no dependency name found"))? .to_string(); - (Some(url), Some(name), tag) + (Some(url), Some(name), tag_or_branch) } else { (None, None, None) }; - Ok(Dependency { name: name.or_else(|| alias.clone()).unwrap(), url, tag, alias }) + Ok(Self { name: name.or_else(|| alias.clone()).unwrap(), url, tag, alias }) } } @@ -138,7 +156,7 @@ impl Dependency { #[cfg(test)] mod tests { use super::*; - use ethers::solc::info::ContractInfo; + use foundry_compilers::info::ContractInfo; #[test] fn parses_dependencies() { @@ -334,6 +352,21 @@ mod tests { ); } + #[test] + fn can_parse_aave() { + let dep = Dependency::from_str("@aave/aave-v3-core").unwrap(); + assert_eq!(dep.name, "aave-v3-core"); + assert_eq!(dep.url, Some("https://github.com/aave/aave-v3-core".to_string())); + } + + #[test] + fn can_parse_aave_with_alias() { + let dep = Dependency::from_str("@aave=aave/aave-v3-core").unwrap(); + assert_eq!(dep.name, "aave-v3-core"); + assert_eq!(dep.alias, Some("@aave".to_string())); + assert_eq!(dep.url, Some("https://github.com/aave/aave-v3-core".to_string())); + } + #[test] fn can_parse_org_ssh_url() { let org_url = "org-git12345678@github.com:my-org/my-repo.git"; @@ -345,4 +378,40 @@ mod tests { let dep: Dependency = "org-git12345678@github.com:my-org/my-repo.git".parse().unwrap(); assert_eq!(dep.url.unwrap(), "https://github.com/my-org/my-repo"); } + + #[test] + fn can_parse_with_explicit_ref_type() { + let dep = Dependency::from_str("smartcontractkit/ccip@tag=contracts-ccip/v1.2.1").unwrap(); + assert_eq!(dep.name, "ccip"); + assert_eq!(dep.url, Some("https://github.com/smartcontractkit/ccip".to_string())); + assert_eq!(dep.tag, Some("contracts-ccip/v1.2.1".to_string())); + assert_eq!(dep.alias, None); + + let dep = + Dependency::from_str("smartcontractkit/ccip@branch=contracts-ccip/v1.2.1").unwrap(); + assert_eq!(dep.name, "ccip"); + assert_eq!(dep.url, Some("https://github.com/smartcontractkit/ccip".to_string())); + assert_eq!(dep.tag, Some("contracts-ccip/v1.2.1".to_string())); + assert_eq!(dep.alias, None); + + let dep = Dependency::from_str("smartcontractkit/ccip@rev=80eb41b").unwrap(); + assert_eq!(dep.name, "ccip"); + assert_eq!(dep.url, Some("https://github.com/smartcontractkit/ccip".to_string())); + assert_eq!(dep.tag, Some("80eb41b".to_string())); + assert_eq!(dep.alias, None); + } + + #[test] + fn can_parse_https_with_github_token() { + // + let dep = Dependency::from_str( + "https://ghp_mytoken@github.com/private-org/precompiles-solidity.git", + ) + .unwrap(); + assert_eq!(dep.name, "precompiles-solidity"); + assert_eq!( + dep.url, + Some("https://ghp_mytoken@github.com/private-org/precompiles-solidity".to_string()) + ); + } } diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs deleted file mode 100644 index 4686925daac3d..0000000000000 --- a/crates/cli/src/opts/ethereum.rs +++ /dev/null @@ -1,143 +0,0 @@ -use super::{ChainValueParser, Wallet, WalletSigner}; -use clap::Parser; -use eyre::Result; -use foundry_config::{ - figment::{ - self, - value::{Dict, Map, Value}, - Metadata, Profile, - }, - impl_figment_convert_cast, Chain, Config, -}; -use serde::Serialize; -use std::borrow::Cow; - -const FLASHBOTS_URL: &str = "https://rpc.flashbots.net"; - -#[derive(Clone, Debug, Default, Parser)] -pub struct RpcOpts { - /// The RPC endpoint. - #[clap(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] - pub url: Option, - - /// Use the Flashbots RPC URL (https://rpc.flashbots.net). - #[clap(long)] - pub flashbots: bool, -} - -impl_figment_convert_cast!(RpcOpts); - -impl figment::Provider for RpcOpts { - fn metadata(&self) -> Metadata { - Metadata::named("RpcOpts") - } - - fn data(&self) -> Result, figment::Error> { - Ok(Map::from([(Config::selected_profile(), self.dict())])) - } -} - -impl RpcOpts { - /// Returns the RPC endpoint. - pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { - let url = match (self.flashbots, self.url.as_deref(), config) { - (true, ..) => Some(Cow::Borrowed(FLASHBOTS_URL)), - (false, Some(url), _) => Some(Cow::Borrowed(url)), - (false, None, Some(config)) => config.get_rpc_url().transpose()?, - (false, None, None) => None, - }; - Ok(url) - } - - pub fn dict(&self) -> Dict { - let mut dict = Dict::new(); - if let Ok(Some(url)) = self.url(None) { - dict.insert("eth_rpc_url".into(), url.into_owned().into()); - } - dict - } -} - -#[derive(Clone, Debug, Default, Parser, Serialize)] -pub struct EtherscanOpts { - /// The Etherscan (or equivalent) API key - #[clap(short = 'e', long = "etherscan-api-key", alias = "api-key", env = "ETHERSCAN_API_KEY")] - #[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")] - pub key: Option, - - /// The chain name or EIP-155 chain ID - #[clap( - short, - long, - alias = "chain-id", - env = "CHAIN", - value_parser = ChainValueParser::default(), - )] - #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none")] - pub chain: Option, -} - -impl_figment_convert_cast!(EtherscanOpts); - -impl figment::Provider for EtherscanOpts { - fn metadata(&self) -> Metadata { - Metadata::named("EtherscanOpts") - } - - fn data(&self) -> Result, figment::Error> { - Ok(Map::from([(Config::selected_profile(), self.dict())])) - } -} - -impl EtherscanOpts { - pub fn key<'a>(&'a self, config: Option<&'a Config>) -> Option> { - match (self.key.as_deref(), config) { - (Some(key), _) => Some(Cow::Borrowed(key)), - (None, Some(config)) => config.get_etherscan_api_key(self.chain).map(Cow::Owned), - (None, None) => None, - } - } - - pub fn dict(&self) -> Dict { - Value::serialize(self).unwrap().into_dict().unwrap() - } -} - -#[derive(Clone, Debug, Default, Parser)] -#[clap(next_help_heading = "Ethereum options")] -pub struct EthereumOpts { - #[clap(flatten)] - pub rpc: RpcOpts, - - #[clap(flatten)] - pub etherscan: EtherscanOpts, - - #[clap(flatten)] - pub wallet: Wallet, -} - -impl_figment_convert_cast!(EthereumOpts); - -impl EthereumOpts { - pub async fn signer(&self) -> Result { - self.wallet.signer(self.etherscan.chain.unwrap_or_default().id()).await - } -} - -// Make this args a `Figment` so that it can be merged into the `Config` -impl figment::Provider for EthereumOpts { - fn metadata(&self) -> Metadata { - Metadata::named("Ethereum Opts Provider") - } - - fn data(&self) -> Result, figment::Error> { - let mut dict = self.etherscan.dict(); - dict.extend(self.rpc.dict()); - - if let Some(from) = self.wallet.from { - dict.insert("sender".to_string(), format!("{from:?}").into()); - } - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} diff --git a/crates/cli/src/opts/global.rs b/crates/cli/src/opts/global.rs new file mode 100644 index 0000000000000..665c504ab904d --- /dev/null +++ b/crates/cli/src/opts/global.rs @@ -0,0 +1,92 @@ +use clap::{ArgAction, Parser}; +use foundry_common::{ + shell::{ColorChoice, OutputFormat, OutputMode, Shell, Verbosity}, + version::{IS_NIGHTLY_VERSION, NIGHTLY_VERSION_WARNING_MESSAGE}, +}; +use serde::{Deserialize, Serialize}; + +/// Global arguments for the CLI. +#[derive(Clone, Debug, Default, Serialize, Deserialize, Parser)] +pub struct GlobalArgs { + /// Verbosity level of the log messages. + /// + /// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). + /// + /// Depending on the context the verbosity levels have different meanings. + /// + /// For example, the verbosity levels of the EVM are: + /// - 2 (-vv): Print logs for all tests. + /// - 3 (-vvv): Print execution traces for failing tests. + /// - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. + /// - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. + #[arg(help_heading = "Display options", global = true, short, long, verbatim_doc_comment, conflicts_with = "quiet", action = ArgAction::Count)] + verbosity: Verbosity, + + /// Do not print log messages. + #[arg(help_heading = "Display options", global = true, short, long, alias = "silent")] + quiet: bool, + + /// Format log messages as JSON. + #[arg(help_heading = "Display options", global = true, long, alias = "format-json", conflicts_with_all = &["quiet", "color"])] + json: bool, + + /// The color of the log messages. + #[arg(help_heading = "Display options", global = true, long, value_enum)] + color: Option, + + /// Number of threads to use. Specifying 0 defaults to the number of logical cores. + #[arg(global = true, long, short = 'j', visible_alias = "jobs")] + threads: Option, +} + +impl GlobalArgs { + /// Initialize the global options. + pub fn init(&self) -> eyre::Result<()> { + // Set the global shell. + self.shell().set(); + + // Initialize the thread pool only if `threads` was requested to avoid unnecessary overhead. + if self.threads.is_some() { + self.force_init_thread_pool()?; + } + + // Display a warning message if the current version is not stable. + if std::env::var("FOUNDRY_DISABLE_NIGHTLY_WARNING").is_err() && + !self.json && + IS_NIGHTLY_VERSION + { + let _ = sh_warn!("{}", NIGHTLY_VERSION_WARNING_MESSAGE); + } + + Ok(()) + } + + /// Create a new shell instance. + pub fn shell(&self) -> Shell { + let mode = match self.quiet { + true => OutputMode::Quiet, + false => OutputMode::Normal, + }; + let color = self.json.then_some(ColorChoice::Never).or(self.color).unwrap_or_default(); + let format = match self.json { + true => OutputFormat::Json, + false => OutputFormat::Text, + }; + + Shell::new_with(format, mode, color, self.verbosity) + } + + /// Initialize the global thread pool. + pub fn force_init_thread_pool(&self) -> eyre::Result<()> { + init_thread_pool(self.threads.unwrap_or(0)) + } +} + +/// Initialize the global thread pool. +pub fn init_thread_pool(threads: usize) -> eyre::Result<()> { + rayon::ThreadPoolBuilder::new() + .thread_name(|i| format!("foundry-{i}")) + .num_threads(threads) + .build_global()?; + Ok(()) +} diff --git a/crates/cli/src/opts/mod.rs b/crates/cli/src/opts/mod.rs index 76480f400ed84..3b6b914c14aaa 100644 --- a/crates/cli/src/opts/mod.rs +++ b/crates/cli/src/opts/mod.rs @@ -1,13 +1,13 @@ mod build; mod chain; mod dependency; -mod ethereum; +mod global; +mod rpc; mod transaction; -mod wallet; pub use build::*; pub use chain::*; pub use dependency::*; -pub use ethereum::*; +pub use global::*; +pub use rpc::*; pub use transaction::*; -pub use wallet::*; diff --git a/crates/cli/src/opts/rpc.rs b/crates/cli/src/opts/rpc.rs new file mode 100644 index 0000000000000..344efe73e8514 --- /dev/null +++ b/crates/cli/src/opts/rpc.rs @@ -0,0 +1,215 @@ +use crate::opts::ChainValueParser; +use alloy_chains::ChainKind; +use clap::Parser; +use eyre::Result; +use foundry_config::{ + figment::{ + self, + value::{Dict, Map}, + Metadata, Profile, + }, + impl_figment_convert_cast, Chain, Config, +}; +use foundry_wallets::WalletOpts; +use serde::Serialize; +use std::borrow::Cow; + +const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast"; + +#[derive(Clone, Debug, Default, Parser)] +pub struct RpcOpts { + /// The RPC endpoint, default value is http://localhost:8545. + #[arg(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] + pub url: Option, + + /// Use the Flashbots RPC URL with fast mode (). + /// + /// This shares the transaction privately with all registered builders. + /// + /// See: + #[arg(long)] + pub flashbots: bool, + + /// JWT Secret for the RPC endpoint. + /// + /// The JWT secret will be used to create a JWT for a RPC. For example, the following can be + /// used to simulate a CL `engine_forkchoiceUpdated` call: + /// + /// cast rpc --jwt-secret engine_forkchoiceUpdatedV2 + /// '["0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", + /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", + /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc"]' + #[arg(long, env = "ETH_RPC_JWT_SECRET")] + pub jwt_secret: Option, + + /// Timeout for the RPC request in seconds. + /// + /// The specified timeout will be used to override the default timeout for RPC requests. + /// + /// Default value: 45 + #[arg(long, env = "ETH_RPC_TIMEOUT")] + pub rpc_timeout: Option, + + /// Specify custom headers for RPC requests. + #[arg(long, alias = "headers", env = "ETH_RPC_HEADERS", value_delimiter(','))] + pub rpc_headers: Option>, +} + +impl_figment_convert_cast!(RpcOpts); + +impl figment::Provider for RpcOpts { + fn metadata(&self) -> Metadata { + Metadata::named("RpcOpts") + } + + fn data(&self) -> Result, figment::Error> { + Ok(Map::from([(Config::selected_profile(), self.dict())])) + } +} + +impl RpcOpts { + /// Returns the RPC endpoint. + pub fn url<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + let url = match (self.flashbots, self.url.as_deref(), config) { + (true, ..) => Some(Cow::Borrowed(FLASHBOTS_URL)), + (false, Some(url), _) => Some(Cow::Borrowed(url)), + (false, None, Some(config)) => config.get_rpc_url().transpose()?, + (false, None, None) => None, + }; + Ok(url) + } + + /// Returns the JWT secret. + pub fn jwt<'a>(&'a self, config: Option<&'a Config>) -> Result>> { + let jwt = match (self.jwt_secret.as_deref(), config) { + (Some(jwt), _) => Some(Cow::Borrowed(jwt)), + (None, Some(config)) => config.get_rpc_jwt_secret()?, + (None, None) => None, + }; + Ok(jwt) + } + + pub fn dict(&self) -> Dict { + let mut dict = Dict::new(); + if let Ok(Some(url)) = self.url(None) { + dict.insert("eth_rpc_url".into(), url.into_owned().into()); + } + if let Ok(Some(jwt)) = self.jwt(None) { + dict.insert("eth_rpc_jwt".into(), jwt.into_owned().into()); + } + if let Some(rpc_timeout) = self.rpc_timeout { + dict.insert("eth_rpc_timeout".into(), rpc_timeout.into()); + } + if let Some(headers) = &self.rpc_headers { + dict.insert("eth_rpc_headers".into(), headers.clone().into()); + } + dict + } +} + +#[derive(Clone, Debug, Default, Serialize, Parser)] +pub struct EtherscanOpts { + /// The Etherscan (or equivalent) API key. + #[arg(short = 'e', long = "etherscan-api-key", alias = "api-key", env = "ETHERSCAN_API_KEY")] + #[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")] + pub key: Option, + + /// The chain name or EIP-155 chain ID. + #[arg( + short, + long, + alias = "chain-id", + env = "CHAIN", + value_parser = ChainValueParser::default(), + )] + #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none")] + pub chain: Option, +} + +impl_figment_convert_cast!(EtherscanOpts); + +impl figment::Provider for EtherscanOpts { + fn metadata(&self) -> Metadata { + Metadata::named("EtherscanOpts") + } + + fn data(&self) -> Result, figment::Error> { + Ok(Map::from([(Config::selected_profile(), self.dict())])) + } +} + +impl EtherscanOpts { + /// Returns true if the Etherscan API key is set. + pub fn has_key(&self) -> bool { + self.key.as_ref().filter(|key| !key.trim().is_empty()).is_some() + } + + /// Returns the Etherscan API key. + pub fn key(&self) -> Option { + self.key.as_ref().filter(|key| !key.trim().is_empty()).cloned() + } + + pub fn dict(&self) -> Dict { + let mut dict = Dict::new(); + if let Some(key) = self.key() { + dict.insert("etherscan_api_key".into(), key.into()); + } + if let Some(chain) = self.chain { + if let ChainKind::Id(id) = chain.kind() { + dict.insert("chain_id".into(), (*id).into()); + } else { + dict.insert("chain_id".into(), chain.to_string().into()); + } + } + dict + } +} + +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Ethereum options")] +pub struct EthereumOpts { + #[command(flatten)] + pub rpc: RpcOpts, + + #[command(flatten)] + pub etherscan: EtherscanOpts, + + #[command(flatten)] + pub wallet: WalletOpts, +} + +impl_figment_convert_cast!(EthereumOpts); + +// Make this args a `Figment` so that it can be merged into the `Config` +impl figment::Provider for EthereumOpts { + fn metadata(&self) -> Metadata { + Metadata::named("Ethereum Opts Provider") + } + + fn data(&self) -> Result, figment::Error> { + let mut dict = self.etherscan.dict(); + dict.extend(self.rpc.dict()); + + if let Some(from) = self.wallet.from { + dict.insert("sender".to_string(), from.to_string().into()); + } + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_etherscan_opts() { + let args: EtherscanOpts = + EtherscanOpts::parse_from(["foundry-cli", "--etherscan-api-key", "dummykey"]); + assert_eq!(args.key(), Some("dummykey".to_string())); + + let args: EtherscanOpts = + EtherscanOpts::parse_from(["foundry-cli", "--etherscan-api-key", ""]); + assert!(!args.has_key()); + } +} diff --git a/crates/cli/src/opts/transaction.rs b/crates/cli/src/opts/transaction.rs index d32cff8a80588..c0e229c35cda3 100644 --- a/crates/cli/src/opts/transaction.rs +++ b/crates/cli/src/opts/transaction.rs @@ -1,17 +1,47 @@ -use crate::utils::{parse_ether_value, parse_u256}; +use std::str::FromStr; + +use crate::utils::{parse_ether_value, parse_json}; +use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; +use alloy_primitives::{hex, Address, U256, U64}; +use alloy_rlp::Decodable; use clap::Parser; -use ethers::types::U256; -use serde::Serialize; -#[derive(Parser, Debug, Clone, Serialize)] -#[clap(next_help_heading = "Transaction options")] +/// CLI helper to parse a EIP-7702 authorization list. +/// Can be either a hex-encoded signed authorization or an address. +#[derive(Clone, Debug)] +pub enum CliAuthorizationList { + /// If an address is provided, we sign the authorization delegating to provided address. + Address(Address), + /// If RLP-encoded authorization is provided, we decode it and attach to transaction. + Signed(SignedAuthorization), +} + +impl FromStr for CliAuthorizationList { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + if let Ok(addr) = Address::from_str(s) { + Ok(Self::Address(addr)) + } else if let Ok(auth) = SignedAuthorization::decode(&mut hex::decode(s)?.as_ref()) { + Ok(Self::Signed(auth)) + } else { + eyre::bail!("Failed to decode authorization") + } + } +} + +#[derive(Clone, Debug, Parser)] +#[command(next_help_heading = "Transaction options")] pub struct TransactionOpts { /// Gas limit for the transaction. - #[clap(long, env = "ETH_GAS_LIMIT", value_parser = parse_u256)] + #[arg(long, env = "ETH_GAS_LIMIT")] pub gas_limit: Option, - /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[clap( + /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions, either + /// specified in wei, or as a string with a unit type. + /// + /// Examples: 1ether, 10gwei, 0.01ether + #[arg( long, env = "ETH_GAS_PRICE", value_parser = parse_ether_value, @@ -20,7 +50,7 @@ pub struct TransactionOpts { pub gas_price: Option, /// Max priority fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_PRIORITY_GAS_PRICE", value_parser = parse_ether_value, @@ -33,16 +63,50 @@ pub struct TransactionOpts { /// /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] pub value: Option, /// Nonce for the transaction. - #[clap(long, value_parser = parse_u256)] - pub nonce: Option, + #[arg(long)] + pub nonce: Option, /// Send a legacy transaction instead of an EIP1559 transaction. /// /// This is automatically enabled for common networks without EIP1559. - #[clap(long)] + #[arg(long)] pub legacy: bool, + + /// Send a EIP-4844 blob transaction. + #[arg(long, conflicts_with = "legacy")] + pub blob: bool, + + /// Gas price for EIP-4844 blob transaction. + #[arg(long, conflicts_with = "legacy", value_parser = parse_ether_value, env = "ETH_BLOB_GAS_PRICE", value_name = "BLOB_PRICE")] + pub blob_gas_price: Option, + + /// EIP-7702 authorization list. + /// + /// Can be either a hex-encoded signed authorization or an address. + #[arg(long, conflicts_with_all = &["legacy", "blob"])] + pub auth: Option, + + /// EIP-2930 access list. + /// + /// Accepts either a JSON-encoded access list or an empty value to create the access list + /// via an RPC call to `eth_createAccessList`. To retrieve only the access list portion, use + /// the `cast access-list` command. + #[arg(long, value_parser = parse_json::)] + pub access_list: Option>, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_priority_gas_tx_opts() { + let args: TransactionOpts = + TransactionOpts::parse_from(["foundry-cli", "--priority-gas-price", "100"]); + assert!(args.priority_gas_price.is_some()); + } } diff --git a/crates/cli/src/opts/wallet/error.rs b/crates/cli/src/opts/wallet/error.rs deleted file mode 100644 index e8ad90a891de3..0000000000000 --- a/crates/cli/src/opts/wallet/error.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Errors when working with wallets - -use hex::FromHexError; - -#[derive(Debug, thiserror::Error)] -pub enum PrivateKeyError { - #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] - InvalidHex(#[from] FromHexError), - #[error("Failed to create wallet from private key. Invalid private key. But env var {0} exists. Is the `$` anchor missing?")] - ExistsAsEnvVar(String), -} diff --git a/crates/cli/src/opts/wallet/mod.rs b/crates/cli/src/opts/wallet/mod.rs deleted file mode 100644 index a85b4e7c77026..0000000000000 --- a/crates/cli/src/opts/wallet/mod.rs +++ /dev/null @@ -1,619 +0,0 @@ -use crate::opts::error::PrivateKeyError; -use async_trait::async_trait; -use clap::Parser; -use ethers::{ - signers::{ - coins_bip39::English, AwsSigner, AwsSignerError, HDPath as LedgerHDPath, Ledger, - LedgerError, LocalWallet, MnemonicBuilder, Signer, Trezor, TrezorError, TrezorHDPath, - WalletError, - }, - types::{ - transaction::{eip2718::TypedTransaction, eip712::Eip712}, - Address, Signature, - }, -}; -use eyre::{bail, Result, WrapErr}; -use foundry_common::fs; -use foundry_config::Config; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; -use serde::{Deserialize, Serialize}; -use std::{ - path::{Path, PathBuf}, - str::FromStr, -}; -use tracing::{instrument, trace}; - -pub mod multi_wallet; -pub use multi_wallet::*; - -pub mod error; - -/// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. -/// The raw wallet options can either be: -/// 1. Private Key (cleartext in CLI) -/// 2. Private Key (interactively via secure prompt) -/// 3. Mnemonic (via file path) -#[derive(Parser, Debug, Default, Clone, Serialize)] -#[clap(next_help_heading = "Wallet options - raw", about = None, long_about = None)] -pub struct RawWallet { - /// Open an interactive prompt to enter your private key. - #[clap(long, short)] - pub interactive: bool, - - /// Use the provided private key. - #[clap( - long, - value_name = "RAW_PRIVATE_KEY", - value_parser = foundry_common::clap_helpers::strip_0x_prefix - )] - pub private_key: Option, - - /// Use the mnemonic phrase of mnemonic file at the specified path. - #[clap(long, alias = "mnemonic-path")] - pub mnemonic: Option, - - /// Use a BIP39 passphrase for the mnemonic. - #[clap(long, value_name = "PASSPHRASE")] - pub mnemonic_passphrase: Option, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[clap(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] - pub hd_path: Option, - - /// Use the private key from the given mnemonic index. - /// - /// Used with --mnemonic-path. - #[clap(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] - pub mnemonic_index: u32, -} - -/// The wallet options can either be: -/// 1. Raw (via private key / mnemonic file, see `RawWallet`) -/// 2. Ledger -/// 3. Trezor -/// 4. Keystore (via file path) -/// 5. AWS KMS -#[derive(Parser, Debug, Default, Clone, Serialize)] -#[clap(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct Wallet { - /// The sender account. - #[clap( - long, - short, - value_name = "ADDRESS", - help_heading = "Wallet options - raw", - env = "ETH_FROM" - )] - pub from: Option
, - - #[clap(flatten)] - pub raw: RawWallet, - - /// Use the keystore in the given folder or file. - #[clap( - long = "keystore", - help_heading = "Wallet options - keystore", - value_name = "PATH", - env = "ETH_KEYSTORE" - )] - pub keystore_path: Option, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[clap( - long = "account", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAME", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_path" - )] - pub keystore_account_name: Option, - - /// The keystore password. - /// - /// Used with --keystore. - #[clap( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD" - )] - pub keystore_password: Option, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[clap( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_path", - value_name = "PASSWORD_FILE", - env = "ETH_PASSWORD" - )] - pub keystore_password_file: Option, - - /// Use a Ledger hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - #[clap(long, help_heading = "Wallet options - AWS KMS")] - pub aws: bool, -} - -impl From for Wallet { - fn from(options: RawWallet) -> Self { - Self { raw: options, ..Default::default() } - } -} - -impl Wallet { - pub fn interactive(&self) -> Result> { - Ok(if self.raw.interactive { Some(self.get_from_interactive()?) } else { None }) - } - - pub fn private_key(&self) -> Result> { - Ok(if let Some(ref private_key) = self.raw.private_key { - Some(self.get_from_private_key(private_key)?) - } else { - None - }) - } - - pub fn keystore(&self) -> Result> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // If keystore path is provided, use it, otherwise use default path + keystore account name - let keystore_path: Option = self.keystore_path.clone().or_else(|| { - self.keystore_account_name.as_ref().map(|keystore_name| { - default_keystore_dir.join(keystore_name).to_string_lossy().into_owned() - }) - }); - - self.get_from_keystore( - keystore_path.as_ref(), - self.keystore_password.as_ref(), - self.keystore_password_file.as_ref(), - ) - } - - pub fn mnemonic(&self) -> Result> { - Ok(if let Some(ref mnemonic) = self.raw.mnemonic { - Some(self.get_from_mnemonic( - mnemonic, - self.raw.mnemonic_passphrase.as_ref(), - self.raw.hd_path.as_ref(), - self.raw.mnemonic_index, - )?) - } else { - None - }) - } - - /// Returns the sender address of the signer or `from`. - pub async fn sender(&self) -> Address { - if let Ok(signer) = self.signer(0).await { - signer.address() - } else { - self.from.unwrap_or_else(Address::zero) - } - } - - /// Tries to resolve a local wallet from the provided options. - #[track_caller] - pub fn try_resolve_local_wallet(&self) -> Result> { - self.private_key() - .transpose() - .or_else(|| self.interactive().transpose()) - .or_else(|| self.mnemonic().transpose()) - .or_else(|| self.keystore().transpose()) - .transpose() - } - /// Returns a [Signer] corresponding to the provided private key, mnemonic or hardware signer. - #[instrument(skip(self), level = "trace")] - pub async fn signer(&self, chain_id: u64) -> Result { - trace!("start finding signer"); - - if self.ledger { - let derivation = match self.raw.hd_path.as_ref() { - Some(hd_path) => LedgerHDPath::Other(hd_path.clone()), - None => LedgerHDPath::LedgerLive(self.raw.mnemonic_index as usize), - }; - let ledger = Ledger::new(derivation, chain_id).await.wrap_err_with(|| { - "\ -Could not connect to Ledger device. -Make sure it's connected and unlocked, with no other desktop wallet apps open." - })?; - - Ok(WalletSigner::Ledger(ledger)) - } else if self.trezor { - let derivation = match self.raw.hd_path.as_ref() { - Some(hd_path) => TrezorHDPath::Other(hd_path.clone()), - None => TrezorHDPath::TrezorLive(self.raw.mnemonic_index as usize), - }; - - // cached to ~/.ethers-rs/trezor/cache/trezor.session - let trezor = Trezor::new(derivation, chain_id, None).await.wrap_err_with(|| { - "\ -Could not connect to Trezor device. -Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." - })?; - - Ok(WalletSigner::Trezor(trezor)) - } else if self.aws { - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); - - let kms = KmsClient::new_with_client(client, AwsRegion::default()); - - let key_id = std::env::var("AWS_KMS_KEY_ID")?; - - let aws_signer = AwsSigner::new(kms, key_id, chain_id).await?; - - Ok(WalletSigner::Aws(aws_signer)) - } else { - trace!("finding local key"); - - let maybe_local = self.try_resolve_local_wallet()?; - - let local = maybe_local.ok_or_else(|| { - eyre::eyre!( - "\ -Error accessing local wallet. Did you set a private key, mnemonic or keystore? -Run `cast send --help` or `forge create --help` and use the corresponding CLI -flag to set your key via: ---private-key, --mnemonic-path, --aws, --interactive, --trezor or --ledger. -Alternatively, if you're using a local node with unlocked accounts, -use the --unlocked flag and either set the `ETH_FROM` environment variable to the address -of the unlocked account you want to use, or provide the --from flag with the address directly." - ) - })?; - - Ok(WalletSigner::Local(local.with_chain_id(chain_id))) - } - } -} - -pub trait WalletTrait { - /// Returns the configured sender. - fn sender(&self) -> Option
; - - fn get_from_interactive(&self) -> Result { - let private_key = rpassword::prompt_password("Enter private key: ")?; - let private_key = private_key.strip_prefix("0x").unwrap_or(&private_key); - Ok(LocalWallet::from_str(private_key)?) - } - - #[track_caller] - fn get_from_private_key(&self, private_key: &str) -> Result { - let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); - match LocalWallet::from_str(privk) { - Ok(pk) => Ok(pk), - Err(err) => { - // helper closure to check if pk was meant to be an env var, this usually happens if - // `$` is missing - let ensure_not_env = |pk: &str| { - // check if pk was meant to be an env var - if !pk.starts_with("0x") && std::env::var(pk).is_ok() { - // SAFETY: at this point we know the user actually wanted to use an env var - // and most likely forgot the `$` anchor, so the - // `private_key` here is an unresolved env var - return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string())) - } - Ok(()) - }; - match err { - WalletError::HexError(err) => { - ensure_not_env(private_key)?; - return Err(PrivateKeyError::InvalidHex(err).into()) - } - WalletError::EcdsaError(_) => { - ensure_not_env(private_key)?; - } - _ => {} - }; - bail!("Failed to create wallet from private key: {err}") - } - } - } - - fn get_from_mnemonic( - &self, - mnemonic: &String, - passphrase: Option<&String>, - derivation_path: Option<&String>, - index: u32, - ) -> Result { - let mnemonic = if Path::new(mnemonic).is_file() { - fs::read_to_string(mnemonic)?.replace('\n', "") - } else { - mnemonic.to_owned() - }; - let builder = MnemonicBuilder::::default().phrase(mnemonic.as_str()); - let builder = if let Some(passphrase) = passphrase { - builder.password(passphrase.as_str()) - } else { - builder - }; - let builder = if let Some(hd_path) = derivation_path { - builder.derivation_path(hd_path.as_str())? - } else { - builder.index(index)? - }; - Ok(builder.build()?) - } - - /// Ensures the path to the keystore exists. - /// - /// if the path is a directory, it bails and asks the user to specify the keystore file - /// directly. - fn find_keystore_file(&self, path: impl AsRef) -> Result { - let path = path.as_ref(); - if !path.exists() { - bail!("Keystore file `{path:?}` does not exist") - } - - if path.is_dir() { - bail!("Keystore path `{path:?}` is a directory. Please specify the keystore file directly.") - } - - Ok(path.to_path_buf()) - } - - fn get_from_keystore( - &self, - keystore_path: Option<&String>, - keystore_password: Option<&String>, - keystore_password_file: Option<&String>, - ) -> Result> { - Ok(match (keystore_path, keystore_password, keystore_password_file) { - // Path and password provided - (Some(path), Some(password), _) => { - let path = self.find_keystore_file(path)?; - Some( - LocalWallet::decrypt_keystore(&path, password) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?, - ) - } - // Path and password file provided - (Some(path), _, Some(password_file)) => { - let path = self.find_keystore_file(path)?; - Some( - LocalWallet::decrypt_keystore(&path, self.password_from_file(password_file)?) - .wrap_err_with(|| format!("Failed to decrypt keystore {path:?} with password file {password_file:?}"))?, - ) - } - // Only Path provided -> interactive - (Some(path), None, None) => { - let path = self.find_keystore_file(path)?; - let password = rpassword::prompt_password("Enter keystore password:")?; - Some(LocalWallet::decrypt_keystore(path, password)?) - } - // Nothing provided - (None, _, _) => None, - }) - } - - /// Attempts to read the keystore password from the password file. - fn password_from_file(&self, password_file: impl AsRef) -> Result { - let password_file = password_file.as_ref(); - if !password_file.is_file() { - bail!("Keystore password file `{password_file:?}` does not exist") - } - - Ok(fs::read_to_string(password_file)?.trim_end().to_string()) - } -} - -impl WalletTrait for Wallet { - fn sender(&self) -> Option
{ - self.from - } -} - -#[derive(Debug, thiserror::Error)] -pub enum WalletSignerError { - #[error(transparent)] - Local(#[from] WalletError), - #[error(transparent)] - Ledger(#[from] LedgerError), - #[error(transparent)] - Trezor(#[from] TrezorError), - #[error(transparent)] - Aws(#[from] AwsSignerError), -} - -#[derive(Debug)] -pub enum WalletSigner { - Local(LocalWallet), - Ledger(Ledger), - Trezor(Trezor), - Aws(AwsSigner), -} - -impl From for WalletSigner { - fn from(wallet: LocalWallet) -> Self { - Self::Local(wallet) - } -} - -impl From for WalletSigner { - fn from(hw: Ledger) -> Self { - Self::Ledger(hw) - } -} - -impl From for WalletSigner { - fn from(hw: Trezor) -> Self { - Self::Trezor(hw) - } -} - -impl From for WalletSigner { - fn from(wallet: AwsSigner) -> Self { - Self::Aws(wallet) - } -} - -macro_rules! delegate { - ($s:ident, $inner:ident => $e:expr) => { - match $s { - Self::Local($inner) => $e, - Self::Ledger($inner) => $e, - Self::Trezor($inner) => $e, - Self::Aws($inner) => $e, - } - }; -} - -#[async_trait] -impl Signer for WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - delegate!(self, inner => inner.sign_message(message).await.map_err(Into::into)) - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - delegate!(self, inner => inner.sign_transaction(message).await.map_err(Into::into)) - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - delegate!(self, inner => inner.sign_typed_data(payload).await.map_err(Into::into)) - } - - fn address(&self) -> Address { - delegate!(self, inner => inner.address()) - } - - fn chain_id(&self) -> u64 { - delegate!(self, inner => inner.chain_id()) - } - - fn with_chain_id>(self, chain_id: T) -> Self { - match self { - Self::Local(inner) => Self::Local(inner.with_chain_id(chain_id)), - Self::Ledger(inner) => Self::Ledger(inner.with_chain_id(chain_id)), - Self::Trezor(inner) => Self::Trezor(inner.with_chain_id(chain_id)), - Self::Aws(inner) => Self::Aws(inner.with_chain_id(chain_id)), - } - } -} - -#[async_trait] -impl Signer for &WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>( - &self, - message: S, - ) -> Result { - (*self).sign_message(message).await - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - (*self).sign_transaction(message).await - } - - async fn sign_typed_data( - &self, - payload: &T, - ) -> Result { - (*self).sign_typed_data(payload).await - } - - fn address(&self) -> Address { - (*self).address() - } - - fn chain_id(&self) -> u64 { - (*self).chain_id() - } - - fn with_chain_id>(self, chain_id: T) -> Self { - let _ = chain_id; - self - } -} - -/// Excerpt of a keystore file. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct KeystoreFile { - pub address: Address, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn find_keystore() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-10-30T06-51-20.130356000Z--560d246fcddc9ea98a8b032c9a2f474efb493c28"); - let wallet: Wallet = Wallet::parse_from([ - "foundry-cli", - "--from", - "560d246fcddc9ea98a8b032c9a2f474efb493c28", - ]); - let file = wallet.find_keystore_file(&keystore_file).unwrap(); - assert_eq!(file, keystore_file); - } - - #[test] - fn illformed_private_key_generates_user_friendly_error() { - let wallet = Wallet { - raw: RawWallet { - interactive: false, - private_key: Some("123".to_string()), - mnemonic: None, - mnemonic_passphrase: None, - hd_path: None, - mnemonic_index: 0, - }, - from: None, - keystore_path: None, - keystore_account_name: None, - keystore_password: None, - keystore_password_file: None, - ledger: false, - trezor: false, - aws: false, - }; - match wallet.private_key() { - Ok(_) => { - panic!("illformed private key shouldn't decode") - } - Err(x) => { - assert!( - x.to_string().contains("Failed to create wallet"), - "Error message is not user-friendly" - ); - } - } - } - - #[test] - fn gets_password_from_file() { - let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore/password"); - let wallet: Wallet = Wallet::parse_from(["foundry-cli"]); - let password = wallet.password_from_file(path).unwrap(); - assert_eq!(password, "this is keystore password") - } -} diff --git a/crates/cli/src/opts/wallet/multi_wallet.rs b/crates/cli/src/opts/wallet/multi_wallet.rs deleted file mode 100644 index 92091e2263d6c..0000000000000 --- a/crates/cli/src/opts/wallet/multi_wallet.rs +++ /dev/null @@ -1,521 +0,0 @@ -use super::{WalletSigner, WalletTrait}; -use clap::Parser; -use ethers::{ - prelude::{Middleware, Signer}, - signers::{AwsSigner, HDPath as LedgerHDPath, Ledger, LocalWallet, Trezor, TrezorHDPath}, - types::Address, -}; -use eyre::{Context, ContextCompat, Result}; -use foundry_common::RetryProvider; -use foundry_config::Config; -use itertools::izip; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; -use serde::Serialize; -use std::{ - collections::{HashMap, HashSet}, - iter::repeat, - sync::Arc, -}; -use tracing::trace; - -macro_rules! get_wallets { - ($id:ident, [ $($wallets:expr),+ ], $call:expr) => { - $( - if let Some($id) = $wallets { - $call; - } - )+ - }; -} - -/// A macro that initializes multiple wallets -/// -/// Should be used with a [`MultiWallet`] instance -macro_rules! create_hw_wallets { - ($self:ident, $chain_id:ident ,$get_wallet:ident, $wallets:ident) => { - let mut $wallets = vec![]; - - if let Some(hd_paths) = &$self.hd_paths { - for path in hd_paths { - if let Some(hw) = $self.$get_wallet($chain_id, Some(path), None).await? { - $wallets.push(hw); - } - } - } - - if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { - for index in mnemonic_indexes { - if let Some(hw) = $self.$get_wallet($chain_id, None, Some(*index as usize)).await? { - $wallets.push(hw); - } - } - } - - if $wallets.is_empty() { - if let Some(hw) = $self.$get_wallet($chain_id, None, Some(0)).await? { - $wallets.push(hw); - } - } - }; -} - -/// The wallet options can either be: -/// 1. Ledger -/// 2. Trezor -/// 3. Mnemonics (via file path) -/// 4. Keystores (via file path) -/// 5. Private Keys (cleartext in CLI) -/// 6. Private Keys (interactively via secure prompt) -/// 7. AWS KMS -#[derive(Parser, Debug, Clone, Serialize, Default)] -#[clap(next_help_heading = "Wallet options", about = None, long_about = None)] -pub struct MultiWallet { - /// The sender accounts. - #[clap( - long, - short = 'a', - help_heading = "Wallet options - raw", - value_name = "ADDRESSES", - env = "ETH_FROM", - num_args(0..), - )] - pub froms: Option>, - - /// Open an interactive prompt to enter your private key. - /// - /// Takes a value for the number of keys to enter. - #[clap( - long, - short, - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "NUM" - )] - pub interactives: u32, - - /// Use the provided private keys. - #[clap( - long, - help_heading = "Wallet options - raw", - value_name = "RAW_PRIVATE_KEYS", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - )] - pub private_keys: Option>, - - /// Use the provided private key. - #[clap( - long, - help_heading = "Wallet options - raw", - conflicts_with = "private_keys", - value_name = "RAW_PRIVATE_KEY", - value_parser = foundry_common::clap_helpers::strip_0x_prefix, - )] - pub private_key: Option, - - /// Use the mnemonic phrases of mnemonic files at the specified paths. - #[clap(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] - pub mnemonics: Option>, - - /// Use a BIP39 passphrases for the mnemonic. - #[clap(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] - pub mnemonic_passphrases: Option>, - - /// The wallet derivation path. - /// - /// Works with both --mnemonic-path and hardware wallets. - #[clap( - long = "mnemonic-derivation-paths", - alias = "hd-paths", - help_heading = "Wallet options - raw", - value_name = "PATH" - )] - pub hd_paths: Option>, - - /// Use the private key from the given mnemonic index. - /// - /// Can be used with --mnemonics, --ledger, --aws and --trezor. - #[clap( - long, - conflicts_with = "hd_paths", - help_heading = "Wallet options - raw", - default_value = "0", - value_name = "INDEXES" - )] - pub mnemonic_indexes: Option>, - - /// Use the keystore in the given folder or file. - #[clap( - long = "keystore", - visible_alias = "keystores", - help_heading = "Wallet options - keystore", - value_name = "PATHS", - env = "ETH_KEYSTORE" - )] - pub keystore_paths: Option>, - - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[clap( - long = "account", - visible_alias = "accounts", - help_heading = "Wallet options - keystore", - value_name = "ACCOUNT_NAMES", - env = "ETH_KEYSTORE_ACCOUNT", - conflicts_with = "keystore_paths" - )] - pub keystore_account_names: Option>, - - /// The keystore password. - /// - /// Used with --keystore. - #[clap( - long = "password", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PASSWORDS" - )] - pub keystore_passwords: Option>, - - /// The keystore password file path. - /// - /// Used with --keystore. - #[clap( - long = "password-file", - help_heading = "Wallet options - keystore", - requires = "keystore_paths", - value_name = "PATHS", - env = "ETH_PASSWORD" - )] - pub keystore_password_files: Option>, - - /// Use a Ledger hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub ledger: bool, - - /// Use a Trezor hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] - pub trezor: bool, - - /// Use AWS Key Management Service. - #[clap(long, help_heading = "Wallet options - remote")] - pub aws: bool, -} - -impl WalletTrait for MultiWallet { - fn sender(&self) -> Option
{ - self.froms.as_ref()?.first().copied() - } -} - -impl MultiWallet { - /// Given a list of addresses, it finds all the associated wallets if they exist. Throws an - /// error, if it can't find all. - pub async fn find_all( - &self, - provider: Arc, - mut addresses: HashSet
, - script_wallets: &[LocalWallet], - ) -> Result> { - println!("\n###\nFinding wallets for all the necessary addresses..."); - let chain = provider.get_chainid().await?.as_u64(); - - let mut local_wallets = HashMap::new(); - let mut unused_wallets = vec![]; - - get_wallets!( - wallets, - [ - self.trezors(chain).await?, - self.ledgers(chain).await?, - self.private_keys()?, - self.interactives()?, - self.mnemonics()?, - self.keystores()?, - self.aws_signers(chain).await?, - (!script_wallets.is_empty()).then(|| script_wallets.to_vec()) - ], - for wallet in wallets.into_iter() { - let address = wallet.address(); - if addresses.contains(&address) { - addresses.remove(&address); - - let signer = WalletSigner::from(wallet.with_chain_id(chain)); - local_wallets.insert(address, signer); - - if addresses.is_empty() { - return Ok(local_wallets) - } - } else { - // Just to show on error. - unused_wallets.push(address); - } - } - ); - - let mut error_msg = String::new(); - - // This is an actual used address - if addresses.contains(&Config::DEFAULT_SENDER) { - error_msg += "\nYou seem to be using Foundry's default sender. Be sure to set your own --sender.\n"; - } - - unused_wallets.extend(local_wallets.into_keys()); - eyre::bail!( - "{}No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", - error_msg, - addresses, - unused_wallets - ) - } - - pub fn interactives(&self) -> Result>> { - if self.interactives != 0 { - let mut wallets = vec![]; - for _ in 0..self.interactives { - wallets.push(self.get_from_interactive()?); - } - return Ok(Some(wallets)) - } - Ok(None) - } - - pub fn private_keys(&self) -> Result>> { - if let Some(private_keys) = &self.private_keys { - let mut wallets = vec![]; - for private_key in private_keys.iter() { - wallets.push(self.get_from_private_key(private_key.trim())?); - } - return Ok(Some(wallets)) - } - Ok(None) - } - - /// Returns all wallets read from the provided keystores arguments - /// - /// Returns `Ok(None)` if no keystore provided. - pub fn keystores(&self) -> Result>> { - let default_keystore_dir = Config::foundry_keystores_dir() - .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; - // If keystore paths are provided, use them, else, use default path + keystore account names - let keystore_paths = self.keystore_paths.clone().or_else(|| { - self.keystore_account_names.as_ref().map(|keystore_names| { - keystore_names - .iter() - .map(|keystore_name| { - default_keystore_dir.join(keystore_name).to_string_lossy().into_owned() - }) - .collect() - }) - }); - - if let Some(keystore_paths) = keystore_paths { - let mut wallets = Vec::with_capacity(keystore_paths.len()); - - let mut passwords_iter = - self.keystore_passwords.clone().unwrap_or_default().into_iter(); - - let mut password_files_iter = - self.keystore_password_files.clone().unwrap_or_default().into_iter(); - - for path in keystore_paths { - let wallet = self.get_from_keystore(Some(&path), passwords_iter.next().as_ref(), password_files_iter.next().as_ref())?.wrap_err("Keystore paths do not have the same length as provided passwords or password files.")?; - wallets.push(wallet); - } - return Ok(Some(wallets)) - } - Ok(None) - } - - pub fn mnemonics(&self) -> Result>> { - if let Some(ref mnemonics) = self.mnemonics { - let mut wallets = vec![]; - let hd_paths: Vec<_> = if let Some(ref hd_paths) = self.hd_paths { - hd_paths.iter().map(Some).collect() - } else { - repeat(None).take(mnemonics.len()).collect() - }; - let mnemonic_passphrases: Vec<_> = - if let Some(ref mnemonic_passphrases) = self.mnemonic_passphrases { - mnemonic_passphrases.iter().map(Some).collect() - } else { - repeat(None).take(mnemonics.len()).collect() - }; - let mnemonic_indexes: Vec<_> = if let Some(ref mnemonic_indexes) = self.mnemonic_indexes - { - mnemonic_indexes.to_vec() - } else { - repeat(0).take(mnemonics.len()).collect() - }; - for (mnemonic, mnemonic_passphrase, hd_path, mnemonic_index) in - izip!(mnemonics, mnemonic_passphrases, hd_paths, mnemonic_indexes) - { - wallets.push(self.get_from_mnemonic( - mnemonic, - mnemonic_passphrase, - hd_path, - mnemonic_index, - )?) - } - return Ok(Some(wallets)) - } - Ok(None) - } - - pub async fn ledgers(&self, chain_id: u64) -> Result>> { - if self.ledger { - let mut args = self.clone(); - - if let Some(paths) = &args.hd_paths { - if paths.len() > 1 { - eyre::bail!("Ledger only supports one signer."); - } - args.mnemonic_indexes = None; - } - - create_hw_wallets!(args, chain_id, get_from_ledger, wallets); - return Ok(Some(wallets)) - } - Ok(None) - } - - pub async fn trezors(&self, chain_id: u64) -> Result>> { - if self.trezor { - create_hw_wallets!(self, chain_id, get_from_trezor, wallets); - return Ok(Some(wallets)) - } - Ok(None) - } - - pub async fn aws_signers(&self, chain_id: u64) -> Result>> { - if self.aws { - let mut wallets = vec![]; - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); - - let kms = KmsClient::new_with_client(client, AwsRegion::default()); - - let env_key_ids = std::env::var("AWS_KMS_KEY_IDS"); - let key_ids = - if env_key_ids.is_ok() { env_key_ids? } else { std::env::var("AWS_KMS_KEY_ID")? }; - - for key in key_ids.split(',') { - let aws_signer = AwsSigner::new(kms.clone(), key, chain_id).await?; - wallets.push(aws_signer) - } - - return Ok(Some(wallets)) - } - Ok(None) - } - - async fn get_from_trezor( - &self, - chain_id: u64, - hd_path: Option<&str>, - mnemonic_index: Option, - ) -> Result> { - let derivation = match &hd_path { - Some(hd_path) => TrezorHDPath::Other(hd_path.to_string()), - None => TrezorHDPath::TrezorLive(mnemonic_index.unwrap_or(0)), - }; - - Ok(Some(Trezor::new(derivation, chain_id, None).await?)) - } - - async fn get_from_ledger( - &self, - chain_id: u64, - hd_path: Option<&str>, - mnemonic_index: Option, - ) -> Result> { - let derivation = match hd_path { - Some(hd_path) => LedgerHDPath::Other(hd_path.to_string()), - None => LedgerHDPath::LedgerLive(mnemonic_index.unwrap_or(0)), - }; - - trace!(?chain_id, "Creating new ledger signer"); - Ok(Some(Ledger::new(derivation, chain_id).await.wrap_err("Ledger device not available.")?)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::path::Path; - - #[test] - fn parse_keystore_args() { - let args: MultiWallet = - MultiWallet::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); - assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); - - std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); - let args: MultiWallet = MultiWallet::parse_from(["foundry-cli"]); - assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); - - std::env::remove_var("ETH_KEYSTORE"); - } - - #[test] - fn parse_keystore_password_file() { - let keystore = - Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); - let keystore_file = keystore - .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); - - let keystore_password_file = keystore.join("password-ec554").into_os_string(); - - let args: MultiWallet = MultiWallet::parse_from([ - "foundry-cli", - "--keystores", - keystore_file.to_str().unwrap(), - "--password-file", - keystore_password_file.to_str().unwrap(), - ]); - assert_eq!( - args.keystore_password_files, - Some(vec![keystore_password_file.to_str().unwrap().to_string()]) - ); - - let wallets = args.keystores().unwrap().unwrap(); - assert_eq!(wallets.len(), 1); - assert_eq!( - wallets[0].address(), - "ec554aeafe75601aaab43bd4621a22284db566c2".parse().unwrap() - ); - } - - // https://github.com/foundry-rs/foundry/issues/5179 - #[test] - fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { - let wallet_options = vec![ - ("ledger", "--mnemonic-indexes", 1), - ("trezor", "--mnemonic-indexes", 2), - ("aws", "--mnemonic-indexes", 10), - ]; - - for test_case in wallet_options { - let args: MultiWallet = MultiWallet::parse_from([ - "foundry-cli", - &format!("--{}", test_case.0), - test_case.1, - &test_case.2.to_string(), - ]); - - match test_case.0 { - "ledger" => assert!(args.ledger), - "trezor" => assert!(args.trezor), - "aws" => assert!(args.aws), - _ => panic!("Should have matched one of the previous wallet options"), - } - - assert_eq!( - args.mnemonic_indexes.expect("--mnemonic-indexes should have been set")[0], - test_case.2 - ) - } - } -} diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs new file mode 100644 index 0000000000000..c7f4d260416d7 --- /dev/null +++ b/crates/cli/src/utils/abi.rs @@ -0,0 +1,57 @@ +use alloy_chains::Chain; +use alloy_json_abi::Function; +use alloy_primitives::{hex, Address}; +use alloy_provider::{network::AnyNetwork, Provider}; +use eyre::{OptionExt, Result}; +use foundry_common::{ + abi::{encode_function_args, get_func, get_func_etherscan}, + ens::NameOrAddress, +}; +use futures::future::join_all; + +async fn resolve_name_args>(args: &[String], provider: &P) -> Vec { + join_all(args.iter().map(|arg| async { + if arg.contains('.') { + let addr = NameOrAddress::Name(arg.to_string()).resolve(provider).await; + match addr { + Ok(addr) => addr.to_string(), + Err(_) => arg.to_string(), + } + } else { + arg.to_string() + } + })) + .await +} + +pub async fn parse_function_args>( + sig: &str, + args: Vec, + to: Option
, + chain: Chain, + provider: &P, + etherscan_api_key: Option<&str>, +) -> Result<(Vec, Option)> { + if sig.trim().is_empty() { + eyre::bail!("Function signature or calldata must be provided.") + } + + let args = resolve_name_args(&args, provider).await; + + if let Ok(data) = hex::decode(sig) { + return Ok((data, None)) + } + + let func = if sig.contains('(') { + // a regular function signature with parentheses + get_func(sig)? + } else { + let etherscan_api_key = etherscan_api_key.ok_or_eyre( + "If you wish to fetch function data from Etherscan, please provide an Etherscan API key.", + )?; + let to = to.ok_or_eyre("A 'to' address must be provided to fetch function data.")?; + get_func_etherscan(sig, to, &args, chain, etherscan_api_key).await? + }; + + Ok((encode_function_args(&func, &args)?, Some(func))) +} diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 44a5dbc6f4b92..c226054dd7313 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -1,45 +1,52 @@ -use ethers::{ - abi::Abi, - core::types::Chain, - solc::{ - artifacts::{CompactBytecode, CompactDeployedBytecode, ContractBytecodeSome}, - cache::{CacheEntry, SolFilesCache}, - info::ContractInfo, - utils::read_json_file, - Artifact, ArtifactId, ProjectCompileOutput, - }, -}; +use alloy_json_abi::JsonAbi; +use alloy_primitives::Address; use eyre::{Result, WrapErr}; -use foundry_common::{cli_warn, fs, TestFunctionExt}; -use foundry_config::{error::ExtractConfigError, figment::Figment, Chain as ConfigChain, Config}; +use foundry_common::{compile::ProjectCompiler, fs, shell, ContractsByArtifact, TestFunctionExt}; +use foundry_compilers::{ + artifacts::{CompactBytecode, Settings}, + cache::{CacheEntry, CompilerCache}, + utils::read_json_file, + Artifact, ArtifactId, ProjectCompileOutput, +}; +use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; +use foundry_debugger::Debugger; use foundry_evm::{ - debug::DebugArena, - executor::{opts::EvmOpts, DeployResult, EvmError, ExecutionErr, RawCallResult}, - trace::{ - identifier::{EtherscanIdentifier, SignaturesIdentifier}, - CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, + executors::{DeployResult, EvmError, RawCallResult}, + opts::EvmOpts, + traces::{ + debug::{ContractSources, DebugTraceIdentifier}, + decode_trace_arena, + identifier::{CachedSignatures, SignaturesIdentifier, TraceIdentifiers}, + render_trace_arena_inner, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, }; -use std::{collections::BTreeMap, fmt::Write, path::PathBuf, str::FromStr}; -use tracing::trace; -use ui::{TUIExitReason, Tui, Ui}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + str::FromStr, +}; use yansi::Paint; /// Given a `Project`'s output, removes the matching ABI, Bytecode and /// Runtime Bytecode of the given contract. #[track_caller] pub fn remove_contract( - output: &mut ProjectCompileOutput, - info: &ContractInfo, -) -> Result<(Abi, CompactBytecode, CompactDeployedBytecode)> { - let contract = if let Some(contract) = output.remove_contract(info) { - contract - } else { - let mut err = format!("could not find artifact: `{}`", info.name); - if let Some(suggestion) = - super::did_you_mean(&info.name, output.artifacts().map(|(name, _)| name)).pop() - { - if suggestion != info.name { + output: ProjectCompileOutput, + path: &Path, + name: &str, +) -> Result<(JsonAbi, CompactBytecode, ArtifactId)> { + let mut other = Vec::new(); + let Some((id, contract)) = output.into_artifacts().find_map(|(id, artifact)| { + if id.name == name && id.source == path { + Some((id, artifact)) + } else { + other.push(id.name); + None + } + }) else { + let mut err = format!("could not find artifact: `{name}`"); + if let Some(suggestion) = super::did_you_mean(name, other).pop() { + if suggestion != name { err = format!( r#"{err} @@ -52,27 +59,22 @@ pub fn remove_contract( let abi = contract .get_abi() - .ok_or_else(|| eyre::eyre!("contract {} does not contain abi", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain abi", name))? .into_owned(); let bin = contract .get_bytecode() - .ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", info))? - .into_owned(); - - let runtime = contract - .get_deployed_bytecode() - .ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", name))? .into_owned(); - Ok((abi, bin, runtime)) + Ok((abi, bin, id)) } /// Helper function for finding a contract by ContractName // TODO: Is there a better / more ergonomic way to get the artifacts given a project and a // contract name? pub fn get_cached_entry_by_name( - cache: &SolFilesCache, + cache: &CompilerCache, name: &str, ) -> Result<(PathBuf, CacheEntry)> { let mut cached_entry = None; @@ -95,7 +97,7 @@ pub fn get_cached_entry_by_name( } if let Some(entry) = cached_entry { - return Ok(entry) + return Ok(entry); } let mut err = format!("could not find artifact: `{name}`"); @@ -110,7 +112,7 @@ pub fn get_cached_entry_by_name( } /// Returns error if constructor has arguments. -pub fn ensure_clean_constructor(abi: &Abi) -> Result<()> { +pub fn ensure_clean_constructor(abi: &JsonAbi) -> Result<()> { if let Some(constructor) = &abi.constructor { if !constructor.inputs.is_empty() { eyre::bail!("Contract constructor should have no arguments. Add those arguments to `run(...)` instead, and call it with `--sig run(...)`."); @@ -119,14 +121,13 @@ pub fn ensure_clean_constructor(abi: &Abi) -> Result<()> { Ok(()) } -pub fn needs_setup(abi: &Abi) -> bool { +pub fn needs_setup(abi: &JsonAbi) -> bool { let setup_fns: Vec<_> = abi.functions().filter(|func| func.name.is_setup()).collect(); for setup_fn in setup_fns.iter() { if setup_fn.name != "setUp" { - println!( - "{} Found invalid setup function \"{}\" did you mean \"setUp()\"?", - Paint::yellow("Warning:").bold(), + let _ = sh_warn!( + "Found invalid setup function \"{}\" did you mean \"setUp()\"?", setup_fn.signature() ); } @@ -139,101 +140,98 @@ pub fn eta_key(state: &indicatif::ProgressState, f: &mut dyn Write) { write!(f, "{:.1}s", state.eta().as_secs_f64()).unwrap() } -#[macro_export] -macro_rules! init_progress { - ($local:expr, $label:expr) => {{ - let pb = indicatif::ProgressBar::new($local.len() as u64); - let mut template = - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ".to_string(); - template += $label; - template += " ({eta})"; - pb.set_style( - indicatif::ProgressStyle::with_template(&template) - .unwrap() - .with_key("eta", $crate::utils::eta_key) - .progress_chars("#>-"), - ); - pb - }}; -} - -#[macro_export] -macro_rules! update_progress { - ($pb:ident, $index:expr) => { - $pb.set_position(($index + 1) as u64); - }; +pub fn init_progress(len: u64, label: &str) -> indicatif::ProgressBar { + let pb = indicatif::ProgressBar::new(len); + let mut template = + "{prefix}{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} " + .to_string(); + write!(template, "{label}").unwrap(); + template += " ({eta})"; + pb.set_style( + indicatif::ProgressStyle::with_template(&template) + .unwrap() + .with_key("eta", crate::utils::eta_key) + .progress_chars("#>-"), + ); + pb } /// True if the network calculates gas costs differently. -pub fn has_different_gas_calc(chain: u64) -> bool { - if let ConfigChain::Named(chain) = ConfigChain::from(chain) { - return matches!(chain, Chain::Arbitrum | Chain::ArbitrumTestnet | Chain::ArbitrumGoerli) +pub fn has_different_gas_calc(chain_id: u64) -> bool { + if let Some(chain) = Chain::from(chain_id).named() { + return chain.is_arbitrum() || + matches!( + chain, + NamedChain::Acala | + NamedChain::AcalaMandalaTestnet | + NamedChain::AcalaTestnet | + NamedChain::Etherlink | + NamedChain::EtherlinkTestnet | + NamedChain::Karura | + NamedChain::KaruraTestnet | + NamedChain::Mantle | + NamedChain::MantleSepolia | + NamedChain::MantleTestnet | + NamedChain::Moonbase | + NamedChain::Moonbeam | + NamedChain::MoonbeamDev | + NamedChain::Moonriver | + NamedChain::Metis + ); } false } /// True if it supports broadcasting in batches. -pub fn has_batch_support(chain: u64) -> bool { - if let ConfigChain::Named(chain) = ConfigChain::from(chain) { - return !matches!(chain, Chain::Arbitrum | Chain::ArbitrumTestnet | Chain::ArbitrumGoerli) +pub fn has_batch_support(chain_id: u64) -> bool { + if let Some(chain) = Chain::from(chain_id).named() { + return !chain.is_arbitrum(); } true } /// Helpers for loading configuration. /// -/// This is usually implicitly implemented on a "&CmdArgs" struct via impl macros defined in -/// `forge_config` (See [`forge_config::impl_figment_convert`] for more details) and the impl -/// definition on `T: Into + Into` below. +/// This is usually implemented through the macros defined in [`foundry_config`]. See +/// [`foundry_config::impl_figment_convert`] for more details. /// -/// Each function also has an `emit_warnings` form which does the same thing as its counterpart but -/// also prints `Config::__warnings` to stderr +/// By default each function will emit warnings generated during loading, unless the `_no_warnings` +/// variant is used. pub trait LoadConfig { - /// Load and sanitize the [`Config`] based on the options provided in self - /// - /// Returns an error if loading the config failed - fn try_load_config(self) -> Result; - /// Load and sanitize the [`Config`] based on the options provided in self - fn load_config(self) -> Config; - /// Load and sanitize the [`Config`], as well as extract [`EvmOpts`] from self - fn load_config_and_evm_opts(self) -> Result<(Config, EvmOpts)>; - /// Load [`Config`] but do not sanitize. See [`Config::sanitized`] for more information - fn load_config_unsanitized(self) -> Config; + /// Load the [`Config`] based on the options provided in self. + fn figment(&self) -> Figment; + + /// Load and sanitize the [`Config`] based on the options provided in self. + fn load_config(&self) -> Result { + self.load_config_no_warnings().inspect(emit_warnings) + } + + /// Same as [`LoadConfig::load_config`] but does not emit warnings. + fn load_config_no_warnings(&self) -> Result { + self.load_config_unsanitized_no_warnings().map(Config::sanitized) + } + /// Load [`Config`] but do not sanitize. See [`Config::sanitized`] for more information. - /// - /// Returns an error if loading failed - fn try_load_config_unsanitized(self) -> Result; - /// Same as [`LoadConfig::load_config`] but also emits warnings generated - fn load_config_emit_warnings(self) -> Config; - /// Same as [`LoadConfig::load_config`] but also emits warnings generated - /// - /// Returns an error if loading failed - fn try_load_config_emit_warnings(self) -> Result; - /// Same as [`LoadConfig::load_config_and_evm_opts`] but also emits warnings generated - fn load_config_and_evm_opts_emit_warnings(self) -> Result<(Config, EvmOpts)>; - /// Same as [`LoadConfig::load_config_unsanitized`] but also emits warnings generated - fn load_config_unsanitized_emit_warnings(self) -> Config; - fn try_load_config_unsanitized_emit_warnings(self) -> Result; -} + fn load_config_unsanitized(&self) -> Result { + self.load_config_unsanitized_no_warnings().inspect(emit_warnings) + } -impl LoadConfig for T -where - T: Into + Into, -{ - fn try_load_config(self) -> Result { - let figment: Figment = self.into(); - Ok(Config::try_from(figment)?.sanitized()) + /// Same as [`LoadConfig::load_config_unsanitized`] but also emits warnings generated + fn load_config_unsanitized_no_warnings(&self) -> Result { + Config::from_provider(self.figment()) } - fn load_config(self) -> Config { - self.into() + /// Load and sanitize the [`Config`], as well as extract [`EvmOpts`] from self + fn load_config_and_evm_opts(&self) -> Result<(Config, EvmOpts)> { + self.load_config_and_evm_opts_no_warnings().inspect(|(config, _)| emit_warnings(config)) } - fn load_config_and_evm_opts(self) -> Result<(Config, EvmOpts)> { - let figment: Figment = self.into(); + /// Same as [`LoadConfig::load_config_and_evm_opts`] but also emits warnings generated + fn load_config_and_evm_opts_no_warnings(&self) -> Result<(Config, EvmOpts)> { + let figment = self.figment(); let mut evm_opts = figment.extract::().map_err(ExtractConfigError::new)?; - let config = Config::try_from(figment)?.sanitized(); + let config = Config::from_provider(figment)?.sanitized(); // update the fork url if it was an alias if let Some(fork_url) = config.get_rpc_url() { @@ -243,45 +241,20 @@ where Ok((config, evm_opts)) } +} - fn load_config_unsanitized(self) -> Config { - let figment: Figment = self.into(); - Config::from_provider(figment) - } - - fn try_load_config_unsanitized(self) -> Result { - let figment: Figment = self.into(); - Config::try_from(figment) - } - - fn load_config_emit_warnings(self) -> Config { - let config = self.load_config(); - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); - config - } - - fn try_load_config_emit_warnings(self) -> Result { - let config = self.try_load_config()?; - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); - Ok(config) - } - - fn load_config_and_evm_opts_emit_warnings(self) -> Result<(Config, EvmOpts)> { - let (config, evm_opts) = self.load_config_and_evm_opts()?; - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); - Ok((config, evm_opts)) - } - - fn load_config_unsanitized_emit_warnings(self) -> Config { - let config = self.load_config_unsanitized(); - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); - config +impl LoadConfig for T +where + for<'a> Figment: From<&'a T>, +{ + fn figment(&self) -> Figment { + self.into() } +} - fn try_load_config_unsanitized_emit_warnings(self) -> Result { - let config = self.try_load_config_unsanitized()?; - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); - Ok(config) +fn emit_warnings(config: &Config) { + for warning in &config.warnings { + let _ = sh_warn!("{warning}"); } } @@ -302,54 +275,52 @@ pub fn read_constructor_args_file(constructor_args_path: PathBuf) -> Result, pub gas_used: u64, } -impl From for TraceResult { - fn from(result: RawCallResult) -> Self { - let RawCallResult { gas_used, traces, reverted, debug, .. } = result; - - Self { - success: !reverted, - traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], - debug: debug.unwrap_or_default(), - gas_used, - } +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, .. } = raw; + Self { success: !reverted, traces: traces.map(|arena| vec![(trace_kind, arena)]), gas_used } } } impl From for TraceResult { fn from(result: DeployResult) -> Self { - let DeployResult { gas_used, traces, debug, .. } = result; + Self::from_raw(result.raw, TraceKind::Deployment) + } +} + +impl TryFrom> for TraceResult { + type Error = EvmError; - Self { - success: true, - traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], - debug: debug.unwrap_or_default(), - gas_used, + fn try_from(value: Result) -> Result { + match value { + Ok(result) => Ok(Self::from(result)), + Err(EvmError::Execution(err)) => Ok(Self::from_raw(err.raw, TraceKind::Deployment)), + Err(err) => Err(err), } } } -impl TryFrom for TraceResult { +impl From for TraceResult { + fn from(result: RawCallResult) -> Self { + Self::from_raw(result, TraceKind::Execution) + } +} + +impl TryFrom> for TraceResult { type Error = EvmError; - fn try_from(err: EvmError) -> Result { - match err { - EvmError::Execution(err) => { - let ExecutionErr { reverted, gas_used, traces, debug: run_debug, .. } = *err; - Ok(TraceResult { - success: !reverted, - traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], - debug: run_debug.unwrap_or_default(), - gas_used, - }) - } - _ => Err(err), + fn try_from(value: Result) -> Result { + match value { + Ok(result) => Ok(Self::from(result)), + Err(err) => Err(EvmError::from(err)), } } } @@ -358,101 +329,142 @@ impl TryFrom for TraceResult { pub async fn handle_traces( mut result: TraceResult, config: &Config, - chain: Option, + chain: Option, labels: Vec, - verbose: bool, + with_local_artifacts: bool, debug: bool, + decode_internal: bool, ) -> Result<()> { - let mut etherscan_identifier = EtherscanIdentifier::new(config, chain)?; + let (known_contracts, mut sources) = if with_local_artifacts { + let _ = sh_println!("Compiling project to generate artifacts"); + let project = config.project()?; + let compiler = ProjectCompiler::new(); + let output = compiler.compile(&project)?; + ( + Some(ContractsByArtifact::new( + output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), + )), + ContractSources::from_project_output(&output, project.root(), None)?, + ) + } else { + (None, ContractSources::default()) + }; - let labeled_addresses = labels.iter().filter_map(|label_str| { + let labels = labels.iter().filter_map(|label_str| { let mut iter = label_str.split(':'); if let Some(addr) = iter.next() { - if let (Ok(address), Some(label)) = - (ethers::types::Address::from_str(addr), iter.next()) - { - return Some((address, label.to_string())) + if let (Ok(address), Some(label)) = (Address::from_str(addr), iter.next()) { + return Some((address, label.to_string())); } } None }); + let config_labels = config.labels.clone().into_iter(); + + let mut builder = CallTraceDecoderBuilder::new() + .with_labels(labels.chain(config_labels)) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + config.offline, + )?); + let mut identifier = TraceIdentifiers::new().with_etherscan(config, chain)?; + if let Some(contracts) = &known_contracts { + builder = builder.with_known_contracts(contracts); + identifier = identifier.with_local(contracts); + } - let mut decoder = CallTraceDecoderBuilder::new().with_labels(labeled_addresses).build(); - - decoder.add_signature_identifier(SignaturesIdentifier::new( - Config::foundry_cache_dir(), - config.offline, - )?); + let mut decoder = builder.build(); - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut etherscan_identifier); + for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { + decoder.identify(trace, &mut identifier); } - if debug { - let (sources, bytecode) = etherscan_identifier.get_compiled_contracts().await?; - run_debugger(result, decoder, bytecode, sources)?; - } else { - print_traces(&mut result, decoder, verbose).await?; + if decode_internal || debug { + if let Some(ref etherscan_identifier) = identifier.etherscan { + sources.merge(etherscan_identifier.get_compiled_contracts().await?); + } + + if debug { + let mut debugger = Debugger::builder() + .traces(result.traces.expect("missing traces")) + .decoder(&decoder) + .sources(sources) + .build(); + debugger.try_run_tui()?; + return Ok(()) + } + + decoder.debug_identifier = Some(DebugTraceIdentifier::new(sources)); } + print_traces(&mut result, &decoder, shell::verbosity() > 0, shell::verbosity() > 4).await?; + Ok(()) } pub async fn print_traces( result: &mut TraceResult, - decoder: CallTraceDecoder, + decoder: &CallTraceDecoder, verbose: bool, + state_changes: bool, ) -> Result<()> { - if result.traces.is_empty() { - panic!("No traces found") + let traces = result.traces.as_mut().expect("No traces found"); + + if !shell::is_json() { + sh_println!("Traces:")?; } - println!("Traces:"); - for (_, trace) in &mut result.traces { - decoder.decode(trace).await; - if !verbose { - println!("{trace}"); - } else { - println!("{trace:#}"); - } + for (_, arena) in traces { + decode_trace_arena(arena, decoder).await?; + sh_println!("{}", render_trace_arena_inner(arena, verbose, state_changes))?; + } + + if shell::is_json() { + return Ok(()); } - println!(); + sh_println!()?; if result.success { - println!("{}", Paint::green("Transaction successfully executed.")); + sh_println!("{}", "Transaction successfully executed.".green())?; } else { - println!("{}", Paint::red("Transaction failed.")); + sh_err!("Transaction failed.")?; } + sh_println!("Gas used: {}", result.gas_used)?; - println!("Gas used: {}", result.gas_used); Ok(()) } -pub fn run_debugger( - result: TraceResult, - decoder: CallTraceDecoder, - known_contracts: BTreeMap, - sources: BTreeMap, -) -> Result<()> { - let calls: Vec = vec![result.debug]; - let flattened = calls.last().expect("we should have collected debug info").flatten(0); - let tui = Tui::new( - flattened, - 0, - decoder.contracts, - known_contracts.into_iter().map(|(id, artifact)| (id.name, artifact)).collect(), - sources - .into_iter() - .map(|(id, source)| { - let mut sources = BTreeMap::new(); - sources.insert(0, source); - (id.name, sources) - }) - .collect(), - Default::default(), - )?; - match tui.start().expect("Failed to start tui") { - TUIExitReason::CharExit => Ok(()), - } +/// Traverse the artifacts in the project to generate local signatures and merge them into the cache +/// file. +pub fn cache_local_signatures(output: &ProjectCompileOutput, cache_path: PathBuf) -> Result<()> { + let path = cache_path.join("signatures"); + let mut cached_signatures = CachedSignatures::load(cache_path); + output.artifacts().for_each(|(_, artifact)| { + if let Some(abi) = &artifact.abi { + for func in abi.functions() { + cached_signatures.functions.insert(func.selector().to_string(), func.signature()); + } + for event in abi.events() { + cached_signatures + .events + .insert(event.selector().to_string(), event.full_signature()); + } + for error in abi.errors() { + cached_signatures.errors.insert(error.selector().to_string(), error.signature()); + } + // External libraries doesn't have functions included in abi, but `methodIdentifiers`. + if let Some(method_identifiers) = &artifact.method_identifiers { + method_identifiers.iter().for_each(|(signature, selector)| { + cached_signatures + .functions + .entry(format!("0x{selector}")) + .or_insert(signature.to_string()); + }); + } + } + }); + + fs::write_json_file(&path, &cached_signatures)?; + Ok(()) } diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index d94119cd5ad62..42582e8de59d2 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -1,24 +1,21 @@ -use ethers::{ - abi::token::{LenientTokenizer, Tokenizer}, - prelude::TransactionReceipt, - providers::Middleware, - types::U256, - utils::{format_units, to_checksum}, +use alloy_json_abi::JsonAbi; +use alloy_primitives::U256; +use alloy_provider::{network::AnyNetwork, Provider}; +use eyre::{ContextCompat, Result}; +use foundry_common::{ + provider::{ProviderBuilder, RetryProvider}, + shell, }; -use eyre::Result; use foundry_config::{Chain, Config}; +use serde::de::DeserializeOwned; use std::{ ffi::OsStr, future::Future, - ops::Mul, path::{Path, PathBuf}, process::{Command, Output, Stdio}, - str::FromStr, time::{Duration, SystemTime, UNIX_EPOCH}, }; -use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; -use yansi::Paint; mod cmd; pub use cmd::*; @@ -26,6 +23,9 @@ pub use cmd::*; mod suggestions; pub use suggestions::*; +mod abi; +pub use abi::*; + // reexport all `foundry_config::utils` #[doc(hidden)] pub use foundry_config::utils::*; @@ -33,7 +33,7 @@ pub use foundry_config::utils::*; /// Deterministic fuzzer seed used for gas snapshots and coverage reports. /// /// The keccak256 hash of "foundry rulez" -pub static STATIC_FUZZ_SEED: [u8; 32] = [ +pub const STATIC_FUZZ_SEED: [u8; 32] = [ 0x01, 0x00, 0xfa, 0x69, 0xa5, 0xf1, 0x71, 0x0a, 0x95, 0xcd, 0xef, 0x94, 0x88, 0x9b, 0x02, 0x84, 0x5d, 0x64, 0x0b, 0x19, 0xad, 0xf0, 0xe3, 0x57, 0xb8, 0xd4, 0xbe, 0x7d, 0x49, 0xee, 0x70, 0xe6, ]; @@ -69,45 +69,59 @@ 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() } -/// parse a hex str or decimal str as U256 -pub fn parse_u256(s: &str) -> Result { - Ok(if s.starts_with("0x") { U256::from_str(s)? } else { U256::from_dec_str(s)? }) +pub fn abi_to_solidity(abi: &JsonAbi, name: &str) -> Result { + let s = abi.to_sol(name, None); + let s = forge_fmt::format(&s)?; + Ok(s) } -/// Returns a [RetryProvider](foundry_common::RetryProvider) instantiated using [Config]'s RPC URL -/// and chain. -/// -/// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider(config: &Config) -> Result { +/// Returns a [RetryProvider] instantiated using [Config]'s +/// RPC +pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } -/// Returns a [ProviderBuilder](foundry_common::ProviderBuilder) instantiated using [Config]'s RPC -/// URL and chain. + +/// Returns a [ProviderBuilder] instantiated using [Config] values. /// /// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider_builder(config: &Config) -> Result { +pub fn get_provider_builder(config: &Config) -> Result { let url = config.get_rpc_url_or_localhost_http()?; - let chain = config.chain_id.unwrap_or_default(); - Ok(foundry_common::ProviderBuilder::new(url.as_ref()).chain(chain)) + let mut builder = ProviderBuilder::new(url.as_ref()); + + if let Ok(chain) = config.chain.unwrap_or_default().try_into() { + builder = builder.chain(chain); + } + + if let Some(jwt) = config.get_rpc_jwt_secret()? { + builder = builder.jwt(jwt.as_ref()); + } + + if let Some(rpc_timeout) = config.eth_rpc_timeout { + builder = builder.timeout(Duration::from_secs(rpc_timeout)); + } + + if let Some(rpc_headers) = config.eth_rpc_headers.clone() { + builder = builder.headers(rpc_headers); + } + + Ok(builder) } -pub async fn get_chain(chain: Option, provider: M) -> Result +pub async fn get_chain

(chain: Option, provider: P) -> Result where - M: Middleware, - M::Error: 'static, + P: Provider, { match chain { Some(chain) => Ok(chain), - None => Ok(Chain::Id(provider.get_chainid().await?.as_u64())), + None => Ok(Chain::from_id(provider.get_chain_id().await?)), } } @@ -119,12 +133,20 @@ where /// it is interpreted as wei. pub fn parse_ether_value(value: &str) -> Result { Ok(if value.starts_with("0x") { - U256::from_str(value)? + U256::from_str_radix(value, 16)? } else { - U256::from(LenientTokenizer::tokenize_uint(value)?) + alloy_dyn_abi::DynSolType::coerce_str(&alloy_dyn_abi::DynSolType::Uint(256), value)? + .as_uint() + .wrap_err("Could not parse ether value from string")? + .0 }) } +/// Parses a `T` from a string using [`serde_json::from_str`]. +pub fn parse_json(value: &str) -> serde_json::Result { + serde_json::from_str(value) +} + /// Parses a `Duration` from a &str pub fn parse_delay(delay: &str) -> Result { let delay = if delay.ends_with("ms") { @@ -148,32 +170,14 @@ pub fn now() -> Duration { } /// Runs the `future` in a new [`tokio::runtime::Runtime`] -#[allow(unused)] pub fn block_on(future: F) -> F::Output { let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt"); rt.block_on(future) } -/// Conditionally print a message -/// -/// This macro accepts a predicate and the message to print if the predicate is tru -/// -/// ```ignore -/// let quiet = true; -/// p_println!(!quiet => "message"); -/// ``` -#[macro_export] -macro_rules! p_println { - ($p:expr => $($arg:tt)*) => {{ - if $p { - println!($($arg)*) - } - }} -} - /// Loads a dotenv file, from the cwd and the project root, ignoring potential failure. /// -/// We could use `tracing::warn!` here, but that would imply that the dotenv file can't configure +/// We could use `warn!` here, but that would imply that the dotenv file can't configure /// the logging behavior of Foundry. /// /// Similarly, we could just use `eprintln!`, but colors are off limits otherwise dotenv is implied @@ -184,9 +188,9 @@ pub fn load_dotenv() { }; // we only want the .env file of the cwd and project root - // `find_project_root_path` calls `current_dir` internally so both paths are either both `Ok` or + // `find_project_root` calls `current_dir` internally so both paths are either both `Ok` or // both `Err` - if let (Ok(cwd), Ok(prj_root)) = (std::env::current_dir(), find_project_root_path(None)) { + if let (Ok(cwd), Ok(prj_root)) = (std::env::current_dir(), find_project_root(None)) { load(&prj_root); if cwd != prj_root { // prj root and cwd can be identical @@ -195,49 +199,10 @@ pub fn load_dotenv() { }; } -/// Disables terminal colours if either: -/// - Running windows and the terminal does not support colour codes. -/// - Colour has been disabled by some environment variable. -/// - We are running inside a test +/// Sets the default [`yansi`] color output condition. pub fn enable_paint() { - let is_windows = cfg!(windows) && !Paint::enable_windows_ascii(); - let env_colour_disabled = std::env::var("NO_COLOR").is_ok(); - if is_windows || env_colour_disabled { - Paint::disable(); - } -} - -/// Prints parts of the receipt to stdout -pub fn print_receipt(chain: Chain, receipt: &TransactionReceipt) { - let gas_used = receipt.gas_used.unwrap_or_default(); - let gas_price = receipt.effective_gas_price.unwrap_or_default(); - foundry_common::shell::println(format!( - "\n##### {chain}\n{status}Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n", - status = if receipt.status.map_or(true, |s| s.is_zero()) { - "❌ [Failed]" - } else { - "✅ [Success]" - }, - tx_hash = receipt.transaction_hash, - caddr = if let Some(addr) = &receipt.contract_address { - format!("\nContract Address: {}", to_checksum(addr, None)) - } else { - String::new() - }, - bn = receipt.block_number.unwrap_or_default(), - gas = if gas_price.is_zero() { - format!("Gas Used: {gas_used}") - } else { - let paid = format_units(gas_used.mul(gas_price), 18).unwrap_or_else(|_| "N/A".into()); - let gas_price = format_units(gas_price, 9).unwrap_or_else(|_| "N/A".into()); - format!( - "Paid: {} ETH ({gas_used} gas * {} gwei)", - paid.trim_end_matches('0'), - gas_price.trim_end_matches('0').trim_end_matches('.') - ) - }, - )) - .expect("could not print receipt"); + let enable = yansi::Condition::os_support() && yansi::Condition::tty_and_color_live(); + yansi::whenever(yansi::Condition::cached(enable)); } /// Useful extensions to [`std::process::Command`]. @@ -252,21 +217,26 @@ pub trait CommandUtils { impl CommandUtils for Command { #[track_caller] fn exec(&mut self) -> Result { - tracing::trace!(command=?self, "executing"); + trace!(command=?self, "executing"); let output = self.output()?; - tracing::trace!(code=?output.status.code(), ?output); + trace!(code=?output.status.code(), ?output); if output.status.success() { Ok(output) } else { - let mut stderr = String::from_utf8_lossy(&output.stderr); - let mut msg = stderr.trim(); - if msg.is_empty() { - stderr = String::from_utf8_lossy(&output.stdout); - msg = stderr.trim(); - } + let stdout = String::from_utf8_lossy(&output.stdout); + let stdout = stdout.trim(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stderr = stderr.trim(); + let msg = if stdout.is_empty() { + stderr.to_string() + } else if stderr.is_empty() { + stdout.to_string() + } else { + format!("stdout:\n{stdout}\n\nstderr:\n{stderr}") + }; let mut name = self.get_program().to_string_lossy(); if let Some(arg) = self.get_args().next() { @@ -283,8 +253,9 @@ impl CommandUtils for Command { None => format!("{name} terminated by a signal"), }; if !msg.is_empty() { - err.push_str(": "); - err.push_str(msg); + err.push(':'); + err.push(if msg.lines().count() == 0 { ' ' } else { '\n' }); + err.push_str(&msg); } Err(eyre::eyre!(err)) } @@ -308,12 +279,12 @@ pub struct Git<'a> { impl<'a> Git<'a> { #[inline] pub fn new(root: &'a Path) -> Self { - Self { root, quiet: false, shallow: false } + Self { root, quiet: shell::is_quiet(), shallow: false } } #[inline] pub fn from_config(config: &'a Config) -> Self { - Self::new(config.__root.0.as_path()) + Self::new(config.root.as_path()) } pub fn root_of(relative_to: &Path) -> Result { @@ -324,6 +295,25 @@ impl<'a> Git<'a> { Ok(PathBuf::from(output)) } + pub fn clone_with_branch( + shallow: bool, + from: impl AsRef, + branch: impl AsRef, + to: Option>, + ) -> Result<()> { + Self::cmd_no_root() + .stderr(Stdio::inherit()) + .args(["clone", "--recurse-submodules"]) + .args(shallow.then_some("--depth=1")) + .args(shallow.then_some("--shallow-submodules")) + .arg("-b") + .arg(branch) + .arg(from) + .args(to) + .exec() + .map(drop) + } + pub fn clone( shallow: bool, from: impl AsRef, @@ -340,6 +330,23 @@ impl<'a> Git<'a> { .map(drop) } + pub fn fetch( + self, + shallow: bool, + remote: impl AsRef, + branch: Option>, + ) -> Result<()> { + self.cmd() + .stderr(Stdio::inherit()) + .arg("fetch") + .args(shallow.then_some("--no-tags")) + .args(shallow.then_some("--depth=1")) + .arg(remote) + .args(branch) + .exec() + .map(drop) + } + #[inline] pub fn root(self, root: &Path) -> Git<'_> { Git { root, ..self } @@ -378,6 +385,23 @@ impl<'a> Git<'a> { self.cmd().arg("add").args(paths).exec().map(drop) } + pub fn reset(self, hard: bool, tree: impl AsRef) -> Result<()> { + self.cmd().arg("reset").args(hard.then_some("--hard")).arg(tree).exec().map(drop) + } + + pub fn commit_tree( + self, + tree: impl AsRef, + msg: Option>, + ) -> Result { + self.cmd() + .arg("commit-tree") + .arg(tree) + .args(msg.as_ref().is_some().then_some("-m")) + .args(msg) + .get_stdout_lossy() + } + pub fn rm(self, force: bool, paths: I) -> Result<()> where I: IntoIterator, @@ -403,7 +427,7 @@ impl<'a> Git<'a> { output.status.code(), stdout.trim(), stderr.trim() - )) + )); } } Ok(()) @@ -417,8 +441,8 @@ impl<'a> Git<'a> { self.cmd().args(["status", "--porcelain"]).exec().map(|out| out.stdout.is_empty()) } - pub fn has_branch(self, branch: impl AsRef) -> Result { - self.cmd() + pub fn has_branch(self, branch: impl AsRef, at: &Path) -> Result { + self.cmd_at(at) .args(["branch", "--list", "--no-color"]) .arg(branch) .get_stdout_lossy() @@ -436,16 +460,17 @@ and it requires clean working and staging areas, including no untracked files. Check the current git repository's status with `git status`. Then, you can track files with `git add ...` and then commit them with `git commit`, -ignore them in the `.gitignore` file, or run this command again with the `--no-commit` flag. - -If none of the previous steps worked, please open an issue at: -https://github.com/foundry-rs/foundry/issues/new/choose" +ignore them in the `.gitignore` file." )) } } - pub fn commit_hash(self, short: bool) -> Result { - self.cmd().arg("rev-parse").args(short.then_some("--short")).arg("HEAD").get_stdout_lossy() + pub fn commit_hash(self, short: bool, revision: &str) -> Result { + self.cmd() + .arg("rev-parse") + .args(short.then_some("--short")) + .arg(revision) + .get_stdout_lossy() } pub fn tag(self) -> Result { @@ -464,6 +489,19 @@ https://github.com/foundry-rs/foundry/issues/new/choose" .map(|stdout| stdout.lines().any(|line| line.starts_with('-'))) } + /// Returns true if the given path has no submodules by checking `git submodule status` + pub fn has_submodules(self, paths: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + self.cmd() + .args(["submodule", "status"]) + .args(paths) + .get_stdout_lossy() + .map(|stdout| stdout.trim().lines().next().is_some()) + } + pub fn submodule_add( self, force: bool, @@ -481,28 +519,57 @@ https://github.com/foundry-rs/foundry/issues/new/choose" .map(drop) } - pub fn submodule_update(self, force: bool, remote: bool, paths: I) -> Result<()> + pub fn submodule_update( + self, + force: bool, + remote: bool, + no_fetch: bool, + recursive: bool, + paths: I, + ) -> Result<()> where I: IntoIterator, S: AsRef, { self.cmd() .stderr(self.stderr()) - .args(["submodule", "update", "--progress", "--init", "--recursive"]) + .args(["submodule", "update", "--progress", "--init"]) .args(self.shallow.then_some("--depth=1")) .args(force.then_some("--force")) .args(remote.then_some("--remote")) + .args(no_fetch.then_some("--no-fetch")) + .args(recursive.then_some("--recursive")) .args(paths) .exec() .map(drop) } + pub fn submodule_foreach(self, recursive: bool, cmd: impl AsRef) -> Result<()> { + self.cmd() + .stderr(self.stderr()) + .args(["submodule", "foreach"]) + .args(recursive.then_some("--recursive")) + .arg(cmd) + .exec() + .map(drop) + } + + pub fn submodule_init(self) -> Result<()> { + self.cmd().stderr(self.stderr()).args(["submodule", "init"]).exec().map(drop) + } + pub fn cmd(self) -> Command { let mut cmd = Self::cmd_no_root(); cmd.current_dir(self.root); cmd } + pub fn cmd_at(self, path: &Path) -> Command { + let mut cmd = Self::cmd_no_root(); + cmd.current_dir(path); + cmd + } + pub fn cmd_no_root() -> Command { let mut cmd = Command::new("git"); cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); @@ -535,7 +602,7 @@ mod tests { assert!(!p.is_sol_test()); } - // loads .env from cwd and project dir, See [`find_project_root_path()`] + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { let temp = tempdir().unwrap(); diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 0333b6d6b0950..985809bec96e9 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -9,40 +9,72 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -# foundry internal +foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } +foundry-common-fmt.workspace = true +foundry-compilers.workspace = true foundry-config.workspace = true -foundry-macros.workspace = true -# eth -ethers-core.workspace = true -ethers-solc.workspace = true -ethers-providers.workspace = true -ethers-middleware.workspace = true -ethers-etherscan = { workspace = true, features = ["ethers-solc"] } +alloy-contract.workspace = true +alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-eips.workspace = true +alloy-json-abi.workspace = true +alloy-json-rpc.workspace = true +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-provider.workspace = true +alloy-pubsub.workspace = true +alloy-rpc-client.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } +alloy-serde.workspace = true +alloy-sol-types.workspace = true +alloy-transport-http = { workspace = true, features = [ + "reqwest", + "reqwest-rustls-tls", +] } +alloy-transport-ipc.workspace = true +alloy-transport-ws.workspace = true +alloy-transport.workspace = true +alloy-consensus = { workspace = true, features = ["k256"] } +alloy-network.workspace = true -# io -reqwest = { version = "0.11", default-features = false } +tower.workspace = true -# cli +async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } -comfy-table = "6" -tracing = "0.1" -yansi = "0.5" -tempfile = "3" - -# misc -auto_impl = "1.1.0" -serde = "1" -serde_json = "1" -thiserror = "1" -eyre = "0.6" -walkdir = "2" -semver = "1" -once_cell = "1" -dunce = "1" -regex = "1" -globset = "0.4" +comfy-table.workspace = true +dunce.workspace = true +eyre.workspace = true +itertools.workspace = true +num-format.workspace = true +reqwest.workspace = true +semver.workspace = true +serde_json.workspace = true +serde = { workspace = true, features = ["derive"] } +thiserror.workspace = true +tokio.workspace = true +tracing.workspace = true +url.workspace = true +walkdir.workspace = true +yansi.workspace = true + +anstream.workspace = true +anstyle.workspace = true +terminal_size.workspace = true + +[build-dependencies] +chrono.workspace = true +vergen = { workspace = true, features = ["build", "git", "gitcl"] } [dev-dependencies] -tokio = { version = "1", features = ["rt-multi-thread", "macros"] } +foundry-macros.workspace = true +similar-asserts.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +axum = { workspace = true } diff --git a/crates/common/build.rs b/crates/common/build.rs new file mode 100644 index 0000000000000..54890ffdaff0f --- /dev/null +++ b/crates/common/build.rs @@ -0,0 +1,90 @@ +use std::{env, error::Error}; + +use chrono::DateTime; +use vergen::EmitBuilder; + +#[allow(clippy::disallowed_macros)] +fn main() -> Result<(), Box> { + // Re-run the build script if the build script itself changes or if the + // environment variables change. + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=TAG_NAME"); + println!("cargo:rerun-if-env-changed=PROFILE"); + + EmitBuilder::builder() + .build_date() + .build_timestamp() + .git_describe(false, true, None) + .git_sha(false) + .emit_and_set()?; + + // Set the Git SHA of the latest commit. + let sha = env::var("VERGEN_GIT_SHA")?; + let sha_short = &sha[..10]; + + // Set the version suffix and whether the version is a nightly build. + // if not on a tag: 0.3.0-dev+ba03de0019.1737036656.debug + // if on a tag: 0.3.0-stable+ba03de0019.1737036656.release + let tag_name = env::var("TAG_NAME") + .or_else(|_| env::var("CARGO_TAG_NAME")) + .unwrap_or_else(|_| String::from("dev")); + let (is_nightly, version_suffix) = if tag_name.contains("nightly") { + (true, "-nightly".to_string()) + } else { + (false, format!("-{tag_name}")) + }; + + // Whether the version is a nightly build. + if is_nightly { + println!("cargo:rustc-env=FOUNDRY_IS_NIGHTLY_VERSION=true"); + } + + // Set formatted version strings + let pkg_version = env::var("CARGO_PKG_VERSION")?; + + // Append the profile to the version string + let out_dir = env::var("OUT_DIR").unwrap(); + let profile = out_dir.rsplit(std::path::MAIN_SEPARATOR).nth(3).unwrap(); + + // Set the build timestamp. + let build_timestamp = env::var("VERGEN_BUILD_TIMESTAMP")?; + let build_timestamp_unix = DateTime::parse_from_rfc3339(&build_timestamp)?.timestamp(); + + // The SemVer compatible version information for Foundry. + // - The latest version from Cargo.toml. + // - The short SHA of the latest commit. + // - The UNIX formatted build timestamp. + // - The build profile. + // Example: forge 0.3.0-nightly+3cb96bde9b.1737036656.debug + println!( + "cargo:rustc-env=FOUNDRY_SEMVER_VERSION={pkg_version}{version_suffix}+{sha_short}.{build_timestamp_unix}.{profile}" + ); + + // The short version information for the Foundry CLI. + // - The latest version from Cargo.toml + // - The short SHA of the latest commit. + // Example: 0.3.0-dev (3cb96bde9b) + println!("cargo:rustc-env=FOUNDRY_SHORT_VERSION={pkg_version}{version_suffix} ({sha_short} {build_timestamp})"); + + // The long version information for the Foundry CLI. + // - The latest version from Cargo.toml. + // - The long SHA of the latest commit. + // - The build timestamp in RFC3339 format and UNIX format in seconds. + // - The build profile. + // + // Example: + // + // ```text + // + // Version: 0.3.0-dev + // Commit SHA: 5186142d3bb4d1be7bb4ade548b77c8e2270717e + // Build Timestamp: 2025-01-16T15:04:03.522021223Z (1737039843) + // Build Profile: debug + // ``` + println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_0=Version: {pkg_version}{version_suffix}"); + println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_1=Commit SHA: {sha}"); + println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_2=Build Timestamp: {build_timestamp} ({build_timestamp_unix})"); + println!("cargo:rustc-env=FOUNDRY_LONG_VERSION_3=Build Profile: {profile}"); + + Ok(()) +} diff --git a/crates/common/fmt/Cargo.toml b/crates/common/fmt/Cargo.toml new file mode 100644 index 0000000000000..2a56b3b10e010 --- /dev/null +++ b/crates/common/fmt/Cargo.toml @@ -0,0 +1,34 @@ +[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"] } + +# ui +alloy-consensus.workspace = true +alloy-network.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 +yansi.workspace = true + +[dev-dependencies] +foundry-macros.workspace = true +similar-asserts.workspace = true diff --git a/crates/common/fmt/src/console.rs b/crates/common/fmt/src/console.rs new file mode 100644 index 0000000000000..5bc291e03e090 --- /dev/null +++ b/crates/common/fmt/src/console.rs @@ -0,0 +1,613 @@ +use super::UIfmt; +use alloy_primitives::{Address, Bytes, FixedBytes, I256, U256}; +use std::fmt::{self, Write}; + +/// A piece is a portion of the format string which represents the next part to emit. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Piece<'a> { + /// A literal string which should directly be emitted. + String(&'a str), + /// A format specifier which should be replaced with the next argument. + NextArgument(FormatSpec), +} + +/// A format specifier. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum FormatSpec { + /// `%s` + #[default] + String, + /// `%d` + Number, + /// `%i` + Integer, + /// `%o` + Object, + /// `%e`, `%18e` + Exponential(Option), + /// `%x` + Hexadecimal, +} + +impl fmt::Display for FormatSpec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("%")?; + match *self { + Self::String => f.write_str("s"), + Self::Number => f.write_str("d"), + Self::Integer => f.write_str("i"), + Self::Object => f.write_str("o"), + Self::Exponential(Some(n)) => write!(f, "{n}e"), + Self::Exponential(None) => f.write_str("e"), + Self::Hexadecimal => f.write_str("x"), + } + } +} + +enum ParseArgError { + /// Failed to parse the argument. + Err, + /// Escape `%%`. + Skip, +} + +/// Parses a format string into a sequence of [pieces][Piece]. +#[derive(Debug)] +pub struct Parser<'a> { + input: &'a str, + chars: std::str::CharIndices<'a>, +} + +impl<'a> Parser<'a> { + /// Creates a new parser for the given input. + pub fn new(input: &'a str) -> Self { + Self { input, chars: input.char_indices() } + } + + /// Parses a string until the next format specifier. + /// + /// `skip` is the number of format specifier characters (`%`) to ignore before returning the + /// string. + fn string(&mut self, start: usize, mut skip: usize) -> &'a str { + while let Some((pos, c)) = self.peek() { + if c == '%' { + if skip == 0 { + return &self.input[start..pos]; + } + skip -= 1; + } + self.chars.next(); + } + &self.input[start..] + } + + /// Parses a format specifier. + /// + /// If `Err` is returned, the internal iterator may have been advanced and it may be in an + /// invalid state. + fn argument(&mut self) -> Result { + let (start, ch) = self.peek().ok_or(ParseArgError::Err)?; + let simple_spec = match ch { + 's' => Some(FormatSpec::String), + 'd' => Some(FormatSpec::Number), + 'i' => Some(FormatSpec::Integer), + 'o' => Some(FormatSpec::Object), + 'e' => Some(FormatSpec::Exponential(None)), + 'x' => Some(FormatSpec::Hexadecimal), + // "%%" is a literal '%'. + '%' => return Err(ParseArgError::Skip), + _ => None, + }; + if let Some(spec) = simple_spec { + self.chars.next(); + return Ok(spec); + } + + // %e + if ch.is_ascii_digit() { + let n = self.integer(start); + if let Some((_, 'e')) = self.peek() { + self.chars.next(); + return Ok(FormatSpec::Exponential(n)); + } + } + + Err(ParseArgError::Err) + } + + fn integer(&mut self, start: usize) -> Option { + let mut end = start; + while let Some((pos, ch)) = self.peek() { + if !ch.is_ascii_digit() { + end = pos; + break; + } + self.chars.next(); + } + self.input[start..end].parse().ok() + } + + fn current_pos(&mut self) -> usize { + self.peek().map(|(n, _)| n).unwrap_or(self.input.len()) + } + + fn peek(&mut self) -> Option<(usize, char)> { + self.peek_n(0) + } + + fn peek_n(&mut self, n: usize) -> Option<(usize, char)> { + self.chars.clone().nth(n) + } +} + +impl<'a> Iterator for Parser<'a> { + type Item = Piece<'a>; + + fn next(&mut self) -> Option { + let (mut start, ch) = self.peek()?; + let mut skip = 0; + if ch == '%' { + let prev = self.chars.clone(); + self.chars.next(); + match self.argument() { + Ok(arg) => { + debug_assert_eq!(arg.to_string(), self.input[start..self.current_pos()]); + return Some(Piece::NextArgument(arg)); + } + + // Skip the argument if we encountered "%%". + Err(ParseArgError::Skip) => { + start = self.current_pos(); + skip += 1; + } + + // Reset the iterator if we failed to parse the argument, and include any + // parsed and unparsed specifier in `String`. + Err(ParseArgError::Err) => { + self.chars = prev; + skip += 1; + } + } + } + Some(Piece::String(self.string(start, skip))) + } +} + +/// Formats a value using a [FormatSpec]. +pub trait ConsoleFmt { + /// Formats a value using a [FormatSpec]. + fn fmt(&self, spec: FormatSpec) -> String; +} + +impl ConsoleFmt for String { + fn fmt(&self, spec: FormatSpec) -> String { + match spec { + FormatSpec::String => self.clone(), + FormatSpec::Object => format!("'{}'", self.clone()), + FormatSpec::Number | + FormatSpec::Integer | + FormatSpec::Exponential(_) | + FormatSpec::Hexadecimal => Self::from("NaN"), + } + } +} + +impl ConsoleFmt for bool { + fn fmt(&self, spec: FormatSpec) -> String { + match spec { + FormatSpec::String => self.pretty(), + FormatSpec::Object => format!("'{}'", self.pretty()), + FormatSpec::Number => (*self as i32).to_string(), + FormatSpec::Integer | FormatSpec::Exponential(_) | FormatSpec::Hexadecimal => { + String::from("NaN") + } + } + } +} + +impl ConsoleFmt for U256 { + fn fmt(&self, spec: FormatSpec) -> String { + match spec { + FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => { + self.pretty() + } + FormatSpec::Hexadecimal => { + let hex = format!("{self:x}"); + format!("0x{}", hex.trim_start_matches('0')) + } + FormatSpec::Exponential(None) => { + let log = self.pretty().len() - 1; + let exp10 = Self::from(10).pow(Self::from(log)); + let amount = *self; + let integer = amount / exp10; + let decimal = (amount % exp10).to_string(); + let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); + if !decimal.is_empty() { + format!("{integer}.{decimal}e{log}") + } else { + format!("{integer}e{log}") + } + } + FormatSpec::Exponential(Some(precision)) => { + let exp10 = Self::from(10).pow(Self::from(precision)); + let amount = *self; + let integer = amount / exp10; + let decimal = (amount % exp10).to_string(); + let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string(); + if !decimal.is_empty() { + format!("{integer}.{decimal}") + } else { + format!("{integer}") + } + } + } + } +} + +impl ConsoleFmt for I256 { + fn fmt(&self, spec: FormatSpec) -> String { + match spec { + FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => { + self.pretty() + } + FormatSpec::Hexadecimal => { + let hex = format!("{self:x}"); + format!("0x{}", hex.trim_start_matches('0')) + } + FormatSpec::Exponential(None) => { + let amount = *self; + let sign = if amount.is_negative() { "-" } else { "" }; + let log = if amount.is_negative() { + self.pretty().len() - 2 + } else { + self.pretty().len() - 1 + }; + let exp10 = Self::exp10(log); + let integer = (amount / exp10).twos_complement(); + let decimal = (amount % exp10).twos_complement().to_string(); + let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); + if !decimal.is_empty() { + format!("{sign}{integer}.{decimal}e{log}") + } else { + format!("{sign}{integer}e{log}") + } + } + FormatSpec::Exponential(Some(precision)) => { + let amount = *self; + let sign = if amount.is_negative() { "-" } else { "" }; + let exp10 = Self::exp10(precision); + let integer = (amount / exp10).twos_complement(); + let decimal = (amount % exp10).twos_complement().to_string(); + let decimal = format!("{decimal:0>precision$}").trim_end_matches('0').to_string(); + if !decimal.is_empty() { + format!("{sign}{integer}.{decimal}") + } else { + format!("{sign}{integer}") + } + } + } + } +} + +impl ConsoleFmt for Address { + fn fmt(&self, spec: FormatSpec) -> String { + match spec { + FormatSpec::String | FormatSpec::Hexadecimal => self.pretty(), + FormatSpec::Object => format!("'{}'", self.pretty()), + FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential(_) => { + String::from("NaN") + } + } + } +} + +impl ConsoleFmt for Vec { + fn fmt(&self, spec: FormatSpec) -> String { + self[..].fmt(spec) + } +} + +impl ConsoleFmt for Bytes { + fn fmt(&self, spec: FormatSpec) -> String { + self[..].fmt(spec) + } +} + +impl ConsoleFmt for [u8; N] { + fn fmt(&self, spec: FormatSpec) -> String { + self[..].fmt(spec) + } +} + +impl ConsoleFmt for FixedBytes { + fn fmt(&self, spec: FormatSpec) -> String { + self[..].fmt(spec) + } +} + +impl ConsoleFmt for [u8] { + fn fmt(&self, spec: FormatSpec) -> String { + match spec { + FormatSpec::String | FormatSpec::Hexadecimal => self.pretty(), + FormatSpec::Object => format!("'{}'", self.pretty()), + FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential(_) => { + String::from("NaN") + } + } + } +} + +/// Formats a string using the input values. +/// +/// Formatting rules are the same as Hardhat. The supported format specifiers are as follows: +/// - %s: Converts the value using its String representation. This is equivalent to applying +/// [`UIfmt::pretty()`] on the format string. +/// - %o: Treats the format value as a javascript "object" and converts it to its string +/// representation. +/// - %d, %i: Converts the value to an integer. If a non-numeric value, such as String or Address, +/// is passed, then the spec is formatted as `NaN`. +/// - %x: Converts the value to a hexadecimal string. If a non-numeric value, such as String or +/// Address, is passed, then the spec is formatted as `NaN`. +/// - %e: Converts the value to an exponential notation string. If a non-numeric value, such as +/// String or Address, is passed, then the spec is formatted as `NaN`. +/// - %%: This is parsed as a single percent sign ('%') without consuming any input value. +/// +/// Unformatted values are appended to the end of the formatted output using [`UIfmt::pretty()`]. +/// If there are more format specifiers than values, then the remaining unparsed format specifiers +/// appended to the formatted output as-is. +/// +/// # Examples +/// +/// ```ignore (not implemented for integers) +/// let formatted = foundry_common::fmt::console_format("%s has %d characters", &[&"foo", &3]); +/// assert_eq!(formatted, "foo has 3 characters"); +/// ``` +pub fn console_format(spec: &str, values: &[&dyn ConsoleFmt]) -> String { + let mut values = values.iter().copied(); + let mut result = String::with_capacity(spec.len()); + + // for the first space + let mut write_space = if spec.is_empty() { + false + } else { + format_spec(spec, &mut values, &mut result); + true + }; + + // append any remaining values with the standard format + for v in values { + let fmt = v.fmt(FormatSpec::String); + if write_space { + result.push(' '); + } + result.push_str(&fmt); + write_space = true; + } + + result +} + +fn format_spec<'a>( + s: &str, + mut values: impl Iterator, + result: &mut String, +) { + for piece in Parser::new(s) { + match piece { + Piece::String(s) => result.push_str(s), + Piece::NextArgument(spec) => { + if let Some(value) = values.next() { + result.push_str(&value.fmt(spec)); + } else { + // Write the format specifier as-is if there are no more values. + write!(result, "{spec}").unwrap(); + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + use foundry_macros::ConsoleFmt; + use std::str::FromStr; + + macro_rules! logf1 { + ($a:ident) => { + console_format(&$a.p_0, &[&$a.p_1]) + }; + } + + macro_rules! logf2 { + ($a:ident) => { + console_format(&$a.p_0, &[&$a.p_1, &$a.p_2]) + }; + } + + macro_rules! logf3 { + ($a:ident) => { + console_format(&$a.p_0, &[&$a.p_1, &$a.p_2, &$a.p_3]) + }; + } + + #[derive(Clone, Debug, ConsoleFmt)] + struct Log1 { + p_0: String, + p_1: U256, + } + + #[derive(Clone, Debug, ConsoleFmt)] + struct Log2 { + p_0: String, + p_1: bool, + p_2: U256, + } + + #[derive(Clone, Debug, ConsoleFmt)] + struct Log3 { + p_0: String, + p_1: Address, + p_2: bool, + p_3: U256, + } + + #[allow(unused)] + #[derive(Clone, Debug, ConsoleFmt)] + enum Logs { + Log1(Log1), + Log2(Log2), + Log3(Log3), + } + + #[test] + fn test_console_log_format_specifiers() { + let fmt_1 = |spec: &str, arg: &dyn ConsoleFmt| console_format(spec, &[arg]); + + assert_eq!("foo", fmt_1("%s", &String::from("foo"))); + assert_eq!("NaN", fmt_1("%d", &String::from("foo"))); + assert_eq!("NaN", fmt_1("%i", &String::from("foo"))); + assert_eq!("NaN", fmt_1("%e", &String::from("foo"))); + assert_eq!("NaN", fmt_1("%x", &String::from("foo"))); + assert_eq!("'foo'", fmt_1("%o", &String::from("foo"))); + assert_eq!("%s foo", fmt_1("%%s", &String::from("foo"))); + assert_eq!("% foo", fmt_1("%", &String::from("foo"))); + assert_eq!("% foo", fmt_1("%%", &String::from("foo"))); + + assert_eq!("true", fmt_1("%s", &true)); + assert_eq!("1", fmt_1("%d", &true)); + assert_eq!("0", fmt_1("%d", &false)); + assert_eq!("NaN", fmt_1("%i", &true)); + assert_eq!("NaN", fmt_1("%e", &true)); + assert_eq!("NaN", fmt_1("%x", &true)); + assert_eq!("'true'", fmt_1("%o", &true)); + + let b32 = + B256::from_str("0xdeadbeef00000000000000000000000000000000000000000000000000000000") + .unwrap(); + assert_eq!( + "0xdeadbeef00000000000000000000000000000000000000000000000000000000", + fmt_1("%s", &b32) + ); + assert_eq!( + "0xdeadbeef00000000000000000000000000000000000000000000000000000000", + fmt_1("%x", &b32) + ); + assert_eq!("NaN", fmt_1("%d", &b32)); + assert_eq!("NaN", fmt_1("%i", &b32)); + assert_eq!("NaN", fmt_1("%e", &b32)); + assert_eq!( + "'0xdeadbeef00000000000000000000000000000000000000000000000000000000'", + fmt_1("%o", &b32) + ); + + let addr = Address::from_str("0xdEADBEeF00000000000000000000000000000000").unwrap(); + assert_eq!("0xdEADBEeF00000000000000000000000000000000", fmt_1("%s", &addr)); + assert_eq!("NaN", fmt_1("%d", &addr)); + assert_eq!("NaN", fmt_1("%i", &addr)); + assert_eq!("NaN", fmt_1("%e", &addr)); + assert_eq!("0xdEADBEeF00000000000000000000000000000000", fmt_1("%x", &addr)); + assert_eq!("'0xdEADBEeF00000000000000000000000000000000'", fmt_1("%o", &addr)); + + let bytes = Bytes::from_str("0xdeadbeef").unwrap(); + assert_eq!("0xdeadbeef", fmt_1("%s", &bytes)); + assert_eq!("NaN", fmt_1("%d", &bytes)); + assert_eq!("NaN", fmt_1("%i", &bytes)); + assert_eq!("NaN", fmt_1("%e", &bytes)); + assert_eq!("0xdeadbeef", fmt_1("%x", &bytes)); + assert_eq!("'0xdeadbeef'", fmt_1("%o", &bytes)); + + assert_eq!("100", fmt_1("%s", &U256::from(100))); + assert_eq!("100", fmt_1("%d", &U256::from(100))); + assert_eq!("100", fmt_1("%i", &U256::from(100))); + assert_eq!("1e2", fmt_1("%e", &U256::from(100))); + assert_eq!("1.0023e6", fmt_1("%e", &U256::from(1002300))); + assert_eq!("1.23e5", fmt_1("%e", &U256::from(123000))); + assert_eq!("0x64", fmt_1("%x", &U256::from(100))); + assert_eq!("100", fmt_1("%o", &U256::from(100))); + + assert_eq!("100", fmt_1("%s", &I256::try_from(100).unwrap())); + assert_eq!("100", fmt_1("%d", &I256::try_from(100).unwrap())); + assert_eq!("100", fmt_1("%i", &I256::try_from(100).unwrap())); + assert_eq!("1e2", fmt_1("%e", &I256::try_from(100).unwrap())); + assert_eq!("-1e2", fmt_1("%e", &I256::try_from(-100).unwrap())); + assert_eq!("-1.0023e6", fmt_1("%e", &I256::try_from(-1002300).unwrap())); + assert_eq!("-1.23e5", fmt_1("%e", &I256::try_from(-123000).unwrap())); + assert_eq!("1.0023e6", fmt_1("%e", &I256::try_from(1002300).unwrap())); + assert_eq!("1.23e5", fmt_1("%e", &I256::try_from(123000).unwrap())); + + // %ne + assert_eq!("10", fmt_1("%1e", &I256::try_from(100).unwrap())); + assert_eq!("-1", fmt_1("%2e", &I256::try_from(-100).unwrap())); + assert_eq!("123000", fmt_1("%0e", &I256::try_from(123000).unwrap())); + assert_eq!("12300", fmt_1("%1e", &I256::try_from(123000).unwrap())); + assert_eq!("0.0123", fmt_1("%7e", &I256::try_from(123000).unwrap())); + assert_eq!("-0.0123", fmt_1("%7e", &I256::try_from(-123000).unwrap())); + + assert_eq!("0x64", fmt_1("%x", &I256::try_from(100).unwrap())); + assert_eq!( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c", + fmt_1("%x", &I256::try_from(-100).unwrap()) + ); + assert_eq!( + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffe8b7891800", + fmt_1("%x", &I256::try_from(-100000000000i64).unwrap()) + ); + assert_eq!("100", fmt_1("%o", &I256::try_from(100).unwrap())); + + // make sure that %byte values are not consumed when there are no values + assert_eq!("%333d%3e%5F", console_format("%333d%3e%5F", &[])); + assert_eq!( + "%5d123456.789%2f%3f%e1", + console_format("%5d%3e%2f%3f%e1", &[&U256::from(123456789)]) + ); + } + + #[test] + fn test_console_log_format() { + let mut log1 = Log1 { p_0: "foo %s".to_string(), p_1: U256::from(100) }; + assert_eq!("foo 100", logf1!(log1)); + log1.p_0 = String::from("foo"); + assert_eq!("foo 100", logf1!(log1)); + log1.p_0 = String::from("%s foo"); + assert_eq!("100 foo", logf1!(log1)); + + let mut log2 = Log2 { p_0: "foo %s %s".to_string(), p_1: true, p_2: U256::from(100) }; + assert_eq!("foo true 100", logf2!(log2)); + log2.p_0 = String::from("foo"); + assert_eq!("foo true 100", logf2!(log2)); + log2.p_0 = String::from("%s %s foo"); + assert_eq!("true 100 foo", logf2!(log2)); + + let log3 = Log3 { + p_0: String::from("foo %s %%s %s and %d foo %%"), + p_1: Address::from_str("0xdEADBEeF00000000000000000000000000000000").unwrap(), + p_2: true, + p_3: U256::from(21), + }; + assert_eq!( + "foo 0xdEADBEeF00000000000000000000000000000000 %s true and 21 foo %", + logf3!(log3) + ); + + // %ne + let log4 = Log1 { p_0: String::from("%5e"), p_1: U256::from(123456789) }; + assert_eq!("1234.56789", logf1!(log4)); + + let log5 = Log1 { p_0: String::from("foo %3e bar"), p_1: U256::from(123456789) }; + assert_eq!("foo 123456.789 bar", logf1!(log5)); + + let log6 = + Log2 { p_0: String::from("%e and %12e"), p_1: false, p_2: U256::from(123456789) }; + assert_eq!("NaN and 0.000123456789", logf2!(log6)); + } + + #[test] + fn test_derive_format() { + let log1 = Log1 { p_0: String::from("foo %s bar"), p_1: U256::from(42) }; + assert_eq!(log1.fmt(Default::default()), "foo 42 bar"); + let call = Logs::Log1(log1); + assert_eq!(call.fmt(Default::default()), "foo 42 bar"); + } +} diff --git a/crates/common/fmt/src/dynamic.rs b/crates/common/fmt/src/dynamic.rs new file mode 100644 index 0000000000000..2ba40286dd108 --- /dev/null +++ b/crates/common/fmt/src/dynamic.rs @@ -0,0 +1,185 @@ +use super::{format_int_exp, format_uint_exp}; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::hex; +use std::fmt; + +/// [`DynSolValue`] formatter. +struct DynValueFormatter { + raw: bool, +} + +impl DynValueFormatter { + /// Recursively formats a [`DynSolValue`]. + fn value(&self, value: &DynSolValue, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match value { + DynSolValue::Address(inner) => write!(f, "{inner}"), + DynSolValue::Function(inner) => write!(f, "{inner}"), + DynSolValue::Bytes(inner) => f.write_str(&hex::encode_prefixed(inner)), + DynSolValue::FixedBytes(word, size) => { + f.write_str(&hex::encode_prefixed(&word[..*size])) + } + DynSolValue::Uint(inner, _) => { + if self.raw { + write!(f, "{inner}") + } else { + f.write_str(&format_uint_exp(*inner)) + } + } + DynSolValue::Int(inner, _) => { + if self.raw { + write!(f, "{inner}") + } else { + f.write_str(&format_int_exp(*inner)) + } + } + DynSolValue::Array(values) | DynSolValue::FixedArray(values) => { + f.write_str("[")?; + self.list(values, f)?; + f.write_str("]") + } + DynSolValue::Tuple(values) => self.tuple(values, f), + DynSolValue::String(inner) => { + if self.raw { + write!(f, "{}", inner.escape_debug()) + } else { + write!(f, "{inner:?}") // escape strings + } + } + DynSolValue::Bool(inner) => write!(f, "{inner}"), + DynSolValue::CustomStruct { name, prop_names, tuple } => { + if self.raw { + return self.tuple(tuple, f); + } + + f.write_str(name)?; + + if prop_names.len() == tuple.len() { + f.write_str("({ ")?; + + for (i, (prop_name, value)) in std::iter::zip(prop_names, tuple).enumerate() { + if i > 0 { + f.write_str(", ")?; + } + f.write_str(prop_name)?; + f.write_str(": ")?; + self.value(value, f)?; + } + + f.write_str(" })") + } else { + self.tuple(tuple, f) + } + } + } + } + + /// Recursively formats a comma-separated list of [`DynSolValue`]s. + fn list(&self, values: &[DynSolValue], f: &mut fmt::Formatter<'_>) -> fmt::Result { + for (i, value) in values.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + self.value(value, f)?; + } + Ok(()) + } + + /// Formats the given values as a tuple. + fn tuple(&self, values: &[DynSolValue], f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("(")?; + self.list(values, f)?; + f.write_str(")") + } +} + +/// Wrapper that implements [`Display`](fmt::Display) for a [`DynSolValue`]. +struct DynValueDisplay<'a> { + /// The value to display. + value: &'a DynSolValue, + /// The formatter. + formatter: DynValueFormatter, +} + +impl<'a> DynValueDisplay<'a> { + /// Creates a new [`Display`](fmt::Display) wrapper for the given value. + #[inline] + fn new(value: &'a DynSolValue, raw: bool) -> Self { + Self { value, formatter: DynValueFormatter { raw } } + } +} + +impl fmt::Display for DynValueDisplay<'_> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.formatter.value(self.value, f) + } +} + +/// Parses string input as Token against the expected ParamType +pub fn parse_tokens<'a, I: IntoIterator>( + params: I, +) -> 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`]. +pub fn format_tokens(tokens: &[DynSolValue]) -> impl Iterator + '_ { + tokens.iter().map(format_token) +} + +/// Pretty-prints a slice of tokens using [`format_token_raw`]. +pub fn format_tokens_raw(tokens: &[DynSolValue]) -> impl Iterator + '_ { + tokens.iter().map(format_token_raw) +} + +/// Pretty-prints the given value into a string suitable for user output. +pub fn format_token(value: &DynSolValue) -> String { + DynValueDisplay::new(value, false).to_string() +} + +/// Pretty-prints the given value into a string suitable for re-parsing as values later. +/// +/// This means: +/// - integers are not formatted with exponential notation hints +/// - structs are formatted as tuples, losing the struct and property names +pub fn format_token_raw(value: &DynSolValue) -> String { + DynValueDisplay::new(value, true).to_string() +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{address, U256}; + + #[test] + fn parse_hex_uint() { + let ty = DynSolType::Uint(256); + + let values = parse_tokens(std::iter::once((&ty, "100"))).unwrap(); + assert_eq!(values, [DynSolValue::Uint(U256::from(100), 256)]); + + let val: U256 = U256::from(100u64); + let hex_val = format!("0x{val:x}"); + let values = parse_tokens(std::iter::once((&ty, hex_val.as_str()))).unwrap(); + assert_eq!(values, [DynSolValue::Uint(U256::from(100), 256)]); + } + + #[test] + fn format_addr() { + // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md + assert_eq!( + format_token(&DynSolValue::Address(address!( + "5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed" + ))), + "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", + ); + + // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md + assert_ne!( + format_token(&DynSolValue::Address(address!( + "Fb6916095cA1Df60bb79ce92cE3EA74c37c5d359" + ))), + "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359" + ); + } +} diff --git a/crates/common/fmt/src/eof.rs b/crates/common/fmt/src/eof.rs new file mode 100644 index 0000000000000..1ae9de70b6b3a --- /dev/null +++ b/crates/common/fmt/src/eof.rs @@ -0,0 +1,79 @@ +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, 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.apply_modifier(UTF8_ROUND_CORNERS); + 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.apply_modifier(UTF8_ROUND_CORNERS); + 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.apply_modifier(UTF8_ROUND_CORNERS); + 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.apply_modifier(UTF8_ROUND_CORNERS); + 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/fmt/src/exp.rs b/crates/common/fmt/src/exp.rs new file mode 100644 index 0000000000000..84444615e6d09 --- /dev/null +++ b/crates/common/fmt/src/exp.rs @@ -0,0 +1,123 @@ +use alloy_primitives::{Sign, I256, U256}; +use yansi::Paint; + +/// 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}") +} + +/// 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 +/// trailing zeros. +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::U256; +/// use foundry_common_fmt::format_uint_exp as f; +/// +/// # yansi::disable(); +/// assert_eq!(f(U256::from(0)), "0"); +/// assert_eq!(f(U256::from(1234)), "1234"); +/// assert_eq!(f(U256::from(1234567890)), "1234567890 [1.234e9]"); +/// assert_eq!(f(U256::from(1000000000000000000_u128)), "1000000000000000000 [1e18]"); +/// assert_eq!(f(U256::from(10000000000000000000000_u128)), "10000000000000000000000 [1e22]"); +/// ``` +pub fn format_uint_exp(num: U256) -> String { + if num < U256::from(10_000) { + return num.to_string() + } + + let exp = to_exp_notation(num, 4, true, Sign::Positive); + format!("{num} {}", format!("[{exp}]").dim()) +} + +/// Formats a U256 number to string, adding an exponential notation _hint_. +/// +/// Same as [`format_uint_exp`]. +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::I256; +/// use foundry_common_fmt::format_int_exp as f; +/// +/// # yansi::disable(); +/// assert_eq!(f(I256::try_from(0).unwrap()), "0"); +/// assert_eq!(f(I256::try_from(-1).unwrap()), "-1"); +/// assert_eq!(f(I256::try_from(1234).unwrap()), "1234"); +/// assert_eq!(f(I256::try_from(1234567890).unwrap()), "1234567890 [1.234e9]"); +/// assert_eq!(f(I256::try_from(-1234567890).unwrap()), "-1234567890 [-1.234e9]"); +/// assert_eq!(f(I256::try_from(1000000000000000000_u128).unwrap()), "1000000000000000000 [1e18]"); +/// assert_eq!( +/// f(I256::try_from(10000000000000000000000_u128).unwrap()), +/// "10000000000000000000000 [1e22]" +/// ); +/// assert_eq!( +/// f(I256::try_from(-10000000000000000000000_i128).unwrap()), +/// "-10000000000000000000000 [-1e22]" +/// ); +/// ``` +pub fn format_int_exp(num: I256) -> String { + let (sign, abs) = num.into_sign_and_abs(); + if abs < U256::from(10_000) { + return format!("{sign}{abs}"); + } + + 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 0000000000000..b76016cd45d25 --- /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, format_tokens_raw, 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/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs new file mode 100644 index 0000000000000..1fa58ff088c99 --- /dev/null +++ b/crates/common/fmt/src/ui.rs @@ -0,0 +1,1464 @@ +//! Helper trait and functions to format Ethereum types. + +use alloy_consensus::{ + Eip658Value, Receipt, ReceiptWithBloom, Transaction as TxTrait, TxEnvelope, TxType, Typed2718, +}; +use alloy_network::{ + AnyHeader, AnyReceiptEnvelope, AnyRpcBlock, AnyTransactionReceipt, AnyTxEnvelope, + ReceiptResponse, +}; +use alloy_primitives::{hex, Address, Bloom, Bytes, FixedBytes, Uint, I256, U256, U64, U8}; +use alloy_rpc_types::{ + AccessListItem, Block, BlockTransactions, Header, Log, Transaction, TransactionReceipt, +}; +use alloy_serde::{OtherFields, WithOtherFields}; +use serde::Deserialize; + +/// length of the name column for pretty formatting `{:>20}{value}` +const NAME_COLUMN_LEN: usize = 20usize; + +/// Helper trait to format Ethereum types. +/// +/// # Examples +/// +/// ``` +/// use foundry_common_fmt::UIfmt; +/// +/// let boolean: bool = true; +/// let string = boolean.pretty(); +/// ``` +pub trait UIfmt { + /// Return a prettified string version of the value + fn pretty(&self) -> String; +} + +impl UIfmt for &T { + fn pretty(&self) -> String { + (*self).pretty() + } +} + +impl UIfmt for Option { + fn pretty(&self) -> String { + if let Some(ref inner) = self { + inner.pretty() + } else { + String::new() + } + } +} + +impl UIfmt for [T] { + fn pretty(&self) -> String { + if !self.is_empty() { + let mut s = String::with_capacity(self.len() * 64); + s.push_str("[\n"); + for item in self { + for line in item.pretty().lines() { + s.push('\t'); + s.push_str(line); + s.push('\n'); + } + } + s.push(']'); + s + } else { + "[]".to_string() + } + } +} + +impl UIfmt for String { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for u64 { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for u128 { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for bool { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for Uint { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for I256 { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for Address { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for Bloom { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for TxType { + fn pretty(&self) -> String { + (*self as u8).to_string() + } +} + +impl UIfmt for Vec { + fn pretty(&self) -> String { + self[..].pretty() + } +} + +impl UIfmt for Bytes { + fn pretty(&self) -> String { + self[..].pretty() + } +} + +impl UIfmt for [u8; N] { + fn pretty(&self) -> String { + self[..].pretty() + } +} + +impl UIfmt for FixedBytes { + fn pretty(&self) -> String { + self[..].pretty() + } +} + +impl UIfmt for [u8] { + fn pretty(&self) -> String { + hex::encode_prefixed(self) + } +} + +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 { + fn pretty(&self) -> String { + let Self { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + from, + to, + gas_used, + contract_address, + effective_gas_price, + inner: + AnyReceiptEnvelope { + r#type: transaction_type, + inner: + ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, + }, + }, + blob_gas_price, + blob_gas_used, + }, + other, + } = self; + + let mut pretty = format!( + " +blockHash {} +blockNumber {} +contractAddress {} +cumulativeGasUsed {} +effectiveGasPrice {} +from {} +gasUsed {} +logs {} +logsBloom {} +root {} +status {} +transactionHash {} +transactionIndex {} +type {} +blobGasPrice {} +blobGasUsed {}", + block_hash.pretty(), + block_number.pretty(), + contract_address.pretty(), + cumulative_gas_used.pretty(), + effective_gas_price.pretty(), + from.pretty(), + gas_used.pretty(), + serde_json::to_string(&logs).unwrap(), + logs_bloom.pretty(), + self.state_root().pretty(), + status.pretty(), + transaction_hash.pretty(), + transaction_index.pretty(), + transaction_type, + blob_gas_price.pretty(), + blob_gas_used.pretty() + ); + + if let Some(to) = to { + pretty.push_str(&format!("\nto {}", to.pretty())); + } + + // additional captured fields + pretty.push_str(&other.pretty()); + + pretty + } +} + +impl UIfmt for Log { + fn pretty(&self) -> String { + format!( + " +address: {} +blockHash: {} +blockNumber: {} +data: {} +logIndex: {} +removed: {} +topics: {} +transactionHash: {} +transactionIndex: {}", + self.address().pretty(), + self.block_hash.pretty(), + self.block_number.pretty(), + self.data().data.pretty(), + self.log_index.pretty(), + self.removed.pretty(), + self.topics().pretty(), + self.transaction_hash.pretty(), + self.transaction_index.pretty(), + ) + } +} + +impl UIfmt for Block> { + fn pretty(&self) -> String { + format!( + " +{} +transactions: {}", + pretty_block_basics(self), + self.transactions.pretty() + ) + } +} + +impl UIfmt for BlockTransactions { + fn pretty(&self) -> String { + match self { + Self::Hashes(hashes) => hashes.pretty(), + Self::Full(transactions) => transactions.pretty(), + Self::Uncle => String::new(), + } + } +} + +impl UIfmt for OtherFields { + fn pretty(&self) -> String { + let mut s = String::with_capacity(self.len() * 30); + if !self.is_empty() { + s.push('\n'); + } + for (key, value) in self.iter() { + let val = EthValue::from(value.clone()).pretty(); + let offset = NAME_COLUMN_LEN.saturating_sub(key.len()); + s.push_str(key); + s.extend(std::iter::repeat_n(' ', offset + 1)); + s.push_str(&val); + s.push('\n'); + } + s + } +} + +impl UIfmt for AccessListItem { + fn pretty(&self) -> String { + let mut s = String::with_capacity(42 + self.storage_keys.len() * 66); + s.push_str(self.address.pretty().as_str()); + s.push_str(" => "); + s.push_str(self.storage_keys.pretty().as_str()); + s + } +} + +impl UIfmt for TxEnvelope { + fn pretty(&self) -> String { + match &self { + Self::Eip2930(tx) => format!( + " +accessList {} +chainId {} +gasLimit {} +gasPrice {} +hash {} +input {} +nonce {} +r {} +s {} +to {} +type {} +value {} +yParity {}", + self.access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.chain_id().pretty(), + self.gas_limit().pretty(), + self.gas_price().pretty(), + self.tx_hash().pretty(), + self.input().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + Self::Eip1559(tx) => format!( + " +accessList {} +chainId {} +gasLimit {} +hash {} +input {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +r {} +s {} +to {} +type {} +value {} +yParity {}", + self.access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.chain_id().pretty(), + self.gas_limit().pretty(), + self.tx_hash().pretty(), + self.input().pretty(), + self.max_fee_per_gas().pretty(), + self.max_priority_fee_per_gas().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + Self::Eip4844(tx) => format!( + " +accessList {} +blobVersionedHashes {} +chainId {} +gasLimit {} +hash {} +input {} +maxFeePerBlobGas {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +r {} +s {} +to {} +type {} +value {} +yParity {}", + self.access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.blob_versioned_hashes().unwrap_or(&[]).pretty(), + self.chain_id().pretty(), + self.gas_limit().pretty(), + self.tx_hash().pretty(), + self.input().pretty(), + self.max_fee_per_blob_gas().pretty(), + self.max_fee_per_gas().pretty(), + self.max_priority_fee_per_gas().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + Self::Eip7702(tx) => format!( + " +accessList {} +authorizationList {} +chainId {} +gasLimit {} +hash {} +input {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +r {} +s {} +to {} +type {} +value {} +yParity {}", + self.access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.authorization_list() + .as_ref() + .map(|l| serde_json::to_string(&l).unwrap()) + .unwrap_or_default(), + self.chain_id().pretty(), + self.gas_limit().pretty(), + self.tx_hash().pretty(), + self.input().pretty(), + self.max_fee_per_gas().pretty(), + self.max_priority_fee_per_gas().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + _ => format!( + " +gas {} +gasPrice {} +hash {} +input {} +nonce {} +r {} +s {} +to {} +type {} +v {} +value {}", + self.gas_limit().pretty(), + self.gas_price().pretty(), + self.tx_hash().pretty(), + self.input().pretty(), + self.nonce().pretty(), + self.as_legacy() + .map(|tx| FixedBytes::from(tx.signature().r()).pretty()) + .unwrap_or_default(), + self.as_legacy() + .map(|tx| FixedBytes::from(tx.signature().s()).pretty()) + .unwrap_or_default(), + self.to().pretty(), + self.ty(), + self.as_legacy() + .map(|tx| (if tx.signature().v() { 1u64 } else { 0 }).pretty()) + .unwrap_or_default(), + self.value().pretty(), + ), + } + } +} + +impl UIfmt for AnyTxEnvelope { + fn pretty(&self) -> String { + match self { + Self::Ethereum(envelop) => envelop.pretty(), + Self::Unknown(tx) => { + format!( + " +hash {} +type {} +{} + ", + tx.hash.pretty(), + tx.ty(), + tx.inner.fields.pretty(), + ) + } + } + } +} +impl UIfmt for Transaction { + fn pretty(&self) -> String { + match &self.inner { + TxEnvelope::Eip2930(tx) => format!( + " +accessList {} +blockHash {} +blockNumber {} +chainId {} +from {} +gasLimit {} +gasPrice {} +hash {} +input {} +nonce {} +r {} +s {} +to {} +transactionIndex {} +type {} +value {} +yParity {}", + self.inner + .access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.block_hash.pretty(), + self.block_number.pretty(), + self.chain_id().pretty(), + self.from.pretty(), + self.gas_limit().pretty(), + self.gas_price().pretty(), + self.inner.tx_hash().pretty(), + self.input().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.transaction_index.pretty(), + self.inner.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + TxEnvelope::Eip1559(tx) => format!( + " +accessList {} +blockHash {} +blockNumber {} +chainId {} +from {} +gasLimit {} +hash {} +input {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +r {} +s {} +to {} +transactionIndex {} +type {} +value {} +yParity {}", + self.inner + .access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.block_hash.pretty(), + self.block_number.pretty(), + self.chain_id().pretty(), + self.from.pretty(), + self.gas_limit().pretty(), + tx.hash().pretty(), + self.input().pretty(), + self.max_fee_per_gas().pretty(), + self.max_priority_fee_per_gas().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.transaction_index.pretty(), + self.inner.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + TxEnvelope::Eip4844(tx) => format!( + " +accessList {} +blobVersionedHashes {} +blockHash {} +blockNumber {} +chainId {} +from {} +gasLimit {} +hash {} +input {} +maxFeePerBlobGas {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +r {} +s {} +to {} +transactionIndex {} +type {} +value {} +yParity {}", + self.inner + .access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.blob_versioned_hashes().unwrap_or(&[]).pretty(), + self.block_hash.pretty(), + self.block_number.pretty(), + self.chain_id().pretty(), + self.from.pretty(), + self.gas_limit().pretty(), + tx.hash().pretty(), + self.input().pretty(), + self.max_fee_per_blob_gas().pretty(), + self.max_fee_per_gas().pretty(), + self.max_priority_fee_per_gas().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.transaction_index.pretty(), + self.inner.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + TxEnvelope::Eip7702(tx) => format!( + " +accessList {} +authorizationList {} +blockHash {} +blockNumber {} +chainId {} +from {} +gasLimit {} +hash {} +input {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +r {} +s {} +to {} +transactionIndex {} +type {} +value {} +yParity {}", + self.inner + .access_list() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + self.authorization_list() + .as_ref() + .map(|l| serde_json::to_string(&l).unwrap()) + .unwrap_or_default(), + self.block_hash.pretty(), + self.block_number.pretty(), + self.chain_id().pretty(), + self.from.pretty(), + self.gas_limit().pretty(), + tx.hash().pretty(), + self.input().pretty(), + self.max_fee_per_gas().pretty(), + self.max_priority_fee_per_gas().pretty(), + self.nonce().pretty(), + FixedBytes::from(tx.signature().r()).pretty(), + FixedBytes::from(tx.signature().s()).pretty(), + self.to().pretty(), + self.transaction_index.pretty(), + self.inner.ty(), + self.value().pretty(), + (if tx.signature().v() { 1u64 } else { 0 }).pretty(), + ), + _ => format!( + " +blockHash {} +blockNumber {} +from {} +gas {} +gasPrice {} +hash {} +input {} +nonce {} +r {} +s {} +to {} +transactionIndex {} +v {} +value {}", + self.block_hash.pretty(), + self.block_number.pretty(), + self.from.pretty(), + self.gas_limit().pretty(), + self.gas_price().pretty(), + self.inner.tx_hash().pretty(), + self.input().pretty(), + self.nonce().pretty(), + self.inner + .as_legacy() + .map(|tx| FixedBytes::from(tx.signature().r()).pretty()) + .unwrap_or_default(), + self.inner + .as_legacy() + .map(|tx| FixedBytes::from(tx.signature().s()).pretty()) + .unwrap_or_default(), + self.to().pretty(), + self.transaction_index.pretty(), + self.inner + .as_legacy() + .map(|tx| (if tx.signature().v() { 1u64 } else { 0 }).pretty()) + .unwrap_or_default(), + self.value().pretty(), + ), + } + } +} + +impl UIfmt for Transaction { + fn pretty(&self) -> String { + format!( + " +blockHash {} +blockNumber {} +from {} +transactionIndex {} +effectiveGasPrice {} +{} + ", + self.block_hash.pretty(), + self.block_number.pretty(), + self.from.pretty(), + self.transaction_index.pretty(), + self.effective_gas_price.pretty(), + self.inner.pretty(), + ) + } +} + +impl UIfmt for WithOtherFields { + fn pretty(&self) -> String { + format!("{}{}", self.inner.pretty(), self.other.pretty()) + } +} + +/// Various numerical ethereum types used for pretty printing +#[derive(Clone, Debug, Deserialize)] +#[serde(untagged)] +#[allow(missing_docs)] +pub enum EthValue { + U64(U64), + U256(U256), + U64Array(Vec), + U256Array(Vec), + Other(serde_json::Value), +} + +impl From for EthValue { + fn from(val: serde_json::Value) -> Self { + serde_json::from_value(val).expect("infallible") + } +} + +impl UIfmt for EthValue { + fn pretty(&self) -> String { + match self { + Self::U64(num) => num.pretty(), + Self::U256(num) => num.pretty(), + Self::U64Array(arr) => arr.pretty(), + Self::U256Array(arr) => arr.pretty(), + Self::Other(val) => val.to_string().trim_matches('"').to_string(), + } + } +} + +/// Returns the `UiFmt::pretty()` formatted attribute of the transactions +pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option { + let sig = match &transaction.inner { + AnyTxEnvelope::Ethereum(envelope) => match &envelope { + TxEnvelope::Eip2930(tx) => Some(tx.signature()), + TxEnvelope::Eip1559(tx) => Some(tx.signature()), + TxEnvelope::Eip4844(tx) => Some(tx.signature()), + TxEnvelope::Eip7702(tx) => Some(tx.signature()), + TxEnvelope::Legacy(tx) => Some(tx.signature()), + }, + _ => None, + }; + match attr { + "blockHash" | "block_hash" => Some(transaction.block_hash.pretty()), + "blockNumber" | "block_number" => Some(transaction.block_number.pretty()), + "from" => Some(transaction.from.pretty()), + "gas" => Some(transaction.gas_limit().pretty()), + "gasPrice" | "gas_price" => Some(Transaction::gas_price(transaction).pretty()), + "hash" => Some(alloy_network::TransactionResponse::tx_hash(transaction).pretty()), + "input" => Some(transaction.input().pretty()), + "nonce" => Some(transaction.nonce().to_string()), + "s" => sig.map(|s| FixedBytes::from(s.s()).pretty()), + "r" => sig.map(|s| FixedBytes::from(s.r()).pretty()), + "to" => Some(transaction.to().pretty()), + "transactionIndex" | "transaction_index" => Some(transaction.transaction_index.pretty()), + "v" => sig.map(|s| U8::from_be_slice(&s.as_bytes()[64..]).pretty()), + "value" => Some(transaction.value().pretty()), + _ => None, + } +} + +/// Returns the `UiFmt::pretty()` formatted attribute of the given block +pub fn get_pretty_block_attr(block: &AnyRpcBlock, attr: &str) -> Option { + match attr { + "baseFeePerGas" | "base_fee_per_gas" => Some(block.header.base_fee_per_gas.pretty()), + "difficulty" => Some(block.header.difficulty.pretty()), + "extraData" | "extra_data" => Some(block.header.extra_data.pretty()), + "gasLimit" | "gas_limit" => Some(block.header.gas_limit.pretty()), + "gasUsed" | "gas_used" => Some(block.header.gas_used.pretty()), + "hash" => Some(block.header.hash.pretty()), + "logsBloom" | "logs_bloom" => Some(block.header.logs_bloom.pretty()), + "miner" | "author" => Some(block.header.inner.beneficiary.pretty()), + "mixHash" | "mix_hash" => Some(block.header.mix_hash.pretty()), + "nonce" => Some(block.header.nonce.pretty()), + "number" => Some(block.header.number.pretty()), + "parentHash" | "parent_hash" => Some(block.header.parent_hash.pretty()), + "transactionsRoot" | "transactions_root" => Some(block.header.transactions_root.pretty()), + "receiptsRoot" | "receipts_root" => Some(block.header.receipts_root.pretty()), + "sha3Uncles" | "sha_3_uncles" => Some(block.header.ommers_hash.pretty()), + "size" => Some(block.header.size.pretty()), + "stateRoot" | "state_root" => Some(block.header.state_root.pretty()), + "timestamp" => Some(block.header.timestamp.pretty()), + "totalDifficulty" | "total_difficult" => Some(block.header.total_difficulty.pretty()), + "blobGasUsed" | "blob_gas_used" => Some(block.header.blob_gas_used.pretty()), + "excessBlobGas" | "excess_blob_gas" => Some(block.header.excess_blob_gas.pretty()), + "requestsHash" | "requests_hash" => Some(block.header.requests_hash.pretty()), + other => { + if let Some(value) = block.other.get(other) { + let val = EthValue::from(value.clone()); + return Some(val.pretty()) + } + None + } + } +} + +fn pretty_block_basics(block: &Block>) -> String { + let Block { + header: + Header { + hash, + size, + total_difficulty, + inner: + AnyHeader { + parent_hash, + ommers_hash, + beneficiary, + state_root, + transactions_root, + receipts_root, + logs_bloom, + difficulty, + number, + gas_limit, + gas_used, + timestamp, + extra_data, + mix_hash, + nonce, + base_fee_per_gas, + withdrawals_root, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root, + requests_hash, + }, + }, + uncles: _, + transactions: _, + withdrawals: _, + } = block; + format!( + " +baseFeePerGas {} +difficulty {} +extraData {} +gasLimit {} +gasUsed {} +hash {} +logsBloom {} +miner {} +mixHash {} +nonce {} +number {} +parentHash {} +parentBeaconRoot {} +transactionsRoot {} +receiptsRoot {} +sha3Uncles {} +size {} +stateRoot {} +timestamp {} ({}) +withdrawalsRoot {} +totalDifficulty {} +blobGasUsed {} +excessBlobGas {} +requestsHash {}", + base_fee_per_gas.pretty(), + difficulty.pretty(), + extra_data.pretty(), + gas_limit.pretty(), + gas_used.pretty(), + hash.pretty(), + logs_bloom.pretty(), + beneficiary.pretty(), + mix_hash.pretty(), + nonce.pretty(), + number.pretty(), + parent_hash.pretty(), + parent_beacon_block_root.pretty(), + transactions_root.pretty(), + receipts_root.pretty(), + ommers_hash.pretty(), + size.pretty(), + state_root.pretty(), + timestamp.pretty(), + chrono::DateTime::from_timestamp(*timestamp as i64, 0) + .expect("block timestamp in range") + .to_rfc2822(), + withdrawals_root.pretty(), + total_difficulty.pretty(), + blob_gas_used.pretty(), + excess_blob_gas.pretty(), + requests_hash.pretty(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::B256; + use similar_asserts::assert_eq; + use std::str::FromStr; + + #[test] + fn can_format_bytes32() { + let val = hex::decode("7465737400000000000000000000000000000000000000000000000000000000") + .unwrap(); + let mut b32 = [0u8; 32]; + b32.copy_from_slice(&val); + + assert_eq!( + b32.pretty(), + "0x7465737400000000000000000000000000000000000000000000000000000000" + ); + let b: Bytes = val.into(); + assert_eq!(b.pretty(), b32.pretty()); + } + + #[test] + fn can_pretty_print_optimism_tx() { + let s = r#" + { + "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae", + "blockNumber": "0x1b4", + "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6", + "gas": "0x11cbbdc", + "gasPrice": "0x0", + "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f", + "input": "0xd294f093", + "nonce": "0xa2", + "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF", + "transactionIndex": "0x0", + "value": "0x0", + "v": "0x38", + "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee", + "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583", + "queueOrigin": "sequencer", + "txType": "", + "l1TxOrigin": null, + "l1BlockNumber": "0xc1a65c", + "l1Timestamp": "0x60d34b60", + "index": "0x1b3", + "queueIndex": null, + "rawTransaction": "0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583" + } + "#; + + let tx: WithOtherFields = serde_json::from_str(s).unwrap(); + assert_eq!(tx.pretty().trim(), + r" +blockHash 0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae +blockNumber 436 +from 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6 +gas 18660316 +gasPrice 0 +hash 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f +input 0xd294f093 +nonce 162 +r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee +s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +to 0x4a16A42407AA491564643E1dfc1fd50af29794eF +transactionIndex 0 +v 1 +value 0 +index 435 +l1BlockNumber 12691036 +l1Timestamp 1624460128 +l1TxOrigin null +queueIndex null +queueOrigin sequencer +rawTransaction 0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 +txType 0 +".trim() + ); + } + + #[test] + fn can_pretty_print_eip2930() { + let s = r#"{ + "type": "0x1", + "blockHash": "0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81", + "blockNumber": "0x12b1d", + "from": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", + "gas": "0x6bdf", + "gasPrice": "0x3b9aca00", + "hash": "0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090", + "input": "0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a", + "nonce": "0x1c", + "to": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", + "transactionIndex": "0x2", + "value": "0x0", + "v": "0x1", + "r": "0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1", + "s": "0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc", + "chainId": "0x66a", + "accessList": [ + { "address": "0x2b371c0262ceab27face32fbb5270ddc6aa01ba4", "storageKeys": ["0x1122334455667788990011223344556677889900112233445566778899001122", "0x0000000000000000000000000000000000000000000000000000000000000000"] }, + { "address": "0x8e730df7c70d33118d9e5f79ab81aed0be6f6635", "storageKeys": [] } + ] + } + "#; + + let tx: Transaction = serde_json::from_str(s).unwrap(); + assert_eq!(tx.pretty().trim(), + r" +accessList [ + 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 => [ + 0x1122334455667788990011223344556677889900112233445566778899001122 + 0x0000000000000000000000000000000000000000000000000000000000000000 + ] + 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 => [] +] +blockHash 0x2b27fe2bbc8ce01ac7ae8bf74f793a197cf7edbe82727588811fa9a2c4776f81 +blockNumber 76573 +chainId 1642 +from 0x2b371c0262CEAb27fAcE32FBB5270dDc6Aa01ba4 +gasLimit 27615 +gasPrice 1000000000 +hash 0xbddbb685774d8a3df036ed9fb920b48f876090a57e9e90ee60921e0510ef7090 +input 0x9c0e3f7a0000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000002a +nonce 28 +r 0x2a98c51c2782f664d3ce571fef0491b48f5ebbc5845fa513192e6e6b24ecdaa1 +s 0x29b8e0c67aa9c11327e16556c591dc84a7aac2f6fc57c7f93901be8ee867aebc +to 0x8E730Df7C70D33118D9e5F79ab81aEd0bE6F6635 +transactionIndex 2 +type 1 +value 0 +yParity 1 +".trim() + ); + } + + #[test] + fn can_pretty_print_eip1559() { + let s = r#"{ + "type": "0x2", + "blockHash": "0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125", + "blockNumber": "0x7647", + "from": "0xbaadf00d42264eeb3fafe6799d0b56cf55df0f00", + "gas": "0x186a0", + "hash": "0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244", + "input": "0x48600055323160015500", + "nonce": "0x12c", + "to": null, + "transactionIndex": "0x41", + "value": "0x0", + "v": "0x1", + "yParity": "0x1", + "r": "0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34", + "s": "0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158", + "gasPrice": "0x4a817c800", + "maxFeePerGas": "0x4a817c800", + "maxPriorityFeePerGas": "0x4a817c800", + "chainId": "0x66a", + "accessList": [ + { + "address": "0xc141a9a7463e6c4716d9fc0c056c054f46bb2993", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000000" + ] + } + ] + } +"#; + let tx: Transaction = serde_json::from_str(s).unwrap(); + assert_eq!( + tx.pretty().trim(), + r" +accessList [ + 0xC141a9A7463e6C4716d9FC0C056C054F46Bb2993 => [ + 0x0000000000000000000000000000000000000000000000000000000000000000 + ] +] +blockHash 0x61abbe5e22738de0462046f5a5d6c4cd6bc1f3a6398e4457d5e293590e721125 +blockNumber 30279 +chainId 1642 +from 0xBaaDF00d42264eEb3FAFe6799d0b56cf55DF0F00 +gasLimit 100000 +hash 0xa7231d4da0576fade5d3b9481f4cd52459ec59b9bbdbf4f60d6cd726b2a3a244 +input 0x48600055323160015500 +maxFeePerGas 20000000000 +maxPriorityFeePerGas 20000000000 +nonce 300 +r 0x396864e5f9132327defdb1449504252e1fa6bce73feb8cd6f348a342b198af34 +s 0x44dbba72e6d3304104848277143252ee43627c82f02d1ef8e404e1bf97c70158 +to +transactionIndex 65 +type 2 +value 0 +yParity 1 +" + .trim() + ); + } + + #[test] + fn can_pretty_print_eip4884() { + let s = r#"{ + "blockHash": "0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052", + "blockNumber": "0x2a1cb", + "from": "0xad01b55d7c3448b8899862eb335fbb17075d8de2", + "gas": "0x5208", + "gasPrice": "0x1d1a94a201c", + "maxFeePerGas": "0x1d1a94a201c", + "maxPriorityFeePerGas": "0x1d1a94a201c", + "maxFeePerBlobGas": "0x3e8", + "hash": "0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00", + "input": "0x", + "nonce": "0x1b483", + "to": "0x000000000000000000000000000000000000f1c1", + "transactionIndex": "0x0", + "value": "0x0", + "type": "0x3", + "accessList": [], + "chainId": "0x1a1f0ff42", + "blobVersionedHashes": [ + "0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76" + ], + "v": "0x0", + "r": "0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1", + "s": "0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b", + "yParity": "0x0" + } +"#; + let tx: Transaction = serde_json::from_str(s).unwrap(); + assert_eq!( + tx.pretty().trim(), + r" +accessList [] +blobVersionedHashes [ + 0x01a128c46fc61395706686d6284f83c6c86dfc15769b9363171ea9d8566e6e76 +] +blockHash 0xfc2715ff196e23ae613ed6f837abd9035329a720a1f4e8dce3b0694c867ba052 +blockNumber 172491 +chainId 7011893058 +from 0xAD01b55d7c3448B8899862eb335FBb17075d8DE2 +gasLimit 21000 +hash 0x5ceec39b631763ae0b45a8fb55c373f38b8fab308336ca1dc90ecd2b3cf06d00 +input 0x +maxFeePerBlobGas 1000 +maxFeePerGas 2000000000028 +maxPriorityFeePerGas 2000000000028 +nonce 111747 +r 0x343c6239323a81ef61293cb4a4d37b6df47fbf68114adb5dd41581151a077da1 +s 0x48c21f6872feaf181d37cc4f9bbb356d3f10b352ceb38d1c3b190d749f95a11b +to 0x000000000000000000000000000000000000f1C1 +transactionIndex 0 +type 3 +value 0 +yParity 0 +" + .trim() + ); + } + + #[test] + fn print_block_w_txs() { + let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; + let block: Block = serde_json::from_str(block).unwrap(); + let output ="\nblockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 +blockNumber 3 +from 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a +gas 90000 +gasPrice 20000000000 +hash 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067 +input 0x +nonce 2 +r 0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88 +s 0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e +to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e +transactionIndex 0 +v 0 +value 0".to_string(); + let txs = match block.transactions { + BlockTransactions::Full(txs) => txs, + _ => panic!("not full transactions"), + }; + let generated = txs[0].pretty(); + assert_eq!(generated.as_str(), output.as_str()); + } + + #[test] + fn uifmt_option_u64() { + assert_eq!(None::.pretty(), ""); + assert_eq!(U64::from(100).pretty(), "100"); + assert_eq!(Some(U64::from(100)).pretty(), "100"); + } + + #[test] + fn uifmt_option_h64() { + assert_eq!(None::.pretty(), ""); + assert_eq!( + B256::with_last_byte(100).pretty(), + "0x0000000000000000000000000000000000000000000000000000000000000064", + ); + assert_eq!( + Some(B256::with_last_byte(100)).pretty(), + "0x0000000000000000000000000000000000000000000000000000000000000064", + ); + } + + #[test] + fn uifmt_option_bytes() { + assert_eq!(None::.pretty(), ""); + assert_eq!( + Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000064") + .unwrap() + .pretty(), + "0x0000000000000000000000000000000000000000000000000000000000000064", + ); + assert_eq!( + Some( + Bytes::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000064" + ) + .unwrap() + ) + .pretty(), + "0x0000000000000000000000000000000000000000000000000000000000000064", + ); + } + + #[test] + fn test_pretty_tx_attr() { + let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; + let block: Block> = serde_json::from_str(block).unwrap(); + let txs = match block.transactions { + BlockTransactions::Full(txes) => txes, + _ => panic!("not full transactions"), + }; + + assert_eq!(None, get_pretty_tx_attr(&txs[0], "")); + assert_eq!(Some("3".to_string()), get_pretty_tx_attr(&txs[0], "blockNumber")); + assert_eq!( + Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), + get_pretty_tx_attr(&txs[0], "from") + ); + assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&txs[0], "gas")); + assert_eq!(Some("20000000000".to_string()), get_pretty_tx_attr(&txs[0], "gasPrice")); + assert_eq!( + Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), + get_pretty_tx_attr(&txs[0], "hash") + ); + assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&txs[0], "input")); + assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&txs[0], "nonce")); + assert_eq!( + Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), + get_pretty_tx_attr(&txs[0], "r") + ); + assert_eq!( + Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), + get_pretty_tx_attr(&txs[0], "s") + ); + assert_eq!( + Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), + get_pretty_tx_attr(&txs[0], "to") + ); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "transactionIndex")); + assert_eq!(Some("27".to_string()), get_pretty_tx_attr(&txs[0], "v")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "value")); + } + + #[test] + fn test_pretty_block_attr() { + let json = serde_json::json!( + { + "baseFeePerGas": "0x7", + "miner": "0x0000000000000000000000000000000000000001", + "number": "0x1b4", + "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", + "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5", + "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010", + "nonce": "0x0000000000000000", + "sealFields": [ + "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2", + "0x0000000000000042" + ], + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", + "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff", + "difficulty": "0x27f07", + "totalDifficulty": "0x27f07", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "size": "0x27f07", + "gasLimit": "0x9f759", + "minGasPrice": "0x9f759", + "gasUsed": "0x9f759", + "timestamp": "0x54e34e8e", + "transactions": [], + "uncles": [] + } + ); + + let block: AnyRpcBlock = serde_json::from_value(json).unwrap(); + + assert_eq!(None, get_pretty_block_attr(&block, "")); + assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); + assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "difficulty")); + assert_eq!( + Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()), + get_pretty_block_attr(&block, "extraData") + ); + assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasLimit")); + assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasUsed")); + assert_eq!( + Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), + get_pretty_block_attr(&block, "hash") + ); + assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr(&block, "logsBloom")); + assert_eq!( + Some("0x0000000000000000000000000000000000000001".to_string()), + get_pretty_block_attr(&block, "miner") + ); + assert_eq!( + Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()), + get_pretty_block_attr(&block, "mixHash") + ); + assert_eq!(Some("0x0000000000000000".to_string()), get_pretty_block_attr(&block, "nonce")); + assert_eq!(Some("436".to_string()), get_pretty_block_attr(&block, "number")); + assert_eq!( + Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()), + get_pretty_block_attr(&block, "parentHash") + ); + assert_eq!( + Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), + get_pretty_block_attr(&block, "transactionsRoot") + ); + assert_eq!( + Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), + get_pretty_block_attr(&block, "receiptsRoot") + ); + assert_eq!( + Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()), + get_pretty_block_attr(&block, "sha3Uncles") + ); + assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "size")); + assert_eq!( + Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()), + get_pretty_block_attr(&block, "stateRoot") + ); + assert_eq!(Some("1424182926".to_string()), get_pretty_block_attr(&block, "timestamp")); + assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "totalDifficulty")); + } + + #[test] + fn test_receipt_other_fields_alignment() { + let receipt_json = serde_json::json!( + { + "status": "0x1", + "cumulativeGasUsed": "0x74e483", + "logs": [], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "type": "0x2", + "transactionHash": "0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d", + "transactionIndex": "0x10", + "blockHash": "0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d", + "blockNumber": "0x7b1ab93", + "gasUsed": "0xc222", + "effectiveGasPrice": "0x18961", + "from": "0x2d815240a61731c75fa01b2793e1d3ed09f289d0", + "to": "0x4200000000000000000000000000000000000000", + "contractAddress": null, + "l1BaseFeeScalar": "0x146b", + "l1BlobBaseFee": "0x6a83078", + "l1BlobBaseFeeScalar": "0xf79c5", + "l1Fee": "0x51a9af7fd3", + "l1GasPrice": "0x972fe4acc", + "l1GasUsed": "0x640" + }); + + let receipt: AnyTransactionReceipt = serde_json::from_value(receipt_json).unwrap(); + let formatted = receipt.pretty(); + + let expected = r#" +blockHash 0x54bafb12e8cea9bb355fbf03a4ac49e42a2a1a80fa6cf4364b342e2de6432b5d +blockNumber 129084307 +contractAddress +cumulativeGasUsed 7660675 +effectiveGasPrice 100705 +from 0x2D815240A61731c75Fa01b2793E1D3eD09F289d0 +gasUsed 49698 +logs [] +logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +root +status 1 (success) +transactionHash 0x91181b0dca3b29aa136eeb2f536be5ce7b0aebc949be1c44b5509093c516097d +transactionIndex 16 +type 2 +blobGasPrice +blobGasUsed +to 0x4200000000000000000000000000000000000000 +l1BaseFeeScalar 5227 +l1BlobBaseFee 111685752 +l1BlobBaseFeeScalar 1014213 +l1Fee 350739202003 +l1GasPrice 40583973580 +l1GasUsed 1600 +"#; + + assert_eq!(formatted.trim(), expected.trim()); + } +} diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index 5497858cfacc1..fa9f241719fdb 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -1,52 +1,71 @@ -//! ABI related helper functions - -use ethers_core::{ - abi::{ - token::{LenientTokenizer, StrictTokenizer, Tokenizer}, - Event, Function, HumanReadableParser, ParamType, RawLog, Token, - }, - types::{Address, Chain, I256, U256}, - utils::{hex, to_checksum}, -}; -use ethers_etherscan::{contract::ContractMetadata, errors::EtherscanError, Client}; -use eyre::{ContextCompat, Result, WrapErr}; -use std::{future::Future, pin::Pin, str::FromStr}; -use yansi::Paint; - -use crate::calc::to_exponential_notation; - -/// Given a function and a vector of string arguments, it proceeds to convert the args to ethabi -/// Tokens and then ABI encode them. -pub fn encode_args(func: &Function, args: &[impl AsRef]) -> Result> { - let params = func - .inputs - .iter() - .zip(args) - .map(|(input, arg)| (&input.kind, arg.as_ref())) - .collect::>(); - let tokens = parse_tokens(params, true)?; - Ok(func.encode_input(&tokens)?) +//! ABI related helper functions. + +use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt, JsonAbiExt}; +use alloy_json_abi::{Error, Event, Function, Param}; +use alloy_primitives::{hex, Address, LogData}; +use eyre::{Context, ContextCompat, Result}; +use foundry_block_explorers::{contract::ContractMetadata, errors::EtherscanError, Client}; +use foundry_config::Chain; +use std::{future::Future, pin::Pin}; + +pub fn encode_args(inputs: &[Param], args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + std::iter::zip(inputs, args) + .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref())) + .collect() +} + +/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy +/// [DynSolValue]s and then ABI encode them. +pub fn encode_function_args(func: &Function, args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + Ok(func.abi_encode_input(&encode_args(&func.inputs, args)?)?) +} + +/// Given a function and a vector of string arguments, it proceeds to convert the args to alloy +/// [DynSolValue]s and encode them using the packed encoding. +pub fn encode_function_args_packed(func: &Function, args: I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ + let params: Vec> = std::iter::zip(&func.inputs, args) + .map(|(input, arg)| coerce_value(&input.selector_type(), arg.as_ref())) + .collect::>>()? + .into_iter() + .map(|v| v.abi_encode_packed()) + .collect(); + + Ok(params.concat()) } /// Decodes the calldata of the function -/// -/// # Panics -/// -/// If the `sig` is an invalid function signature -pub fn abi_decode(sig: &str, calldata: &str, input: bool, fn_selector: bool) -> Result> { - let func = IntoFunction::into(sig); - let calldata = calldata.strip_prefix("0x").unwrap_or(calldata); +pub fn abi_decode_calldata( + sig: &str, + calldata: &str, + input: bool, + fn_selector: bool, +) -> Result> { + let func = get_func(sig)?; let calldata = hex::decode(calldata)?; + + let mut calldata = calldata.as_slice(); + // If function selector is prefixed in "calldata", remove it (first 4 bytes) + if input && fn_selector && calldata.len() >= 4 { + calldata = &calldata[4..]; + } + let res = if input { - // If function selector is prefixed in "calldata", remove it (first 4 bytes) - if fn_selector { - func.decode_input(&calldata[4..])? - } else { - func.decode_input(&calldata)? - } + func.abi_decode_input(calldata, false) } else { - func.decode_output(&calldata)? - }; + func.abi_decode_output(calldata, false) + }?; // in case the decoding worked but nothing was decoded if res.is_empty() { @@ -56,244 +75,35 @@ pub fn abi_decode(sig: &str, calldata: &str, input: bool, fn_selector: bool) -> Ok(res) } -/// Parses string input as Token against the expected ParamType -pub fn parse_tokens<'a, I: IntoIterator>( - params: I, - lenient: bool, -) -> Result> { - let mut tokens = Vec::new(); - - for (param, value) in params.into_iter() { - let mut token = if lenient { - LenientTokenizer::tokenize(param, value) - } else { - StrictTokenizer::tokenize(param, value) - }; - if token.is_err() && value.starts_with("0x") { - match param { - ParamType::FixedBytes(32) => { - if value.len() < 66 { - let padded_value = [value, &"0".repeat(66 - value.len())].concat(); - token = if lenient { - LenientTokenizer::tokenize(param, &padded_value) - } else { - StrictTokenizer::tokenize(param, &padded_value) - }; - } - } - ParamType::Uint(_) => { - // try again if value is hex - if let Ok(value) = U256::from_str(value).map(|v| v.to_string()) { - token = if lenient { - LenientTokenizer::tokenize(param, &value) - } else { - StrictTokenizer::tokenize(param, &value) - }; - } - } - // TODO: Not sure what to do here. Put the no effect in for now, but that is not - // ideal. We could attempt massage for every value type? - _ => {} - } - } - - let token = token.map(sanitize_token).wrap_err_with(|| { - format!("Failed to parse `{value}`, expected value of type: {param}") - })?; - tokens.push(token); - } - Ok(tokens) -} - -/// Cleans up potential shortcomings of the ethabi Tokenizer. -/// -/// For example: parsing a string array with a single empty string: `[""]`, is returned as -/// -/// ```text -/// [ -/// String( -/// "\"\"", -/// ), -/// ], -/// ``` -/// -/// But should just be -/// -/// ```text -/// [ -/// String( -/// "", -/// ), -/// ], -/// ``` -/// -/// This will handle this edge case -pub fn sanitize_token(token: Token) -> Token { - match token { - Token::Array(tokens) => { - let mut sanitized = Vec::with_capacity(tokens.len()); - for token in tokens { - let token = match token { - Token::String(val) => { - let val = match val.as_str() { - // this is supposed to be an empty string - "\"\"" | "''" => "".to_string(), - _ => val, - }; - Token::String(val) - } - _ => sanitize_token(token), - }; - sanitized.push(token) - } - Token::Array(sanitized) - } - _ => token, - } -} - -/// Pretty print a slice of tokens. -pub fn format_tokens(tokens: &[Token]) -> impl Iterator + '_ { - tokens.iter().map(format_token) -} - -/// Gets pretty print strings for tokens -pub fn format_token(param: &Token) -> String { - match param { - Token::Address(addr) => to_checksum(addr, None), - Token::FixedBytes(bytes) => format!("0x{}", hex::encode(bytes)), - Token::Bytes(bytes) => format!("0x{}", hex::encode(bytes)), - Token::Int(num) => format!("{}", I256::from_raw(*num)), - Token::Uint(num) => format_uint_with_exponential_notation_hint(*num), - Token::Bool(b) => format!("{b}"), - Token::String(s) => s.to_string(), - Token::FixedArray(tokens) => { - let string = tokens.iter().map(format_token).collect::>().join(", "); - format!("[{string}]") - } - Token::Array(tokens) => { - let string = tokens.iter().map(format_token).collect::>().join(", "); - format!("[{string}]") - } - Token::Tuple(tokens) => { - let string = tokens.iter().map(format_token).collect::>().join(", "); - format!("({string})") - } - } -} - -/// Gets pretty print strings for tokens, without adding -/// exponential notation hints for large numbers (e.g. [1e7] for 10000000) -pub fn format_token_raw(param: &Token) -> String { - match param { - Token::Uint(num) => format!("{}", num), - Token::FixedArray(tokens) | Token::Array(tokens) => { - let string = tokens.iter().map(format_token_raw).collect::>().join(", "); - format!("[{string}]") - } - Token::Tuple(tokens) => { - let string = tokens.iter().map(format_token_raw).collect::>().join(", "); - format!("({string})") - } - _ => format_token(param), - } -} - -/// 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 -/// trailing zeros. -/// -/// Examples: -/// -/// ```text -/// 0 -> "0" -/// 1234 -> "1234" -/// 1234567890 -> "1234567890 [1.234e9]" -/// 1000000000000000000 -> "1000000000000000000 [1e18]" -/// 10000000000000000000000 -> "10000000000000000000000 [1e22]" -/// ``` -pub fn format_uint_with_exponential_notation_hint(num: U256) -> String { - if num.lt(&U256::from(10_000)) { - return num.to_string() - } - - let exp = to_exponential_notation(num, 4, true); - format!("{} {}", num, Paint::default(format!("[{}]", exp)).dimmed()) -} - -/// Helper trait for converting types to Functions. Helpful for allowing the `call` -/// function on the EVM to be generic over `String`, `&str` and `Function`. -pub trait IntoFunction { - /// Consumes self and produces a function - /// - /// # Panic - /// - /// This function does not return a Result, so it is expected that the consumer - /// uses it correctly so that it does not panic. - fn into(self) -> Function; -} - -impl IntoFunction for Function { - fn into(self) -> Function { - self - } -} - -impl IntoFunction for String { - fn into(self) -> Function { - IntoFunction::into(self.as_str()) - } -} - -impl<'a> IntoFunction for &'a str { - fn into(self) -> Function { - HumanReadableParser::parse_function(self) - .unwrap_or_else(|_| panic!("could not convert {self} to function")) - } -} - /// Given a function signature string, it tries to parse it as a `Function` pub fn get_func(sig: &str) -> Result { - Ok(match HumanReadableParser::parse_function(sig) { - Ok(func) => func, - Err(err) => { - if let Ok(constructor) = HumanReadableParser::parse_constructor(sig) { - #[allow(deprecated)] - Function { - name: "constructor".to_string(), - inputs: constructor.inputs, - outputs: vec![], - constant: None, - state_mutability: Default::default(), - } - } else { - // we return the `Function` parse error as this case is more likely - return Err(err.into()) - } - } - }) + Function::parse(sig).wrap_err("could not parse function signature") } /// Given an event signature string, it tries to parse it as a `Event` pub fn get_event(sig: &str) -> Result { - Ok(HumanReadableParser::parse_event(sig)?) + Event::parse(sig).wrap_err("could not parse event signature") +} + +/// Given an error signature string, it tries to parse it as a `Error` +pub fn get_error(sig: &str) -> Result { + Error::parse(sig).wrap_err("could not parse event signature") } /// Given an event without indexed parameters and a rawlog, it tries to return the event with the /// proper indexed parameters. Otherwise, it returns the original event. -pub fn get_indexed_event(mut event: Event, raw_log: &RawLog) -> Event { - if !event.anonymous && raw_log.topics.len() > 1 { - let indexed_params = raw_log.topics.len() - 1; +pub fn get_indexed_event(mut event: Event, raw_log: &LogData) -> Event { + if !event.anonymous && raw_log.topics().len() > 1 { + let indexed_params = raw_log.topics().len() - 1; let num_inputs = event.inputs.len(); - let num_address_params = - event.inputs.iter().filter(|p| p.kind == ParamType::Address).count(); + let num_address_params = event.inputs.iter().filter(|p| p.ty == "address").count(); event.inputs.iter_mut().enumerate().for_each(|(index, param)| { if param.name.is_empty() { param.name = format!("param{index}"); } if num_inputs == indexed_params || - (num_address_params == indexed_params && param.kind == ParamType::Address) + (num_address_params == indexed_params && param.ty == "address") { param.indexed = true; } @@ -319,7 +129,7 @@ pub async fn get_func_etherscan( let funcs = abi.functions.remove(function_name).unwrap_or_default(); for func in funcs { - let res = encode_args(&func, args); + let res = encode_function_args(&func, args); if res.is_ok() { return Ok(func) } @@ -334,22 +144,22 @@ pub fn find_source( address: Address, ) -> Pin>>> { Box::pin(async move { - tracing::trace!("find etherscan source for: {:?}", address); + trace!(%address, "find Etherscan source"); let source = client.contract_source_code(address).await?; let metadata = source.items.first().wrap_err("Etherscan returned no data")?; if metadata.proxy == 0 { Ok(source) } else { let implementation = metadata.implementation.unwrap(); - println!( - "Contract at {address} is a proxy, trying to fetch source at {implementation:?}..." - ); + sh_println!( + "Contract at {address} is a proxy, trying to fetch source at {implementation}..." + )?; match find_source(client, implementation).await { impl_source @ Ok(_) => impl_source, Err(e) => { let err = EtherscanError::ContractCodeNotVerified(address).to_string(); if e.to_string() == err { - tracing::error!("{}", err); + error!(%err); Ok(source) } else { Err(e) @@ -360,119 +170,81 @@ pub fn find_source( }) } +/// Helper function to coerce a value to a [DynSolValue] given a type string +pub fn coerce_value(ty: &str, arg: &str) -> Result { + let ty = DynSolType::parse(ty)?; + Ok(DynSolType::coerce_str(&ty, arg)?) +} + #[cfg(test)] mod tests { use super::*; - use ethers_core::types::H256; - - #[test] - fn can_sanitize_token() { - let token = - Token::Array(LenientTokenizer::tokenize_array("[\"\"]", &ParamType::String).unwrap()); - let sanitized = sanitize_token(token); - assert_eq!(sanitized, Token::Array(vec![Token::String("".to_string())])); - - let token = - Token::Array(LenientTokenizer::tokenize_array("['']", &ParamType::String).unwrap()); - let sanitized = sanitize_token(token); - assert_eq!(sanitized, Token::Array(vec![Token::String("".to_string())])); - - let token = Token::Array( - LenientTokenizer::tokenize_array("[\"\",\"\"]", &ParamType::String).unwrap(), - ); - let sanitized = sanitize_token(token); - assert_eq!( - sanitized, - Token::Array(vec![Token::String("".to_string()), Token::String("".to_string())]) - ); - - let token = - Token::Array(LenientTokenizer::tokenize_array("['','']", &ParamType::String).unwrap()); - let sanitized = sanitize_token(token); - assert_eq!( - sanitized, - Token::Array(vec![Token::String("".to_string()), Token::String("".to_string())]) - ); - } + use alloy_dyn_abi::EventExt; + use alloy_primitives::{B256, U256}; #[test] - fn parse_hex_uint_tokens() { - let param = ParamType::Uint(256); - - let tokens = parse_tokens(std::iter::once((¶m, "100")), true).unwrap(); - assert_eq!(tokens, vec![Token::Uint(100u64.into())]); - - let val: U256 = 100u64.into(); - let hex_val = format!("0x{val:x}"); - let tokens = parse_tokens(std::iter::once((¶m, hex_val.as_str())), true).unwrap(); - assert_eq!(tokens, vec![Token::Uint(100u64.into())]); + fn test_get_func() { + let func = get_func("function foo(uint256 a, uint256 b) returns (uint256)"); + assert!(func.is_ok()); + let func = func.unwrap(); + assert_eq!(func.name, "foo"); + assert_eq!(func.inputs.len(), 2); + assert_eq!(func.inputs[0].ty, "uint256"); + assert_eq!(func.inputs[1].ty, "uint256"); + + // Stripped down function, which [Function] can parse. + let func = get_func("foo(bytes4 a, uint8 b)(bytes4)"); + assert!(func.is_ok()); + let func = func.unwrap(); + assert_eq!(func.name, "foo"); + assert_eq!(func.inputs.len(), 2); + assert_eq!(func.inputs[0].ty, "bytes4"); + assert_eq!(func.inputs[1].ty, "uint8"); + assert_eq!(func.outputs[0].ty, "bytes4"); } #[test] fn test_indexed_only_address() { let event = get_event("event Ev(address,uint256,address)").unwrap(); - let param0 = H256::random(); + let param0 = B256::random(); let param1 = vec![3; 32]; - let param2 = H256::random(); - let log = RawLog { topics: vec![event.signature(), param0, param2], data: param1.clone() }; + let param2 = B256::random(); + 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); // Only the address fields get indexed since total_params > num_indexed_params - let parsed = event.parse_log(log).unwrap(); + let parsed = event.decode_log(&log, false).unwrap(); assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 2); - assert_eq!(parsed.params[0].name, "param0"); - assert_eq!(parsed.params[0].value, Token::Address(param0.into())); - assert_eq!(parsed.params[1].name, "param1"); - assert_eq!(parsed.params[1].value, Token::Uint(U256::from_big_endian(¶m1))); - assert_eq!(parsed.params[2].name, "param2"); - assert_eq!(parsed.params[2].value, Token::Address(param2.into())); + assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); + assert_eq!(parsed.body[0], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); + assert_eq!(parsed.indexed[1], DynSolValue::Address(Address::from_word(param2))); } #[test] fn test_indexed_all() { let event = get_event("event Ev(address,uint256,address)").unwrap(); - let param0 = H256::random(); + let param0 = B256::random(); let param1 = vec![3; 32]; - let param2 = H256::random(); - let log = RawLog { - topics: vec![event.signature(), param0, H256::from_slice(¶m1), param2], - data: vec![], - }; + let param2 = B256::random(); + let log = LogData::new_unchecked( + vec![event.selector(), param0, B256::from_slice(¶m1), param2], + vec![].into(), + ); let event = get_indexed_event(event, &log); assert_eq!(event.inputs.len(), 3); // All parameters get indexed since num_indexed_params == total_params assert_eq!(event.inputs.iter().filter(|param| param.indexed).count(), 3); - let parsed = event.parse_log(log).unwrap(); - - assert_eq!(parsed.params[0].name, "param0"); - assert_eq!(parsed.params[0].value, Token::Address(param0.into())); - assert_eq!(parsed.params[1].name, "param1"); - assert_eq!(parsed.params[1].value, Token::Uint(U256::from_big_endian(¶m1))); - assert_eq!(parsed.params[2].name, "param2"); - assert_eq!(parsed.params[2].value, Token::Address(param2.into())); - } + let parsed = event.decode_log(&log, false).unwrap(); - #[test] - fn test_format_token_addr() { - // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md - let eip55 = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"; - assert_eq!( - format_token(&Token::Address(Address::from_str(&eip55.to_lowercase()).unwrap())), - eip55.to_string() - ); - - // copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md - let eip1191 = "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359"; - assert_ne!( - format_token(&Token::Address(Address::from_str(&eip1191.to_lowercase()).unwrap())), - eip1191.to_string() - ); + assert_eq!(parsed.indexed[0], DynSolValue::Address(Address::from_word(param0))); + assert_eq!(parsed.indexed[1], DynSolValue::Uint(U256::from_be_bytes([3; 32]), 256)); + assert_eq!(parsed.indexed[2], DynSolValue::Address(Address::from_word(param2))); } } diff --git a/crates/common/src/calc.rs b/crates/common/src/calc.rs index 295fb0a45d22e..2d7d6fb9ebdc7 100644 --- a/crates/common/src/calc.rs +++ b/crates/common/src/calc.rs @@ -1,97 +1,56 @@ -//! commonly used calculations +//! Commonly used calculations. -use ethers_core::types::U256; -use std::ops::{Add, Div}; - -/// Returns the mean of the slice +/// Returns the mean of the slice. #[inline] -pub fn mean(values: &[T]) -> U256 -where - T: Into + Copy, -{ +pub fn mean(values: &[u64]) -> u64 { if values.is_empty() { - return U256::zero() + return 0; } - values.iter().copied().fold(U256::zero(), |sum, val| sum + val.into()) / values.len() + (values.iter().map(|x| *x as u128).sum::() / values.len() as u128) as u64 } -/// Returns the median of a _sorted_ slice +/// Returns the median of a _sorted_ slice. #[inline] -pub fn median_sorted(values: &[T]) -> T -where - T: Add + Div + From + Copy, -{ +pub fn median_sorted(values: &[u64]) -> u64 { if values.is_empty() { - return 0u64.into() + return 0; } let len = values.len(); let mid = len / 2; if len % 2 == 0 { - (values[mid - 1] + values[mid]) / 2u64 + (values[mid - 1] + values[mid]) / 2 } else { values[mid] } } -/// 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_exponential_notation(value: U256, precision: usize, trim_end_zeros: bool) -> 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!("{}e{}", mantissa, exponent) -} - #[cfg(test)] mod tests { use super::*; #[test] fn calc_mean_empty() { - let values: [u64; 0] = []; - let m = mean(&values); - assert_eq!(m, U256::zero()); + let m = mean(&[]); + assert_eq!(m, 0); } #[test] fn calc_mean() { - let values = [0u64, 1u64, 2u64, 3u64, 4u64, 5u64, 6u64]; - let m = mean(&values); - assert_eq!(m, 3u64.into()); + let m = mean(&[0, 1, 2, 3, 4, 5, 6]); + assert_eq!(m, 3); + } + + #[test] + fn calc_mean_overflow() { + let m = mean(&[0, 1, 2, u32::MAX as u64, 3, u16::MAX as u64, u64::MAX, 6]); + assert_eq!(m, 2305843009750573057); } #[test] fn calc_median_empty() { - let values: Vec = vec![]; - let m = median_sorted(&values); + let m = median_sorted(&[]); assert_eq!(m, 0); } @@ -110,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_exponential_notation(value.into(), 4, false); - assert_eq!(formatted, "1.234e9"); - - let formatted = to_exponential_notation(value.into(), 3, true); - assert_eq!(formatted, "1.23e9"); - - let value = 10000000u64; - - let formatted = to_exponential_notation(value.into(), 4, false); - assert_eq!(formatted, "1.000e7"); - - let formatted = to_exponential_notation(value.into(), 3, true); - assert_eq!(formatted, "1e7"); - } } diff --git a/crates/common/src/clap_helpers.rs b/crates/common/src/clap_helpers.rs deleted file mode 100644 index f26455b764148..0000000000000 --- a/crates/common/src/clap_helpers.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Additional utils for clap - -/// A `clap` `value_parser` that removes a `0x` prefix if it exists -pub fn strip_0x_prefix(s: &str) -> Result { - Ok(s.strip_prefix("0x").unwrap_or(s).to_string()) -} diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 37d765ae2b747..36a81f2cd541a 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -1,117 +1,207 @@ -//! Support for compiling [ethers::solc::Project] -use crate::{glob::GlobMatcher, term, TestFunctionExt}; -use comfy_table::{presets::ASCII_MARKDOWN, *}; -use ethers_etherscan::contract::Metadata; -use ethers_solc::{ - artifacts::{BytecodeObject, ContractBytecodeSome}, - remappings::Remapping, - report::NoReporter, - Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, ProjectPathsConfig, - Solc, SolcConfig, +//! Support for compiling [foundry_compilers::Project] + +use crate::{ + reports::{report_kind, ReportKind}, + shell, + term::SpinnerReporter, + TestFunctionExt, }; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; use eyre::Result; +use foundry_block_explorers::contract::Metadata; +use foundry_compilers::{ + artifacts::{remappings::Remapping, BytecodeObject, Contract, Source}, + compilers::{ + solc::{Solc, SolcCompiler}, + Compiler, + }, + info::ContractInfo as CompilerContractInfo, + report::{BasicStdoutReporter, NoReporter, Report}, + solc::SolcSettings, + Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, +}; +use num_format::{Locale, ToFormattedString}; use std::{ collections::BTreeMap, - convert::Infallible, fmt::Display, + io::IsTerminal, path::{Path, PathBuf}, - result, str::FromStr, + time::Instant, }; -/// Helper type to configure how to compile a project +/// Builder type to configure how to compile a project. /// -/// This is merely a wrapper for [Project::compile()] which also prints to stdout dependent on its -/// settings -#[derive(Debug, Clone, Default)] +/// This is merely a wrapper for [`Project::compile()`] which also prints to stdout depending on its +/// settings. +#[must_use = "ProjectCompiler does nothing unless you call a `compile*` method"] pub struct ProjectCompiler { - /// whether to also print the contract names - print_names: bool, - /// whether to also print the contract sizes - print_sizes: bool, - /// files to exclude - filters: Vec, + /// Whether we are going to verify the contracts after compilation. + verify: Option, + + /// Whether to also print contract names. + print_names: Option, + + /// Whether to also print contract sizes. + print_sizes: Option, + + /// Whether to print anything at all. Overrides other `print` options. + quiet: Option, + + /// Whether to bail on compiler errors. + bail: Option, + + /// Whether to ignore the contract initcode size limit introduced by EIP-3860. + ignore_eip_3860: bool, + + /// Extra files to include, that are not necessarily in the project's source dir. + files: Vec, +} + +impl Default for ProjectCompiler { + #[inline] + fn default() -> Self { + Self::new() + } } impl ProjectCompiler { - /// Create a new instance with the settings - pub fn new(print_names: bool, print_sizes: bool) -> Self { - Self::with_filter(print_names, print_sizes, Vec::new()) + /// Create a new builder with the default settings. + #[inline] + pub fn new() -> Self { + Self { + verify: None, + print_names: None, + print_sizes: None, + quiet: Some(crate::shell::is_quiet()), + bail: None, + ignore_eip_3860: false, + files: Vec::new(), + } } - /// Create a new instance with all settings - pub fn with_filter( - print_names: bool, - print_sizes: bool, - filters: Vec, - ) -> Self { - Self { print_names, print_sizes, filters } + /// Sets whether we are going to verify the contracts after compilation. + #[inline] + pub fn verify(mut self, yes: bool) -> Self { + self.verify = Some(yes); + self } - /// Compiles the project with [`Project::compile()`] - pub fn compile(self, project: &Project) -> Result { - let filters = self.filters.clone(); - self.compile_with(project, |prj| { - let output = if filters.is_empty() { - prj.compile() - } else { - prj.compile_sparse(SkipBuildFilters(filters)) - }?; - Ok(output) - }) + /// Sets whether to print contract names. + #[inline] + pub fn print_names(mut self, yes: bool) -> Self { + self.print_names = Some(yes); + self } - /// Compiles the project with [`Project::compile_parse()`] and the given filter. - /// - /// This will emit artifacts only for files that match the given filter. - /// Files that do _not_ match the filter are given a pruned output selection and do not generate - /// artifacts. - pub fn compile_sparse( - self, - project: &Project, - filter: F, - ) -> Result { - self.compile_with(project, |prj| Ok(prj.compile_sparse(filter)?)) + /// Sets whether to print contract sizes. + #[inline] + pub fn print_sizes(mut self, yes: bool) -> Self { + self.print_sizes = Some(yes); + self + } + + /// Sets whether to print anything at all. Overrides other `print` options. + #[inline] + #[doc(alias = "silent")] + pub fn quiet(mut self, yes: bool) -> Self { + self.quiet = Some(yes); + self + } + + /// Sets whether to bail on compiler errors. + #[inline] + pub fn bail(mut self, yes: bool) -> Self { + self.bail = Some(yes); + self + } + + /// Sets whether to ignore EIP-3860 initcode size limits. + #[inline] + pub fn ignore_eip_3860(mut self, yes: bool) -> Self { + self.ignore_eip_3860 = yes; + self + } + + /// Sets extra files to include, that are not necessarily in the project's source dir. + #[inline] + pub fn files(mut self, files: impl IntoIterator) -> Self { + self.files.extend(files); + self + } + + /// Compiles the project. + pub fn compile>( + mut self, + project: &Project, + ) -> Result> { + // TODO: Avoid process::exit + if !project.paths.has_input_files() && self.files.is_empty() { + sh_println!("Nothing to compile")?; + // nothing to do here + std::process::exit(0); + } + + // Taking is fine since we don't need these in `compile_with`. + let files = std::mem::take(&mut self.files); + self.compile_with(|| { + let sources = if !files.is_empty() { + Source::read_all(files)? + } else { + project.paths.read_input_files()? + }; + + foundry_compilers::project::ProjectCompiler::with_sources(project, sources)? + .compile() + .map_err(Into::into) + }) } /// Compiles the project with the given closure /// /// # Example /// - /// ```no_run + /// ```ignore /// use foundry_common::compile::ProjectCompiler; - /// let config = foundry_config::Config::load(); - /// ProjectCompiler::default() - /// .compile_with(&config.project().unwrap(), |prj| Ok(prj.compile()?)).unwrap(); + /// let config = foundry_config::Config::load().unwrap(); + /// let prj = config.project().unwrap(); + /// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap(); /// ``` - #[tracing::instrument(target = "forge::compile", skip_all)] - pub fn compile_with(self, project: &Project, f: F) -> Result + #[instrument(target = "forge::compile", skip_all)] + fn compile_with, F>( + self, + f: F, + ) -> Result> where - F: FnOnce(&Project) -> Result, + F: FnOnce() -> Result>, { - if !project.paths.has_input_files() { - println!("Nothing to compile"); - // nothing to do here - std::process::exit(0); - } + let quiet = self.quiet.unwrap_or(false); + let bail = self.bail.unwrap_or(true); - let now = std::time::Instant::now(); - tracing::trace!("start compiling project"); + let output = with_compilation_reporter(self.quiet.unwrap_or(false), || { + tracing::debug!("compiling project"); - let output = term::with_spinner_reporter(|| f(project))?; + let timer = Instant::now(); + let r = f(); + let elapsed = timer.elapsed(); - let elapsed = now.elapsed(); - tracing::trace!(?elapsed, "finished compiling"); + tracing::debug!("finished compiling in {:.3}s", elapsed.as_secs_f64()); + r + })?; - if output.has_compiler_errors() { - tracing::warn!("compiled with errors"); - eyre::bail!(output.to_string()) - } else if output.is_unchanged() { - println!("No files changed, compilation skipped"); - self.handle_output(&output); - } else { - // print the compiler output / warnings - println!("{output}"); + if bail && output.has_compiler_errors() { + eyre::bail!("{output}") + } + + if !quiet { + if !shell::is_json() { + if output.is_unchanged() { + sh_println!("No files changed, compilation skipped")?; + } else { + // print the compiler output / warnings + sh_println!("{output}")?; + } + } self.handle_output(&output); } @@ -120,51 +210,84 @@ impl ProjectCompiler { } /// If configured, this will print sizes or names - fn handle_output(&self, output: &ProjectCompileOutput) { + fn handle_output>( + &self, + output: &ProjectCompileOutput, + ) { + let print_names = self.print_names.unwrap_or(false); + let print_sizes = self.print_sizes.unwrap_or(false); + // print any sizes or names - if self.print_names { + if print_names { let mut artifacts: BTreeMap<_, Vec<_>> = BTreeMap::new(); for (name, (_, version)) in output.versioned_artifacts() { artifacts.entry(version).or_default().push(name); } - for (version, names) in artifacts { - println!( - " compiler version: {}.{}.{}", - version.major, version.minor, version.patch - ); - for name in names { - println!(" - {name}"); + + if shell::is_json() { + let _ = sh_println!("{}", serde_json::to_string(&artifacts).unwrap()); + } else { + for (version, names) in artifacts { + let _ = sh_println!( + " compiler version: {}.{}.{}", + version.major, + version.minor, + version.patch + ); + for name in names { + let _ = sh_println!(" - {name}"); + } } } } - if self.print_sizes { + + if print_sizes { // add extra newline if names were already printed - if self.print_names { - println!(); + if print_names && !shell::is_json() { + let _ = sh_println!(); } - let mut size_report = SizeReport { contracts: BTreeMap::new() }; - let artifacts: BTreeMap<_, _> = output.artifacts().collect(); + + let mut size_report = + SizeReport { report_kind: report_kind(), contracts: BTreeMap::new() }; + + 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 size = deployed_contract_size(artifact).unwrap_or_default(); + let runtime_size = contract_size(artifact, false).unwrap_or_default(); + let init_size = contract_size(artifact, true).unwrap_or_default(); - let dev_functions = artifact + let is_dev_contract = artifact .abi .as_ref() - .map(|abi| 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; - size_report.contracts.insert(name, ContractInfo { size, is_dev_contract }); + .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 { runtime_size, init_size, is_dev_contract }); } - println!("{size_report}"); + let _ = sh_println!("{size_report}"); + // TODO: avoid process::exit // exit with error if any contract exceeds the size limit, excluding test contracts. - if size_report.exceeds_size_limit() { + if size_report.exceeds_runtime_size_limit() { + std::process::exit(1); + } + + // Check size limits only if not ignoring EIP-3860 + if !self.ignore_eip_3860 && size_report.exceeds_initcode_size_limit() { std::process::exit(1); } } @@ -172,72 +295,150 @@ impl ProjectCompiler { } // https://eips.ethereum.org/EIPS/eip-170 -const CONTRACT_SIZE_LIMIT: usize = 24576; +const CONTRACT_RUNTIME_SIZE_LIMIT: usize = 24576; + +// https://eips.ethereum.org/EIPS/eip-3860 +const CONTRACT_INITCODE_SIZE_LIMIT: usize = 49152; /// Contracts with info about their size pub struct SizeReport { - /// `:info>` + /// What kind of report to generate. + report_kind: ReportKind, + /// `contract name -> info` pub contracts: BTreeMap, } impl SizeReport { - /// Returns the size of the largest contract, excluding test contracts. - pub fn max_size(&self) -> usize { - let mut max_size = 0; - for contract in self.contracts.values() { - if !contract.is_dev_contract && contract.size > max_size { - max_size = contract.size; - } - } - max_size + /// Returns the maximum runtime code size, excluding dev contracts. + pub fn max_runtime_size(&self) -> usize { + self.contracts + .values() + .filter(|c| !c.is_dev_contract) + .map(|c| c.runtime_size) + .max() + .unwrap_or(0) + } + + /// Returns the maximum initcode size, excluding dev contracts. + pub fn max_init_size(&self) -> usize { + self.contracts + .values() + .filter(|c| !c.is_dev_contract) + .map(|c| c.init_size) + .max() + .unwrap_or(0) } - /// Returns true if any contract exceeds the size limit, excluding test contracts. - pub fn exceeds_size_limit(&self) -> bool { - self.max_size() > CONTRACT_SIZE_LIMIT + /// Returns true if any contract exceeds the runtime size limit, excluding dev contracts. + pub fn exceeds_runtime_size_limit(&self) -> bool { + self.max_runtime_size() > CONTRACT_RUNTIME_SIZE_LIMIT + } + + /// Returns true if any contract exceeds the initcode size limit, excluding dev contracts. + pub fn exceeds_initcode_size_limit(&self) -> bool { + self.max_init_size() > CONTRACT_INITCODE_SIZE_LIMIT } } impl Display for SizeReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self.report_kind { + ReportKind::Text => { + writeln!(f, "\n{}", self.format_table_output())?; + } + ReportKind::JSON => { + writeln!(f, "{}", self.format_json_output())?; + } + } + + Ok(()) + } +} + +impl SizeReport { + fn format_json_output(&self) -> String { + let contracts = self + .contracts + .iter() + .filter(|(_, c)| !c.is_dev_contract && (c.runtime_size > 0 || c.init_size > 0)) + .map(|(name, contract)| { + ( + name.clone(), + serde_json::json!({ + "runtime_size": contract.runtime_size, + "init_size": contract.init_size, + "runtime_margin": CONTRACT_RUNTIME_SIZE_LIMIT as isize - contract.runtime_size as isize, + "init_margin": CONTRACT_INITCODE_SIZE_LIMIT as isize - contract.init_size as isize, + }), + ) + }) + .collect::>(); + + serde_json::to_string(&contracts).unwrap() + } + + fn format_table_output(&self) -> Table { let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); + table.apply_modifier(UTF8_ROUND_CORNERS); + table.set_header(vec![ - Cell::new("Contract").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Size (kB)").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Margin (kB)").add_attribute(Attribute::Bold).fg(Color::Blue), + Cell::new("Contract"), + Cell::new("Runtime Size (B)"), + Cell::new("Initcode Size (B)"), + Cell::new("Runtime Margin (B)"), + Cell::new("Initcode Margin (B)"), ]); - let contracts = self.contracts.iter().filter(|(_, c)| !c.is_dev_contract && c.size > 0); + // Filters out dev contracts (Test or Script) + let contracts = self + .contracts + .iter() + .filter(|(_, c)| !c.is_dev_contract && (c.runtime_size > 0 || c.init_size > 0)); for (name, contract) in contracts { - let margin = CONTRACT_SIZE_LIMIT as isize - contract.size as isize; - let color = match contract.size { - 0..=17999 => Color::Reset, - 18000..=CONTRACT_SIZE_LIMIT => Color::Yellow, + let runtime_margin = + CONTRACT_RUNTIME_SIZE_LIMIT as isize - contract.runtime_size as isize; + let init_margin = CONTRACT_INITCODE_SIZE_LIMIT as isize - contract.init_size as isize; + + let runtime_color = match contract.runtime_size { + ..18_000 => Color::Reset, + 18_000..=CONTRACT_RUNTIME_SIZE_LIMIT => Color::Yellow, + _ => Color::Red, + }; + + let init_color = match contract.init_size { + ..36_000 => Color::Reset, + 36_000..=CONTRACT_INITCODE_SIZE_LIMIT => Color::Yellow, _ => Color::Red, }; - table.add_row(vec![ - Cell::new(name).fg(color), - Cell::new(contract.size as f64 / 1000.0).fg(color), - Cell::new(margin as f64 / 1000.0).fg(color), + let locale = &Locale::en; + table.add_row([ + Cell::new(name), + Cell::new(contract.runtime_size.to_formatted_string(locale)).fg(runtime_color), + Cell::new(contract.init_size.to_formatted_string(locale)).fg(init_color), + Cell::new(runtime_margin.to_formatted_string(locale)).fg(runtime_color), + Cell::new(init_margin.to_formatted_string(locale)).fg(init_color), ]); } - writeln!(f, "{table}")?; - Ok(()) + table } } -/// Returns the size of the deployed contract -pub fn deployed_contract_size(artifact: &T) -> Option { - let bytecode = artifact.get_deployed_bytecode_object()?; +/// Returns the deployed or init size of the contract. +fn contract_size(artifact: &T, initcode: bool) -> Option { + let bytecode = if initcode { + artifact.get_bytecode_object()? + } else { + artifact.get_deployed_bytecode_object()? + }; + let size = match bytecode.as_ref() { BytecodeObject::Bytecode(bytes) => bytes.len(), BytecodeObject::Unlinked(unlinked) => { // we don't need to account for placeholders here, because library placeholders take up // 40 characters: `__$$__` which is the same as a 20byte address in hex. - let mut size = unlinked.as_bytes().len(); + let mut size = unlinked.len(); if unlinked.starts_with("0x") { size -= 2; } @@ -245,195 +446,46 @@ pub fn deployed_contract_size(artifact: &T) -> Option { size / 2 } }; + Some(size) } /// How big the contract is and whether it is a dev contract where size limits can be neglected -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub struct ContractInfo { - /// size of the contract in bytes - pub size: usize, + /// Size of the runtime code in bytes + pub runtime_size: usize, + /// Size of the initcode in bytes + pub init_size: usize, /// A development contract is either a Script or a Test contract. pub is_dev_contract: bool, } -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -pub fn compile( - project: &Project, - print_names: bool, - print_sizes: bool, -) -> Result { - ProjectCompiler::new(print_names, print_sizes).compile(project) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// -/// Takes a list of [`SkipBuildFilter`] for files to exclude from the build. -pub fn compile_with_filter( - project: &Project, - print_names: bool, - print_sizes: bool, - skip: Vec, -) -> Result { - ProjectCompiler::with_filter(print_names, print_sizes, skip).compile(project) -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// Doesn't print anything to stdout, thus is "suppressed". -pub fn suppress_compile(project: &Project) -> Result { - let output = ethers_solc::report::with_scoped( - ðers_solc::report::Report::new(NoReporter::default()), - || project.compile(), - )?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - - Ok(output) -} - -/// Depending on whether the `skip` is empty this will [`suppress_compile_sparse`] or -/// [`suppress_compile`] -pub fn suppress_compile_with_filter( - project: &Project, - skip: Vec, -) -> Result { - if skip.is_empty() { - suppress_compile(project) - } else { - suppress_compile_sparse(project, SkipBuildFilters(skip)) - } -} - -/// Compiles the provided [`Project`], throws if there's any compiler error and logs whether -/// compilation was successful or if there was a cache hit. -/// Doesn't print anything to stdout, thus is "suppressed". -/// -/// See [`Project::compile_sparse`] -pub fn suppress_compile_sparse( - project: &Project, - filter: F, -) -> Result { - let output = ethers_solc::report::with_scoped( - ðers_solc::report::Report::new(NoReporter::default()), - || project.compile_sparse(filter), - )?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - - Ok(output) -} - -/// Compile a set of files not necessarily included in the `project`'s source dir -/// -/// If `silent` no solc related output will be emitted to stdout -pub fn compile_files( - project: &Project, - files: Vec, - silent: bool, -) -> Result { - let output = if silent { - ethers_solc::report::with_scoped( - ðers_solc::report::Report::new(NoReporter::default()), - || project.compile_files(files), - ) - } else { - term::with_spinner_reporter(|| project.compile_files(files)) - }?; - - if output.has_compiler_errors() { - eyre::bail!(output.to_string()) - } - if !silent { - println!("{output}"); - } - - Ok(output) -} - /// Compiles target file path. /// -/// If `silent` no solc related output will be emitted to stdout. +/// If `quiet` no solc related output will be emitted to stdout. /// /// If `verify` and it's a standalone script, throw error. Only allowed for projects. /// /// **Note:** this expects the `target_path` to be absolute -pub fn compile_target( +pub fn compile_target>( target_path: &Path, - project: &Project, - silent: bool, - verify: bool, -) -> Result { - compile_target_with_filter(target_path, project, silent, verify, Vec::new()) -} - -/// Compiles target file path. -pub fn compile_target_with_filter( - target_path: &Path, - project: &Project, - silent: bool, - verify: bool, - skip: Vec, -) -> Result { - let graph = Graph::resolve(&project.paths)?; - - // Checking if it's a standalone script, or part of a project. - if graph.files().get(target_path).is_none() { - if verify { - eyre::bail!("You can only verify deployments from inside a project! Make sure it exists with `forge tree`."); - } - return compile_files(project, vec![target_path.to_path_buf()], silent) - } - - if silent { - suppress_compile_with_filter(project, skip) - } else { - compile_with_filter(project, false, false, skip) - } -} - -/// Creates and compiles a project from an Etherscan source. -pub async fn compile_from_source( - metadata: &Metadata, -) -> Result<(ArtifactId, ContractBytecodeSome)> { - let root = tempfile::tempdir()?; - let root_path = root.path(); - let project = etherscan_project(metadata, root_path)?; - - let project_output = project.compile()?; - - if project_output.has_compiler_errors() { - eyre::bail!(project_output.to_string()) - } - - let (artifact_id, contract) = project_output - .into_contract_bytecodes() - .find(|(artifact_id, _)| artifact_id.name == metadata.contract_name) - .expect("there should be a contract with bytecode"); - let bytecode = ContractBytecodeSome { - abi: contract.abi.unwrap(), - bytecode: contract.bytecode.unwrap().into(), - deployed_bytecode: contract.deployed_bytecode.unwrap().into(), - }; - - root.close()?; - - Ok((artifact_id, bytecode)) + project: &Project, + quiet: bool, +) -> Result> { + ProjectCompiler::new().quiet(quiet).files([target_path.into()]).compile(project) } /// Creates a [Project] from an Etherscan source. -pub fn etherscan_project(metadata: &Metadata, target_path: impl AsRef) -> Result { +pub fn etherscan_project( + metadata: &Metadata, + target_path: impl AsRef, +) -> Result> { let target_path = dunce::canonicalize(target_path.as_ref())?; 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() { @@ -460,82 +512,92 @@ pub fn etherscan_project(metadata: &Metadata, target_path: impl AsRef) -> .build_with_root(sources_path); let v = metadata.compiler_version()?; - let v = format!("{}.{}.{}", v.major, v.minor, v.patch); - let solc = Solc::find_or_install_svm_version(v)?; + let solc = Solc::find_or_install(&v)?; + + let compiler = SolcCompiler::Specific(solc); - Ok(Project::builder() - .solc_config(SolcConfig::builder().settings(settings).build()) - .no_auto_detect() + Ok(ProjectBuilder::::default() + .settings(SolcSettings { + settings: SolcConfig::builder().settings(settings).build(), + ..Default::default() + }) .paths(paths) - .solc(solc) .ephemeral() .no_artifacts() - .build()?) + .build(compiler)?) } -/// Bundles multiple `SkipBuildFilter` into a single `FileFilter` -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SkipBuildFilters(pub Vec); +/// Configures the reporter and runs the given closure. +pub fn with_compilation_reporter(quiet: bool, f: impl FnOnce() -> O) -> O { + #[allow(clippy::collapsible_else_if)] + let reporter = if quiet || shell::is_json() { + Report::new(NoReporter::default()) + } else { + if std::io::stdout().is_terminal() { + Report::new(SpinnerReporter::spawn()) + } else { + Report::new(BasicStdoutReporter::default()) + } + }; -impl FileFilter for SkipBuildFilters { - /// Only returns a match if _no_ exclusion filter matches - fn is_match(&self, file: &Path) -> bool { - self.0.iter().all(|filter| filter.is_match(file)) - } + foundry_compilers::report::with_scoped(&reporter, f) } -/// A filter that excludes matching contracts from the build -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum SkipBuildFilter { - /// Exclude all `.t.sol` contracts - Tests, - /// Exclude all `.s.sol` contracts - Scripts, - /// Exclude if the file matches - Custom(String), +/// Container type for parsing contract identifiers from CLI. +/// +/// Passed string can be of the following forms: +/// - `src/Counter.sol` - path to the contract file, in the case where it only contains one contract +/// - `src/Counter.sol:Counter` - path to the contract file and the contract name +/// - `Counter` - contract name only +#[derive(Clone, PartialEq, Eq)] +pub enum PathOrContractInfo { + /// Non-canoncalized path provided via CLI. + Path(PathBuf), + /// Contract info provided via CLI. + ContractInfo(CompilerContractInfo), } -impl SkipBuildFilter { - /// Returns the pattern to match against a file - fn file_pattern(&self) -> &str { +impl PathOrContractInfo { + /// Returns the path to the contract file if provided. + pub fn path(&self) -> Option { match self { - SkipBuildFilter::Tests => ".t.sol", - SkipBuildFilter::Scripts => ".s.sol", - SkipBuildFilter::Custom(s) => s.as_str(), + Self::Path(path) => Some(path.to_path_buf()), + Self::ContractInfo(info) => info.path.as_ref().map(PathBuf::from), } } -} -impl> From for SkipBuildFilter { - fn from(s: T) -> Self { - match s.as_ref() { - "test" | "tests" => SkipBuildFilter::Tests, - "script" | "scripts" => SkipBuildFilter::Scripts, - s => SkipBuildFilter::Custom(s.to_string()), + /// Returns the contract name if provided. + pub fn name(&self) -> Option<&str> { + match self { + Self::Path(_) => None, + Self::ContractInfo(info) => Some(&info.name), } } } -impl FromStr for SkipBuildFilter { - type Err = Infallible; +impl FromStr for PathOrContractInfo { + type Err = eyre::Error; - fn from_str(s: &str) -> result::Result { - Ok(s.into()) + fn from_str(s: &str) -> Result { + if let Ok(contract) = CompilerContractInfo::from_str(s) { + return Ok(Self::ContractInfo(contract)); + } + let path = PathBuf::from(s); + if path.extension().is_some_and(|ext| ext == "sol" || ext == "vy") { + return Ok(Self::Path(path)); + } + Err(eyre::eyre!("Invalid contract identifier, file is not *.sol or *.vy: {}", s)) } } -impl FileFilter for SkipBuildFilter { - /// Matches file only if the filter does not apply - /// - /// This is returns the inverse of `file.name.contains(pattern) || matcher.is_match(file)` - fn is_match(&self, file: &Path) -> bool { - fn exclude(file: &Path, pattern: &str) -> Option { - let matcher: GlobMatcher = pattern.parse().unwrap(); - let file_name = file.file_name()?.to_str()?; - Some(file_name.contains(pattern) || matcher.is_match(file.as_os_str().to_str()?)) +impl std::fmt::Debug for PathOrContractInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Path(path) => write!(f, "Path({})", path.display()), + Self::ContractInfo(info) => { + write!(f, "ContractInfo({info})") + } } - - !exclude(file, self.file_pattern()).unwrap_or_default() } } @@ -544,20 +606,28 @@ mod tests { use super::*; #[test] - fn test_build_filter() { - let file = Path::new("A.t.sol"); - assert!(!SkipBuildFilter::Tests.is_match(file)); - assert!(SkipBuildFilter::Scripts.is_match(file)); - assert!(!SkipBuildFilter::Custom("A.t".to_string()).is_match(file)); - - let file = Path::new("A.s.sol"); - assert!(SkipBuildFilter::Tests.is_match(file)); - assert!(!SkipBuildFilter::Scripts.is_match(file)); - assert!(!SkipBuildFilter::Custom("A.s".to_string()).is_match(file)); - - let file = Path::new("/home/test/Foo.sol"); - assert!(!SkipBuildFilter::Custom("*/test/**".to_string()).is_match(file)); - let file = Path::new("/home/script/Contract.sol"); - assert!(!SkipBuildFilter::Custom("*/script/**".to_string()).is_match(file)); + fn parse_contract_identifiers() { + let t = ["src/Counter.sol", "src/Counter.sol:Counter", "Counter"]; + + let i1 = PathOrContractInfo::from_str(t[0]).unwrap(); + assert_eq!(i1, PathOrContractInfo::Path(PathBuf::from(t[0]))); + + let i2 = PathOrContractInfo::from_str(t[1]).unwrap(); + assert_eq!( + i2, + PathOrContractInfo::ContractInfo(CompilerContractInfo { + path: Some("src/Counter.sol".to_string()), + name: "Counter".to_string() + }) + ); + + let i3 = PathOrContractInfo::from_str(t[2]).unwrap(); + assert_eq!( + i3, + PathOrContractInfo::ContractInfo(CompilerContractInfo { + path: None, + name: "Counter".to_string() + }) + ); } } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 2478fa81edb93..c67131585ba29 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -1,5 +1,8 @@ -//! Commonly used constants +//! Commonly used constants. +use alloy_consensus::Typed2718; +use alloy_network::AnyTxEnvelope; +use alloy_primitives::{address, Address, PrimitiveSignature, B256}; use std::time::Duration; /// The dev chain-id, inherited from hardhat @@ -18,10 +21,69 @@ pub const CONTRACT_MAX_SIZE: usize = 24576; /// responses. This timeout should be a reasonable amount of time to wait for a request. pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(45); -/// Alchemy free tier cups +/// Alchemy free tier cups: pub const ALCHEMY_FREE_TIER_CUPS: u64 = 330; /// Logged when an error is indicative that the user is trying to fork from a non-archive node. pub const NON_ARCHIVE_NODE_WARNING: &str = "\ It looks like you're trying to fork from an older block with a non-archive node which is not \ supported. Please try to change your RPC url to an archive node if the issue persists."; + +/// Arbitrum L1 sender address of the first transaction in every block. +/// `0x00000000000000000000000000000000000a4b05` +pub const ARBITRUM_SENDER: Address = address!("00000000000000000000000000000000000a4b05"); + +/// The system address, the sender of the first transaction in every block: +/// `0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001` +/// +/// See also +pub const OPTIMISM_SYSTEM_ADDRESS: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001"); + +/// Transaction identifier of System transaction types +pub const SYSTEM_TRANSACTION_TYPE: u8 = 126; + +/// Default user agent set as the header for requests that don't specify one. +pub const DEFAULT_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); + +/// Returns whether the sender is a known L2 system sender that is the first tx in every block. +/// +/// Transactions from these senders usually don't have a any fee information. +/// +/// See: [ARBITRUM_SENDER], [OPTIMISM_SYSTEM_ADDRESS] +#[inline] +pub fn is_known_system_sender(sender: Address) -> bool { + [ARBITRUM_SENDER, OPTIMISM_SYSTEM_ADDRESS].contains(&sender) +} + +pub fn is_impersonated_tx(tx: &AnyTxEnvelope) -> bool { + if let AnyTxEnvelope::Ethereum(tx) = tx { + return is_impersonated_sig(tx.signature(), tx.ty()); + } + false +} + +pub fn is_impersonated_sig(sig: &PrimitiveSignature, ty: u8) -> bool { + let impersonated_sig = PrimitiveSignature::from_scalars_and_parity( + B256::with_last_byte(1), + B256::with_last_byte(1), + false, + ); + if ty != SYSTEM_TRANSACTION_TYPE && sig == &impersonated_sig { + return true; + } + false +} + +#[cfg(test)] +mod tests { + use super::*; + use std::str::FromStr; + + #[test] + fn test_constant_sender() { + let arb = Address::from_str("0x00000000000000000000000000000000000a4b05").unwrap(); + assert_eq!(arb, ARBITRUM_SENDER); + let base = Address::from_str("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001").unwrap(); + assert_eq!(base, OPTIMISM_SYSTEM_ADDRESS); + } +} diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 0440b000d9821..1e78751fde964 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -1,36 +1,260 @@ -//! commonly used contract types and functions - -use ethers_core::{ - abi::{Abi, Event, Function}, - types::{Address, H256}, - utils::hex, +//! Commonly used contract types and functions. + +use crate::compile::PathOrContractInfo; +use alloy_json_abi::{Event, Function, JsonAbi}; +use alloy_primitives::{hex, Address, Bytes, Selector, B256}; +use eyre::{OptionExt, Result}; +use foundry_compilers::{ + artifacts::{ + BytecodeObject, CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, + ConfigurableContractArtifact, ContractBytecodeSome, Offsets, + }, + utils::canonicalized, + ArtifactId, Project, ProjectCompileOutput, }; -use ethers_solc::{artifacts::ContractBytecodeSome, ArtifactId, ProjectPathsConfig}; -use once_cell::sync::Lazy; -use regex::Regex; use std::{ collections::BTreeMap, - ops::{Deref, DerefMut}, - path::PathBuf, + ops::Deref, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, }; -type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a (Abi, Vec)); +/// Libraries' runtime code always starts with the following instruction: +/// `PUSH20 0x0000000000000000000000000000000000000000` +/// +/// See: +const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] = + hex!("730000000000000000000000000000000000000000"); + +/// Subset of [CompactBytecode] excluding sourcemaps. +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct BytecodeData { + pub object: Option, + pub link_references: BTreeMap>>, + pub immutable_references: BTreeMap>, +} + +impl BytecodeData { + fn bytes(&self) -> Option<&Bytes> { + self.object.as_ref().and_then(|b| b.as_bytes()) + } +} + +impl From for BytecodeData { + fn from(bytecode: CompactBytecode) -> Self { + Self { + object: Some(bytecode.object), + link_references: bytecode.link_references, + immutable_references: BTreeMap::new(), + } + } +} + +impl From for BytecodeData { + fn from(bytecode: CompactDeployedBytecode) -> Self { + let (object, link_references) = if let Some(compact) = bytecode.bytecode { + (Some(compact.object), compact.link_references) + } else { + (None, BTreeMap::new()) + }; + Self { object, link_references, immutable_references: bytecode.immutable_references } + } +} + +/// Container for commonly used contract data. +#[derive(Debug)] +pub struct ContractData { + /// Contract name. + pub name: String, + /// Contract ABI. + pub abi: JsonAbi, + /// Contract creation code. + pub bytecode: Option, + /// Contract runtime code. + pub deployed_bytecode: Option, +} + +impl ContractData { + /// Returns reference to bytes of contract creation code, if present. + pub fn bytecode(&self) -> Option<&Bytes> { + self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) + } + + /// Returns reference to bytes of contract deployed code, if present. + pub fn deployed_bytecode(&self) -> Option<&Bytes> { + self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) + } +} + +type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData); /// Wrapper type that maps an artifact to a contract ABI and bytecode. -#[derive(Default, Clone)] -pub struct ContractsByArtifact(pub BTreeMap)>); +#[derive(Clone, Default, Debug)] +pub struct ContractsByArtifact(Arc>); impl ContractsByArtifact { + /// Creates a new instance by collecting all artifacts with present bytecode from an iterator. + pub fn new(artifacts: impl IntoIterator) -> Self { + let map = artifacts + .into_iter() + .filter_map(|(id, artifact)| { + let name = id.name.clone(); + let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact; + Some(( + id, + ContractData { + name, + abi: abi?, + bytecode: bytecode.map(Into::into), + deployed_bytecode: deployed_bytecode.map(Into::into), + }, + )) + }) + .collect(); + Self(Arc::new(map)) + } + + /// Clears all contracts. + pub fn clear(&mut self) { + *self = Self::default(); + } + /// Finds a contract which has a similar bytecode as `code`. - pub fn find_by_code(&self, code: &[u8]) -> Option { - self.iter().find(|(_, (_, known_code))| diff_score(known_code, code) < 0.1) + pub fn find_by_creation_code(&self, code: &[u8]) -> Option> { + self.find_by_code(code, 0.1, ContractData::bytecode) + } + + /// Finds a contract which has a similar deployed bytecode as `code`. + pub fn find_by_deployed_code(&self, code: &[u8]) -> Option> { + self.find_by_code(code, 0.15, ContractData::deployed_bytecode) + } + + /// Finds a contract based on provided bytecode and accepted match score. + fn find_by_code( + &self, + code: &[u8], + accepted_score: f64, + 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 <= accepted_score).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; + }; + let Some(deployed_code) = &deployed_bytecode.object else { + return false; + }; + + let len = match deployed_code { + BytecodeObject::Bytecode(ref bytes) => bytes.len(), + BytecodeObject::Unlinked(ref bytes) => bytes.len() / 2, + }; + + if len != code.len() { + return false; + } + + // Collect ignored offsets by chaining link and immutable references. + let mut ignored = deployed_bytecode + .immutable_references + .values() + .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values())) + .flatten() + .cloned() + .collect::>(); + + // For libraries solidity adds a call protection prefix to the bytecode. We need to + // ignore it as it includes library address determined at runtime. + // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and + // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172 + let has_call_protection = match deployed_code { + BytecodeObject::Bytecode(ref bytes) => { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = + Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2]) + { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } else { + false + } + } + }; + + if has_call_protection { + ignored.push(Offsets { start: 1, length: 20 }); + } + + ignored.sort_by_key(|o| o.start); + + let mut left = 0; + for offset in ignored { + let right = offset.start as usize; + + let matched = match deployed_code { + BytecodeObject::Bytecode(ref bytes) => bytes[left..right] == code[left..right], + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) { + bytes == code[left..right] + } else { + false + } + } + }; + + if !matched { + return false; + } + + left = right + offset.length as usize; + } + + if left < code.len() { + match deployed_code { + BytecodeObject::Bytecode(ref bytes) => bytes[left..] == code[left..], + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) { + bytes == code[left..] + } else { + false + } + } + } + } else { + true + } + }) } + /// Finds a contract which has the same contract name or identifier as `id`. If more than one is /// found, return error. pub fn find_by_name_or_identifier( &self, id: &str, - ) -> eyre::Result> { + ) -> Result>> { let contracts = self .iter() .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) @@ -43,100 +267,125 @@ impl ContractsByArtifact { Ok(contracts.first().cloned()) } - /// Flattens a group of contracts into maps of all events and functions - pub fn flatten(&self) -> (BTreeMap<[u8; 4], Function>, BTreeMap, Abi) { - let flattened_funcs: BTreeMap<[u8; 4], Function> = self - .iter() - .flat_map(|(_name, (abi, _code))| { - abi.functions() - .map(|func| (func.short_signature(), func.clone())) - .collect::>() + /// Finds abi for contract which has the same contract name or identifier as `id`. + pub fn find_abi_by_name_or_identifier(&self, id: &str) -> Option { + self.iter() + .find(|(artifact, _)| { + artifact.name.split(".").next().unwrap() == id || artifact.identifier() == id }) - .collect(); + .map(|(_, contract)| contract.abi.clone()) + } - let flattened_events: BTreeMap = self - .iter() - .flat_map(|(_name, (abi, _code))| { - abi.events() - .map(|event| (event.signature(), event.clone())) - .collect::>() + /// Finds abi by name or source path + /// + /// Returns the abi and the contract name. + pub fn find_abi_by_name_or_src_path(&self, name_or_path: &str) -> Option<(JsonAbi, String)> { + self.iter() + .find(|(artifact, _)| { + artifact.name == name_or_path || artifact.source == PathBuf::from(name_or_path) }) - .collect(); + .map(|(_, contract)| (contract.abi.clone(), contract.name.clone())) + } + + /// Flattens the contracts into functions, events and errors. + pub fn flatten(&self) -> (BTreeMap, BTreeMap, JsonAbi) { + let mut funcs = BTreeMap::new(); + let mut events = BTreeMap::new(); + let mut errors_abi = JsonAbi::new(); + for (_name, contract) in self.iter() { + for func in contract.abi.functions() { + funcs.insert(func.selector(), func.clone()); + } + for event in contract.abi.events() { + events.insert(event.selector(), event.clone()); + } + for error in contract.abi.errors() { + errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone()); + } + } + (funcs, events, errors_abi) + } +} - // We need this for better revert decoding, and want it in abi form - let mut errors_abi = Abi::default(); - self.iter().for_each(|(_name, (abi, _code))| { - abi.errors().for_each(|error| { - let entry = - errors_abi.errors.entry(error.name.clone()).or_insert_with(Default::default); - entry.push(error.clone()); - }); - }); - (flattened_funcs, flattened_events, errors_abi) +impl From for ContractsByArtifact { + fn from(value: ProjectCompileOutput) -> Self { + Self::new(value.into_artifacts().map(|(id, ar)| { + ( + id, + CompactContractBytecode { + abi: ar.abi, + bytecode: ar.bytecode, + deployed_bytecode: ar.deployed_bytecode, + }, + ) + })) } } impl Deref for ContractsByArtifact { - type Target = BTreeMap)>; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for ContractsByArtifact { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - /// Wrapper type that maps an address to a contract identifier and contract ABI. -pub type ContractsByAddress = BTreeMap; +pub type ContractsByAddress = BTreeMap; /// Very simple fuzzy matching of contract bytecode. /// -/// Will fail for small contracts that are essentially all immutable variables. -pub fn diff_score(a: &[u8], b: &[u8]) -> f64 { - let cutoff_len = usize::min(a.len(), b.len()); - if cutoff_len == 0 { - return 1.0 +/// Returns a value between `0.0` (identical) and `1.0` (completely different). +pub fn bytecode_diff_score<'a>(mut a: &'a [u8], mut b: &'a [u8]) -> f64 { + // Make sure `a` is the longer one. + if a.len() < b.len() { + std::mem::swap(&mut a, &mut b); } - let a = &a[..cutoff_len]; - let b = &b[..cutoff_len]; - let mut diff_chars = 0; - for i in 0..cutoff_len { - if a[i] != b[i] { - diff_chars += 1; - } + // Account for different lengths. + let mut n_different_bytes = a.len() - b.len(); + + // If the difference is more than 32 bytes and more than 10% of the total length, + // we assume the bytecodes are completely different. + // This is a simple heuristic to avoid checking every byte when the lengths are very different. + // 32 is chosen to be a reasonable minimum as it's the size of metadata hashes and one EVM word. + if n_different_bytes > 32 && n_different_bytes * 10 > a.len() { + return 1.0; } - diff_chars as f64 / cutoff_len as f64 -} -/// Flattens the contracts into (`id` -> (`Abi`, `Vec`)) pairs -pub fn flatten_contracts( - contracts: &BTreeMap, - deployed_code: bool, -) -> ContractsByArtifact { - ContractsByArtifact( - contracts - .iter() - .filter_map(|(id, c)| { - let bytecode = if deployed_code { - c.deployed_bytecode.clone().into_bytes() - } else { - c.bytecode.clone().object.into_bytes() - }; + // Count different bytes. + // SAFETY: `a` is longer than `b`. + n_different_bytes += unsafe { count_different_bytes(a, b) }; - if let Some(bytecode) = bytecode { - return Some((id.clone(), (c.abi.clone(), bytecode.to_vec()))) - } - None - }) - .collect(), - ) + n_different_bytes as f64 / a.len() as f64 +} + +/// Returns the amount of different bytes between two slices. +/// +/// # Safety +/// +/// `a` must be at least as long as `b`. +unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { + // This could've been written as `std::iter::zip(a, b).filter(|(x, y)| x != y).count()`, + // however this function is very hot, and has been written to be as primitive as + // possible for lower optimization levels. + + let a_ptr = a.as_ptr(); + let b_ptr = b.as_ptr(); + let len = b.len(); + + let mut sum = 0; + let mut i = 0; + while i < len { + // SAFETY: `a` is at least as long as `b`, and `i` is in bound of `b`. + sum += unsafe { *a_ptr.add(i) != *b_ptr.add(i) } as usize; + i += 1; + } + sum } +/// Returns contract name for a given contract identifier. +/// /// Artifact/Contract identifier can take the following form: /// `:`, the `artifact file name` is the name of the json file of /// the contract's artifact and the contract name is the name of the solidity contract, like @@ -172,96 +421,97 @@ pub fn get_file_name(id: &str) -> &str { id.split(':').next().unwrap_or(id) } -/// Returns the path to the json artifact depending on the input -pub fn get_artifact_path(paths: &ProjectPathsConfig, path: &str) -> PathBuf { - if path.ends_with(".json") { - PathBuf::from(path) - } else { - let parts: Vec<&str> = path.split(':').collect(); - let file = parts[0]; - let contract_name = - if parts.len() == 1 { parts[0].replace(".sol", "") } else { parts[1].to_string() }; - paths.artifacts.join(format!("{file}/{contract_name}.json")) - } +/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome +pub fn compact_to_contract(contract: CompactContractBytecode) -> Result { + Ok(ContractBytecodeSome { + abi: contract.abi.ok_or_else(|| eyre::eyre!("No contract abi"))?, + bytecode: contract.bytecode.ok_or_else(|| eyre::eyre!("No contract bytecode"))?.into(), + deployed_bytecode: contract + .deployed_bytecode + .ok_or_else(|| eyre::eyre!("No contract deployed bytecode"))? + .into(), + }) } -/// Given the transaction data tries to identify the constructor arguments -/// The constructor data is encoded as: Constructor Code + Contract Code + Constructor arguments -/// decoding the arguments here with only the transaction data is not trivial here, we try to find -/// the beginning of the constructor arguments by finding the length of the code, which is PUSH op -/// code which holds the code size and the code starts after the invalid op code (0xfe) -/// -/// finding the `0xfe` (invalid opcode) in the data which should mark the beginning of constructor -/// arguments -pub fn find_constructor_args(data: &[u8]) -> Option<&[u8]> { - // ref - static CONSTRUCTOR_CODE_RE: Lazy = Lazy::new(|| { - Regex::new(r"(?m)(?:5b)?(?:60([a-z0-9]{2})|61([a-z0-9_]{4})|62([a-z0-9_]{6}))80(?:60([a-z0-9]{2})|61([a-z0-9_]{4})|62([a-z0-9_]{6}))(6000396000f3fe)").unwrap() - }); - let s = hex::encode(data); - - // we're only interested in the last occurrence which skips additional CREATE inside the - // constructor itself - let caps = CONSTRUCTOR_CODE_RE.captures_iter(&s).last()?; - - let contract_len = u64::from_str_radix( - caps.get(1).or_else(|| caps.get(2)).or_else(|| caps.get(3))?.as_str(), - 16, - ) - .unwrap(); - - // the end position of the constructor code, we use this instead of the contract offset , since - // there could be multiple CREATE inside the data we need to divide by 2 for hex conversion - let constructor_end = (caps.get(7)?.end() / 2) as u64; - let start = (contract_len + constructor_end) as usize; - let args = &data[start..]; - - Some(args) +/// Returns the canonicalized target path for the given identifier. +pub fn find_target_path(project: &Project, identifier: &PathOrContractInfo) -> Result { + match identifier { + PathOrContractInfo::Path(path) => Ok(canonicalized(project.root().join(path))), + PathOrContractInfo::ContractInfo(info) => { + let path = project.find_contract_path(&info.name)?; + Ok(path) + } + } } -#[cfg(test)] -mod tests { - use super::*; - use ethers_core::{abi, abi::ParamType}; +/// Returns the target artifact given the path and name. +pub fn find_matching_contract_artifact( + output: &mut ProjectCompileOutput, + target_path: &Path, + target_name: Option<&str>, +) -> eyre::Result { + if let Some(name) = target_name { + output + .remove(target_path, name) + .ok_or_eyre(format!("Could not find artifact `{name}` in the compiled artifacts")) + } else { + let possible_targets = output + .artifact_ids() + .filter(|(id, _artifact)| id.source == target_path) + .collect::>(); - // - #[test] - fn test_find_constructor_args() { - let code = "6080604052348015600f57600080fd5b50604051610121380380610121833981016040819052602c91606e565b600080546001600160a01b0319166001600160a01b0396909616959095179094556001929092556002556003556004805460ff191691151591909117905560d4565b600080600080600060a08688031215608557600080fd5b85516001600160a01b0381168114609b57600080fd5b809550506020860151935060408601519250606086015191506080860151801515811460c657600080fd5b809150509295509295909350565b603f806100e26000396000f3fe6080604052600080fdfea264697066735822122089f2c61beace50d105ec1b6a56a1204301b5595e850e7576f6f3aa8e76f12d0b64736f6c6343000810003300000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea720000000000000000000000000000000000000000000000000000000100000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60000000000000000000000000000000000000000000000000000000000000001"; + if possible_targets.is_empty() { + eyre::bail!("Could not find artifact linked to source `{target_path:?}` in the compiled artifacts"); + } - let code = hex::decode(code).unwrap(); + let (target_id, target_artifact) = possible_targets[0].clone(); + if possible_targets.len() == 1 { + return Ok(target_artifact.clone()); + } - let args = find_constructor_args(&code).unwrap(); + // If all artifact_ids in `possible_targets` have the same name (without ".", indicates + // additional compiler profiles), it means that there are multiple contracts in the + // same file. + if !target_id.name.contains(".") && + possible_targets.iter().any(|(id, _)| id.name != target_id.name) + { + eyre::bail!("Multiple contracts found in the same file, please specify the target : or "); + } - let params = vec![ - ParamType::Address, - ParamType::Uint(256), - ParamType::Int(256), - ParamType::FixedBytes(32), - ParamType::Bool, - ]; + // Otherwise, we're dealing with additional compiler profiles wherein `id.source` is the + // same but `id.path` is different. + let artifact = possible_targets + .iter() + .find_map(|(id, artifact)| if id.profile == "default" { Some(*artifact) } else { None }) + .unwrap_or(target_artifact); - let _decoded = abi::decode(¶ms, args).unwrap(); + Ok(artifact.clone()) } +} +#[cfg(test)] +mod tests { + use super::*; #[test] - fn test_find_constructor_args_nested_deploy() { - let code = "608060405234801561001057600080fd5b5060405161066d38038061066d83398101604081905261002f9161014a565b868686868686866040516100429061007c565b610052979695949392919061022f565b604051809103906000f08015801561006e573d6000803e3d6000fd5b50505050505050505061028a565b610396806102d783390190565b634e487b7160e01b600052604160045260246000fd5b60005b838110156100ba5781810151838201526020016100a2565b50506000910152565b600082601f8301126100d457600080fd5b81516001600160401b03808211156100ee576100ee610089565b604051601f8301601f19908116603f0116810190828211818310171561011657610116610089565b8160405283815286602085880101111561012f57600080fd5b61014084602083016020890161009f565b9695505050505050565b600080600080600080600060e0888a03121561016557600080fd5b87516001600160a01b038116811461017c57600080fd5b80975050602088015195506040880151945060608801519350608088015180151581146101a857600080fd5b60a08901519093506001600160401b03808211156101c557600080fd5b6101d18b838c016100c3565b935060c08a01519150808211156101e757600080fd5b506101f48a828b016100c3565b91505092959891949750929550565b6000815180845261021b81602086016020860161009f565b601f01601f19169290920160200192915050565b60018060a01b0388168152866020820152856040820152846060820152831515608082015260e060a0820152600061026a60e0830185610203565b82810360c084015261027c8185610203565b9a9950505050505050505050565b603f806102986000396000f3fe6080604052600080fdfea264697066735822122072aeef1567521008007b956bd7c6e9101a9b49fbce1f45210fa929c79d28bd9364736f6c63430008110033608060405234801561001057600080fd5b5060405161039638038061039683398101604081905261002f91610148565b600080546001600160a01b0319166001600160a01b0389161790556001869055600285905560038490556004805460ff19168415151790556005610073838261028a565b506006610080828261028a565b5050505050505050610349565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126100b457600080fd5b81516001600160401b03808211156100ce576100ce61008d565b604051601f8301601f19908116603f011681019082821181831017156100f6576100f661008d565b8160405283815260209250868385880101111561011257600080fd5b600091505b838210156101345785820183015181830184015290820190610117565b600093810190920192909252949350505050565b600080600080600080600060e0888a03121561016357600080fd5b87516001600160a01b038116811461017a57600080fd5b80975050602088015195506040880151945060608801519350608088015180151581146101a657600080fd5b60a08901519093506001600160401b03808211156101c357600080fd5b6101cf8b838c016100a3565b935060c08a01519150808211156101e557600080fd5b506101f28a828b016100a3565b91505092959891949750929550565b600181811c9082168061021557607f821691505b60208210810361023557634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561028557600081815260208120601f850160051c810160208610156102625750805b601f850160051c820191505b818110156102815782815560010161026e565b5050505b505050565b81516001600160401b038111156102a3576102a361008d565b6102b7816102b18454610201565b8461023b565b602080601f8311600181146102ec57600084156102d45750858301515b600019600386901b1c1916600185901b178555610281565b600085815260208120601f198616915b8281101561031b578886015182559484019460019091019084016102fc565b50858210156103395787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b603f806103576000396000f3fe6080604052600080fdfea2646970667358221220a468ac913d3ecf191b6559ae7dca58e05ba048434318f393b86640b25cbbf1ed64736f6c6343000811003300000000000000000000000000a329c0648769a73afac7f9381e08fb43dbea720000000000000000000000000000000000000000000000000000000100000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000066162636465660000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000568656c6c6f000000000000000000000000000000000000000000000000000000"; - - let code = hex::decode(code).unwrap(); - - let args = find_constructor_args(&code).unwrap(); + fn bytecode_diffing() { + assert_eq!(bytecode_diff_score(b"a", b"a"), 0.0); + assert_eq!(bytecode_diff_score(b"a", b"b"), 1.0); + + let a_100 = &b"a".repeat(100)[..]; + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(100)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(99)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(101)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(120)), 1.0); + assert_eq!(bytecode_diff_score(a_100, &b"b".repeat(1000)), 1.0); + + let a_99 = &b"a".repeat(99)[..]; + assert!(bytecode_diff_score(a_100, a_99) <= 0.01); + } - let params = vec![ - ParamType::Address, - ParamType::Uint(256), - ParamType::Int(256), - ParamType::FixedBytes(32), - ParamType::Bool, - ParamType::Bytes, - ParamType::String, - ]; + #[test] + fn find_by_deployed_code_exact_with_empty_deployed() { + let contracts = ContractsByArtifact::new(vec![]); - let _decoded = abi::decode(¶ms, args).unwrap(); + assert!(contracts.find_by_deployed_code_exact(&[]).is_none()); } } diff --git a/crates/common/src/ens.rs b/crates/common/src/ens.rs new file mode 100644 index 0000000000000..bff7c4c882662 --- /dev/null +++ b/crates/common/src/ens.rs @@ -0,0 +1,254 @@ +//! ENS Name resolving utilities. + +#![allow(missing_docs)] + +use self::EnsResolver::EnsResolverInstance; +use alloy_primitives::{address, Address, Keccak256, B256}; +use alloy_provider::{Network, Provider}; +use alloy_sol_types::sol; +use async_trait::async_trait; +use std::{borrow::Cow, str::FromStr}; + +// ENS Registry and Resolver contracts. +sol! { + /// ENS Registry contract. + #[sol(rpc)] + contract EnsRegistry { + /// Returns the resolver for the specified node. + function resolver(bytes32 node) view returns (address); + } + + /// ENS Resolver interface. + #[sol(rpc)] + contract EnsResolver { + /// Returns the address associated with the specified node. + function addr(bytes32 node) view returns (address); + + /// Returns the name associated with an ENS node, for reverse records. + function name(bytes32 node) view returns (string); + } +} + +/// ENS registry address (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`) +pub const ENS_ADDRESS: Address = address!("00000000000C2E074eC69A0dFb2997BA6C7d2e1e"); + +pub const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse"; + +/// Error type for ENS resolution. +#[derive(Debug, thiserror::Error)] +pub enum EnsError { + /// Failed to get resolver from the ENS registry. + #[error("Failed to get resolver from the ENS registry: {0}")] + Resolver(alloy_contract::Error), + /// Failed to get resolver from the ENS registry. + #[error("ENS resolver not found for name {0:?}")] + ResolverNotFound(String), + /// Failed to lookup ENS name from an address. + #[error("Failed to lookup ENS name from an address: {0}")] + Lookup(alloy_contract::Error), + /// Failed to resolve ENS name to an address. + #[error("Failed to resolve ENS name to an address: {0}")] + Resolve(alloy_contract::Error), +} + +/// ENS name or Ethereum Address. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NameOrAddress { + /// An ENS Name (format does not get checked) + Name(String), + /// An Ethereum Address + Address(Address), +} + +impl NameOrAddress { + /// Resolves the name to an Ethereum Address. + pub async fn resolve>( + &self, + provider: &P, + ) -> Result { + match self { + Self::Name(name) => provider.resolve_name(name).await, + Self::Address(addr) => Ok(*addr), + } + } +} + +impl From for NameOrAddress { + fn from(name: String) -> Self { + Self::Name(name) + } +} + +impl From<&String> for NameOrAddress { + fn from(name: &String) -> Self { + Self::Name(name.clone()) + } +} + +impl From

for NameOrAddress { + fn from(addr: Address) -> Self { + Self::Address(addr) + } +} + +impl FromStr for NameOrAddress { + type Err =
::Err; + + fn from_str(s: &str) -> Result { + match Address::from_str(s) { + Ok(addr) => Ok(Self::Address(addr)), + Err(err) => { + if s.contains('.') { + Ok(Self::Name(s.to_string())) + } else { + Err(err) + } + } + } + } +} + +/// Extension trait for ENS contract calls. +#[async_trait] +pub trait ProviderEnsExt> { + /// Returns the resolver for the specified node. The `&str` is only used for error messages. + async fn get_resolver( + &self, + node: B256, + error_name: &str, + ) -> Result, EnsError>; + + /// Performs a forward lookup of an ENS name to an address. + async fn resolve_name(&self, name: &str) -> Result { + let node = namehash(name); + let resolver = self.get_resolver(node, name).await?; + let addr = resolver + .addr(node) + .call() + .await + .map_err(EnsError::Resolve) + .inspect_err(|e| { + let _ = sh_eprintln!("{e:?}"); + })? + ._0; + Ok(addr) + } + + /// Performs a reverse lookup of an address to an ENS name. + async fn lookup_address(&self, address: &Address) -> Result { + let name = reverse_address(address); + let node = namehash(&name); + let resolver = self.get_resolver(node, &name).await?; + let name = resolver.name(node).call().await.map_err(EnsError::Lookup)?._0; + Ok(name) + } +} + +#[async_trait] +impl ProviderEnsExt for P +where + P: Provider, + N: Network, +{ + async fn get_resolver( + &self, + node: B256, + error_name: &str, + ) -> Result, EnsError> { + let registry = EnsRegistry::new(ENS_ADDRESS, self); + let address = registry.resolver(node).call().await.map_err(EnsError::Resolver)?._0; + if address == Address::ZERO { + return Err(EnsError::ResolverNotFound(error_name.to_string())); + } + Ok(EnsResolverInstance::new(address, self)) + } +} + +/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137) +pub fn namehash(name: &str) -> B256 { + if name.is_empty() { + return B256::ZERO + } + + // Remove the variation selector `U+FE0F` if present. + const VARIATION_SELECTOR: char = '\u{fe0f}'; + let name = if name.contains(VARIATION_SELECTOR) { + Cow::Owned(name.replace(VARIATION_SELECTOR, "")) + } else { + Cow::Borrowed(name) + }; + + // Generate the node starting from the right. + // This buffer is `[node @ [u8; 32], label_hash @ [u8; 32]]`. + let mut buffer = [0u8; 64]; + for label in name.rsplit('.') { + // node = keccak256([node, keccak256(label)]) + + // Hash the label. + let mut label_hasher = Keccak256::new(); + label_hasher.update(label.as_bytes()); + label_hasher.finalize_into(&mut buffer[32..]); + + // Hash both the node and the label hash, writing into the node. + let mut buffer_hasher = Keccak256::new(); + buffer_hasher.update(buffer.as_slice()); + buffer_hasher.finalize_into(&mut buffer[..32]); + } + buffer[..32].try_into().unwrap() +} + +/// Returns the reverse-registrar name of an address. +pub fn reverse_address(addr: &Address) -> String { + format!("{addr:x}.{ENS_REVERSE_REGISTRAR_DOMAIN}") +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_primitives::hex; + + fn assert_hex(hash: B256, val: &str) { + assert_eq!(hash.0[..], hex::decode(val).unwrap()[..]); + } + + #[test] + fn test_namehash() { + for (name, expected) in &[ + ("", "0x0000000000000000000000000000000000000000000000000000000000000000"), + ("eth", "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"), + ("foo.eth", "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"), + ("alice.eth", "0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec"), + ("ret↩️rn.eth", "0x3de5f4c02db61b221e7de7f1c40e29b6e2f07eb48d65bf7e304715cd9ed33b24"), + ] { + assert_hex(namehash(name), expected); + } + } + + #[test] + fn test_reverse_address() { + for (addr, expected) in [ + ( + "0x314159265dd8dbb310642f98f50c066173c1259b", + "314159265dd8dbb310642f98f50c066173c1259b.addr.reverse", + ), + ( + "0x28679A1a632125fbBf7A68d850E50623194A709E", + "28679a1a632125fbbf7a68d850e50623194a709e.addr.reverse", + ), + ] { + assert_eq!(reverse_address(&addr.parse().unwrap()), expected, "{addr}"); + } + } + + #[test] + fn test_invalid_address() { + for addr in [ + "0x314618", + "0x000000000000000000000000000000000000000", // 41 + "0x00000000000000000000000000000000000000000", // 43 + "0x28679A1a632125fbBf7A68d850E50623194A709E123", // 44 + ] { + assert!(NameOrAddress::from_str(addr).is_err()); + } + } +} diff --git a/crates/common/src/errors/fs.rs b/crates/common/src/errors/fs.rs index b929e7838140e..9ea84b9ce4444 100644 --- a/crates/common/src/errors/fs.rs +++ b/crates/common/src/errors/fs.rs @@ -3,36 +3,39 @@ use std::{ path::{Path, PathBuf}, }; -/// Various error variants for `std::fs` operations that serve as an addition to the io::Error which +#[allow(unused_imports)] +use std::fs::{self, File}; + +/// Various error variants for `fs` operations that serve as an addition to the io::Error which /// does not provide any information about the path. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum FsPathError { - /// Provides additional path context for [`std::fs::write`]. + /// Provides additional path context for [`fs::write`]. #[error("failed to write to {path:?}: {source}")] Write { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::read`]. + /// Provides additional path context for [`fs::read`]. #[error("failed to read from {path:?}: {source}")] Read { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::copy`]. + /// Provides additional path context for [`fs::copy`]. #[error("failed to copy from {from:?} to {to:?}: {source}")] Copy { source: io::Error, from: PathBuf, to: PathBuf }, - /// Provides additional path context for [`std::fs::read_link`]. + /// Provides additional path context for [`fs::read_link`]. #[error("failed to read from {path:?}: {source}")] ReadLink { source: io::Error, path: PathBuf }, /// Provides additional path context for [`File::create`]. #[error("failed to create file {path:?}: {source}")] CreateFile { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::remove_file`]. + /// Provides additional path context for [`fs::remove_file`]. #[error("failed to remove file {path:?}: {source}")] RemoveFile { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::create_dir`]. + /// Provides additional path context for [`fs::create_dir`]. #[error("failed to create dir {path:?}: {source}")] CreateDir { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::remove_dir`]. + /// Provides additional path context for [`fs::remove_dir`]. #[error("failed to remove dir {path:?}: {source}")] RemoveDir { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::File::open`]. + /// Provides additional path context for [`File::open`]. #[error("failed to open file {path:?}: {source}")] Open { source: io::Error, path: PathBuf }, /// Provides additional path context for the file whose contents should be parsed as JSON. @@ -44,49 +47,49 @@ pub enum FsPathError { } impl FsPathError { - /// Returns the complementary error variant for [`std::fs::write`]. + /// Returns the complementary error variant for [`fs::write`]. pub fn write(source: io::Error, path: impl Into) -> Self { - FsPathError::Write { source, path: path.into() } + Self::Write { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::read`]. + /// Returns the complementary error variant for [`fs::read`]. pub fn read(source: io::Error, path: impl Into) -> Self { - FsPathError::Read { source, path: path.into() } + Self::Read { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::copy`]. + /// Returns the complementary error variant for [`fs::copy`]. pub fn copy(source: io::Error, from: impl Into, to: impl Into) -> Self { - FsPathError::Copy { source, from: from.into(), to: to.into() } + Self::Copy { source, from: from.into(), to: to.into() } } - /// Returns the complementary error variant for [`std::fs::read_link`]. + /// Returns the complementary error variant for [`fs::read_link`]. pub fn read_link(source: io::Error, path: impl Into) -> Self { - FsPathError::ReadLink { source, path: path.into() } + Self::ReadLink { source, path: path.into() } } /// Returns the complementary error variant for [`File::create`]. pub fn create_file(source: io::Error, path: impl Into) -> Self { - FsPathError::CreateFile { source, path: path.into() } + Self::CreateFile { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::remove_file`]. + /// Returns the complementary error variant for [`fs::remove_file`]. pub fn remove_file(source: io::Error, path: impl Into) -> Self { - FsPathError::RemoveFile { source, path: path.into() } + Self::RemoveFile { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::create_dir`]. + /// Returns the complementary error variant for [`fs::create_dir`]. pub fn create_dir(source: io::Error, path: impl Into) -> Self { - FsPathError::CreateDir { source, path: path.into() } + Self::CreateDir { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::remove_dir`]. + /// Returns the complementary error variant for [`fs::remove_dir`]. pub fn remove_dir(source: io::Error, path: impl Into) -> Self { - FsPathError::RemoveDir { source, path: path.into() } + Self::RemoveDir { source, path: path.into() } } /// Returns the complementary error variant for [`File::open`]. pub fn open(source: io::Error, path: impl Into) -> Self { - FsPathError::Open { source, path: path.into() } + Self::Open { source, path: path.into() } } } diff --git a/crates/common/src/errors/mod.rs b/crates/common/src/errors/mod.rs index cfd9a307ea461..5ecd1dcc04cb0 100644 --- a/crates/common/src/errors/mod.rs +++ b/crates/common/src/errors/mod.rs @@ -5,3 +5,64 @@ pub use fs::FsPathError; mod artifacts; pub use artifacts::*; + +mod private { + use eyre::Chain; + use std::error::Error; + + pub trait ErrorChain { + fn chain(&self) -> Chain<'_>; + } + + impl ErrorChain for dyn Error + 'static { + fn chain(&self) -> Chain<'_> { + Chain::new(self) + } + } + + impl ErrorChain for eyre::Report { + fn chain(&self) -> Chain<'_> { + self.chain() + } + } +} + +/// Displays a chain of errors in a single line. +pub fn display_chain(error: &E) -> String { + dedup_chain(error).join("; ") +} + +/// Deduplicates a chain of errors. +pub fn dedup_chain(error: &E) -> Vec { + let mut causes = all_sources(error); + // Deduplicate the common pattern `msg1: msg2; msg2` -> `msg1: msg2`. + causes.dedup_by(|b, a| a.contains(b.as_str())); + causes +} + +fn all_sources(err: &E) -> Vec { + err.chain().map(|cause| cause.to_string().trim().to_string()).collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dedups_contained() { + #[derive(thiserror::Error, Debug)] + #[error("my error: {0}")] + struct A(#[from] B); + + #[derive(thiserror::Error, Debug)] + #[error("{0}")] + struct B(String); + + let ee = eyre::Report::from(A(B("hello".into()))); + assert_eq!(ee.chain().count(), 2, "{ee:?}"); + let full = all_sources(&ee).join("; "); + assert_eq!(full, "my error: hello; hello"); + let chained = display_chain(&ee); + assert_eq!(chained, "my error: hello"); + } +} diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index 23990af881363..493d638162f30 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -1,6 +1,7 @@ -//! cli arguments for configuring the evm settings -use clap::{ArgAction, Parser}; -use ethers_core::types::{Address, H256, U256}; +//! CLI arguments for configuring the EVM settings. + +use alloy_primitives::{map::HashMap, Address, B256, U256}; +use clap::Parser; use eyre::ContextCompat; use foundry_config::{ figment::{ @@ -12,12 +13,14 @@ use foundry_config::{ Chain, Config, }; use serde::Serialize; -use std::collections::HashMap; + +use crate::shell; /// Map keyed by breakpoints char to their location (contract address, pc) pub type Breakpoints = HashMap; /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. +/// /// All vars are opt-in, their default values are expected to be set by the /// [`foundry_config::Config`], and are always present ([`foundry_config::Config::default`]) /// @@ -38,27 +41,34 @@ pub type Breakpoints = HashMap; /// let opts = figment.extract::().unwrap(); /// # } /// ``` -#[derive(Debug, Clone, Default, Parser, Serialize)] -#[clap(next_help_heading = "EVM options", about = None, long_about = None)] // override doc +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "EVM options", about = None, long_about = None)] // override doc pub struct EvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, see --fork-block-number. - #[clap(long, short, visible_alias = "rpc-url", value_name = "URL")] + #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")] #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] pub fork_url: Option, /// Fetch state from a specific block number over a remote endpoint. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BLOCK")] + #[arg(long, requires = "fork_url", value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, + /// Number of retries. + /// + /// See --fork-url. + #[arg(long, requires = "fork_url", value_name = "RETRIES")] + #[serde(skip_serializing_if = "Option::is_none")] + pub fork_retries: Option, + /// Initial retry backoff on encountering errors. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BACKOFF")] + #[arg(long, requires = "fork_url", value_name = "BACKOFF")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retry_backoff: Option, @@ -69,58 +79,48 @@ pub struct EvmArgs { /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub no_storage_caching: bool, /// The initial balance of deployed test contracts. - #[clap(long, value_name = "BALANCE")] + #[arg(long, value_name = "BALANCE")] #[serde(skip_serializing_if = "Option::is_none")] pub initial_balance: Option, - /// The address which will be executing tests. - #[clap(long, value_name = "ADDRESS")] + /// The address which will be executing tests/scripts. + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub sender: Option
, /// Enable the FFI cheatcode. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub ffi: bool, - /// Verbosity of the EVM. - /// - /// Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). - /// - /// Verbosity levels: - /// - 2: Print logs for all tests - /// - 3: Print execution traces for failing tests - /// - 4: Print execution traces for all tests, and setup traces for failing tests - /// - 5: Print execution and setup traces for all tests - #[clap(long, short, verbatim_doc_comment, action = ArgAction::Count)] + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + #[arg(long)] #[serde(skip)] - pub verbosity: u8, + pub always_use_create_2_factory: bool, + + /// The CREATE2 deployer address to use, this will override the one in the config. + #[arg(long, value_name = "ADDRESS")] + #[serde(skip_serializing_if = "Option::is_none")] + pub create2_deployer: Option
, /// Sets the number of assumed available compute units per second for this provider /// /// default value: 330 /// - /// See also --fork-url and https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups - #[clap( - long, - requires = "fork_url", - alias = "cups", - value_name = "CUPS", - help_heading = "Fork config" - )] + /// See also --fork-url and + #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. /// - /// See also --fork-url and https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups - #[clap( + /// See also --fork-url and + #[arg( long, - requires = "fork_url", value_name = "NO_RATE_LIMITS", help_heading = "Fork config", visible_alias = "no-rate-limit" @@ -129,9 +129,21 @@ pub struct EvmArgs { pub no_rpc_rate_limit: bool, /// All ethereum environment related arguments - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub env: EnvArgs, + + /// Whether to enable isolation of calls. + /// In isolation mode all top-level calls are executed as a separate transaction in a separate + /// EVM context, enabling more precise gas accounting and transaction state changes. + #[arg(long)] + #[serde(skip)] + pub isolate: bool, + + /// Whether to enable Odyssey features. + #[arg(long, alias = "alphanet")] + #[serde(skip)] + pub odyssey: bool, } // Make this set of options a `figment::Provider` so that it can be merged into the `Config` @@ -145,15 +157,30 @@ impl Provider for EvmArgs { let error = InvalidType(value.to_actual(), "map".into()); let mut dict = value.into_dict().ok_or(error)?; - if self.verbosity > 0 { + if shell::verbosity() > 0 { // need to merge that manually otherwise `from_occurrences` does not work - dict.insert("verbosity".to_string(), self.verbosity.into()); + dict.insert("verbosity".to_string(), shell::verbosity().into()); } if self.ffi { dict.insert("ffi".to_string(), self.ffi.into()); } + if self.isolate { + dict.insert("isolate".to_string(), self.isolate.into()); + } + + if self.odyssey { + dict.insert("odyssey".to_string(), self.odyssey.into()); + } + + if self.always_use_create_2_factory { + dict.insert( + "always_use_create_2_factory".to_string(), + self.always_use_create_2_factory.into(), + ); + } + if self.no_storage_caching { dict.insert("no_storage_caching".to_string(), self.no_storage_caching.into()); } @@ -171,74 +198,77 @@ impl Provider for EvmArgs { } /// Configures the executor environment during tests. -#[derive(Debug, Clone, Default, Parser, Serialize)] -#[clap(next_help_heading = "Executor environment config")] +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Executor environment config")] pub struct EnvArgs { - /// The block gas limit. - #[clap(long, value_name = "GAS_LIMIT")] - #[serde(skip_serializing_if = "Option::is_none")] - pub gas_limit: Option, - /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). - #[clap(long, value_name = "CODE_SIZE")] + #[arg(long, value_name = "CODE_SIZE")] #[serde(skip_serializing_if = "Option::is_none")] pub code_size_limit: Option, - /// The chain ID. - #[clap(long, alias = "chain", value_name = "CHAIN_ID")] - #[serde(skip_serializing_if = "Option::is_none")] - pub chain_id: Option, + /// The chain name or EIP-155 chain ID. + #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")] + #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")] + pub chain: Option, /// The gas price. - #[clap(long, value_name = "GAS_PRICE")] + #[arg(long, value_name = "GAS_PRICE")] #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, /// The base fee in a block. - #[clap(long, visible_alias = "base-fee", value_name = "FEE")] + #[arg(long, visible_alias = "base-fee", value_name = "FEE")] #[serde(skip_serializing_if = "Option::is_none")] pub block_base_fee_per_gas: Option, /// The transaction origin. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub tx_origin: Option
, /// The coinbase of the block. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub block_coinbase: Option
, /// The timestamp of the block. - #[clap(long, value_name = "TIMESTAMP")] + #[arg(long, value_name = "TIMESTAMP")] #[serde(skip_serializing_if = "Option::is_none")] pub block_timestamp: Option, /// The block number. - #[clap(long, value_name = "BLOCK")] + #[arg(long, value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub block_number: Option, /// The block difficulty. - #[clap(long, value_name = "DIFFICULTY")] + #[arg(long, value_name = "DIFFICULTY")] #[serde(skip_serializing_if = "Option::is_none")] pub block_difficulty: Option, /// The block prevrandao value. NOTE: Before merge this field was mix_hash. - #[clap(long, value_name = "PREVRANDAO")] + #[arg(long, value_name = "PREVRANDAO")] #[serde(skip_serializing_if = "Option::is_none")] - pub block_prevrandao: Option, + pub block_prevrandao: Option, /// The block gas limit. - #[clap(long, value_name = "GAS_LIMIT")] + #[arg(long, visible_alias = "gas-limit", value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub block_gas_limit: Option, - /// The memory limit of the EVM in bytes (32 MB by default) - #[clap(long, value_name = "MEMORY_LIMIT")] + /// The memory limit per EVM execution in bytes. + /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. + /// + /// The default is 128MiB. + #[arg(long, value_name = "MEMORY_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub memory_limit: Option, + + /// Whether to disable the block gas limit checks. + #[arg(long, visible_alias = "no-gas-limit")] + #[serde(skip_serializing_if = "std::ops::Not::not")] + pub disable_block_gas_limit: bool, } impl EvmArgs { @@ -248,39 +278,58 @@ impl EvmArgs { } } +/// We have to serialize chain IDs and not names because when extracting an EVM `Env`, it expects +/// `chain_id` to be `u64`. +#[allow(clippy::trivially_copy_pass_by_ref)] +fn id(chain: &Option, s: S) -> Result { + if let Some(chain) = chain { + s.serialize_u64(chain.id()) + } else { + // skip_serializing_if = "Option::is_none" should prevent this branch from being taken + unreachable!() + } +} + #[cfg(test)] mod tests { use super::*; + use foundry_config::NamedChain; #[test] fn can_parse_chain_id() { let args = EvmArgs { - env: EnvArgs { - chain_id: Some(ethers_core::types::Chain::Mainnet.into()), - ..Default::default() - }, + env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() }, ..Default::default() }; - let config = Config::from_provider(Config::figment().merge(args)); - assert_eq!(config.chain_id, Some(ethers_core::types::Chain::Mainnet.into())); + let config = Config::from_provider(Config::figment().merge(args)).unwrap(); + assert_eq!(config.chain, Some(NamedChain::Mainnet.into())); let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "goerli"]); - assert_eq!(env.chain_id, Some(ethers_core::types::Chain::Goerli.into())); + assert_eq!(env.chain, Some(NamedChain::Goerli.into())); } #[test] fn test_memory_limit() { let args = EvmArgs { - env: EnvArgs { - chain_id: Some(ethers_core::types::Chain::Mainnet.into()), - ..Default::default() - }, + env: EnvArgs { chain: Some(NamedChain::Mainnet.into()), ..Default::default() }, ..Default::default() }; - let config = Config::from_provider(Config::figment().merge(args)); + let config = Config::from_provider(Config::figment().merge(args)).unwrap(); assert_eq!(config.memory_limit, Config::default().memory_limit); let env = EnvArgs::parse_from(["foundry-common", "--memory-limit", "100"]); assert_eq!(env.memory_limit, Some(100)); } + + #[test] + fn test_chain_id() { + let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "1"]); + assert_eq!(env.chain, Some(Chain::mainnet())); + + let env = EnvArgs::parse_from(["foundry-common", "--chain-id", "mainnet"]); + assert_eq!(env.chain, Some(Chain::mainnet())); + let args = EvmArgs { env, ..Default::default() }; + let config = Config::from_provider(Config::figment().merge(args)).unwrap(); + assert_eq!(config.chain, Some(Chain::mainnet())); + } } diff --git a/crates/common/src/fmt.rs b/crates/common/src/fmt.rs deleted file mode 100644 index 67eac0e6f49f2..0000000000000 --- a/crates/common/src/fmt.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Helpers for formatting ethereum types - -use crate::TransactionReceiptWithRevertReason; - -pub use foundry_macros::fmt::*; - -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() - } - } -} - -/// 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.cumulative_gas_used.pretty()) - } - "effectiveGasPrice" | "effective_gas_price" => { - Some(receipt.receipt.effective_gas_price.pretty()) - } - "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.pretty()), - "logs" => Some(receipt.receipt.logs.pretty()), - "logsBloom" | "logs_bloom" => Some(receipt.receipt.logs_bloom.pretty()), - "root" => Some(receipt.receipt.root.pretty()), - "status" => Some(receipt.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.transaction_type.pretty()), - "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), - _ => None, - } -} diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 25c20ab49f50a..3d061759c50cf 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -1,4 +1,5 @@ -//! Contains various `std::fs` wrapper functions that also contain the target path in their errors +//! Contains various `std::fs` wrapper functions that also contain the target path in their errors. + use crate::errors::FsPathError; use serde::{de::DeserializeOwned, Serialize}; use std::{ @@ -7,13 +8,15 @@ use std::{ path::{Component, Path, PathBuf}, }; -type Result = std::result::Result; +/// The [`fs`](self) result type. +pub type Result = std::result::Result; /// Wrapper for [`File::create`]. pub fn create_file(path: impl AsRef) -> Result { let path = path.as_ref(); File::create(path).map_err(|err| FsPathError::create_file(err, path)) } + /// Wrapper for [`std::fs::remove_file`]. pub fn remove_file(path: impl AsRef) -> Result<()> { let path = path.as_ref(); @@ -42,9 +45,8 @@ pub fn read_to_string(path: impl AsRef) -> Result { pub fn read_json_file(path: &Path) -> Result { // read the file into a byte array first // https://github.com/serde-rs/json/issues/160 - let bytes = read(path)?; - serde_json::from_slice(&bytes) - .map_err(|source| FsPathError::ReadJson { source, path: path.into() }) + let s = read_to_string(path)?; + serde_json::from_str(&s).map_err(|source| FsPathError::ReadJson { source, path: path.into() }) } /// Writes the object as a JSON object. @@ -56,6 +58,15 @@ pub fn write_json_file(path: &Path, obj: &T) -> Result<()> { writer.flush().map_err(|e| FsPathError::write(e, path)) } +/// Writes the object as a pretty JSON object. +pub fn write_pretty_json_file(path: &Path, obj: &T) -> Result<()> { + let file = create_file(path)?; + let mut writer = BufWriter::new(file); + serde_json::to_writer_pretty(&mut writer, obj) + .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?; + writer.flush().map_err(|e| FsPathError::write(e, path)) +} + /// Wrapper for `std::fs::write` pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<()> { let path = path.as_ref(); @@ -132,22 +143,29 @@ pub fn normalize_path(path: &Path) -> PathBuf { ret } -/// Returns all files with the given extension under the `root` dir -pub fn files_with_ext(root: impl AsRef, ext: &str) -> Vec { +/// Returns an iterator over all files with the given extension under the `root` dir. +pub fn files_with_ext<'a>(root: &Path, ext: &'a str) -> impl Iterator + 'a { walkdir::WalkDir::new(root) + .sort_by_file_name() .into_iter() .filter_map(walkdir::Result::ok) - .filter(|e| e.file_type().is_file()) - .filter(|e| e.path().extension().map(|e| e == ext).unwrap_or_default()) - .map(|e| e.path().into()) - .collect() + .filter(|e| e.file_type().is_file() && e.path().extension() == Some(ext.as_ref())) + .map(walkdir::DirEntry::into_path) } -/// Returns a list of absolute paths to all the json files under the root -pub fn json_files(root: impl AsRef) -> Vec { +/// Returns an iterator over all JSON files under the `root` dir. +pub fn json_files(root: &Path) -> impl Iterator { files_with_ext(root, "json") } +/// Canonicalize a path, returning an error if the path does not exist. +/// +/// Mainly useful to apply canonicalization to paths obtained from project files but still error +/// properly instead of flattening the errors. +pub fn canonicalize_path(path: impl AsRef) -> std::io::Result { + dunce::canonicalize(path) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/common/src/glob.rs b/crates/common/src/glob.rs deleted file mode 100644 index c6bff0176ee83..0000000000000 --- a/crates/common/src/glob.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Contains `globset::Glob` wrapper functions used for filtering -use std::{fmt, str::FromStr}; -/// A `globset::Glob` that creates its `globset::GlobMatcher` when its created, so it doesn't need -/// to be compiled when the filter functions `TestFilter` functions are called. -#[derive(Debug, Clone)] -pub struct GlobMatcher { - /// The parsed glob - pub glob: globset::Glob, - /// The compiled glob - pub matcher: globset::GlobMatcher, -} - -// === impl GlobMatcher === - -impl GlobMatcher { - /// Tests whether the given path matches this pattern or not. - /// - /// The glob `./test/*` won't match absolute paths like `test/Contract.sol`, which is common - /// format here, so we also handle this case here - pub fn is_match(&self, path: &str) -> bool { - let mut matches = self.matcher.is_match(path); - if !matches && !path.starts_with("./") && self.as_str().starts_with("./") { - matches = self.matcher.is_match(format!("./{path}")); - } - matches - } - - /// Returns the `Glob` string used to compile this matcher. - pub fn as_str(&self) -> &str { - self.glob.glob() - } -} - -impl fmt::Display for GlobMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.glob.fmt(f) - } -} - -impl FromStr for GlobMatcher { - type Err = globset::Error; - - fn from_str(s: &str) -> Result { - s.parse::().map(Into::into) - } -} - -impl From for GlobMatcher { - fn from(glob: globset::Glob) -> Self { - let matcher = glob.compile_matcher(); - Self { glob, matcher } - } -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_match_glob_paths() { - let matcher: GlobMatcher = "./test/*".parse().unwrap(); - assert!(matcher.is_match("test/Contract.sol")); - assert!(matcher.is_match("./test/Contract.sol")); - } -} diff --git a/crates/common/src/io/macros.rs b/crates/common/src/io/macros.rs new file mode 100644 index 0000000000000..10e7cca4a2e3a --- /dev/null +++ b/crates/common/src/io/macros.rs @@ -0,0 +1,198 @@ +/// Prints a message to [`stdout`][std::io::stdout] and reads a line from stdin into a String. +/// +/// Returns `Result`, so sometimes `T` must be explicitly specified, like in `str::parse`. +/// +/// # Examples +/// +/// ```no_run +/// use foundry_common::prompt; +/// +/// let response: String = prompt!("Would you like to continue? [y/N] ")?; +/// if !matches!(response.as_str(), "y" | "Y") { +/// return Ok(()) +/// } +/// # Ok::<(), Box>(()) +/// ``` +#[macro_export] +macro_rules! prompt { + () => { + $crate::stdin::parse_line() + }; + + ($($tt:tt)+) => {{ + let _ = $crate::sh_print!($($tt)+); + match ::std::io::Write::flush(&mut ::std::io::stdout()) { + ::core::result::Result::Ok(()) => $crate::prompt!(), + ::core::result::Result::Err(e) => ::core::result::Result::Err(::eyre::eyre!("Could not flush stdout: {e}")) + } + }}; +} + +/// Prints a formatted error to stderr. +/// +/// **Note**: will log regardless of the verbosity level. +#[macro_export] +macro_rules! sh_err { + ($($args:tt)*) => { + $crate::__sh_dispatch!(error $($args)*) + }; +} + +/// Prints a formatted warning to stderr. +/// +/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. +#[macro_export] +macro_rules! sh_warn { + ($($args:tt)*) => { + $crate::__sh_dispatch!(warn $($args)*) + }; +} + +/// Prints a raw formatted message to stdout. +/// +/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. +#[macro_export] +macro_rules! sh_print { + ($($args:tt)*) => { + $crate::__sh_dispatch!(print_out $($args)*) + }; + + ($shell:expr, $($args:tt)*) => { + $crate::__sh_dispatch!(print_out $shell, $($args)*) + }; +} + +/// Prints a raw formatted message to stderr. +/// +/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. +#[macro_export] +macro_rules! sh_eprint { + ($($args:tt)*) => { + $crate::__sh_dispatch!(print_err $($args)*) + }; + + ($shell:expr, $($args:tt)*) => { + $crate::__sh_dispatch!(print_err $shell, $($args)*) + }; +} + +/// Prints a raw formatted message to stdout, with a trailing newline. +/// +/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. +#[macro_export] +macro_rules! sh_println { + () => { + $crate::sh_print!("\n") + }; + + ($fmt:literal $($args:tt)*) => { + $crate::sh_print!("{}\n", ::core::format_args!($fmt $($args)*)) + }; + + ($shell:expr $(,)?) => { + $crate::sh_print!($shell, "\n").expect("failed to write newline") + }; + + ($shell:expr, $($args:tt)*) => { + $crate::sh_print!($shell, "{}\n", ::core::format_args!($($args)*)) + }; + + ($($args:tt)*) => { + $crate::sh_print!("{}\n", ::core::format_args!($($args)*)) + }; +} + +/// Prints a raw formatted message to stderr, with a trailing newline. +/// +/// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. +#[macro_export] +macro_rules! sh_eprintln { + () => { + $crate::sh_eprint!("\n") + }; + + ($fmt:literal $($args:tt)*) => { + $crate::sh_eprint!("{}\n", ::core::format_args!($fmt $($args)*)) + }; + + ($shell:expr $(,)?) => { + $crate::sh_eprint!($shell, "\n") + }; + + ($shell:expr, $($args:tt)*) => { + $crate::sh_eprint!($shell, "{}\n", ::core::format_args!($($args)*)) + }; + + ($($args:tt)*) => { + $crate::sh_eprint!("{}\n", ::core::format_args!($($args)*)) + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __sh_dispatch { + ($f:ident $fmt:literal $($args:tt)*) => { + $crate::__sh_dispatch!(@impl $f &mut *$crate::Shell::get(), $fmt $($args)*) + }; + + ($f:ident $shell:expr, $($args:tt)*) => { + $crate::__sh_dispatch!(@impl $f $shell, $($args)*) + }; + + ($f:ident $($args:tt)*) => { + $crate::__sh_dispatch!(@impl $f &mut *$crate::Shell::get(), $($args)*) + }; + + // Ensure that the global shell lock is held for as little time as possible. + // Also avoids deadlocks in case of nested calls. + (@impl $f:ident $shell:expr, $($args:tt)*) => { + match ::core::format_args!($($args)*) { + fmt => $crate::Shell::$f($shell, fmt), + } + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn macros() -> eyre::Result<()> { + sh_err!("err")?; + sh_err!("err {}", "arg")?; + + sh_warn!("warn")?; + sh_warn!("warn {}", "arg")?; + + sh_print!("print -")?; + sh_print!("print {} -", "arg")?; + + sh_println!()?; + sh_println!("println")?; + sh_println!("println {}", "arg")?; + + sh_eprint!("eprint -")?; + sh_eprint!("eprint {} -", "arg")?; + + sh_eprintln!()?; + sh_eprintln!("eprintln")?; + sh_eprintln!("eprintln {}", "arg")?; + + sh_println!("{:?}", { + sh_println!("hi")?; + "nested" + })?; + + Ok(()) + } + + #[test] + fn macros_with_shell() -> eyre::Result<()> { + let shell = &mut crate::Shell::new(); + sh_eprintln!(shell)?; + sh_eprintln!(shell,)?; + sh_eprintln!(shell, "shelled eprintln")?; + sh_eprintln!(shell, "shelled eprintln {}", "arg")?; + sh_eprintln!(&mut crate::Shell::new(), "shelled eprintln {}", "arg")?; + + Ok(()) + } +} diff --git a/crates/common/src/io/mod.rs b/crates/common/src/io/mod.rs new file mode 100644 index 0000000000000..f62fd034617bf --- /dev/null +++ b/crates/common/src/io/mod.rs @@ -0,0 +1,11 @@ +//! Utilities for working with standard input, output, and error. + +#[macro_use] +mod macros; + +pub mod shell; +pub mod stdin; +pub mod style; + +#[doc(no_inline)] +pub use shell::Shell; diff --git a/crates/common/src/io/shell.rs b/crates/common/src/io/shell.rs new file mode 100644 index 0000000000000..19b3ae07e7900 --- /dev/null +++ b/crates/common/src/io/shell.rs @@ -0,0 +1,555 @@ +//! Utility functions for writing to [`stdout`](std::io::stdout) and [`stderr`](std::io::stderr). +//! +//! Originally from [cargo](https://github.com/rust-lang/cargo/blob/35814255a1dbaeca9219fae81d37a8190050092c/src/cargo/core/shell.rs). + +use super::style::*; +use anstream::AutoStream; +use anstyle::Style; +use clap::ValueEnum; +use eyre::Result; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + io::{prelude::*, IsTerminal}, + ops::DerefMut, + sync::{ + atomic::{AtomicBool, Ordering}, + Mutex, OnceLock, PoisonError, + }, +}; + +/// Returns the current color choice. +pub fn color_choice() -> ColorChoice { + Shell::get().color_choice() +} + +/// Returns the currently set verbosity level. +pub fn verbosity() -> Verbosity { + Shell::get().verbosity() +} + +/// Set the verbosity level. +pub fn set_verbosity(verbosity: Verbosity) { + Shell::get().set_verbosity(verbosity); +} + +/// Returns whether the output mode is [`OutputMode::Quiet`]. +pub fn is_quiet() -> bool { + Shell::get().output_mode().is_quiet() +} + +/// Returns whether the output format is [`OutputFormat::Json`]. +pub fn is_json() -> bool { + Shell::get().is_json() +} + +/// The global shell instance. +static GLOBAL_SHELL: OnceLock> = OnceLock::new(); + +/// Terminal width. +pub enum TtyWidth { + /// Not a terminal, or could not determine size. + NoTty, + /// A known width. + Known(usize), + /// A guess at the width. + Guess(usize), +} + +impl TtyWidth { + /// Returns the width of the terminal from the environment, if known. + pub fn get() -> Self { + // use stderr + #[cfg(unix)] + let opt = terminal_size::terminal_size_of(std::io::stderr()); + #[cfg(not(unix))] + let opt = terminal_size::terminal_size(); + match opt { + Some((w, _)) => Self::Known(w.0 as usize), + None => Self::NoTty, + } + } + + /// Returns the width used by progress bars for the tty. + pub fn progress_max_width(&self) -> Option { + match *self { + Self::NoTty => None, + Self::Known(width) | Self::Guess(width) => Some(width), + } + } +} + +#[derive(Debug, Default, Clone, Copy, PartialEq)] +/// The requested output mode. +pub enum OutputMode { + /// Default output + #[default] + Normal, + /// No output + Quiet, +} + +impl OutputMode { + /// Returns true if the output mode is `Normal`. + #[inline] + pub fn is_normal(self) -> bool { + self == Self::Normal + } + + /// Returns true if the output mode is `Quiet`. + #[inline] + pub fn is_quiet(self) -> bool { + self == Self::Quiet + } +} + +/// The requested output format. +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub enum OutputFormat { + /// Plain text output. + #[default] + Text, + /// JSON output. + Json, +} + +impl OutputFormat { + /// Returns true if the output format is `Text`. + #[inline] + pub fn is_text(self) -> bool { + self == Self::Text + } + + /// Returns true if the output format is `Json`. + #[inline] + pub fn is_json(self) -> bool { + self == Self::Json + } +} + +/// The verbosity level. +pub type Verbosity = u8; + +/// An abstraction around console output that remembers preferences for output +/// verbosity and color. +pub struct Shell { + /// Wrapper around stdout/stderr. This helps with supporting sending + /// output to a memory buffer which is useful for tests. + output: ShellOut, + + /// The format to use for message output. + output_format: OutputFormat, + + /// The verbosity mode to use for message output. + output_mode: OutputMode, + + /// The verbosity level to use for message output. + verbosity: Verbosity, + + /// Flag that indicates the current line needs to be cleared before + /// printing. Used when a progress bar is currently displayed. + needs_clear: AtomicBool, +} + +impl fmt::Debug for Shell { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut s = f.debug_struct("Shell"); + s.field("output_format", &self.output_format); + s.field("output_mode", &self.output_mode); + s.field("verbosity", &self.verbosity); + if let ShellOut::Stream { color_choice, .. } = self.output { + s.field("color_choice", &color_choice); + } + s.finish() + } +} + +/// A `Write`able object, either with or without color support. +enum ShellOut { + /// Color-enabled stdio, with information on whether color should be used. + Stream { + stdout: AutoStream, + stderr: AutoStream, + stderr_tty: bool, + color_choice: ColorChoice, + }, + /// A write object that ignores all output. + Empty(std::io::Empty), +} + +/// Whether messages should use color output. +#[derive(Debug, Default, PartialEq, Clone, Copy, Serialize, Deserialize, ValueEnum)] +pub enum ColorChoice { + /// Intelligently guess whether to use color output (default). + #[default] + Auto, + /// Force color output. + Always, + /// Force disable color output. + Never, +} + +impl Default for Shell { + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl Shell { + /// Creates a new shell (color choice and verbosity), defaulting to 'auto' color and verbose + /// output. + #[inline] + pub fn new() -> Self { + Self::new_with( + OutputFormat::Text, + OutputMode::Normal, + ColorChoice::Auto, + Verbosity::default(), + ) + } + + /// Creates a new shell with the given color choice and verbosity. + #[inline] + pub fn new_with( + format: OutputFormat, + mode: OutputMode, + color: ColorChoice, + verbosity: Verbosity, + ) -> Self { + Self { + output: ShellOut::Stream { + stdout: AutoStream::new(std::io::stdout(), color.to_anstream_color_choice()), + stderr: AutoStream::new(std::io::stderr(), color.to_anstream_color_choice()), + color_choice: color, + stderr_tty: std::io::stderr().is_terminal(), + }, + output_format: format, + output_mode: mode, + verbosity, + needs_clear: AtomicBool::new(false), + } + } + + /// Creates a shell that ignores all output. + #[inline] + pub fn empty() -> Self { + Self { + output: ShellOut::Empty(std::io::empty()), + output_format: OutputFormat::Text, + output_mode: OutputMode::Quiet, + verbosity: 0, + needs_clear: AtomicBool::new(false), + } + } + + /// Acquire a lock to the global shell. + /// + /// Initializes it with the default values if it has not been set yet. + pub fn get() -> impl DerefMut + 'static { + GLOBAL_SHELL.get_or_init(Default::default).lock().unwrap_or_else(PoisonError::into_inner) + } + + /// Set the global shell. + /// + /// # Panics + /// + /// Panics if the global shell has already been set. + #[track_caller] + pub fn set(self) { + GLOBAL_SHELL + .set(Mutex::new(self)) + .unwrap_or_else(|_| panic!("attempted to set global shell twice")) + } + + /// Sets whether the next print should clear the current line and returns the previous value. + #[inline] + pub fn set_needs_clear(&self, needs_clear: bool) -> bool { + self.needs_clear.swap(needs_clear, Ordering::Relaxed) + } + + /// Returns `true` if the output format is JSON. + pub fn is_json(&self) -> bool { + self.output_format.is_json() + } + + /// Returns `true` if the verbosity level is `Quiet`. + pub fn is_quiet(&self) -> bool { + self.output_mode.is_quiet() + } + + /// Returns `true` if the `needs_clear` flag is set. + #[inline] + pub fn needs_clear(&self) -> bool { + self.needs_clear.load(Ordering::Relaxed) + } + + /// Returns `true` if the `needs_clear` flag is unset. + #[inline] + pub fn is_cleared(&self) -> bool { + !self.needs_clear() + } + + /// Returns the width of the terminal in spaces, if any. + #[inline] + pub fn err_width(&self) -> TtyWidth { + match self.output { + ShellOut::Stream { stderr_tty: true, .. } => TtyWidth::get(), + _ => TtyWidth::NoTty, + } + } + + /// Gets the output format of the shell. + #[inline] + pub fn output_format(&self) -> OutputFormat { + self.output_format + } + + /// Gets the output mode of the shell. + #[inline] + pub fn output_mode(&self) -> OutputMode { + self.output_mode + } + + /// Gets the verbosity of the shell when [`OutputMode::Normal`] is set. + #[inline] + pub fn verbosity(&self) -> Verbosity { + self.verbosity + } + + /// Sets the verbosity level. + pub fn set_verbosity(&mut self, verbosity: Verbosity) { + self.verbosity = verbosity; + } + + /// Gets the current color choice. + /// + /// If we are not using a color stream, this will always return `Never`, even if the color + /// choice has been set to something else. + #[inline] + pub fn color_choice(&self) -> ColorChoice { + match self.output { + ShellOut::Stream { color_choice, .. } => color_choice, + ShellOut::Empty(_) => ColorChoice::Never, + } + } + + /// Returns `true` if stderr is a tty. + #[inline] + pub fn is_err_tty(&self) -> bool { + match self.output { + ShellOut::Stream { stderr_tty, .. } => stderr_tty, + ShellOut::Empty(_) => false, + } + } + + /// Whether `stderr` supports color. + #[inline] + pub fn err_supports_color(&self) -> bool { + match &self.output { + ShellOut::Stream { stderr, .. } => supports_color(stderr.current_choice()), + ShellOut::Empty(_) => false, + } + } + + /// Whether `stdout` supports color. + #[inline] + pub fn out_supports_color(&self) -> bool { + match &self.output { + ShellOut::Stream { stdout, .. } => supports_color(stdout.current_choice()), + ShellOut::Empty(_) => false, + } + } + + /// Gets a reference to the underlying stdout writer. + pub fn out(&mut self) -> &mut dyn Write { + self.maybe_err_erase_line(); + self.output.stdout() + } + + /// Gets a reference to the underlying stderr writer. + pub fn err(&mut self) -> &mut dyn Write { + self.maybe_err_erase_line(); + self.output.stderr() + } + + /// Erase from cursor to end of line if needed. + pub fn maybe_err_erase_line(&mut self) { + if self.err_supports_color() && self.set_needs_clear(false) { + // This is the "EL - Erase in Line" sequence. It clears from the cursor + // to the end of line. + // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences + let _ = self.output.stderr().write_all(b"\x1B[K"); + } + } + + /// Prints a red 'error' message. Use the [`sh_err!`] macro instead. + /// This will render a message in [ERROR] style with a bold `Error: ` prefix. + /// + /// **Note**: will log regardless of the verbosity level. + pub fn error(&mut self, message: impl fmt::Display) -> Result<()> { + self.maybe_err_erase_line(); + self.output.message_stderr(&"Error", &ERROR, Some(&message), false) + } + + /// Prints an amber 'warning' message. Use the [`sh_warn!`] macro instead. + /// This will render a message in [WARN] style with a bold `Warning: `prefix. + /// + /// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. + pub fn warn(&mut self, message: impl fmt::Display) -> Result<()> { + match self.output_mode { + OutputMode::Quiet => Ok(()), + _ => self.print(&"Warning", &WARN, Some(&message), false), + } + } + + /// Write a styled fragment. + /// + /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. + pub fn write_stdout(&mut self, fragment: impl fmt::Display, color: &Style) -> Result<()> { + self.output.write_stdout(fragment, color) + } + + /// Write a styled fragment with the default color. Use the [`sh_print!`] macro instead. + /// + /// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. + pub fn print_out(&mut self, fragment: impl fmt::Display) -> Result<()> { + match self.output_mode { + OutputMode::Quiet => Ok(()), + _ => self.write_stdout(fragment, &Style::new()), + } + } + + /// Write a styled fragment + /// + /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. + pub fn write_stderr(&mut self, fragment: impl fmt::Display, color: &Style) -> Result<()> { + self.output.write_stderr(fragment, color) + } + + /// Write a styled fragment with the default color. Use the [`sh_eprint!`] macro instead. + /// + /// **Note**: if `verbosity` is set to `Quiet`, this is a no-op. + pub fn print_err(&mut self, fragment: impl fmt::Display) -> Result<()> { + match self.output_mode { + OutputMode::Quiet => Ok(()), + _ => self.write_stderr(fragment, &Style::new()), + } + } + + /// Prints a message, where the status will have `color` color, and can be justified. The + /// messages follows without color. + fn print( + &mut self, + status: &dyn fmt::Display, + style: &Style, + message: Option<&dyn fmt::Display>, + justified: bool, + ) -> Result<()> { + match self.output_mode { + OutputMode::Quiet => Ok(()), + _ => { + self.maybe_err_erase_line(); + self.output.message_stderr(status, style, message, justified) + } + } + } +} + +impl ShellOut { + /// Prints out a message with a status to stderr. The status comes first, and is bold plus the + /// given color. The status can be justified, in which case the max width that will right + /// align is 12 chars. + fn message_stderr( + &mut self, + status: &dyn fmt::Display, + style: &Style, + message: Option<&dyn fmt::Display>, + justified: bool, + ) -> Result<()> { + let buffer = Self::format_message(status, message, style, justified)?; + self.stderr().write_all(&buffer)?; + Ok(()) + } + + /// Write a styled fragment + fn write_stdout(&mut self, fragment: impl fmt::Display, style: &Style) -> Result<()> { + let mut buffer = Vec::new(); + write!(buffer, "{style}{fragment}{style:#}")?; + self.stdout().write_all(&buffer)?; + Ok(()) + } + + /// Write a styled fragment + fn write_stderr(&mut self, fragment: impl fmt::Display, style: &Style) -> Result<()> { + let mut buffer = Vec::new(); + write!(buffer, "{style}{fragment}{style:#}")?; + self.stderr().write_all(&buffer)?; + Ok(()) + } + + /// Gets stdout as a [`io::Write`](Write) trait object. + #[inline] + fn stdout(&mut self) -> &mut dyn Write { + match self { + Self::Stream { stdout, .. } => stdout, + Self::Empty(e) => e, + } + } + + /// Gets stderr as a [`io::Write`](Write) trait object. + #[inline] + fn stderr(&mut self) -> &mut dyn Write { + match self { + Self::Stream { stderr, .. } => stderr, + Self::Empty(e) => e, + } + } + + /// Formats a message with a status and optional message. + fn format_message( + status: &dyn fmt::Display, + message: Option<&dyn fmt::Display>, + style: &Style, + justified: bool, + ) -> Result> { + let bold = anstyle::Style::new().bold(); + + let mut buffer = Vec::new(); + if justified { + write!(buffer, "{style}{status:>12}{style:#}")?; + } else { + write!(buffer, "{style}{status}{style:#}{bold}:{bold:#}")?; + } + match message { + Some(message) => { + writeln!(buffer, " {message}")?; + } + None => write!(buffer, " ")?, + } + + Ok(buffer) + } +} + +impl ColorChoice { + /// Converts our color choice to [`anstream`]'s version. + #[inline] + fn to_anstream_color_choice(self) -> anstream::ColorChoice { + match self { + Self::Always => anstream::ColorChoice::Always, + Self::Never => anstream::ColorChoice::Never, + Self::Auto => anstream::ColorChoice::Auto, + } + } +} + +#[inline] +fn supports_color(choice: anstream::ColorChoice) -> bool { + match choice { + anstream::ColorChoice::Always | + anstream::ColorChoice::AlwaysAnsi | + anstream::ColorChoice::Auto => true, + anstream::ColorChoice::Never => false, + } +} diff --git a/crates/cli/src/stdin.rs b/crates/common/src/io/stdin.rs similarity index 76% rename from crates/cli/src/stdin.rs rename to crates/common/src/io/stdin.rs index 8242cc8057724..17b40a2cff1fe 100644 --- a/crates/cli/src/stdin.rs +++ b/crates/common/src/io/stdin.rs @@ -7,37 +7,6 @@ use std::{ str::FromStr, }; -/// Prints a message to [`stdout`][io::stdout] and [reads a line from stdin into a String](read). -/// -/// Returns `Result`, so sometimes `T` must be explicitly specified, like in `str::parse`. -/// -/// # Examples -/// -/// ```no_run -/// # use foundry_cli::prompt; -/// let response: String = prompt!("Would you like to continue? [y/N] ")?; -/// if !matches!(response.as_str(), "y" | "Y") { -/// return Ok(()) -/// } -/// # Ok::<(), Box>(()) -/// ``` -#[macro_export] -macro_rules! prompt { - () => { - $crate::stdin::parse_line() - }; - - ($($tt:tt)+) => { - { - ::std::print!($($tt)+); - match ::std::io::Write::flush(&mut ::std::io::stdout()) { - ::core::result::Result::Ok(_) => $crate::prompt!(), - ::core::result::Result::Err(e) => ::core::result::Result::Err(::eyre::eyre!("Could not flush stdout: {}", e)) - } - } - }; -} - /// Unwraps the given `Option` or [reads stdin into a String](read) and parses it as `T`. pub fn unwrap(value: Option, read_line: bool) -> Result where @@ -50,6 +19,7 @@ where } } +/// Shortcut for `(unwrap(a), unwrap(b))`. #[inline] pub fn unwrap2(a: Option, b: Option) -> Result<(A, B)> where diff --git a/crates/common/src/io/style.rs b/crates/common/src/io/style.rs new file mode 100644 index 0000000000000..6103b2d37d105 --- /dev/null +++ b/crates/common/src/io/style.rs @@ -0,0 +1,5 @@ +#![allow(missing_docs)] +use anstyle::*; + +pub const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD); +pub const WARN: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD); diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index cb045a25bb279..88643a67c8fd4 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,27 +1,44 @@ +//! # foundry-common +//! //! Common utilities for building and using foundry's tools. -#![warn(missing_docs, unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[allow(unused_extern_crates)] // Used by `ConsoleFmt`. +extern crate self as foundry_common; + +#[macro_use] +extern crate tracing; + +#[macro_use] +pub mod io; + +pub use foundry_common_fmt as fmt; pub mod abi; pub mod calc; -pub mod clap_helpers; pub mod compile; pub mod constants; pub mod contracts; +pub mod ens; pub mod errors; pub mod evm; -pub mod fmt; pub mod fs; -pub mod glob; pub mod provider; +pub mod reports; +pub mod retry; pub mod selectors; -pub mod shell; +pub mod serde_helpers; pub mod term; pub mod traits; pub mod transactions; +mod utils; +pub mod version; pub use constants::*; pub use contracts::*; -pub use provider::*; +pub use io::{shell, stdin, Shell}; pub use traits::*; pub use transactions::*; +pub use utils::*; diff --git a/crates/common/src/provider.rs b/crates/common/src/provider.rs deleted file mode 100644 index f0322722cf6bf..0000000000000 --- a/crates/common/src/provider.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! Commonly used helpers to construct `Provider`s - -use crate::{ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT}; -use ethers_core::types::{Chain, U256}; -use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon}; -use ethers_providers::{ - is_local_endpoint, Http, HttpRateLimitRetryPolicy, Middleware, Provider, RetryClient, - RetryClientBuilder, DEFAULT_LOCAL_POLL_INTERVAL, -}; -use eyre::WrapErr; -use reqwest::{IntoUrl, Url}; -use std::{borrow::Cow, time::Duration}; - -/// Helper type alias for a retry provider -pub type RetryProvider = Provider>; - -/// Helper type alias for a rpc url -pub type RpcUrl = String; - -/// Same as `try_get_http_provider` -/// -/// # Panics -/// -/// If invalid URL -/// -/// # Example -/// -/// ``` -/// use foundry_common::get_http_provider; -/// # fn f() { -/// let retry_provider = get_http_provider("http://localhost:8545"); -/// # } -/// ``` -pub fn get_http_provider(builder: impl Into) -> RetryProvider { - try_get_http_provider(builder).unwrap() -} - -/// Gives out a provider with a `100ms` interval poll if it's a localhost URL (most likely an anvil -/// or other dev node) and with the default, `7s` if otherwise. -pub fn try_get_http_provider(builder: impl Into) -> eyre::Result { - builder.into().build() -} - -/// Helper type to construct a `RetryProvider` -#[derive(Debug)] -pub struct ProviderBuilder { - // Note: this is a result, so we can easily chain builder calls - url: eyre::Result, - chain: Chain, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, -} - -// === impl ProviderBuilder === - -impl ProviderBuilder { - /// Creates a new builder instance - pub fn new(url: impl IntoUrl) -> Self { - let url_str = url.as_str(); - if url_str.starts_with("localhost:") { - // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http - // prefix - return Self::new(format!("http://{url_str}")) - } - let err = format!("Invalid provider url: {url_str}"); - Self { - url: url.into_url().wrap_err(err), - chain: Chain::Mainnet, - max_retry: 100, - timeout_retry: 5, - initial_backoff: 100, - timeout: REQUEST_TIMEOUT, - // alchemy max cpus - compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, - } - } - - /// Enables a request timeout. - /// - /// The timeout is applied from when the request starts connecting until the - /// response body has finished. - /// - /// Default is no timeout. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } - - /// Sets the chain of the node the provider will connect to - pub fn chain(mut self, chain: impl Into) -> Self { - if let foundry_config::Chain::Named(chain) = chain.into() { - self.chain = chain; - } - self - } - - /// How often to retry a failed request - pub fn max_retry(mut self, max_retry: u32) -> Self { - self.max_retry = max_retry; - 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; - self - } - - /// Sets the number of assumed available compute units per second - /// - /// See also, - pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { - self.compute_units_per_second = compute_units_per_second; - self - } - - /// Sets the number of assumed available compute units per second - /// - /// See also, - pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { - if let Some(cups) = compute_units_per_second { - self.compute_units_per_second = cups; - } - self - } - - /// Sets aggressive `max_retry` and `initial_backoff` values - /// - /// This is only recommend for local dev nodes - pub fn aggressive(self) -> Self { - self.max_retry(100).initial_backoff(100) - } - - /// Same as [`Self:build()`] but also retrieves the `chainId` in order to derive an appropriate - /// interval - pub async fn connect(self) -> eyre::Result { - let mut provider = self.build()?; - if let Some(blocktime) = provider.get_chainid().await.ok().and_then(|id| { - Chain::try_from(id).ok().and_then(|chain| chain.average_blocktime_hint()) - }) { - provider = provider.interval(blocktime / 2); - } - Ok(provider) - } - - /// Constructs the `RetryProvider` taking all configs into account - pub fn build(self) -> eyre::Result { - let ProviderBuilder { - url, - chain, - max_retry, - timeout_retry, - initial_backoff, - timeout, - compute_units_per_second, - } = self; - let url = url?; - - let client = reqwest::Client::builder().timeout(timeout).build()?; - let is_local = is_local_endpoint(url.as_str()); - - let provider = Http::new_with_client(url, client); - - #[allow(clippy::box_default)] - let mut provider = Provider::new( - RetryClientBuilder::default() - .initial_backoff(Duration::from_millis(initial_backoff)) - .rate_limit_retries(max_retry) - .timeout_retries(timeout_retry) - .compute_units_per_second(compute_units_per_second) - .build(provider, Box::new(HttpRateLimitRetryPolicy)), - ); - - if is_local { - provider = provider.interval(DEFAULT_LOCAL_POLL_INTERVAL); - } else if let Some(blocktime) = chain.average_blocktime_hint() { - provider = provider.interval(blocktime / 2); - } - Ok(provider) - } -} - -impl<'a> From<&'a str> for ProviderBuilder { - fn from(url: &'a str) -> Self { - Self::new(url) - } -} - -impl<'a> From<&'a String> for ProviderBuilder { - fn from(url: &'a String) -> Self { - url.as_str().into() - } -} - -impl From for ProviderBuilder { - fn from(url: String) -> Self { - url.as_str().into() - } -} - -impl<'a> From> for ProviderBuilder { - fn from(url: Cow<'a, str>) -> Self { - url.as_ref().into() - } -} - -/// Estimates EIP1559 fees depending on the chain -/// -/// Uses custom gas oracles for -/// - polygon -/// -/// Fallback is the default [`Provider::estimate_eip1559_fees`] implementation -pub async fn estimate_eip1559_fees( - provider: &M, - chain: Option, -) -> eyre::Result<(U256, U256)> -where - M::Error: 'static, -{ - let chain = if let Some(chain) = chain { - chain - } else { - provider.get_chainid().await.wrap_err("Failed to get chain id")?.as_u64() - }; - - if let Ok(chain) = Chain::try_from(chain) { - // handle chains that deviate from `eth_feeHistory` and have their own oracle - match chain { - Chain::Polygon | Chain::PolygonMumbai => { - let estimator = Polygon::new(chain)?.category(GasCategory::Standard); - return Ok(estimator.estimate_eip1559_fees().await?) - } - _ => {} - } - } - provider.estimate_eip1559_fees(None).await.wrap_err("Failed fetch EIP1559 fees") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_auto_correct_missing_prefix() { - let builder = ProviderBuilder::new("localhost:8545"); - assert!(builder.url.is_ok()); - - let url = builder.url.unwrap(); - assert_eq!(url, Url::parse("http://localhost:8545").unwrap()); - } -} diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs new file mode 100644 index 0000000000000..6c2d561ba0c1f --- /dev/null +++ b/crates/common/src/provider/mod.rs @@ -0,0 +1,363 @@ +//! Provider-related instantiation and usage utilities. + +pub mod runtime_transport; + +use crate::{ + provider::runtime_transport::RuntimeTransportBuilder, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, +}; +use alloy_provider::{ + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + network::{AnyNetwork, EthereumWallet}, + Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, +}; +use alloy_rpc_client::ClientBuilder; +use alloy_transport::{layers::RetryBackoffLayer, utils::guess_local_url}; +use eyre::{Result, WrapErr}; +use foundry_config::NamedChain; +use reqwest::Url; +use std::{ + net::SocketAddr, + path::{Path, PathBuf}, + str::FromStr, + time::Duration, +}; +use url::ParseError; + +/// The assumed block time for unknown chains. +/// We assume that these are chains have a faster block time. +const DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME: Duration = Duration::from_secs(3); + +/// The factor to scale the block time by to get the poll interval. +const POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR: f32 = 0.6; + +/// Helper type alias for a retry provider +pub type RetryProvider = RootProvider; + +/// Helper type alias for a retry provider with a signer +pub type RetryProviderWithSigner = FillProvider< + JoinFill< + JoinFill< + Identity, + JoinFill< + GasFiller, + JoinFill< + alloy_provider::fillers::BlobGasFiller, + JoinFill, + >, + >, + >, + WalletFiller, + >, + RootProvider, + N, +>; + +/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely +/// an anvil or other dev node) and with the default, or 7 second otherwise. +/// +/// See [`try_get_http_provider`] for more details. +/// +/// # Panics +/// +/// Panics if the URL is invalid. +/// +/// # Examples +/// +/// ``` +/// use foundry_common::provider::get_http_provider; +/// +/// let retry_provider = get_http_provider("http://localhost:8545"); +/// ``` +#[inline] +#[track_caller] +pub fn get_http_provider(builder: impl AsRef) -> RetryProvider { + try_get_http_provider(builder).unwrap() +} + +/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely +/// an anvil or other dev node) and with the default, or 7 second otherwise. +#[inline] +pub fn try_get_http_provider(builder: impl AsRef) -> Result { + ProviderBuilder::new(builder.as_ref()).build() +} + +/// Helper type to construct a `RetryProvider` +#[derive(Debug)] +pub struct ProviderBuilder { + // Note: this is a result, so we can easily chain builder calls + url: Result, + chain: NamedChain, + max_retry: u32, + initial_backoff: u64, + timeout: Duration, + /// available CUPS + compute_units_per_second: u64, + /// JWT Secret + jwt: Option, + headers: Vec, + is_local: bool, +} + +impl ProviderBuilder { + /// Creates a new builder instance + pub fn new(url_str: &str) -> Self { + // a copy is needed for the next lines to work + let mut url_str = url_str; + + // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http + // prefix + let storage; + if url_str.starts_with("localhost:") { + storage = format!("http://{url_str}"); + url_str = storage.as_str(); + } + + let url = Url::parse(url_str) + .or_else(|err| match err { + ParseError::RelativeUrlWithoutBase => { + if SocketAddr::from_str(url_str).is_ok() { + Url::parse(&format!("http://{url_str}")) + } else { + let path = Path::new(url_str); + + if let Ok(path) = resolve_path(path) { + Url::parse(&format!("file://{}", path.display())) + } else { + Err(err) + } + } + } + _ => Err(err), + }) + .wrap_err_with(|| format!("invalid provider URL: {url_str:?}")); + + // Use the final URL string to guess if it's a local URL. + let is_local = url.as_ref().is_ok_and(|url| guess_local_url(url.as_str())); + + Self { + url, + chain: NamedChain::Mainnet, + max_retry: 8, + initial_backoff: 800, + timeout: REQUEST_TIMEOUT, + // alchemy max cpus + compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, + jwt: None, + headers: vec![], + is_local, + } + } + + /// Enables a request timeout. + /// + /// The timeout is applied from when the request starts connecting until the + /// response body has finished. + /// + /// Default is no timeout. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Sets the chain of the node the provider will connect to + pub fn chain(mut self, chain: NamedChain) -> Self { + self.chain = chain; + self + } + + /// How often to retry a failed request + pub fn max_retry(mut self, max_retry: u32) -> Self { + self.max_retry = max_retry; + self + } + + /// How often to retry a failed request. If `None`, defaults to the already-set value. + pub fn maybe_max_retry(mut self, max_retry: Option) -> Self { + self.max_retry = max_retry.unwrap_or(self.max_retry); + self + } + + /// The starting backoff delay to use after the first failed request. If `None`, defaults to + /// the already-set value. + pub fn maybe_initial_backoff(mut self, initial_backoff: Option) -> Self { + self.initial_backoff = initial_backoff.unwrap_or(self.initial_backoff); + 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; + self + } + + /// Sets the number of assumed available compute units per second + /// + /// See also, + pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { + self.compute_units_per_second = compute_units_per_second; + self + } + + /// Sets the number of assumed available compute units per second + /// + /// See also, + pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { + if let Some(cups) = compute_units_per_second { + self.compute_units_per_second = cups; + } + self + } + + /// Sets the provider to be local. + /// + /// This is useful for local dev nodes. + pub fn local(mut self, is_local: bool) -> Self { + self.is_local = is_local; + self + } + + /// Sets aggressive `max_retry` and `initial_backoff` values + /// + /// This is only recommend for local dev nodes + pub fn aggressive(self) -> Self { + self.max_retry(100).initial_backoff(100).local(true) + } + + /// Sets the JWT secret + pub fn jwt(mut self, jwt: impl Into) -> Self { + self.jwt = Some(jwt.into()); + self + } + + /// Sets http headers + pub fn headers(mut self, headers: Vec) -> Self { + self.headers = headers; + + self + } + + /// Sets http headers. If `None`, defaults to the already-set value. + pub fn maybe_headers(mut self, headers: Option>) -> Self { + self.headers = headers.unwrap_or(self.headers); + self + } + + /// Constructs the `RetryProvider` taking all configs into account. + pub fn build(self) -> Result { + let Self { + url, + chain, + max_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + is_local, + } = self; + let url = url?; + + 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) + .build(); + let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); + + if !is_local { + client.set_poll_interval( + chain + .average_blocktime_hint() + // we cap the poll interval because if not provided, chain would default to + // mainnet + .map(|hint| hint.min(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME)) + .unwrap_or(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME) + .mul_f32(POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR), + ); + } + + let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + .on_provider(RootProvider::new(client)); + + Ok(provider) + } + + /// Constructs the `RetryProvider` with a wallet. + pub fn build_with_wallet(self, wallet: EthereumWallet) -> Result { + let Self { + url, + chain, + max_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + is_local, + } = self; + let url = url?; + + 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) + .build(); + + let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); + + if !is_local { + client.set_poll_interval( + chain + .average_blocktime_hint() + .unwrap_or(DEFAULT_UNKNOWN_CHAIN_BLOCK_TIME) + .mul_f32(POLL_INTERVAL_BLOCK_TIME_SCALE_FACTOR), + ); + } + + let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + .with_recommended_fillers() + .wallet(wallet) + .on_provider(RootProvider::new(client)); + + Ok(provider) + } +} + +#[cfg(not(windows))] +fn resolve_path(path: &Path) -> Result { + if path.is_absolute() { + Ok(path.to_path_buf()) + } else { + std::env::current_dir().map(|d| d.join(path)).map_err(drop) + } +} + +#[cfg(windows)] +fn resolve_path(path: &Path) -> Result { + if let Some(s) = path.to_str() { + if s.starts_with(r"\\.\pipe\") { + return Ok(path.to_path_buf()); + } + } + Err(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_auto_correct_missing_prefix() { + let builder = ProviderBuilder::new("localhost:8545"); + assert!(builder.url.is_ok()); + + let url = builder.url.unwrap(); + assert_eq!(url, Url::parse("http://localhost:8545").unwrap()); + } +} diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs new file mode 100644 index 0000000000000..07ab62af58a47 --- /dev/null +++ b/crates/common/src/provider/runtime_transport.rs @@ -0,0 +1,367 @@ +//! Runtime transport that connects on first request, which can take either of an HTTP, +//! WebSocket, or IPC transport and supports retries based on CUPS logic. + +use crate::{DEFAULT_USER_AGENT, REQUEST_TIMEOUT}; +use alloy_json_rpc::{RequestPacket, ResponsePacket}; +use alloy_pubsub::{PubSubConnect, PubSubFrontend}; +use alloy_rpc_types::engine::{Claims, JwtSecret}; +use alloy_transport::{ + Authorization, BoxTransport, TransportError, TransportErrorKind, TransportFut, +}; +use alloy_transport_http::Http; +use alloy_transport_ipc::IpcConnect; +use alloy_transport_ws::WsConnect; +use reqwest::header::{HeaderName, HeaderValue}; +use std::{fmt, path::PathBuf, str::FromStr, sync::Arc}; +use thiserror::Error; +use tokio::sync::RwLock; +use tower::Service; +use url::Url; + +/// An enum representing the different transports that can be used to connect to a runtime. +/// Only meant to be used internally by [RuntimeTransport]. +#[derive(Clone, Debug)] +pub enum InnerTransport { + /// HTTP transport + Http(Http), + /// WebSocket transport + Ws(PubSubFrontend), + /// IPC transport + Ipc(PubSubFrontend), +} + +/// Error type for the runtime transport. +#[derive(Error, Debug)] +pub enum RuntimeTransportError { + /// Internal transport error + #[error("Internal transport error: {0} with {1}")] + TransportError(TransportError, String), + + /// Failed to lock the transport + #[error("Failed to lock the transport")] + LockError, + + /// Invalid URL scheme + #[error("URL scheme is not supported: {0}")] + BadScheme(String), + + /// Invalid HTTP header + #[error("Invalid HTTP header: {0}")] + BadHeader(String), + + /// Invalid file path + #[error("Invalid IPC file path: {0}")] + BadPath(String), + + /// Invalid construction of Http provider + #[error(transparent)] + HttpConstructionError(#[from] reqwest::Error), + + /// Invalid JWT + #[error("Invalid JWT: {0}")] + InvalidJwt(String), +} + +/// Runtime transport that only connects on first request. +/// +/// A runtime transport is a custom [`alloy_transport::Transport`] that only connects when the +/// *first* request is made. When the first request is made, it will connect to the runtime using +/// either an HTTP WebSocket, or IPC transport depending on the URL used. +/// It also supports retries for rate-limiting and timeout-related errors. +#[derive(Clone, Debug, Error)] +pub struct RuntimeTransport { + /// The inner actual transport used. + inner: Arc>>, + /// The URL to connect to. + url: Url, + /// The headers to use for requests. + headers: Vec, + /// The JWT to use for requests. + jwt: Option, + /// The timeout for requests. + timeout: std::time::Duration, +} + +/// A builder for [RuntimeTransport]. +#[derive(Debug)] +pub struct RuntimeTransportBuilder { + url: Url, + headers: Vec, + jwt: Option, + timeout: std::time::Duration, +} + +impl RuntimeTransportBuilder { + /// Create a new builder with the given URL. + pub fn new(url: Url) -> Self { + Self { url, headers: vec![], jwt: None, timeout: REQUEST_TIMEOUT } + } + + /// Set the URL for the transport. + pub fn with_headers(mut self, headers: Vec) -> Self { + self.headers = headers; + self + } + + /// Set the JWT for the transport. + pub fn with_jwt(mut self, jwt: Option) -> Self { + self.jwt = jwt; + self + } + + /// Set the timeout for the transport. + pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self { + self.timeout = timeout; + self + } + + /// Builds the [RuntimeTransport] and returns it in a disconnected state. + /// The runtime transport will then connect when the first request happens. + pub fn build(self) -> RuntimeTransport { + RuntimeTransport { + inner: Arc::new(RwLock::new(None)), + url: self.url, + headers: self.headers, + jwt: self.jwt, + timeout: self.timeout, + } + } +} + +impl fmt::Display for RuntimeTransport { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "RuntimeTransport {}", self.url) + } +} + +impl RuntimeTransport { + /// Connects the underlying transport, depending on the URL scheme. + pub async fn connect(&self) -> Result { + match self.url.scheme() { + "http" | "https" => self.connect_http().await, + "ws" | "wss" => self.connect_ws().await, + "file" => self.connect_ipc().await, + _ => Err(RuntimeTransportError::BadScheme(self.url.scheme().to_string())), + } + } + + /// Creates a new reqwest client from this transport. + pub fn reqwest_client(&self) -> Result { + let mut client_builder = reqwest::Client::builder() + .timeout(self.timeout) + .tls_built_in_root_certs(self.url.scheme() == "https"); + let mut headers = reqwest::header::HeaderMap::new(); + + // If there's a JWT, add it to the headers if we can decode it. + if let Some(jwt) = self.jwt.clone() { + let auth = + build_auth(jwt).map_err(|e| RuntimeTransportError::InvalidJwt(e.to_string()))?; + + let mut auth_value: HeaderValue = + HeaderValue::from_str(&auth.to_string()).expect("Header should be valid string"); + auth_value.set_sensitive(true); + + headers.insert(reqwest::header::AUTHORIZATION, auth_value); + }; + + // Add any custom headers. + for header in self.headers.iter() { + let make_err = || RuntimeTransportError::BadHeader(header.to_string()); + + let (key, val) = header.split_once(':').ok_or_else(make_err)?; + + headers.insert( + HeaderName::from_str(key.trim()).map_err(|_| make_err())?, + HeaderValue::from_str(val.trim()).map_err(|_| make_err())?, + ); + } + + if !headers.contains_key(reqwest::header::USER_AGENT) { + headers.insert( + reqwest::header::USER_AGENT, + HeaderValue::from_str(DEFAULT_USER_AGENT) + .expect("User-Agent should be valid string"), + ); + } + + client_builder = client_builder.default_headers(headers); + + Ok(client_builder.build()?) + } + + /// Connects to an HTTP [alloy_transport_http::Http] transport. + async fn connect_http(&self) -> Result { + let client = self.reqwest_client()?; + Ok(InnerTransport::Http(Http::with_client(client, self.url.clone()))) + } + + /// Connects to a WS transport. + async fn connect_ws(&self) -> Result { + let auth = self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); + let ws = WsConnect { url: self.url.to_string(), auth, config: None } + .into_service() + .await + .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))?; + Ok(InnerTransport::Ws(ws)) + } + + /// Connects to an IPC transport. + async fn connect_ipc(&self) -> Result { + let path = url_to_file_path(&self.url) + .map_err(|_| RuntimeTransportError::BadPath(self.url.to_string()))?; + let ipc_connector = IpcConnect::new(path.clone()); + let ipc = ipc_connector.into_service().await.map_err(|e| { + RuntimeTransportError::TransportError(e, path.clone().display().to_string()) + })?; + Ok(InnerTransport::Ipc(ipc)) + } + + /// Sends a request using the underlying transport. + /// If this is the first request, it will connect to the appropriate transport depending on the + /// URL scheme. When sending the request, retries will be automatically handled depending + /// on the parameters set on the [RuntimeTransport]. + /// For sending the actual request, this action is delegated down to the + /// underlying transport through Tower's [tower::Service::call]. See tower's [tower::Service] + /// trait for more information. + pub fn request(&self, req: RequestPacket) -> TransportFut<'static> { + let this = self.clone(); + Box::pin(async move { + let mut inner = this.inner.read().await; + if inner.is_none() { + drop(inner); + { + let mut inner_mut = this.inner.write().await; + if inner_mut.is_none() { + *inner_mut = + Some(this.connect().await.map_err(TransportErrorKind::custom)?); + } + } + inner = this.inner.read().await; + } + + // SAFETY: We just checked that the inner transport exists. + match inner.clone().expect("must've been initialized") { + InnerTransport::Http(mut http) => http.call(req), + InnerTransport::Ws(mut ws) => ws.call(req), + InnerTransport::Ipc(mut ipc) => ipc.call(req), + } + .await + }) + } + + /// Convert this transport into a boxed trait object. + pub fn boxed(self) -> BoxTransport + where + Self: Sized + Clone + Send + Sync + 'static, + { + BoxTransport::new(self) + } +} + +impl tower::Service for RuntimeTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +impl tower::Service for &RuntimeTransport { + type Response = ResponsePacket; + type Error = TransportError; + type Future = TransportFut<'static>; + + #[inline] + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(Ok(())) + } + + #[inline] + fn call(&mut self, req: RequestPacket) -> Self::Future { + self.request(req) + } +} + +fn build_auth(jwt: String) -> eyre::Result { + // Decode jwt from hex, then generate claims (iat with current timestamp) + let secret = JwtSecret::from_hex(jwt)?; + let claims = Claims::default(); + let token = secret.encode(&claims)?; + + let auth = Authorization::Bearer(token); + + Ok(auth) +} + +#[cfg(windows)] +fn url_to_file_path(url: &Url) -> Result { + const PREFIX: &str = "file:///pipe/"; + + let url_str = url.as_str(); + + if url_str.starts_with(PREFIX) { + let pipe_name = &url_str[PREFIX.len()..]; + let pipe_path = format!(r"\\.\pipe\{}", pipe_name); + return Ok(PathBuf::from(pipe_path)); + } + + url.to_file_path() +} + +#[cfg(not(windows))] +fn url_to_file_path(url: &Url) -> Result { + url.to_file_path() +} + +#[cfg(test)] +mod tests { + use super::*; + use reqwest::header::HeaderMap; + + #[tokio::test] + async fn test_user_agent_header() { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); + let url = Url::parse(&format!("http://{}", listener.local_addr().unwrap())).unwrap(); + + let http_handler = axum::routing::get(|actual_headers: HeaderMap| { + let user_agent = HeaderName::from_str("User-Agent").unwrap(); + assert_eq!(actual_headers[user_agent], HeaderValue::from_str("test-agent").unwrap()); + + async { "" } + }); + + let server_task = tokio::spawn(async move { + axum::serve(listener, http_handler.into_make_service()).await.unwrap() + }); + + let transport = RuntimeTransportBuilder::new(url.clone()) + .with_headers(vec!["User-Agent: test-agent".to_string()]) + .build(); + let inner = transport.connect_http().await.unwrap(); + + match inner { + InnerTransport::Http(http) => { + let _ = http.client().get(url).send().await.unwrap(); + + // assert inside http_handler + } + _ => unreachable!(), + } + + server_task.abort(); + } +} diff --git a/crates/common/src/reports.rs b/crates/common/src/reports.rs new file mode 100644 index 0000000000000..0fdf4502eb68b --- /dev/null +++ b/crates/common/src/reports.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use crate::shell; + +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub enum ReportKind { + #[default] + Text, + JSON, +} + +/// Determine the kind of report to generate based on the current shell. +pub fn report_kind() -> ReportKind { + if shell::is_json() { + ReportKind::JSON + } else { + ReportKind::Text + } +} diff --git a/crates/common/src/retry.rs b/crates/common/src/retry.rs new file mode 100644 index 0000000000000..49eab352b7f09 --- /dev/null +++ b/crates/common/src/retry.rs @@ -0,0 +1,119 @@ +//! Retry utilities. + +use eyre::{Error, Report, Result}; +use std::{future::Future, time::Duration}; + +/// Error type for Retry. +#[derive(Debug, thiserror::Error)] +pub enum RetryError { + /// Continues operation without decrementing retries. + Continue(E), + /// Keeps retrying operation. + Retry(E), + /// Stops retrying operation immediately. + Break(E), +} + +/// A type that keeps track of attempts. +#[derive(Clone, Debug)] +pub struct Retry { + retries: u32, + delay: Duration, +} + +impl Retry { + /// Creates a new `Retry` instance. + pub fn new(retries: u32, delay: Duration) -> Self { + Self { retries, delay } + } + + /// Creates a new `Retry` instance with no delay between retries. + pub fn new_no_delay(retries: u32) -> Self { + Self::new(retries, Duration::ZERO) + } + + /// Runs the given closure in a loop, retrying if it fails up to the specified number of times. + pub fn run Result, T>(mut self, mut callback: F) -> Result { + loop { + match callback() { + Err(e) if self.retries > 0 => { + self.handle_err(e); + if !self.delay.is_zero() { + std::thread::sleep(self.delay); + } + } + res => return res, + } + } + } + + /// Runs the given async closure in a loop, retrying if it fails up to the specified number of + /// times. + pub async fn run_async(mut self, mut callback: F) -> Result + where + F: FnMut() -> Fut, + Fut: Future>, + { + loop { + match callback().await { + Err(e) if self.retries > 0 => { + self.handle_err(e); + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; + } + } + res => return res, + }; + } + } + + /// 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::Continue(e)) => { + self.log(e, false); + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; + } + } + Err(RetryError::Retry(e)) if self.retries > 0 => { + self.handle_err(e); + if !self.delay.is_zero() { + tokio::time::sleep(self.delay).await; + } + } + Err(RetryError::Retry(e) | RetryError::Break(e)) => return Err(e), + Ok(t) => return Ok(t), + }; + } + } + + fn handle_err(&mut self, err: Error) { + debug_assert!(self.retries > 0); + self.retries -= 1; + self.log(err, true); + } + + fn log(&self, err: Error, warn: bool) { + let msg = format!( + "{err}{delay} ({retries} tries remaining)", + delay = if self.delay.is_zero() { + String::new() + } else { + format!("; waiting {} seconds before trying again", self.delay.as_secs()) + }, + retries = self.retries, + ); + if warn { + let _ = sh_warn!("{msg}"); + } else { + tracing::info!("{msg}"); + } + } +} diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index e87c55148b5e0..c360a353c35c3 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -1,11 +1,13 @@ +//! Support for handling/identifying selectors. + #![allow(missing_docs)] -//! Support for handling/identifying selectors -use crate::abi::abi_decode; -use ethers_solc::artifacts::LosslessAbi; -use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; + +use crate::{abi::abi_decode_calldata, provider::runtime_transport::RuntimeTransportBuilder}; +use alloy_json_abi::JsonAbi; +use alloy_primitives::map::HashMap; +use eyre::Context; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ - collections::HashMap, fmt, sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, @@ -13,20 +15,20 @@ use std::{ }, time::Duration, }; -use tracing::warn; -static SELECTOR_DATABASE_URL: &str = "https://api.openchain.xyz/signature-database/v1/"; -static SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; +const BASE_URL: &str = "https://api.openchain.xyz"; +const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup"; +const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; -/// The standard request timeout for API requests +/// The standard request timeout for API requests. const REQ_TIMEOUT: Duration = Duration::from_secs(15); -/// How many request can time out before we decide this is a spurious connection +/// How many request can time out before we decide this is a spurious connection. const MAX_TIMEDOUT_REQ: usize = 4usize; -/// A client that can request API data from `https://api.openchain.xyz` -#[derive(Debug, Clone)] -pub struct SignEthClient { +/// A client that can request API data from OpenChain. +#[derive(Clone, Debug)] +pub struct OpenChainClient { inner: reqwest::Client, /// Whether the connection is spurious, or API is down spurious_connection: Arc, @@ -36,81 +38,69 @@ pub struct SignEthClient { max_timedout_requests: usize, } -impl SignEthClient { - /// Creates a new client with default settings - pub fn new() -> reqwest::Result { - let inner = reqwest::Client::builder() - .default_headers(HeaderMap::from_iter([( - HeaderName::from_static("user-agent"), - HeaderValue::from_static("forge"), - )])) - .timeout(REQ_TIMEOUT) - .build()?; +impl OpenChainClient { + /// Creates a new client with default settings. + pub fn new() -> eyre::Result { + let inner = RuntimeTransportBuilder::new(BASE_URL.parse().unwrap()) + .with_timeout(REQ_TIMEOUT) + .build() + .reqwest_client() + .wrap_err("failed to build OpenChain client")?; Ok(Self { inner, - spurious_connection: Arc::new(Default::default()), - timedout_requests: Arc::new(Default::default()), + spurious_connection: Default::default(), + timedout_requests: Default::default(), max_timedout_requests: MAX_TIMEDOUT_REQ, }) } async fn get_text(&self, url: &str) -> reqwest::Result { + trace!(%url, "GET"); self.inner .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 - async fn post_json( + async fn post_json( &self, url: &str, body: &T, ) -> reqwest::Result { + trace!(%url, body=?serde_json::to_string(body), "POST"); self.inner .post(url) .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) { fn is_connectivity_err(err: &reqwest::Error) -> bool { if err.is_timeout() || err.is_connect() { - return true + return true; } // Error HTTP codes (5xx) are considered connectivity issues and will prompt retry if let Some(status) = err.status() { let code = status.as_u16(); if (500..600).contains(&code) { - return true + return true; } } false } if is_connectivity_err(err) { - warn!("spurious network detected for https://api.openchain.xyz"); + warn!("spurious network detected for OpenChain"); let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst); if previous >= self.max_timedout_requests { self.set_spurious(); @@ -135,25 +125,61 @@ impl SignEthClient { Ok(()) } - /// Decodes the given function or event selector using https://api.openchain.xyz + /// Decodes the given function or event selector using OpenChain pub async fn decode_selector( &self, selector: &str, selector_type: SelectorType, ) -> eyre::Result> { + self.decode_selectors(selector_type, std::iter::once(selector)) + .await? + .pop() // Not returning on the previous line ensures a vector with exactly 1 element + .unwrap() + .ok_or_else(|| eyre::eyre!("No signature found")) + } + + /// Decodes the given function, error or event selectors using OpenChain. + pub async fn decode_selectors( + &self, + selector_type: SelectorType, + selectors: impl IntoIterator>, + ) -> eyre::Result>>> { + let selectors: Vec = selectors + .into_iter() + .map(Into::into) + .map(|s| s.to_lowercase()) + .map(|s| if s.starts_with("0x") { s } else { format!("0x{s}") }) + .collect(); + + if selectors.is_empty() { + return Ok(vec![]); + } + + debug!(len = selectors.len(), "decoding selectors"); + trace!(?selectors, "decoding selectors"); + // exit early if spurious connection self.ensure_not_spurious()?; + let expected_len = match selector_type { + SelectorType::Function | SelectorType::Error => 10, // 0x + hex(4bytes) + SelectorType::Event => 66, // 0x + hex(32bytes) + }; + if let Some(s) = selectors.iter().find(|s| s.len() != expected_len) { + eyre::bail!( + "Invalid selector {s}: expected {expected_len} characters (including 0x prefix)." + ) + } + #[derive(Deserialize)] struct Decoded { name: String, - filtered: bool, } #[derive(Deserialize)] struct ApiResult { - event: HashMap>, - function: HashMap>, + event: HashMap>>, + function: HashMap>>, } #[derive(Deserialize)] @@ -162,12 +188,14 @@ impl SignEthClient { result: ApiResult, } - // using openchain.xyz signature database over 4byte - // see https://github.com/foundry-rs/foundry/issues/1672 - let url = match selector_type { - SelectorType::Function => format!("{SELECTOR_DATABASE_URL}lookup?function={selector}"), - SelectorType::Event => format!("{SELECTOR_DATABASE_URL}lookup?event={selector}"), - }; + let url = format!( + "{SELECTOR_LOOKUP_URL}?{ltype}={selectors_str}", + ltype = match selector_type { + SelectorType::Function | SelectorType::Error => "function", + SelectorType::Event => "event", + }, + selectors_str = selectors.join(",") + ); let res = self.get_text(&url).await?; let api_response = match serde_json::from_str::(&res) { @@ -182,31 +210,22 @@ impl SignEthClient { } let decoded = match selector_type { - SelectorType::Function => api_response.result.function, + SelectorType::Function | SelectorType::Error => api_response.result.function, SelectorType::Event => api_response.result.event, }; - Ok(decoded - .get(selector) - .ok_or(eyre::eyre!("No signature found"))? - .iter() - .filter(|&d| !d.filtered) - .map(|d| d.name.clone()) - .collect::>()) + Ok(selectors + .into_iter() + .map(|selector| match decoded.get(&selector) { + Some(Some(r)) => Some(r.iter().map(|d| d.name.clone()).collect()), + _ => None, + }) + .collect()) } - /// Fetches a function signature given the selector using https://api.openchain.xyz + /// Fetches a function signature given the selector using OpenChain pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result> { - let stripped_selector = selector.strip_prefix("0x").unwrap_or(selector); - let prefixed_selector = format!("0x{}", stripped_selector); - if prefixed_selector.len() != 10 { - eyre::bail!( - "Invalid selector: expected 8 characters (excluding 0x prefix), got {}.", - stripped_selector.len() - ) - } - - self.decode_selector(&prefixed_selector[..10], SelectorType::Function).await + self.decode_selector(selector, SelectorType::Function).await } /// Fetches all possible signatures and attempts to abi decode the calldata @@ -224,29 +243,30 @@ impl SignEthClient { // filter for signatures that can be decoded Ok(sigs .iter() + .filter(|sig| abi_decode_calldata(sig, calldata, true, true).is_ok()) .cloned() - .filter(|sig| abi_decode(sig, calldata, true, true).is_ok()) .collect::>()) } - /// Fetches an event signature given the 32 byte topic using https://api.openchain.xyz + /// Fetches an event signature given the 32 byte topic using OpenChain pub async fn decode_event_topic(&self, topic: &str) -> eyre::Result> { - let prefixed_topic = format!("0x{}", topic.strip_prefix("0x").unwrap_or(topic)); - if prefixed_topic.len() != 66 { - eyre::bail!("Invalid topic: expected 64 characters (excluding 0x prefix), got {} characters (including 0x prefix).", prefixed_topic.len()) - } - self.decode_selector(&prefixed_topic[..66], SelectorType::Event).await + self.decode_selector(topic, SelectorType::Event).await } /// Pretty print calldata and if available, fetch possible function signatures /// /// ```no_run - /// - /// use foundry_common::selectors::SignEthClient; + /// use foundry_common::selectors::OpenChainClient; /// /// # async fn foo() -> eyre::Result<()> { - /// let pretty_data = SignEthClient::new()?.pretty_calldata("0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5".to_string(), false).await?; - /// println!("{}",pretty_data); + /// let pretty_data = OpenChainClient::new()? + /// .pretty_calldata( + /// "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5" + /// .to_string(), + /// false, + /// ) + /// .await?; + /// println!("{}", pretty_data); /// # Ok(()) /// # } /// ``` @@ -285,7 +305,7 @@ impl SignEthClient { Ok(possible_info) } - /// uploads selectors to https://api.openchain.xyz using the given data + /// uploads selectors to OpenChain using the given data pub async fn import_selectors( &self, data: SelectorImportData, @@ -294,21 +314,27 @@ impl SignEthClient { let request = match data { SelectorImportData::Abi(abis) => { - let names: Vec = abis + let functions_and_errors: Vec = abis .iter() .flat_map(|abi| { - abi.abi - .functions() - .map(|func| { - func.signature().split(':').next().unwrap_or("").to_string() - }) + abi.functions() + .map(|func| func.signature()) + .chain(abi.errors().map(|error| error.signature())) .collect::>() }) .collect(); - SelectorImportRequest { function: names, event: Default::default() } + + let events = abis + .iter() + .flat_map(|abi| abi.events().map(|event| event.signature())) + .collect::>(); + + SelectorImportRequest { function: functions_and_errors, event: events } } SelectorImportData::Raw(raw) => { - SelectorImportRequest { function: raw.function, event: raw.event } + let function_and_error = + raw.function.iter().chain(raw.error.iter()).cloned().collect::>(); + SelectorImportRequest { function: function_and_error, event: raw.event } } }; @@ -328,7 +354,7 @@ pub struct PossibleSigs { impl PossibleSigs { fn new() -> Self { - PossibleSigs { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] } + Self { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] } } } @@ -356,56 +382,71 @@ impl fmt::Display for PossibleSigs { } } +/// The type of selector fetched from OpenChain. #[derive(Clone, Copy)] pub enum SelectorType { + /// A function selector. Function, + /// An event selector. Event, + /// An custom error selector. + Error, } -/// Decodes the given function or event selector using https://api.openchain.xyz +/// Decodes the given function or event selector using OpenChain. pub async fn decode_selector( - selector: &str, selector_type: SelectorType, + selector: &str, ) -> eyre::Result> { - SignEthClient::new()?.decode_selector(selector, selector_type).await + OpenChainClient::new()?.decode_selector(selector, selector_type).await +} + +/// Decodes the given function or event selectors using OpenChain. +pub async fn decode_selectors( + selector_type: SelectorType, + selectors: impl IntoIterator>, +) -> eyre::Result>>> { + OpenChainClient::new()?.decode_selectors(selector_type, selectors).await } -/// Fetches a function signature given the selector https://api.openchain.xyz +/// Fetches a function signature given the selector using OpenChain. pub async fn decode_function_selector(selector: &str) -> eyre::Result> { - SignEthClient::new()?.decode_function_selector(selector).await + OpenChainClient::new()?.decode_function_selector(selector).await } -/// Fetches all possible signatures and attempts to abi decode the calldata +/// Fetches all possible signatures and attempts to abi decode the calldata using OpenChain. pub async fn decode_calldata(calldata: &str) -> eyre::Result> { - SignEthClient::new()?.decode_calldata(calldata).await + OpenChainClient::new()?.decode_calldata(calldata).await } -/// Fetches an event signature given the 32 byte topic using https://api.openchain.xyz +/// Fetches an event signature given the 32 byte topic using OpenChain. pub async fn decode_event_topic(topic: &str) -> eyre::Result> { - SignEthClient::new()?.decode_event_topic(topic).await + OpenChainClient::new()?.decode_event_topic(topic).await } -/// Pretty print calldata and if available, fetch possible function signatures +/// Pretty print calldata and if available, fetch possible function signatures. /// /// ```no_run -/// /// use foundry_common::selectors::pretty_calldata; /// /// # async fn foo() -> eyre::Result<()> { -/// let pretty_data = pretty_calldata("0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5".to_string(), false).await?; -/// println!("{}",pretty_data); +/// let pretty_data = pretty_calldata( +/// "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5".to_string(), +/// false, +/// ) +/// .await?; +/// println!("{}", pretty_data); /// # Ok(()) /// # } /// ``` - pub async fn pretty_calldata( calldata: impl AsRef, offline: bool, ) -> eyre::Result { - SignEthClient::new()?.pretty_calldata(calldata, offline).await + OpenChainClient::new()?.pretty_calldata(calldata, offline).await } -#[derive(Default, Serialize, PartialEq, Debug, Eq)] +#[derive(Debug, Default, PartialEq, Eq, Serialize)] pub struct RawSelectorImportData { pub function: Vec, pub event: Vec, @@ -421,7 +462,7 @@ impl RawSelectorImportData { #[derive(Serialize)] #[serde(untagged)] pub enum SelectorImportData { - Abi(Vec), + Abi(Vec), Raw(RawSelectorImportData), } @@ -451,41 +492,37 @@ pub struct SelectorImportResponse { impl SelectorImportResponse { /// Print info about the functions which were uploaded or already known pub fn describe(&self) { - self.result - .function - .imported - .iter() - .for_each(|(k, v)| println!("Imported: Function {k}: {v}")); - self.result.event.imported.iter().for_each(|(k, v)| println!("Imported: Event {k}: {v}")); - self.result - .function - .duplicated - .iter() - .for_each(|(k, v)| println!("Duplicated: Function {k}: {v}")); - self.result - .event - .duplicated - .iter() - .for_each(|(k, v)| println!("Duplicated: Event {k}: {v}")); - - println!("Selectors successfully uploaded to https://api.openchain.xyz"); + self.result.function.imported.iter().for_each(|(k, v)| { + let _ = sh_println!("Imported: Function {k}: {v}"); + }); + self.result.event.imported.iter().for_each(|(k, v)| { + let _ = sh_println!("Imported: Event {k}: {v}"); + }); + self.result.function.duplicated.iter().for_each(|(k, v)| { + let _ = sh_println!("Duplicated: Function {k}: {v}"); + }); + self.result.event.duplicated.iter().for_each(|(k, v)| { + let _ = sh_println!("Duplicated: Event {k}: {v}"); + }); + + let _ = sh_println!("Selectors successfully uploaded to OpenChain"); } } -/// uploads selectors to https://api.openchain.xyz using the given data +/// uploads selectors to OpenChain using the given data pub async fn import_selectors(data: SelectorImportData) -> eyre::Result { - SignEthClient::new()?.import_selectors(data).await + OpenChainClient::new()?.import_selectors(data).await } -#[derive(PartialEq, Default, Debug)] +#[derive(Debug, Default, PartialEq, Eq)] pub struct ParsedSignatures { pub signatures: RawSelectorImportData, - pub abis: Vec, + pub abis: Vec, } #[derive(Deserialize)] struct Artifact { - abi: LosslessAbi, + abi: JsonAbi, } /// Parses a list of tokens into function, event, and error signatures. @@ -541,57 +578,8 @@ pub fn parse_signatures(tokens: Vec) -> ParsedSignatures { mod tests { use super::*; - #[tokio::test(flavor = "multi_thread")] - async fn test_decode_selector() { - let sigs = decode_function_selector("0xa9059cbb").await; - assert_eq!(sigs.unwrap()[0], "transfer(address,uint256)".to_string()); - - let sigs = decode_function_selector("a9059cbb").await; - assert_eq!(sigs.unwrap()[0], "transfer(address,uint256)".to_string()); - - // invalid signature - decode_function_selector("0xa9059c") - .await - .map_err(|e| { - assert_eq!( - e.to_string(), - "Invalid selector: expected 8 characters (excluding 0x prefix), got 6." - ) - }) - .map(|_| panic!("Expected fourbyte error")) - .ok(); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_decode_calldata() { - let decoded = decode_calldata("0xa9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79").await; - assert_eq!(decoded.unwrap()[0], "transfer(address,uint256)".to_string()); - - let decoded = decode_calldata("a9059cbb0000000000000000000000000a2ac0c368dc8ec680a0c98c907656bd970675950000000000000000000000000000000000000000000000000000000767954a79").await; - assert_eq!(decoded.unwrap()[0], "transfer(address,uint256)".to_string()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_import_selectors() { - let mut data = RawSelectorImportData::default(); - data.function.push("transfer(address,uint256)".to_string()); - let result = import_selectors(SelectorImportData::Raw(data)).await; - assert_eq!( - result.unwrap().result.function.duplicated.get("transfer(address,uint256)").unwrap(), - "0xa9059cbb" - ); - - let abi: LosslessAbi = serde_json::from_str(r#"[{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function", "methodIdentifiers": {"transfer(address,uint256)(uint256)": "0xa9059cbb"}}]"#).unwrap(); - let result = import_selectors(SelectorImportData::Abi(vec![abi])).await; - println!("{:?}", result); - assert_eq!( - result.unwrap().result.function.duplicated.get("transfer(address,uint256)").unwrap(), - "0xa9059cbb" - ); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_parse_signatures() { + #[test] + fn test_parse_signatures() { let result = parse_signatures(vec!["transfer(address,uint256)".to_string()]); assert_eq!( result, @@ -625,6 +613,7 @@ mod tests { let result = parse_signatures(vec![ "transfer(address,uint256)".to_string(), "event Approval(address,address,uint256)".to_string(), + "error ERC20InsufficientBalance(address,uint256,uint256)".to_string(), ]); assert_eq!( result, @@ -632,7 +621,7 @@ mod tests { signatures: RawSelectorImportData { function: vec!["transfer(address,uint256)".to_string()], event: vec!["Approval(address,address,uint256)".to_string()], - ..Default::default() + error: vec!["ERC20InsufficientBalance(address,uint256,uint256)".to_string()] }, ..Default::default() } @@ -645,29 +634,4 @@ mod tests { ParsedSignatures { signatures: Default::default(), ..Default::default() } ); } - - #[tokio::test(flavor = "multi_thread")] - async fn test_decode_event_topic() { - let decoded = decode_event_topic( - "0x7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6", - ) - .await; - assert_eq!(decoded.unwrap()[0], "updateAuthority(address,uint8)".to_string()); - - let decoded = - decode_event_topic("7e1db2a1cd12f0506ecd806dba508035b290666b84b096a87af2fd2a1516ede6") - .await; - assert_eq!(decoded.unwrap()[0], "updateAuthority(address,uint8)".to_string()); - - let decoded = decode_event_topic( - "0xb7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd", - ) - .await; - assert_eq!(decoded.unwrap()[0], "canCall(address,address,bytes4)".to_string()); - - let decoded = - decode_event_topic("b7009613e63fb13fd59a2fa4c206a992c1f090a44e5d530be255aa17fed0b3dd") - .await; - assert_eq!(decoded.unwrap()[0], "canCall(address,address,bytes4)".to_string()); - } } diff --git a/crates/common/src/serde_helpers.rs b/crates/common/src/serde_helpers.rs new file mode 100644 index 0000000000000..a7cc8bee12308 --- /dev/null +++ b/crates/common/src/serde_helpers.rs @@ -0,0 +1,127 @@ +//! Misc Serde helpers for foundry crates. + +use alloy_primitives::U256; +use serde::{de, Deserialize, Deserializer}; +use std::str::FromStr; + +/// Helper type to parse both `u64` and `U256` +#[derive(Copy, Clone, Deserialize)] +#[serde(untagged)] +pub enum Numeric { + /// A [U256] value. + U256(U256), + /// A `u64` value. + Num(u64), +} + +impl From for U256 { + fn from(n: Numeric) -> Self { + match n { + Numeric::U256(n) => n, + Numeric::Num(n) => Self::from(n), + } + } +} + +impl FromStr for Numeric { + type Err = String; + + fn from_str(s: &str) -> Result { + if let Ok(val) = s.parse::() { + Ok(Self::U256(U256::from(val))) + } else if s.starts_with("0x") { + U256::from_str_radix(s, 16).map(Numeric::U256).map_err(|err| err.to_string()) + } else { + U256::from_str(s).map(Numeric::U256).map_err(|err| err.to_string()) + } + } +} + +/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the +/// inner value. +pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u256().map(Some), + None => Ok(None), + } +} + +/// An enum that represents either a [serde_json::Number] integer, or a hex [U256]. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum NumberOrHexU256 { + /// An integer + Int(serde_json::Number), + /// A hex U256 + Hex(U256), +} + +impl NumberOrHexU256 { + /// Tries to convert this into a [U256]]. + pub fn try_into_u256(self) -> Result { + match self { + Self::Int(num) => U256::from_str(num.to_string().as_str()).map_err(E::custom), + Self::Hex(val) => Ok(val), + } + } +} + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and decimal strings with +/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number). +pub fn from_int_or_hex<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} + +/// Helper type to deserialize sequence of numbers +#[derive(Deserialize)] +#[serde(untagged)] +pub enum NumericSeq { + /// Single parameter sequence (e.g `[1]`). + Seq([Numeric; 1]), + /// `U256`. + U256(U256), + /// Native `u64`. + Num(u64), +} + +/// Deserializes a number from hex or int +pub fn deserialize_number<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + Numeric::deserialize(deserializer).map(Into::into) +} + +/// Deserializes a number from hex or int, but optionally +pub fn deserialize_number_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let num = match Option::::deserialize(deserializer)? { + Some(Numeric::U256(n)) => Some(n), + Some(Numeric::Num(n)) => Some(U256::from(n)), + _ => None, + }; + + Ok(num) +} + +/// Deserializes single integer params: `1, [1], ["0x01"]` +pub fn deserialize_number_seq<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let num = match NumericSeq::deserialize(deserializer)? { + NumericSeq::Seq(seq) => seq[0].into(), + NumericSeq::U256(n) => n, + NumericSeq::Num(n) => U256::from(n), + }; + + Ok(num) +} diff --git a/crates/common/src/shell.rs b/crates/common/src/shell.rs deleted file mode 100644 index 9b359fbc49350..0000000000000 --- a/crates/common/src/shell.rs +++ /dev/null @@ -1,314 +0,0 @@ -//! Helpers for printing to output - -use once_cell::sync::OnceCell; -use serde::Serialize; -use std::{ - error::Error, - fmt, io, - io::Write, - sync::{Arc, Mutex}, -}; - -/// Stores the configured shell for the duration of the program -static SHELL: OnceCell = OnceCell::new(); - -/// Error indicating that `set_hook` was unable to install the provided ErrorHook -#[derive(Debug, Clone, Copy)] -pub struct InstallError; - -impl fmt::Display for InstallError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("cannot install provided Shell, a shell has already been installed") - } -} - -impl Error for InstallError {} - -/// Install the provided shell -pub fn set_shell(shell: Shell) -> Result<(), InstallError> { - SHELL.set(shell).map_err(|_| InstallError) -} - -/// Runs the given closure with the current shell, or default shell if none was set -pub fn with_shell(f: F) -> R -where - F: FnOnce(&Shell) -> R, -{ - if let Some(shell) = SHELL.get() { - f(shell) - } else { - let shell = Shell::default(); - f(&shell) - } -} - -/// Prints the given message to the shell -pub fn println(msg: impl fmt::Display) -> io::Result<()> { - with_shell(|shell| if !shell.verbosity.is_silent() { shell.write_stdout(msg) } else { Ok(()) }) -} -/// Prints the given message to the shell -pub fn print_json(obj: &T) -> serde_json::Result<()> { - with_shell(|shell| shell.print_json(obj)) -} - -/// Prints the given message to the shell -pub fn eprintln(msg: impl fmt::Display) -> io::Result<()> { - with_shell(|shell| if !shell.verbosity.is_silent() { shell.write_stderr(msg) } else { Ok(()) }) -} - -/// Returns the configured verbosity -pub fn verbosity() -> Verbosity { - with_shell(|shell| shell.verbosity) -} - -/// An abstraction around console output that also considers verbosity -#[derive(Default)] -pub struct Shell { - /// Wrapper around stdout/stderr. - output: ShellOut, - /// How to emit messages. - verbosity: Verbosity, -} - -// === impl Shell === - -impl Shell { - /// Creates a new shell instance - pub fn new(output: ShellOut, verbosity: Verbosity) -> Self { - Self { output, verbosity } - } - - /// Returns a new shell that conforms to the specified verbosity arguments, where `json` takes - /// higher precedence - pub fn from_args(silent: bool, json: bool) -> Self { - match (silent, json) { - (_, true) => Self::json(), - (true, _) => Self::silent(), - _ => Default::default(), - } - } - - /// Returns a new shell that won't emit anything - pub fn silent() -> Self { - Self::from_verbosity(Verbosity::Silent) - } - - /// Returns a new shell that'll only emit json - pub fn json() -> Self { - Self::from_verbosity(Verbosity::Json) - } - - /// Creates a new shell instance with default output and the given verbosity - pub fn from_verbosity(verbosity: Verbosity) -> Self { - Self::new(Default::default(), verbosity) - } - - /// Write a fragment to stdout - /// - /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. - pub fn write_stdout(&self, fragment: impl fmt::Display) -> io::Result<()> { - self.output.write_stdout(fragment) - } - - /// Write a fragment to stderr - /// - /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. - pub fn write_stderr(&self, fragment: impl fmt::Display) -> io::Result<()> { - self.output.write_stderr(fragment) - } - - /// Prints the object to stdout as json - pub fn print_json(&self, obj: &T) -> serde_json::Result<()> { - if self.verbosity.is_json() { - let json = serde_json::to_string(&obj)?; - let _ = self.output.with_stdout(|out| writeln!(out, "{json}")); - } - Ok(()) - } - /// Prints the object to stdout as pretty json - pub fn pretty_print_json(&self, obj: &T) -> serde_json::Result<()> { - if self.verbosity.is_json() { - let json = serde_json::to_string_pretty(&obj)?; - let _ = self.output.with_stdout(|out| writeln!(out, "{json}")); - } - Ok(()) - } -} - -impl fmt::Debug for Shell { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.output { - ShellOut::Write(_) => { - f.debug_struct("Shell").field("verbosity", &self.verbosity).finish() - } - ShellOut::Stream => { - f.debug_struct("Shell").field("verbosity", &self.verbosity).finish() - } - } - } -} - -/// Helper trait for custom shell output -/// -/// Can be used for debugging -pub trait ShellWrite { - /// Write the fragment - fn write(&self, fragment: impl fmt::Display) -> io::Result<()>; - - /// Executes a closure on the current stdout - fn with_stdout(&self, f: F) -> R - where - for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R; - - /// Executes a closure on the current stderr - fn with_err(&self, f: F) -> R - where - for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R; -} - -/// A guarded shell output type -pub struct WriteShellOut(Arc>>); - -unsafe impl Send for WriteShellOut {} -unsafe impl Sync for WriteShellOut {} - -impl ShellWrite for WriteShellOut { - fn write(&self, fragment: impl fmt::Display) -> io::Result<()> { - if let Ok(mut lock) = self.0.lock() { - writeln!(lock, "{fragment}")?; - } - Ok(()) - } - /// Executes a closure on the current stdout - fn with_stdout(&self, f: F) -> R - where - for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R, - { - let mut lock = self.0.lock().unwrap(); - f(&mut *lock) - } - - /// Executes a closure on the current stderr - fn with_err(&self, f: F) -> R - where - for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R, - { - let mut lock = self.0.lock().unwrap(); - f(&mut *lock) - } -} - -/// A `Write`able object, either with or without color support -#[derive(Default)] -pub enum ShellOut { - /// A plain write object - /// - /// Can be used for debug purposes - Write(WriteShellOut), - /// Streams to `stdio` - #[default] - Stream, -} - -// === impl ShellOut === - -impl ShellOut { - /// Creates a new shell that writes to memory - pub fn memory() -> Self { - #[allow(clippy::box_default)] - #[allow(clippy::arc_with_non_send_sync)] - ShellOut::Write(WriteShellOut(Arc::new(Mutex::new(Box::new(Vec::new()))))) - } - - /// Write a fragment to stdout - fn write_stdout(&self, fragment: impl fmt::Display) -> io::Result<()> { - match *self { - ShellOut::Stream => { - let stdout = io::stdout(); - let mut handle = stdout.lock(); - writeln!(handle, "{fragment}")?; - } - ShellOut::Write(ref w) => { - w.write(fragment)?; - } - } - Ok(()) - } - - /// Write output to stderr - fn write_stderr(&self, fragment: impl fmt::Display) -> io::Result<()> { - match *self { - ShellOut::Stream => { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - writeln!(handle, "{fragment}")?; - } - ShellOut::Write(ref w) => { - w.write(fragment)?; - } - } - Ok(()) - } - - /// Executes a closure on the current stdout - fn with_stdout(&self, f: F) -> R - where - for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R, - { - match *self { - ShellOut::Stream => { - let stdout = io::stdout(); - let mut handler = stdout.lock(); - f(&mut handler) - } - ShellOut::Write(ref w) => w.with_stdout(f), - } - } - - /// Executes a closure on the current stderr - #[allow(unused)] - fn with_err(&self, f: F) -> R - where - for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R, - { - match *self { - ShellOut::Stream => { - let stderr = io::stderr(); - let mut handler = stderr.lock(); - f(&mut handler) - } - ShellOut::Write(ref w) => w.with_err(f), - } - } -} - -/// The requested verbosity of output. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub enum Verbosity { - /// only allow json output - Json, - /// print as is - #[default] - Normal, - /// print nothing - Silent, -} - -// === impl Verbosity === - -impl Verbosity { - /// Returns true if json mode - pub fn is_json(&self) -> bool { - matches!(self, Verbosity::Json) - } - - /// Returns true if silent - pub fn is_silent(&self) -> bool { - matches!(self, Verbosity::Silent) - } - - /// Returns true if normal verbosity - pub fn is_normal(&self) -> bool { - matches!(self, Verbosity::Normal) - } -} diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index 8b20006ad3949..1ee62f8aedf6b 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -1,10 +1,10 @@ //! terminal utils -use ethers_solc::{ - remappings::Remapping, - report::{self, BasicStdoutReporter, Reporter, SolcCompilerIoReporter}, - CompilerInput, CompilerOutput, Solc, +use foundry_compilers::{ + artifacts::remappings::Remapping, + report::{self, BasicStdoutReporter, Reporter}, }; -use once_cell::sync::Lazy; +use foundry_config::find_project_root; +use itertools::Itertools; use semver::Version; use std::{ io, @@ -12,12 +12,15 @@ use std::{ path::{Path, PathBuf}, sync::{ mpsc::{self, TryRecvError}, - Arc, Mutex, + LazyLock, }, + thread, time::Duration, }; use yansi::Paint; +use crate::shell; + /// Some spinners // https://github.com/gernest/wow/blob/master/spin/spinners.go pub static SPINNERS: &[&[&str]] = &[ @@ -28,7 +31,7 @@ pub static SPINNERS: &[&[&str]] = &[ &[" ", "▘", "▀", "▜", "█", "▟", "▄", "▖"], ]; -static TERM_SETTINGS: Lazy = Lazy::new(TermSettings::from_env); +static TERM_SETTINGS: LazyLock = LazyLock::new(TermSettings::from_env); /// Helper type to determine the current tty pub struct TermSettings { @@ -37,8 +40,8 @@ pub struct TermSettings { impl TermSettings { /// Returns a new [`TermSettings`], configured from the current environment. - pub fn from_env() -> TermSettings { - TermSettings { indicate_progress: std::io::stdout().is_terminal() } + pub fn from_env() -> Self { + Self { indicate_progress: std::io::stdout().is_terminal() } } } @@ -58,7 +61,7 @@ impl Spinner { } pub fn with_indicator(indicator: &'static [&'static str], msg: impl Into) -> Self { - Spinner { + Self { indicator, no_progress: !TERM_SETTINGS.indicate_progress, message: msg.into(), @@ -71,17 +74,12 @@ impl Spinner { return } - print!( - "\r\x33[2K\r{} {}", - Paint::new(format!("[{}]", Paint::green(self.indicator[self.idx]))).bold(), - self.message - ); + let indicator = self.indicator[self.idx % self.indicator.len()].green(); + let indicator = Paint::new(format!("[{indicator}]")).bold(); + let _ = sh_print!("\r\x33[2K\r{indicator} {}", self.message); io::stdout().flush().unwrap(); - self.idx += 1; - if self.idx >= self.indicator.len() { - self.idx = 0; - } + self.idx = self.idx.wrapping_add(1); } pub fn message(&mut self, msg: impl Into) { @@ -93,13 +91,12 @@ impl Spinner { /// /// This reporter will prefix messages with a spinning cursor #[derive(Debug)] +#[must_use = "Terminates the spinner on drop"] pub struct SpinnerReporter { - /// the timeout in ms - sender: Arc>>, - /// A reporter that logs solc compiler input and output to separate files if configured via env - /// var - solc_io_report: SolcCompilerIoReporter, + /// The sender to the spinner thread. + sender: mpsc::Sender, } + impl SpinnerReporter { /// Spawns the [`Spinner`] on a new thread /// @@ -108,43 +105,37 @@ impl SpinnerReporter { /// On drop the channel will disconnect and the thread will terminate pub fn spawn() -> Self { let (sender, rx) = mpsc::channel::(); - std::thread::spawn(move || { - let mut spinner = Spinner::new("Compiling..."); - loop { - spinner.tick(); - match rx.try_recv() { - Ok(msg) => { - match msg { - SpinnerMsg::Msg(msg) => { - spinner.message(msg); - // new line so past messages are not overwritten - println!(); - } - SpinnerMsg::Shutdown(ack) => { - // end with a newline - println!(); - let _ = ack.send(()); - break - } + + std::thread::Builder::new() + .name("spinner".into()) + .spawn(move || { + let mut spinner = Spinner::new("Compiling..."); + loop { + spinner.tick(); + match rx.try_recv() { + Ok(SpinnerMsg::Msg(msg)) => { + spinner.message(msg); + // new line so past messages are not overwritten + let _ = sh_println!(); } - } - Err(TryRecvError::Disconnected) => break, - Err(TryRecvError::Empty) => { - std::thread::sleep(std::time::Duration::from_millis(100)); + Ok(SpinnerMsg::Shutdown(ack)) => { + // end with a newline + let _ = sh_println!(); + let _ = ack.send(()); + break + } + Err(TryRecvError::Disconnected) => break, + Err(TryRecvError::Empty) => thread::sleep(Duration::from_millis(100)), } } - } - }); - SpinnerReporter { - sender: Arc::new(Mutex::new(sender)), - solc_io_report: SolcCompilerIoReporter::from_default_env(), - } + }) + .expect("failed to spawn thread"); + + Self { sender } } fn send_msg(&self, msg: impl Into) { - if let Ok(sender) = self.sender.lock() { - let _ = sender.send(SpinnerMsg::Msg(msg.into())); - } + let _ = self.sender.send(SpinnerMsg::Msg(msg.into())); } } @@ -155,59 +146,64 @@ enum SpinnerMsg { impl Drop for SpinnerReporter { fn drop(&mut self) { - if let Ok(sender) = self.sender.lock() { - let (tx, rx) = mpsc::channel(); - if sender.send(SpinnerMsg::Shutdown(tx)).is_ok() { - let _ = rx.recv(); - } + let (tx, rx) = mpsc::channel(); + if self.sender.send(SpinnerMsg::Shutdown(tx)).is_ok() { + let _ = rx.recv(); } } } impl Reporter for SpinnerReporter { - fn on_solc_spawn( - &self, - _solc: &Solc, - version: &Version, - input: &CompilerInput, - dirty_files: &[PathBuf], - ) { + fn on_compiler_spawn(&self, compiler_name: &str, version: &Version, dirty_files: &[PathBuf]) { + // Verbose message with dirty files displays first to avoid being overlapped + // by the spinner in .tick() which prints repeatedly over the same line. + if shell::verbosity() >= 5 { + let project_root = find_project_root(None); + + self.send_msg(format!( + "Files to compile:\n{}", + dirty_files + .iter() + .map(|path| { + let trimmed_path = if let Ok(project_root) = &project_root { + path.strip_prefix(project_root).unwrap_or(path) + } else { + path + }; + format!("- {}", trimmed_path.display()) + }) + .sorted() + .format("\n") + )); + } + self.send_msg(format!( - "Compiling {} files with {}.{}.{}", + "Compiling {} files with {} {}.{}.{}", dirty_files.len(), + compiler_name, version.major, version.minor, version.patch )); - self.solc_io_report.log_compiler_input(input, version); } - fn on_solc_success( - &self, - _solc: &Solc, - version: &Version, - output: &CompilerOutput, - duration: &Duration, - ) { - self.solc_io_report.log_compiler_output(output, version); + fn on_compiler_success(&self, compiler_name: &str, version: &Version, duration: &Duration) { self.send_msg(format!( - "Solc {}.{}.{} finished in {duration:.2?}", - version.major, version.minor, version.patch + "{} {}.{}.{} finished in {duration:.2?}", + compiler_name, version.major, version.minor, version.patch )); } - /// Invoked before a new [`Solc`] bin is installed fn on_solc_installation_start(&self, version: &Version) { - self.send_msg(format!("Installing solc version {version}")); + self.send_msg(format!("Installing Solc version {version}")); } - /// Invoked before a new [`Solc`] bin was successfully installed fn on_solc_installation_success(&self, version: &Version) { - self.send_msg(format!("Successfully installed solc {version}")); + self.send_msg(format!("Successfully installed Solc {version}")); } fn on_solc_installation_error(&self, version: &Version, error: &str) { - self.send_msg(Paint::red(format!("Failed to install solc {version}: {error}")).to_string()); + self.send_msg(format!("Failed to install Solc {version}: {error}").red().to_string()); } fn on_unresolved_imports(&self, imports: &[(&Path, &Path)], remappings: &[Remapping]) { @@ -228,21 +224,6 @@ pub fn with_spinner_reporter(f: impl FnOnce() -> T) -> T { report::with_scoped(&reporter, f) } -#[macro_export] -/// Displays warnings on the cli -macro_rules! cli_warn { - ($($arg:tt)*) => { - eprintln!( - "{}{} {}", - yansi::Paint::yellow("warning").bold(), - yansi::Paint::new(":").bold(), - format_args!($($arg)*) - ) - } -} - -pub use cli_warn; - #[cfg(test)] mod tests { use super::*; diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index a80421a4b84e3..f5f3ea14ce460 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -1,96 +1,242 @@ -//! Commonly used traits +//! Commonly used traits. -use ethers_core::abi::Function; +use alloy_json_abi::Function; +use alloy_primitives::Bytes; +use alloy_sol_types::SolError; +use std::{fmt, path::Path}; -/// Extension trait for matching tests -#[auto_impl::auto_impl(&)] +/// Test filter. pub trait TestFilter: Send + Sync { - /// Returns whether the test should be included - fn matches_test(&self, test_name: impl AsRef) -> bool; - /// Returns whether the contract should be included - fn matches_contract(&self, contract_name: impl AsRef) -> bool; - /// Returns a contract with the given path should be included - fn matches_path(&self, path: impl AsRef) -> bool; + /// Returns whether the test should be included. + fn matches_test(&self, test_name: &str) -> bool; + + /// Returns whether the contract should be included. + fn matches_contract(&self, contract_name: &str) -> bool; + + /// Returns a contract with the given path should be included. + fn matches_path(&self, path: &Path) -> bool; } -/// Extension trait for `Function` -#[auto_impl::auto_impl(&)] +/// Extension trait for `Function`. pub trait TestFunctionExt { - /// Whether this function should be executed as invariant test - fn is_invariant_test(&self) -> bool; - /// Whether this function should be executed as fuzz test - fn is_fuzz_test(&self) -> bool; - /// Whether this function is a test - fn is_test(&self) -> bool; - /// Whether this function is a test that should fail - fn is_test_fail(&self) -> bool; - /// Whether this function is a `setUp` function - fn is_setup(&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 `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 `true` if this function is a `beforeTestSetup` function. + fn is_before_test_setup(&self) -> bool { + self.tfe_as_str().eq_ignore_ascii_case("beforetestsetup") + } + + /// Returns `true` if this function is a fuzz test. + fn is_fuzz_test(&self) -> bool { + self.test_function_kind().is_fuzz_test() + } + + /// Returns `true` if this function is an invariant test. + fn is_invariant_test(&self) -> bool { + self.test_function_kind().is_invariant_test() + } + + /// Returns `true` if this function is an `afterInvariant` function. + fn is_after_invariant(&self) -> bool { + self.test_function_kind().is_after_invariant() + } + + /// Returns `true` if this function is a `fixture` function. + fn is_fixture(&self) -> bool { + self.test_function_kind().is_fixture() + } + + #[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() +impl TestFunctionExt for String { + fn tfe_as_str(&self) -> &str { + self } - fn is_test_fail(&self) -> bool { - self.name.is_test_fail() + fn tfe_has_inputs(&self) -> bool { + false } +} - fn is_setup(&self) -> bool { - self.name.is_setup() +impl TestFunctionExt for str { + fn tfe_as_str(&self) -> &str { + self + } + + fn tfe_has_inputs(&self) -> bool { + false } } -impl<'a> TestFunctionExt for &'a str { - fn is_invariant_test(&self) -> bool { - self.starts_with("invariant") || self.starts_with("statefulFuzz") +/// 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, +} + +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_fuzz_test(&self) -> bool { - unimplemented!("no naming convention for fuzz tests.") + /// 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_test(&self) -> bool { - self.starts_with("test") + /// Returns `true` if this function is a `setUp` function. + #[inline] + pub const fn is_setup(&self) -> bool { + matches!(self, Self::Setup) } - fn is_test_fail(&self) -> bool { - self.starts_with("testFail") + /// 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_setup(&self) -> bool { - self.eq_ignore_ascii_case("setup") + /// 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 String { - fn is_invariant_test(&self) -> bool { - self.as_str().is_invariant_test() + /// 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 { - self.as_str().is_fuzz_test() + /// 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.as_str().is_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.as_str().is_test_fail() + /// 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.as_str().is_setup() + /// Returns `true` if this function is a `fixture` function. + #[inline] + pub const fn is_fixture(&self) -> bool { + matches!(self, Self::Fixture) + } + + /// Returns `true` if this function kind is known. + #[inline] + pub const fn is_known(&self) -> bool { + !matches!(self, Self::Unknown) + } + + /// 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) + } +} + +/// An extension trait for `std::error::Error` for ABI encoding. +pub trait ErrorExt: std::error::Error { + /// ABI-encodes the error using `Revert(string)`. + fn abi_encode_revert(&self) -> Bytes; +} + +impl ErrorExt for T { + fn abi_encode_revert(&self) -> Bytes { + alloy_sol_types::Revert::from(self.to_string()).abi_encode().into() } } diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index 78838bb957b49..128db763b71f0 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -1,15 +1,25 @@ -//! wrappers for transactions -use ethers_core::types::{BlockId, TransactionReceipt}; -use ethers_providers::Middleware; +//! Wrappers for transactions. + +use alloy_consensus::{Transaction, TxEnvelope}; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_network::AnyTransactionReceipt; +use alloy_primitives::{Address, TxKind, U256}; +use alloy_provider::{ + network::{AnyNetwork, ReceiptResponse, TransactionBuilder}, + Provider, +}; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_serde::WithOtherFields; use eyre::Result; +use foundry_common_fmt::UIfmt; use serde::{Deserialize, Serialize}; /// Helper type to carry a transaction along with an optional revert reason -#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TransactionReceiptWithRevertReason { /// The underlying transaction receipt #[serde(flatten)] - pub receipt: TransactionReceipt, + pub receipt: AnyTransactionReceipt, /// The revert reason string if the transaction status is failed #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] @@ -18,68 +28,152 @@ pub struct TransactionReceiptWithRevertReason { impl TransactionReceiptWithRevertReason { /// Returns if the status of the transaction is 0 (failure) - pub fn is_failure(&self) -> Option { - self.receipt.status.map(|status| status.as_u64() == 0) + pub fn is_failure(&self) -> bool { + !self.receipt.inner.inner.inner.receipt.status.coerce_status() } /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert /// reason was not successfully updated - pub async fn update_revert_reason(&mut self, provider: &M) -> Result<()> { + pub async fn update_revert_reason>( + &mut self, + provider: &P, + ) -> Result<()> { self.revert_reason = self.fetch_revert_reason(provider).await?; Ok(()) } - async fn fetch_revert_reason(&self, provider: &M) -> Result> { - if let Some(false) | None = self.is_failure() { + async fn fetch_revert_reason>( + &self, + provider: &P, + ) -> Result> { + if !self.is_failure() { return Ok(None) } - if let Some(ref transaction) = provider - .get_transaction(self.receipt.transaction_hash) + let transaction = provider + .get_transaction_by_hash(self.receipt.transaction_hash) .await - .map_err(|_| eyre::eyre!("unable to fetch transaction"))? - { - if let Some(block_hash) = self.receipt.block_hash { - match provider.call(&transaction.into(), Some(BlockId::Hash(block_hash))).await { - Err(e) => return Ok(extract_revert_reason(e.to_string())), - Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), - } + .map_err(|err| eyre::eyre!("unable to fetch transaction: {err}"))? + .ok_or_else(|| eyre::eyre!("transaction not found"))?; + + if let Some(block_hash) = self.receipt.block_hash { + match provider + .call(&transaction.inner.inner.into()) + .block(BlockId::Hash(block_hash.into())) + .await + { + Err(e) => return Ok(extract_revert_reason(e.to_string())), + Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), } - eyre::bail!("unable to fetch block_hash") } - Err(eyre::eyre!("transaction does not exist")) + eyre::bail!("unable to fetch block_hash") } } -impl From for TransactionReceiptWithRevertReason { - fn from(receipt: TransactionReceipt) -> Self { +impl From for TransactionReceiptWithRevertReason { + fn from(receipt: AnyTransactionReceipt) -> Self { Self { receipt, revert_reason: None } } } -impl From for TransactionReceipt { +impl From for AnyTransactionReceipt { fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { receipt_with_reason.receipt } } -fn extract_revert_reason>(error_string: S) -> Option { - let message_substr = "message: execution reverted: "; +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() + } + } +} - let mut temp = ""; +impl UIfmt for TransactionMaybeSigned { + fn pretty(&self) -> String { + match self { + Self::Signed { tx, .. } => tx.pretty(), + Self::Unsigned(tx) => format!( + " +accessList {} +chainId {} +gasLimit {} +gasPrice {} +input {} +maxFeePerBlobGas {} +maxFeePerGas {} +maxPriorityFeePerGas {} +nonce {} +to {} +type {} +value {}", + tx.access_list + .as_ref() + .map(|a| a.iter().collect::>()) + .unwrap_or_default() + .pretty(), + tx.chain_id.pretty(), + tx.gas_limit().unwrap_or_default(), + tx.gas_price.pretty(), + tx.input.input.pretty(), + tx.max_fee_per_blob_gas.pretty(), + tx.max_fee_per_gas.pretty(), + tx.max_priority_fee_per_gas.pretty(), + tx.nonce.pretty(), + tx.to.as_ref().map(|a| a.to()).unwrap_or_default().pretty(), + tx.transaction_type.unwrap_or_default(), + tx.value.pretty(), + ), + } + } +} +fn extract_revert_reason>(error_string: S) -> Option { + let message_substr = "execution reverted: "; error_string .as_ref() .find(message_substr) - .and_then(|index| { - let (_, rest) = error_string.as_ref().split_at(index + message_substr.len()); - temp = rest; - rest.rfind(", ") - }) - .map(|index| { - let (reason, _) = temp.split_at(index); - reason.to_string() - }) + .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)] @@ -88,16 +182,114 @@ mod tests { #[test] fn test_extract_revert_reason() { - let error_string_1 = "(code: 3, message: execution reverted: Transaction too old, data: Some(String(\"0x08c379a0\")))"; - let error_string_2 = "(code: 3, message: execution reverted: missing data: amountIn, amountOut, data: Some(String(\"0x08c379a0\")))"; - let error_string_3 = - "(code: 4, message: invalid signature, data: Some(String(\"0x08c379a0\")))"; + let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; + let error_string_2 = "server returned an error response: error code 3: Invalid signature"; assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); - assert_eq!( - extract_revert_reason(error_string_2), - Some("missing data: amountIn, amountOut".to_string()) - ); - assert_eq!(extract_revert_reason(error_string_3), None); + assert_eq!(extract_revert_reason(error_string_2), None); + } +} + +/// Used for broadcasting transactions +/// A transaction can either be a [`TransactionRequest`] waiting to be signed +/// or a [`TxEnvelope`], already signed +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum TransactionMaybeSigned { + Signed { + #[serde(flatten)] + tx: TxEnvelope, + from: Address, + }, + Unsigned(WithOtherFields), +} + +impl TransactionMaybeSigned { + /// Creates a new (unsigned) transaction for broadcast + pub fn new(tx: WithOtherFields) -> Self { + Self::Unsigned(tx) + } + + /// Creates a new signed transaction for broadcast. + pub fn new_signed( + tx: TxEnvelope, + ) -> core::result::Result { + let from = tx.recover_signer()?; + Ok(Self::Signed { tx, from }) + } + + pub fn is_unsigned(&self) -> bool { + matches!(self, Self::Unsigned(_)) + } + + pub fn as_unsigned_mut(&mut self) -> Option<&mut WithOtherFields> { + match self { + Self::Unsigned(tx) => Some(tx), + _ => None, + } + } + + pub fn from(&self) -> Option
{ + match self { + Self::Signed { from, .. } => Some(*from), + Self::Unsigned(tx) => tx.from, + } + } + + pub fn input(&self) -> Option<&[u8]> { + match self { + Self::Signed { tx, .. } => Some(tx.input()), + Self::Unsigned(tx) => tx.input.input().map(|i| i.as_ref()), + } + } + + pub fn to(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.kind()), + Self::Unsigned(tx) => tx.to, + } + } + + pub fn value(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.value()), + Self::Unsigned(tx) => tx.value, + } + } + + pub fn gas(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.gas_limit() as u128), + Self::Unsigned(tx) => tx.gas_limit().map(|g| g as u128), + } + } + + pub fn nonce(&self) -> Option { + match self { + Self::Signed { tx, .. } => Some(tx.nonce()), + Self::Unsigned(tx) => tx.nonce, + } + } + + pub fn authorization_list(&self) -> Option> { + match self { + Self::Signed { tx, .. } => tx.authorization_list().map(|auths| auths.to_vec()), + Self::Unsigned(tx) => tx.authorization_list.as_deref().map(|auths| auths.to_vec()), + } + .filter(|auths| !auths.is_empty()) + } +} + +impl From for TransactionMaybeSigned { + fn from(tx: TransactionRequest) -> Self { + Self::new(WithOtherFields::new(tx)) + } +} + +impl TryFrom for TransactionMaybeSigned { + type Error = alloy_primitives::SignatureError; + + fn try_from(tx: TxEnvelope) -> core::result::Result { + Self::new_signed(tx) } } diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs new file mode 100644 index 0000000000000..6e8c374ebc047 --- /dev/null +++ b/crates/common/src/utils.rs @@ -0,0 +1,39 @@ +//! Uncategorised utilities. + +use alloy_primitives::{keccak256, B256, U256}; +/// Block on a future using the current tokio runtime on the current thread. +pub fn block_on(future: F) -> F::Output { + block_on_handle(&tokio::runtime::Handle::current(), future) +} + +/// Block on a future using the current tokio runtime on the current thread with the given handle. +pub fn block_on_handle( + handle: &tokio::runtime::Handle, + future: F, +) -> F::Output { + tokio::task::block_in_place(|| handle.block_on(future)) +} + +/// Computes the storage slot as specified by `ERC-7201`, using the `erc7201` formula ID. +/// +/// This is defined as: +/// +/// ```text +/// erc7201(id: string) = keccak256(keccak256(id) - 1) & ~0xff +/// ``` +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::b256; +/// use foundry_common::erc7201; +/// +/// assert_eq!( +/// erc7201("example.main"), +/// b256!("183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500"), +/// ); +/// ``` +pub fn erc7201(id: &str) -> B256 { + let x = U256::from_be_bytes(keccak256(id).0) - U256::from(1); + keccak256(x.to_be_bytes::<32>()) & B256::from(!U256::from(0xff)) +} diff --git a/crates/common/src/version.rs b/crates/common/src/version.rs new file mode 100644 index 0000000000000..f69457bf7c1e3 --- /dev/null +++ b/crates/common/src/version.rs @@ -0,0 +1,27 @@ +//! Foundry version information. + +/// The SemVer compatible version information for Foundry. +pub const SEMVER_VERSION: &str = env!("FOUNDRY_SEMVER_VERSION"); + +/// The short version message information for the Foundry CLI. +pub const SHORT_VERSION: &str = env!("FOUNDRY_SHORT_VERSION"); + +/// The long version message information for the Foundry CLI. +pub const LONG_VERSION: &str = concat!( + env!("FOUNDRY_LONG_VERSION_0"), + "\n", + env!("FOUNDRY_LONG_VERSION_1"), + "\n", + env!("FOUNDRY_LONG_VERSION_2"), + "\n", + env!("FOUNDRY_LONG_VERSION_3"), +); + +/// Whether the version is a nightly build. +pub const IS_NIGHTLY_VERSION: bool = option_env!("FOUNDRY_IS_NIGHTLY_VERSION").is_some(); + +/// The warning message for nightly versions. +pub const NIGHTLY_VERSION_WARNING_MESSAGE: &str = + "This is a nightly build of Foundry. It is recommended to use the latest stable version. \ + Visit https://book.getfoundry.sh/announcements for more information. \n\ + To mute this warning set `FOUNDRY_DISABLE_NIGHTLY_WARNING` in your environment. \n"; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index d24450f0ab3e2..2d2b80b4ad695 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -10,43 +10,49 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -# eth -ethers-core.workspace = true -ethers-solc = { workspace = true, features = ["async", "svm-solc"] } -ethers-etherscan.workspace = true +foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } +foundry-compilers = { workspace = true, features = ["svm-solc"] } + +alloy-chains = { workspace = true, features = ["serde"] } +alloy-primitives = { workspace = true, features = ["serde"] } +revm-primitives.workspace = true + +solar-parse.workspace = true -# formats +dirs.workspace = true +dunce.workspace = true +eyre.workspace = true +figment = { workspace = true, features = ["toml", "env"] } +glob = "0.3" +globset = "0.4" Inflector = "0.11" -figment = { version = "0.10", features = ["toml", "env"] } +itertools.workspace = true +mesc.workspace = true number_prefix = "0.4" -serde = { version = "1", features = ["derive"] } +regex.workspace = true +reqwest.workspace = true +semver = { workspace = true, features = ["serde"] } +serde_json.workspace = true serde_regex = "1" -serde_json = "1" -toml = { version = "0.7", features = ["preserve_order"] } -toml_edit = "0.19" - -# dirs -dirs-next = "2" -globset = "0.4" -walkdir = "2" - -# encoding -open-fastrlp = "0.1" - -# misc -eyre = "0.6" -regex = "1" -semver = { version = "1", features = ["serde"] } -tracing = "0.1" -once_cell = "1" -thiserror = "1" -reqwest = { version = "0.11", default-features = false } +serde.workspace = true +thiserror.workspace = true +toml = { workspace = true, features = ["preserve_order"] } +toml_edit = "0.22" +tracing.workspace = true +walkdir.workspace = true +yansi.workspace = true [target.'cfg(target_os = "windows")'.dependencies] -path-slash = "0.2.1" +path-slash = "0.2" [dev-dependencies] -pretty_assertions = "1" -figment = { version = "0.10", features = ["test"] } -tempfile = "3" +similar-asserts.workspace = true +figment = { workspace = true, features = ["test"] } +tempfile.workspace = true + +[features] +isolate-by-default = [] diff --git a/crates/config/README.md b/crates/config/README.md index 1913cadeeb99c..be3055f5816f7 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -1,11 +1,11 @@ # Configuration -Foundry's configuration system allows you to configure it's tools the way _you_ want while also providing with a +Foundry's configuration system allows you to configure its tools the way _you_ want while also providing with a sensible set of defaults. ## Profiles -Configurations can be arbitrarily namespaced by profiles. Foundry's default config is also named `default`, but can +Configurations can be arbitrarily namespaced with profiles. Foundry's default config is also named `default`, but you can arbitrarily name and configure profiles as you like and set the `FOUNDRY_PROFILE` environment variable to the selected profile's name. This results in foundry's tools (forge) preferring the values in the profile with the named that's set in `FOUNDRY_PROFILE`. But all custom profiles inherit from the `default` profile. @@ -56,7 +56,7 @@ The selected profile is the value of the `FOUNDRY_PROFILE` environment variable, ### All Options -The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](/config/src/lib.rs) and [/cli/tests/it/config.rs](/cli/tests/it/config.rs). +The following is a foundry.toml file with all configuration options set. See also [/config/src/lib.rs](./src/lib.rs) and [/cli/tests/it/config.rs](../forge/tests/it/config.rs). ```toml ## defaults for _all_ profiles @@ -86,7 +86,7 @@ gas_reports_ignore = [] # solc = '0.8.10' auto_detect_solc = true offline = false -optimizer = true +optimizer = false optimizer_runs = 200 model_checker = { contracts = { 'a.sol' = [ 'A1', @@ -100,12 +100,13 @@ model_checker = { contracts = { 'a.sol' = [ ], timeout = 10000 } verbosity = 0 eth_rpc_url = "https://example.com/" -# Setting this option enables decoding of error traces from mainnet deployed / verfied contracts via etherscan +# Setting this option enables decoding of error traces from mainnet deployed / verified contracts via etherscan etherscan_api_key = "YOURETHERSCANAPIKEY" # ignore solc warnings for missing license and exceeded contract size -# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname"] +# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname", "too-many-warnings", "constructor-visibility", "init-code-size", "missing-receive-ether", "unnamed-return", "transient-storage"] # additional warnings can be added using their numeric error code: ["license", 1337] ignored_error_codes = ["license", "code-size"] +ignored_warnings_from = ["path_to_ignore"] deny_warnings = false match_test = "Foo" no_match_test = "Bar" @@ -113,7 +114,14 @@ match_contract = "Foo" no_match_contract = "Bar" match_path = "*/Foo*" no_match_path = "*/Bar*" +no_match_coverage = "Baz" +# Number of threads to use. Specifying 0 defaults to the number of logical cores. +threads = 0 +# whether to show test execution progress +show_progress = true ffi = false +always_use_create_2_factory = false +prompt_timeout = 120 # These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' @@ -121,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' @@ -131,12 +140,13 @@ block_timestamp = 0 block_difficulty = 0 block_prevrandao = '0x0000000000000000000000000000000000000000' block_gas_limit = 30000000 -memory_limit = 33554432 +memory_limit = 134217728 extra_output = ["metadata"] extra_output_files = [] names = false sizes = false via_ir = false +ast = false # caches storage retrieved locally for certain chains and endpoints # can also be restricted to `chains = ["optimism", "mainnet"]` # by default all endpoints will be cached, alternative options are "remote" for only caching non localhost endpoints and "" @@ -149,7 +159,7 @@ use_literal_content = false # use ipfs method to generate the metadata hash, solc's default. # To not include the metadata hash, to allow for deterministic code: https://docs.soliditylang.org/en/latest/metadata.html, use "none" bytecode_hash = "ipfs" -# Whether to append the metadata hash to the bytecode +# Whether to append the CBOR-encoded metadata file. cbor_metadata = true # How to treat revert (and require) reason strings. # Possible values are: "default", "strip", "debug" and "verboseDebug". @@ -175,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 @@ -185,13 +200,13 @@ include_push_bytes = true [invariant] runs = 256 -depth = 15 +depth = 500 fail_on_revert = false call_override = false dictionary_weight = 80 include_storage = true include_push_bytes = true -shrink_sequence = true +shrink_run_limit = 5000 [fmt] line_length = 100 @@ -249,7 +264,7 @@ The optional `url` attribute can be used to explicitly set the Etherscan API url [etherscan] mainnet = { key = "${ETHERSCAN_MAINNET_KEY}" } mainnet2 = { key = "ABCDEFG", chain = "mainnet" } -optimism = { key = "1234576" } +optimism = { key = "1234576", chain = 42 } unknownchain = { key = "ABCDEFG", url = "https://" } ``` @@ -298,5 +313,5 @@ supported, this means that `FOUNDRY_SRC` and `DAPP_SRC` are equivalent. Some exceptions to the above are [explicitly ignored](https://github.com/foundry-rs/foundry/blob/10440422e63aae660104e079dfccd5b0ae5fd720/config/src/lib.rs#L1539-L15522) due to security concerns. -Environment variables take precedence over values in `foundry.toml`. Values are parsed as loose form of TOML syntax. +Environment variables take precedence over values in `foundry.toml`. Values are parsed as a loose form of TOML syntax. Consider the following examples: diff --git a/crates/config/src/bind_json.rs b/crates/config/src/bind_json.rs new file mode 100644 index 0000000000000..71d8d41aa911b --- /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 315edd3e3ad5b..d087b5e6a2c2e 100644 --- a/crates/config/src/cache.rs +++ b/crates/config/src/cache.rs @@ -1,16 +1,16 @@ //! Support types for configuring storage caching -use crate::chain::Chain; +use crate::Chain; use number_prefix::NumberPrefix; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, fmt::Formatter, str::FromStr}; -/// Settings to configure caching of remote -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +/// Settings to configure caching of remote. +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct StorageCachingConfig { - /// chains to cache + /// Chains to cache. pub chains: CachedChains, - /// endpoints to cache + /// Endpoints to cache. pub endpoints: CachedEndpoints, } @@ -31,7 +31,7 @@ impl StorageCachingConfig { } /// What chains to cache -#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub enum CachedChains { /// Cache all chains #[default] @@ -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,17 +79,17 @@ 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)), } } } /// What endpoints to enable caching for -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub enum CachedEndpoints { /// Cache all endpoints #[default] @@ -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()), } } } @@ -185,27 +183,27 @@ impl fmt::Display for Cache { chain.block_explorer as f32 + chain.blocks.iter().map(|x| x.1).sum::() as f32, ) { NumberPrefix::Standalone(size) => { - writeln!(f, "-️ {} ({size:.1} B)", chain.name)?; + writeln!(f, "- {} ({size:.1} B)", chain.name)?; } NumberPrefix::Prefixed(prefix, size) => { - writeln!(f, "-️ {} ({size:.1} {prefix}B)", chain.name)?; + writeln!(f, "- {} ({size:.1} {prefix}B)", chain.name)?; } } match NumberPrefix::decimal(chain.block_explorer as f32) { NumberPrefix::Standalone(size) => { - writeln!(f, "\t-️ Block Explorer ({size:.1} B)\n")?; + writeln!(f, "\t- Block Explorer ({size:.1} B)\n")?; } NumberPrefix::Prefixed(prefix, size) => { - writeln!(f, "\t-️ Block Explorer ({size:.1} {prefix}B)\n")?; + writeln!(f, "\t- Block Explorer ({size:.1} {prefix}B)\n")?; } } for block in &chain.blocks { match NumberPrefix::decimal(block.1 as f32) { NumberPrefix::Standalone(size) => { - writeln!(f, "\t-️ Block {} ({size:.1} B)", block.0)?; + writeln!(f, "\t- Block {} ({size:.1} B)", block.0)?; } NumberPrefix::Prefixed(prefix, size) => { - writeln!(f, "\t-️ Block {} ({size:.1} {prefix}B)", block.0)?; + writeln!(f, "\t- Block {} ({size:.1} {prefix}B)", block.0)?; } } } @@ -229,9 +227,8 @@ pub struct ChainCache { #[cfg(test)] mod tests { - use pretty_assertions::assert_str_eq; - use super::*; + use similar_asserts::assert_eq; #[test] fn can_parse_storage_config() { @@ -255,11 +252,11 @@ mod tests { w.rpc_storage_caching, StorageCachingConfig { chains: CachedChains::Chains(vec![ - Chain::Named(ethers_core::types::Chain::Mainnet), - Chain::Named(ethers_core::types::Chain::Optimism), - Chain::Id(999999) + Chain::mainnet(), + Chain::optimism_mainnet(), + Chain::from_id(999999) ]), - endpoints: CachedEndpoints::All + endpoints: CachedEndpoints::All, } ) } @@ -292,22 +289,22 @@ mod tests { }; let expected = "\ - -️ mainnet (503.0 B)\n\t\ - -️ Block Explorer (500.0 B)\n\n\t\ - -️ Block 1 (1.0 B)\n\t\ - -️ Block 2 (2.0 B)\n\ - -️ ropsten (4.6 kB)\n\t\ - -️ Block Explorer (4.6 kB)\n\n\t\ - -️ Block 1 (1.0 B)\n\t\ - -️ Block 2 (2.0 B)\n\ - -️ rinkeby (6.2 MB)\n\t\ - -️ Block Explorer (4.2 MB)\n\n\t\ - -️ Block 1 (1.0 kB)\n\t\ - -️ Block 2 (2.0 MB)\n\ - -️ mumbai (3.0 B)\n\t\ - -️ Block Explorer (0.0 B)\n\n\t\ - -️ Block 1 (1.0 B)\n\t\ - -️ Block 2 (2.0 B)\n"; - assert_str_eq!(format!("{cache}"), expected); + - mainnet (503.0 B)\n\t\ + - Block Explorer (500.0 B)\n\n\t\ + - Block 1 (1.0 B)\n\t\ + - Block 2 (2.0 B)\n\ + - ropsten (4.6 kB)\n\t\ + - Block Explorer (4.6 kB)\n\n\t\ + - Block 1 (1.0 B)\n\t\ + - Block 2 (2.0 B)\n\ + - rinkeby (6.2 MB)\n\t\ + - Block Explorer (4.2 MB)\n\n\t\ + - Block 1 (1.0 kB)\n\t\ + - Block 2 (2.0 MB)\n\ + - mumbai (3.0 B)\n\t\ + - Block Explorer (0.0 B)\n\n\t\ + - Block 1 (1.0 B)\n\t\ + - Block 2 (2.0 B)\n"; + assert_eq!(format!("{cache}"), expected); } } diff --git a/crates/config/src/chain.rs b/crates/config/src/chain.rs deleted file mode 100644 index 8e60eafdff701..0000000000000 --- a/crates/config/src/chain.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::U256; -use ethers_core::types::{Chain as NamedChain, U64}; -use eyre::Result; -use open_fastrlp::{Decodable, Encodable}; -use serde::{Deserialize, Deserializer, Serialize}; -use std::{fmt, str::FromStr}; - -/// Either a named or chain id or the actual id value -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] -#[serde(untagged)] -pub enum Chain { - /// Contains a known chain - #[serde(serialize_with = "super::from_str_lowercase::serialize")] - Named(NamedChain), - /// Contains the id of a chain - Id(u64), -} - -impl Chain { - /// The id of the chain. - pub const fn id(&self) -> u64 { - match self { - Chain::Named(chain) => *chain as u64, - Chain::Id(id) => *id, - } - } - - /// Returns the wrapped named chain or tries converting the ID into one. - pub fn named(&self) -> Result { - match self { - Self::Named(chain) => Ok(*chain), - Self::Id(id) => { - NamedChain::try_from(*id).map_err(|_| eyre::eyre!("Unsupported chain: {id}")) - } - } - } - - /// Helper function for checking if a chainid corresponds to a legacy chainid - /// without eip1559 - pub fn is_legacy(&self) -> bool { - self.named().map_or(false, |c| c.is_legacy()) - } - - /// Returns the corresponding etherscan URLs - pub fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> { - self.named().ok().and_then(|c| c.etherscan_urls()) - } -} - -impl fmt::Display for Chain { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Chain::Named(chain) => chain.fmt(f), - Chain::Id(id) => { - if let Ok(chain) = NamedChain::try_from(*id) { - chain.fmt(f) - } else { - id.fmt(f) - } - } - } - } -} - -impl From for Chain { - fn from(id: NamedChain) -> Self { - Chain::Named(id) - } -} - -impl From for Chain { - fn from(id: u64) -> Self { - NamedChain::try_from(id).map(Chain::Named).unwrap_or_else(|_| Chain::Id(id)) - } -} - -impl From for Chain { - fn from(id: U256) -> Self { - id.as_u64().into() - } -} - -impl From for u64 { - fn from(c: Chain) -> Self { - match c { - Chain::Named(c) => c as u64, - Chain::Id(id) => id, - } - } -} - -impl From for U64 { - fn from(c: Chain) -> Self { - u64::from(c).into() - } -} - -impl From for U256 { - fn from(c: Chain) -> Self { - u64::from(c).into() - } -} - -impl TryFrom for NamedChain { - type Error = >::Error; - - fn try_from(chain: Chain) -> Result { - match chain { - Chain::Named(chain) => Ok(chain), - Chain::Id(id) => id.try_into(), - } - } -} - -impl FromStr for Chain { - type Err = String; - - fn from_str(s: &str) -> Result { - if let Ok(chain) = NamedChain::from_str(s) { - Ok(Chain::Named(chain)) - } else { - s.parse::() - .map(Chain::Id) - .map_err(|_| format!("Expected known chain or integer, found: {s}")) - } - } -} - -impl<'de> Deserialize<'de> for Chain { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(untagged)] - enum ChainId { - Named(String), - Id(u64), - } - - match ChainId::deserialize(deserializer)? { - ChainId::Named(s) => { - s.to_lowercase().parse().map(Chain::Named).map_err(serde::de::Error::custom) - } - ChainId::Id(id) => { - Ok(NamedChain::try_from(id).map(Chain::Named).unwrap_or_else(|_| Chain::Id(id))) - } - } - } -} - -impl Encodable for Chain { - fn length(&self) -> usize { - match self { - Self::Named(chain) => u64::from(*chain).length(), - Self::Id(id) => id.length(), - } - } - fn encode(&self, out: &mut dyn open_fastrlp::BufMut) { - match self { - Self::Named(chain) => u64::from(*chain).encode(out), - Self::Id(id) => id.encode(out), - } - } -} - -impl Decodable for Chain { - fn decode(buf: &mut &[u8]) -> Result { - Ok(u64::decode(buf)?.into()) - } -} - -impl Default for Chain { - fn default() -> Self { - NamedChain::Mainnet.into() - } -} diff --git a/crates/config/src/compilation.rs b/crates/config/src/compilation.rs new file mode 100644 index 0000000000000..8bb48f525bb54 --- /dev/null +++ b/crates/config/src/compilation.rs @@ -0,0 +1,119 @@ +use crate::{filter::GlobMatcher, serde_helpers}; +use foundry_compilers::{ + artifacts::{BytecodeHash, EvmVersion}, + multi::{MultiCompilerRestrictions, MultiCompilerSettings}, + settings::VyperRestrictions, + solc::{Restriction, SolcRestrictions}, + RestrictionsWithVersion, +}; +use semver::VersionReq; +use serde::{Deserialize, Serialize}; + +/// Keeps possible overrides for default settings which users may configure to construct additional +/// settings profile. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct SettingsOverrides { + pub name: String, + pub via_ir: Option, + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub evm_version: Option, + pub optimizer: Option, + pub optimizer_runs: Option, + pub bytecode_hash: Option, +} + +impl SettingsOverrides { + /// Applies the overrides to the given settings. + pub fn apply(&self, settings: &mut MultiCompilerSettings) { + if let Some(via_ir) = self.via_ir { + settings.solc.via_ir = Some(via_ir); + } + + if let Some(evm_version) = self.evm_version { + settings.solc.evm_version = Some(evm_version); + settings.vyper.evm_version = Some(evm_version); + } + + if let Some(enabled) = self.optimizer { + settings.solc.optimizer.enabled = Some(enabled); + } + + if let Some(optimizer_runs) = self.optimizer_runs { + settings.solc.optimizer.runs = Some(optimizer_runs); + // Enable optimizer in optimizer runs set to a higher value than 0. + if optimizer_runs > 0 && self.optimizer.is_none() { + settings.solc.optimizer.enabled = Some(true); + } + } + + if let Some(bytecode_hash) = self.bytecode_hash { + if let Some(metadata) = settings.solc.metadata.as_mut() { + metadata.bytecode_hash = Some(bytecode_hash); + } else { + settings.solc.metadata = Some(bytecode_hash.into()); + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RestrictionsError { + #[error("specified both exact and relative restrictions for {0}")] + BothExactAndRelative(&'static str), +} + +/// Restrictions for compilation of given paths. +/// +/// Only purpose of this type is to accept user input to later construct +/// `RestrictionsWithVersion`. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct CompilationRestrictions { + pub paths: GlobMatcher, + pub version: Option, + pub via_ir: Option, + pub bytecode_hash: Option, + + pub min_optimizer_runs: Option, + pub optimizer_runs: Option, + pub max_optimizer_runs: Option, + + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub min_evm_version: Option, + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub evm_version: Option, + #[serde(default, with = "serde_helpers::display_from_str_opt")] + pub max_evm_version: Option, +} + +impl TryFrom for RestrictionsWithVersion { + type Error = RestrictionsError; + + fn try_from(value: CompilationRestrictions) -> Result { + let (min_evm, max_evm) = + match (value.min_evm_version, value.max_evm_version, value.evm_version) { + (None, None, Some(exact)) => (Some(exact), Some(exact)), + (min, max, None) => (min, max), + _ => return Err(RestrictionsError::BothExactAndRelative("evm_version")), + }; + let (min_opt, max_opt) = + match (value.min_optimizer_runs, value.max_optimizer_runs, value.optimizer_runs) { + (None, None, Some(exact)) => (Some(exact), Some(exact)), + (min, max, None) => (min, max), + _ => return Err(RestrictionsError::BothExactAndRelative("optimizer_runs")), + }; + Ok(Self { + restrictions: MultiCompilerRestrictions { + solc: SolcRestrictions { + evm_version: Restriction { min: min_evm, max: max_evm }, + via_ir: value.via_ir, + optimizer_runs: Restriction { min: min_opt, max: max_opt }, + bytecode_hash: value.bytecode_hash, + }, + vyper: VyperRestrictions { + evm_version: Restriction { min: min_evm, max: max_evm }, + }, + }, + version: value.version, + }) + } +} diff --git a/crates/config/src/doc.rs b/crates/config/src/doc.rs index 2dfac01b4df44..36e8a12b08df2 100644 --- a/crates/config/src/doc.rs +++ b/crates/config/src/doc.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// Contains the config for parsing and rendering docs -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct DocConfig { /// Doc output path. pub out: PathBuf, diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 1f0708c109694..1758e6a4870bf 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -1,7 +1,7 @@ //! Support for multiple RPC-endpoints use crate::resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use std::{ collections::BTreeMap, fmt, @@ -9,18 +9,26 @@ use std::{ }; /// Container type for API endpoints, like various RPC endpoints -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct RpcEndpoints { endpoints: BTreeMap, } -// === impl RpcEndpoints === - impl RpcEndpoints { /// Creates a new list of endpoints - pub fn new(endpoints: impl IntoIterator, RpcEndpoint)>) -> Self { - Self { endpoints: endpoints.into_iter().map(|(name, url)| (name.into(), url)).collect() } + pub fn new( + endpoints: impl IntoIterator, impl Into)>, + ) -> Self { + Self { + endpoints: endpoints + .into_iter() + .map(|(name, e)| match e.into() { + RpcEndpointType::String(url) => (name.into(), RpcEndpoint::new(url)), + RpcEndpointType::Config(config) => (name.into(), config), + }) + .collect(), + } } /// Returns `true` if this type doesn't contain any endpoints @@ -28,7 +36,7 @@ impl RpcEndpoints { self.endpoints.is_empty() } - /// Returns all (alias -> url) pairs + /// Returns all (alias -> rpc_endpoint) pairs pub fn resolved(self) -> ResolvedRpcEndpoints { ResolvedRpcEndpoints { endpoints: self.endpoints.into_iter().map(|(name, e)| (name, e.resolve())).collect(), @@ -44,14 +52,74 @@ impl Deref for RpcEndpoints { } } +/// RPC endpoint wrapper type +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RpcEndpointType { + /// Raw Endpoint url string + String(RpcEndpointUrl), + /// Config object + Config(RpcEndpoint), +} + +impl RpcEndpointType { + /// Returns the string variant + pub fn as_endpoint_string(&self) -> Option<&RpcEndpointUrl> { + match self { + Self::String(url) => Some(url), + Self::Config(_) => None, + } + } + + /// Returns the config variant + pub fn as_endpoint_config(&self) -> Option<&RpcEndpoint> { + match self { + Self::Config(config) => Some(config), + Self::String(_) => None, + } + } + + /// Returns the url or config this type holds + /// + /// # Error + /// + /// 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 { + Self::String(url) => url.resolve(), + Self::Config(config) => config.endpoint.resolve(), + } + } +} + +impl fmt::Display for RpcEndpointType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::String(url) => url.fmt(f), + Self::Config(config) => config.fmt(f), + } + } +} + +impl TryFrom for String { + type Error = UnresolvedEnvVarError; + + fn try_from(value: RpcEndpointType) -> Result { + match value { + RpcEndpointType::String(url) => url.resolve(), + RpcEndpointType::Config(config) => config.endpoint.resolve(), + } + } +} + /// Represents a single endpoint /// /// This type preserves the value as it's stored in the config. If the value is a reference to an /// env var, then the `Endpoint::Env` var will hold the reference (`${MAIN_NET}`) and _not_ the /// value of the env var itself. /// In other words, this type does not resolve env vars when it's being deserialized -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RpcEndpoint { +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RpcEndpointUrl { /// A raw Url (ws, http) Url(String), /// An endpoint that contains at least one `${ENV_VAR}` placeholder @@ -60,22 +128,20 @@ pub enum RpcEndpoint { Env(String), } -// === impl RpcEndpoint === - -impl RpcEndpoint { +impl RpcEndpointUrl { /// 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, } } @@ -86,30 +152,30 @@ 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), } } } -impl fmt::Display for RpcEndpoint { +impl fmt::Display for RpcEndpointUrl { 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), } } } -impl TryFrom for String { +impl TryFrom for String { type Error = UnresolvedEnvVarError; - fn try_from(value: RpcEndpoint) -> Result { + fn try_from(value: RpcEndpointUrl) -> Result { value.resolve() } } -impl Serialize for RpcEndpoint { +impl Serialize for RpcEndpointUrl { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -118,41 +184,287 @@ impl Serialize for RpcEndpoint { } } -impl<'de> Deserialize<'de> for RpcEndpoint { +impl<'de> Deserialize<'de> for RpcEndpointUrl { fn deserialize(deserializer: D) -> Result where 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) } } -/// Container type for _resolved_ endpoints, see [RpcEndpoints::resolve_all()] -#[derive(Debug, Clone, PartialEq, Eq, Default)] -pub struct ResolvedRpcEndpoints { - /// contains all named endpoints and their URL or an error if we failed to resolve the env var - /// alias - endpoints: BTreeMap>, +impl From for RpcEndpointType { + fn from(endpoint: RpcEndpointUrl) -> Self { + Self::String(endpoint) + } +} + +impl From for RpcEndpoint { + fn from(endpoint: RpcEndpointUrl) -> Self { + Self { endpoint, ..Default::default() } + } } -// === impl ResolvedEndpoints === +/// The auth token to be used for RPC endpoints +/// It works in the same way as the `RpcEndpoint` type, where it can be a raw string or a reference +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RpcAuth { + Raw(String), + Env(String), +} + +impl RpcAuth { + /// Returns the auth token this type holds + /// + /// # Error + /// + /// 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 { + Self::Raw(raw_auth) => Ok(raw_auth), + Self::Env(var) => interpolate(&var), + } + } +} + +impl fmt::Display for RpcAuth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Raw(url) => url.fmt(f), + Self::Env(var) => var.fmt(f), + } + } +} + +impl Serialize for RpcAuth { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for RpcAuth { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let val = String::deserialize(deserializer)?; + let auth = if RE_PLACEHOLDER.is_match(&val) { Self::Env(val) } else { Self::Raw(val) }; + + Ok(auth) + } +} + +// Rpc endpoint configuration +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct RpcEndpointConfig { + /// The number of retries. + pub retries: Option, + + /// Initial retry backoff. + pub retry_backoff: Option, + + /// The available compute units per second. + /// + /// See also + pub compute_units_per_second: Option, +} + +impl fmt::Display for RpcEndpointConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { retries, retry_backoff, compute_units_per_second } = self; + + if let Some(retries) = retries { + write!(f, ", retries={retries}")?; + } + + if let Some(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}")?; + } + + Ok(()) + } +} + +/// Rpc endpoint configuration variant +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RpcEndpoint { + /// endpoint url or env + pub endpoint: RpcEndpointUrl, + + /// Token to be used as authentication + pub auth: Option, + + /// additional configuration + pub config: RpcEndpointConfig, +} + +impl RpcEndpoint { + pub fn new(endpoint: RpcEndpointUrl) -> Self { + Self { endpoint, ..Default::default() } + } + + /// Resolves environment variables in fields into their raw values + pub fn resolve(self) -> ResolvedRpcEndpoint { + ResolvedRpcEndpoint { + endpoint: self.endpoint.resolve(), + auth: self.auth.map(|auth| auth.resolve()), + config: self.config, + } + } +} + +impl fmt::Display for RpcEndpoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { endpoint, auth, config } = self; + write!(f, "{endpoint}")?; + write!(f, "{config}")?; + if let Some(auth) = auth { + write!(f, ", auth={auth}")?; + } + Ok(()) + } +} + +impl Serialize for RpcEndpoint { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.config.retries.is_none() && + self.config.retry_backoff.is_none() && + self.config.compute_units_per_second.is_none() && + self.auth.is_none() + { + // serialize as endpoint if there's no additional config + self.endpoint.serialize(serializer) + } else { + let mut map = serializer.serialize_map(Some(4))?; + map.serialize_entry("endpoint", &self.endpoint)?; + map.serialize_entry("retries", &self.config.retries)?; + map.serialize_entry("retry_backoff", &self.config.retry_backoff)?; + map.serialize_entry("compute_units_per_second", &self.config.compute_units_per_second)?; + map.serialize_entry("auth", &self.auth)?; + map.end() + } + } +} + +impl<'de> Deserialize<'de> for RpcEndpoint { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = serde_json::Value::deserialize(deserializer)?; + if value.is_string() { + return Ok(Self { + endpoint: serde_json::from_value(value).map_err(serde::de::Error::custom)?, + ..Default::default() + }); + } + + #[derive(Deserialize)] + struct RpcEndpointConfigInner { + #[serde(alias = "url")] + endpoint: RpcEndpointUrl, + retries: Option, + retry_backoff: Option, + compute_units_per_second: Option, + auth: Option, + } + + let RpcEndpointConfigInner { + endpoint, + retries, + retry_backoff, + compute_units_per_second, + auth, + } = serde_json::from_value(value).map_err(serde::de::Error::custom)?; + + Ok(Self { + endpoint, + auth, + config: RpcEndpointConfig { retries, retry_backoff, compute_units_per_second }, + }) + } +} + +impl From for RpcEndpointType { + fn from(config: RpcEndpoint) -> Self { + Self::Config(config) + } +} + +impl Default for RpcEndpoint { + fn default() -> Self { + Self { + endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + config: RpcEndpointConfig::default(), + auth: None, + } + } +} + +/// Rpc endpoint with environment variables resolved to values, see [`RpcEndpoint::resolve`]. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ResolvedRpcEndpoint { + pub endpoint: Result, + pub auth: Option>, + pub config: RpcEndpointConfig, +} + +impl ResolvedRpcEndpoint { + /// Returns the url this type holds, see [`RpcEndpoint::resolve`] + pub fn url(&self) -> Result { + self.endpoint.clone() + } + + // Returns true if all environment variables are resolved successfully + pub fn is_unresolved(&self) -> bool { + let endpoint_err = self.endpoint.is_err(); + let auth_err = self.auth.as_ref().map(|auth| auth.is_err()).unwrap_or(false); + endpoint_err || auth_err + } + + // Attempts to resolve unresolved environment variables into a new instance + pub fn try_resolve(mut self) -> Self { + if !self.is_unresolved() { + return self + } + if let Err(err) = self.endpoint { + self.endpoint = err.try_resolve() + } + if let Some(Err(err)) = self.auth { + self.auth = Some(err.try_resolve()) + } + self + } +} + +/// Container type for _resolved_ endpoints. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct ResolvedRpcEndpoints { + endpoints: BTreeMap, +} impl ResolvedRpcEndpoints { /// Returns true if there's an endpoint that couldn't be resolved pub fn has_unresolved(&self) -> bool { - self.endpoints.values().any(|val| val.is_err()) + self.endpoints.values().any(|e| e.is_unresolved()) } } impl Deref for ResolvedRpcEndpoints { - type Target = BTreeMap>; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.endpoints @@ -164,3 +476,47 @@ impl DerefMut for ResolvedRpcEndpoints { &mut self.endpoints } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_rpc_config() { + let s = r#"{ + "endpoint": "http://localhost:8545", + "retries": 5, + "retry_backoff": 250, + "compute_units_per_second": 100, + "auth": "Bearer 123" + }"#; + let config: RpcEndpoint = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpoint { + endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + config: RpcEndpointConfig { + retries: Some(5), + retry_backoff: Some(250), + compute_units_per_second: Some(100), + }, + auth: Some(RpcAuth::Raw("Bearer 123".to_string())), + } + ); + + let s = "\"http://localhost:8545\""; + let config: RpcEndpoint = serde_json::from_str(s).unwrap(); + assert_eq!( + config, + RpcEndpoint { + endpoint: RpcEndpointUrl::Url("http://localhost:8545".to_string()), + config: RpcEndpointConfig { + retries: None, + retry_backoff: None, + compute_units_per_second: None, + }, + auth: None, + } + ); + } +} diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index 2073151533809..eba84a57da0dc 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -1,13 +1,11 @@ //! error handling and solc error codes +use alloy_primitives::map::HashSet; use figment::providers::{Format, Toml}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{collections::HashSet, error::Error, fmt, str::FromStr}; - -/// The message shown upon panic if the config could not be extracted from the figment -pub const FAILED_TO_EXTRACT_CONFIG_PANIC_MSG: &str = "failed to extract foundry config:"; +use std::{error::Error, fmt, str::FromStr}; /// Represents a failed attempt to extract `Config` from a `Figment` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, PartialEq)] pub struct ExtractConfigError { /// error thrown when extracting the `Config` pub(crate) error: figment::Error, @@ -40,7 +38,7 @@ impl fmt::Display for ExtractConfigError { unique_errors.push(err); } } - writeln!(f, "{FAILED_TO_EXTRACT_CONFIG_PANIC_MSG}")?; + writeln!(f, "failed to extract foundry config:")?; for err in unique_errors { writeln!(f, "{err}")?; } @@ -48,6 +46,12 @@ impl fmt::Display for ExtractConfigError { } } +impl fmt::Debug for ExtractConfigError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + impl Error for ExtractConfigError { fn source(&self) -> Option<&(dyn Error + 'static)> { Error::source(&self.error) @@ -55,7 +59,7 @@ impl Error for ExtractConfigError { } /// Represents an error that can occur when constructing the `Config` -#[derive(Debug, Clone, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum FoundryConfigError { /// An error thrown during toml parsing Toml(figment::Error), @@ -75,11 +79,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,24 +94,25 @@ 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), } } } /// A non-exhaustive list of solidity error codes -#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SolidityErrorCode { /// Warning that SPDX license identifier not provided in source file SpdxLicenseNotProvided, + /// Warning: Visibility for constructor is ignored. If you want the contract to be + /// non-deployable, making it "abstract" is sufficient + VisibilityForConstructorIsIgnored, /// Warning that contract code size exceeds 24576 bytes (a limit introduced in Spurious /// Dragon). ContractExceeds24576Bytes, /// Warning after shanghai if init code size exceeds 49152 bytes ContractInitCodeSizeExceeds49152Bytes, - /// Warning that Function state mutability can be restricted to [view,pure] + /// Warning that Function state mutability can be restricted to view/pure. FunctionStateMutabilityCanBeRestricted, /// Warning: Unused local variable UnusedLocalVariable, @@ -131,43 +136,50 @@ pub enum SolidityErrorCode { Unreachable, /// Missing pragma solidity PragmaSolidity, + /// Uses transient opcodes + TransientStorageUsed, + /// There are more than 256 warnings. Ignoring the rest. + TooManyWarnings, /// All other error codes Other(u64), } -// === impl SolidityErrorCode === - impl SolidityErrorCode { /// The textual identifier for this error /// /// Returns `Err(code)` if unknown error pub fn as_str(&self) -> Result<&'static str, u64> { let s = match self { - SolidityErrorCode::SpdxLicenseNotProvided => "license", - 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::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, SolidityErrorCode::ContractExceeds24576Bytes => 5574, + SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => 2018, SolidityErrorCode::UnusedLocalVariable => 2072, SolidityErrorCode::UnusedFunctionParameter => 5667, @@ -179,7 +191,8 @@ impl From for u64 { SolidityErrorCode::UnnamedReturnVariable => 6321, SolidityErrorCode::Unreachable => 5740, SolidityErrorCode::PragmaSolidity => 3420, - SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, + SolidityErrorCode::TransientStorageUsed => 2394, + SolidityErrorCode::TooManyWarnings => 4591, SolidityErrorCode::Other(code) => code, } } @@ -199,19 +212,23 @@ impl FromStr for SolidityErrorCode { fn from_str(s: &str) -> Result { let code = match s { - "unreachable" => SolidityErrorCode::Unreachable, - "unused-return" => SolidityErrorCode::UnnamedReturnVariable, - "unused-param" => SolidityErrorCode::UnusedFunctionParameter, - "unused-var" => SolidityErrorCode::UnusedLocalVariable, - "code-size" => SolidityErrorCode::ContractExceeds24576Bytes, - "init-code-size" => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - "shadowing" => SolidityErrorCode::ShadowsExistingDeclaration, - "func-mutability" => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, - "license" => SolidityErrorCode::SpdxLicenseNotProvided, - "pragma-solidity" => SolidityErrorCode::PragmaSolidity, - "virtual-interfaces" => SolidityErrorCode::InterfacesExplicitlyVirtual, - "missing-receive-ether" => SolidityErrorCode::PayableNoReceiveEther, - "same-varname" => SolidityErrorCode::DeclarationSameNameAsAnother, + "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}")), }; @@ -222,21 +239,23 @@ impl FromStr for SolidityErrorCode { impl From for SolidityErrorCode { fn from(code: u64) -> Self { match code { - 1878 => SolidityErrorCode::SpdxLicenseNotProvided, - 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, - 3420 => SolidityErrorCode::PragmaSolidity, - 5740 => SolidityErrorCode::Unreachable, - 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 aaf1e42abf98e..9dde4b733a543 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -1,8 +1,15 @@ -//! Support for multiple etherscan keys +//! Support for multiple Etherscan keys. + use crate::{ resolve::{interpolate, UnresolvedEnvVarError, RE_PLACEHOLDER}, - Chain, Config, + Chain, Config, NamedChain, +}; +use figment::{ + providers::Env, + value::{Dict, Map}, + Error, Metadata, Profile, Provider, }; +use inflector::Inflector; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ collections::BTreeMap, @@ -10,13 +17,37 @@ use std::{ ops::{Deref, DerefMut}, time::Duration, }; -use tracing::warn; /// The user agent to use when querying the etherscan API. pub const ETHERSCAN_USER_AGENT: &str = concat!("foundry/", env!("CARGO_PKG_VERSION")); +/// A [Provider] that provides Etherscan API key from the environment if it's not empty. +/// +/// This prevents `ETHERSCAN_API_KEY=""` if it's set but empty +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[non_exhaustive] +pub(crate) struct EtherscanEnvProvider; + +impl Provider for EtherscanEnvProvider { + fn metadata(&self) -> Metadata { + Env::raw().metadata() + } + + fn data(&self) -> Result, Error> { + let mut dict = Dict::default(); + let env_provider = Env::raw().only(&["ETHERSCAN_API_KEY"]); + if let Some((key, value)) = env_provider.iter().next() { + if !value.trim().is_empty() { + dict.insert(key.as_str().to_string(), value.into()); + } + } + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + /// Errors that can occur when creating an `EtherscanConfig` -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum EtherscanConfigError { #[error(transparent)] Unresolved(#[from] UnresolvedEnvVarError), @@ -29,14 +60,12 @@ pub enum EtherscanConfigError { } /// Container type for Etherscan API keys and URLs. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct EtherscanConfigs { configs: BTreeMap, } -// === impl Endpoints === - impl EtherscanConfigs { /// Creates a new list of etherscan configs pub fn new(configs: impl IntoIterator, EtherscanConfig)>) -> Self { @@ -82,16 +111,14 @@ impl DerefMut for EtherscanConfigs { } } -/// Container type for _resolved_ etherscan keys, see [EtherscanConfigs::resolve_all()] -#[derive(Debug, Clone, PartialEq, Eq, Default)] +/// Container type for _resolved_ etherscan keys, see [`EtherscanConfigs::resolved`]. +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedEtherscanConfigs { /// contains all named `ResolvedEtherscanConfig` or an error if we failed to resolve the env /// var alias configs: BTreeMap>, } -// === impl ResolvedEtherscanConfigs === - impl ResolvedEtherscanConfigs { /// Creates a new list of resolved etherscan configs pub fn new( @@ -138,9 +165,9 @@ impl DerefMut for ResolvedEtherscanConfigs { } /// Represents all info required to create an etherscan client -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct EtherscanConfig { - /// Chain name/id that can be used to derive the api url + /// The chain name or EIP-155 chain ID used to derive the API URL. #[serde(default, skip_serializing_if = "Option::is_none")] pub chain: Option, /// Etherscan API URL @@ -150,8 +177,6 @@ pub struct EtherscanConfig { pub key: EtherscanApiKey, } -// === impl EtherscanConfig === - impl EtherscanConfig { /// Returns the etherscan config required to create a client. /// @@ -163,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)?; @@ -172,7 +197,19 @@ impl EtherscanConfig { let (chain, alias) = match (chain, alias) { // fill one with the other (Some(chain), None) => (Some(chain), Some(chain.to_string())), - (None, Some(alias)) => (alias.parse().ok(), Some(alias.into())), + (None, Some(alias)) => { + // alloy chain is parsed as kebab case + ( + alias.to_kebab_case().parse().ok().or_else(|| { + // if this didn't work try to parse as json because the deserialize impl + // supports more aliases + serde_json::from_str::(&format!("\"{alias}\"")) + .map(Into::into) + .ok() + }), + Some(alias.into()), + ) + } // leave as is (Some(chain), Some(alias)) => (Some(chain), Some(alias.into())), (None, None) => (None, None), @@ -194,7 +231,9 @@ impl EtherscanConfig { Ok(ResolvedEtherscanConfig { api_url, browser_url: None, key, chain: None }) } (None, None) => { - let msg = alias.map(|a| format!(" for Etherscan config `{a}`")).unwrap_or_default(); + let msg = alias + .map(|a| format!(" for Etherscan config with unknown alias `{a}`")) + .unwrap_or_default(); Err(EtherscanConfigError::MissingUrlOrChain(msg)) } } @@ -202,23 +241,21 @@ impl EtherscanConfig { } /// Contains required url + api key to set up an etherscan client -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ResolvedEtherscanConfig { - /// Etherscan API URL + /// Etherscan API URL. #[serde(rename = "url")] pub api_url: String, - /// Optional browser url + /// Optional browser URL. #[serde(default, skip_serializing_if = "Option::is_none")] pub browser_url: Option, - /// Resolved api key + /// The resolved API key. pub key: String, - /// The chain if set + /// The chain name or EIP-155 chain ID. #[serde(default, skip_serializing_if = "Option::is_none")] pub chain: Option, } -// === impl ResolvedEtherscanConfig === - impl ResolvedEtherscanConfig { /// Creates a new instance using the api key and chain pub fn create(api_key: impl Into, chain: impl Into) -> Option { @@ -251,42 +288,39 @@ impl ResolvedEtherscanConfig { self } - /// Returns the corresponding `ethers_etherscan::Client`, configured with the `api_url`, + /// Returns the corresponding `foundry_block_explorers::Client`, configured with the `api_url`, /// `api_key` and cache pub fn into_client( self, - ) -> Result { - let ResolvedEtherscanConfig { api_url, browser_url, key: api_key, chain } = self; - let (mainnet_api, mainnet_url) = - ethers_core::types::Chain::Mainnet.etherscan_urls().expect("exist; qed"); + ) -> Result + { + 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 - .or_else(|| { - if api_url == mainnet_api { - // try to match against mainnet, which is usually the most common target - Some(ethers_core::types::Chain::Mainnet.into()) - } else { - None - } - }) + // try to match against mainnet, which is usually the most common target + .or_else(|| (api_url == mainnet_api).then(Chain::mainnet)) .and_then(Config::foundry_etherscan_chain_cache_dir); - if let Some(ref cache_path) = cache { + if let Some(cache_path) = &cache { // we also create the `sources` sub dir here if let Err(err) = std::fs::create_dir_all(cache_path.join("sources")) { warn!("could not create etherscan cache dir: {:?}", err); } } - ethers_etherscan::Client::builder() - .with_client(reqwest::Client::builder().user_agent(ETHERSCAN_USER_AGENT).build()?) + let api_url = into_url(&api_url)?; + let client = reqwest::Client::builder() + .user_agent(ETHERSCAN_USER_AGENT) + .tls_built_in_root_certs(api_url.scheme() == "https") + .build()?; + foundry_block_explorers::Client::builder() + .with_client(client) .with_api_key(api_key) - .with_api_url(api_url.as_str())? - .with_url( - // the browser url is not used/required by the client so we can simply set the - // mainnet browser url here - browser_url.as_deref().unwrap_or(mainnet_url), - )? + .with_api_url(api_url)? + // the browser url is not used/required by the client so we can simply set the + // mainnet browser url here + .with_url(browser_url.as_deref().unwrap_or(mainnet_url))? .with_cache(cache, Duration::from_secs(24 * 60 * 60)) .build() } @@ -298,7 +332,7 @@ impl ResolvedEtherscanConfig { /// env var, then the `EtherscanKey::Key` var will hold the reference (`${MAIN_NET}`) and _not_ the /// value of the env var itself. /// In other words, this type does not resolve env vars when it's being deserialized -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum EtherscanApiKey { /// A raw key Key(String), @@ -308,22 +342,20 @@ pub enum EtherscanApiKey { Env(String), } -// === impl EtherscanApiKey === - 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, } } @@ -334,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), } } } @@ -355,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) } @@ -368,16 +396,23 @@ 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), } } } +/// This is a hack to work around `IntoUrl`'s sealed private functions, which can't be called +/// normally. +#[inline] +fn into_url(url: impl reqwest::IntoUrl) -> std::result::Result { + url.into_url() +} + #[cfg(test)] mod tests { use super::*; - use ethers_core::types::Chain::Mainnet; + use NamedChain::Mainnet; #[test] fn can_create_client_via_chain() { @@ -439,4 +474,35 @@ mod tests { std::env::remove_var(env); } + + #[test] + fn resolve_etherscan_alias_config() { + let mut configs = EtherscanConfigs::default(); + configs.insert( + "blast_sepolia".to_string(), + EtherscanConfig { + chain: None, + url: Some("https://api.etherscan.io/api".to_string()), + key: EtherscanApiKey::Key("ABCDEFG".to_string()), + }, + ); + + let mut resolved = configs.clone().resolved(); + let config = resolved.remove("blast_sepolia").unwrap().unwrap(); + assert_eq!(config.chain, Some(Chain::blast_sepolia())); + } + + #[test] + fn resolve_etherscan_alias() { + let config = EtherscanConfig { + chain: None, + url: Some("https://api.etherscan.io/api".to_string()), + key: EtherscanApiKey::Key("ABCDEFG".to_string()), + }; + let resolved = config.clone().resolve(Some("base_sepolia")).unwrap(); + assert_eq!(resolved.chain, Some(Chain::base_sepolia())); + + let resolved = config.resolve(Some("base-sepolia")).unwrap(); + assert_eq!(resolved.chain, Some(Chain::base_sepolia())); + } } diff --git a/crates/config/src/filter.rs b/crates/config/src/filter.rs new file mode 100644 index 0000000000000..e2f542b67f130 --- /dev/null +++ b/crates/config/src/filter.rs @@ -0,0 +1,252 @@ +//! Helpers for constructing and using [FileFilter]s. + +use core::fmt; +use foundry_compilers::FileFilter; +use serde::{Deserialize, Serialize}; +use std::{ + convert::Infallible, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// Expand globs with a root path. +pub fn expand_globs( + root: &Path, + patterns: impl IntoIterator>, +) -> eyre::Result> { + let mut expanded = Vec::new(); + for pattern in patterns { + for paths in glob::glob(&root.join(pattern.as_ref()).display().to_string())? { + expanded.push(paths?); + } + } + Ok(expanded) +} + +/// A `globset::Glob` that creates its `globset::GlobMatcher` when its created, so it doesn't need +/// to be compiled when the filter functions `TestFilter` functions are called. +#[derive(Clone, Debug)] +pub struct GlobMatcher { + /// The compiled glob + pub matcher: globset::GlobMatcher, +} + +impl GlobMatcher { + /// Creates a new `GlobMatcher` from a `globset::Glob`. + pub fn new(glob: globset::Glob) -> Self { + Self { matcher: glob.compile_matcher() } + } + + /// Tests whether the given path matches this pattern or not. + /// + /// The glob `./test/*` won't match absolute paths like `test/Contract.sol`, which is common + /// format here, so we also handle this case here + pub fn is_match(&self, path: &Path) -> bool { + if self.matcher.is_match(path) { + return true; + } + + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { + if file_name.contains(self.as_str()) { + return true; + } + } + + if !path.starts_with("./") && self.as_str().starts_with("./") { + 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 + } + + /// Matches file only if the filter does not apply. + /// + /// This returns the inverse of `self.is_match(file)`. + fn is_match_exclude(&self, path: &Path) -> bool { + !self.is_match(path) + } + + /// Returns the `globset::Glob`. + pub fn glob(&self) -> &globset::Glob { + self.matcher.glob() + } + + /// Returns the `Glob` string used to compile this matcher. + pub fn as_str(&self) -> &str { + self.glob().glob() + } +} + +impl fmt::Display for GlobMatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.glob().fmt(f) + } +} + +impl FromStr for GlobMatcher { + type Err = globset::Error; + + fn from_str(s: &str) -> Result { + s.parse::().map(Self::new) + } +} + +impl From for GlobMatcher { + fn from(glob: globset::Glob) -> Self { + Self::new(glob) + } +} + +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 { + /// All provided filters. + pub matchers: Vec, + /// Root of the project. + pub project_root: PathBuf, +} + +impl FileFilter for SkipBuildFilters { + /// Only returns a match if _no_ exclusion filter matches + fn is_match(&self, file: &Path) -> bool { + self.matchers.iter().all(|matcher| { + if !matcher.is_match_exclude(file) { + false + } else { + file.strip_prefix(&self.project_root) + .map_or(true, |stripped| matcher.is_match_exclude(stripped)) + } + }) + } +} + +impl SkipBuildFilters { + /// Creates a new `SkipBuildFilters` from multiple `SkipBuildFilter`. + pub fn new>( + filters: impl IntoIterator, + project_root: PathBuf, + ) -> Self { + let matchers = filters.into_iter().map(|m| m.into()).collect(); + Self { matchers, project_root } + } +} + +/// A filter that excludes matching contracts from the build +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SkipBuildFilter { + /// Exclude all `.t.sol` contracts + Tests, + /// Exclude all `.s.sol` contracts + Scripts, + /// Exclude if the file matches + Custom(String), +} + +impl SkipBuildFilter { + fn new(s: &str) -> Self { + match s { + "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 { + Self::Tests => ".t.sol", + Self::Scripts => ".s.sol", + Self::Custom(s) => s.as_str(), + } + } +} + +impl FromStr for SkipBuildFilter { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self::new(s)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_filter() { + let tests = GlobMatcher::from_str(SkipBuildFilter::Tests.file_pattern()).unwrap(); + let scripts = GlobMatcher::from_str(SkipBuildFilter::Scripts.file_pattern()).unwrap(); + let custom = |s| GlobMatcher::from_str(s).unwrap(); + + let file = Path::new("A.t.sol"); + assert!(!tests.is_match_exclude(file)); + assert!(scripts.is_match_exclude(file)); + assert!(!custom("A.t").is_match_exclude(file)); + + let file = Path::new("A.s.sol"); + assert!(tests.is_match_exclude(file)); + assert!(!scripts.is_match_exclude(file)); + assert!(!custom("A.s").is_match_exclude(file)); + + let file = Path::new("/home/test/Foo.sol"); + assert!(!custom("*/test/**").is_match_exclude(file)); + + let file = Path::new("/home/script/Contract.sol"); + assert!(!custom("*/script/**").is_match_exclude(file)); + } + + #[test] + fn can_match_relative_glob_paths() { + let matcher: GlobMatcher = "./test/*".parse().unwrap(); + + // 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/fix.rs b/crates/config/src/fix.rs index 9c2c43ef77983..8181339a2d3e6 100644 --- a/crates/config/src/fix.rs +++ b/crates/config/src/fix.rs @@ -1,4 +1,4 @@ -//! Helpers to automatically fix configuration warnings +//! Helpers to automatically fix configuration warnings. use crate::{Config, Warning}; use figment::providers::Env; @@ -10,32 +10,36 @@ use std::{ /// A convenience wrapper around a TOML document and the path it was read from struct TomlFile { - doc: toml_edit::Document, + doc: toml_edit::DocumentMut, path: PathBuf, } impl TomlFile { - fn open(path: impl AsRef) -> Result> { + fn open(path: impl AsRef) -> eyre::Result { let path = path.as_ref().to_owned(); let doc = fs::read_to_string(&path)?.parse()?; Ok(Self { doc, path }) } - fn doc(&self) -> &toml_edit::Document { + + fn doc(&self) -> &toml_edit::DocumentMut { &self.doc } - fn doc_mut(&mut self) -> &mut toml_edit::Document { + + fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { &mut self.doc } + fn path(&self) -> &Path { self.path.as_ref() } + fn save(&self) -> io::Result<()> { fs::write(self.path(), self.doc().to_string()) } } impl Deref for TomlFile { - type Target = toml_edit::Document; + type Target = toml_edit::DocumentMut; fn deref(&self) -> &Self::Target { self.doc() } @@ -47,7 +51,7 @@ impl DerefMut for TomlFile { } } -/// The error emitted when failing to insert a profile into [profile] +/// The error emitted when failing to insert into a profile. #[derive(Debug)] struct InsertProfileError { pub message: String, @@ -65,6 +69,7 @@ impl std::error::Error for InsertProfileError {} impl TomlFile { /// Insert a name as `[profile.name]`. Creating the `[profile]` table where necessary and /// throwing an error if there exists a conflict + #[allow(clippy::result_large_err)] fn insert_profile( &mut self, profile_str: &str, @@ -216,16 +221,16 @@ pub fn fix_tomls() -> Vec { mod tests { use super::*; use figment::Jail; - use pretty_assertions::assert_eq; + use similar_asserts::assert_eq; macro_rules! fix_test { - ($(#[$meta:meta])* $name:ident, $fun:expr) => { + ($(#[$attr:meta])* $name:ident, $fun:expr) => { #[test] - $(#[$meta])* + $(#[$attr])* fn $name() { Jail::expect_with(|jail| { // setup home directory, - // **Note** this only has an effect on unix, as [`dirs_next::home_dir()`] on windows uses `FOLDERID_Profile` + // **Note** this only has an effect on unix, as [`dirs::home_dir()`] on windows uses `FOLDERID_Profile` jail.set_env("HOME", jail.directory().display().to_string()); std::fs::create_dir(jail.directory().join(".foundry")).unwrap(); @@ -297,7 +302,7 @@ mod tests { Ok(()) }); - // mocking the `$HOME` has no effect on windows, see [`dirs_next::home_dir()`] + // mocking the `$HOME` has no effect on windows, see [`dirs::home_dir()`] fix_test!( #[cfg(not(windows))] test_global_toml_is_edited, diff --git a/crates/config/src/fmt.rs b/crates/config/src/fmt.rs index 54fa65ed5ba10..69381171989be 100644 --- a/crates/config/src/fmt.rs +++ b/crates/config/src/fmt.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; /// Contains the config and rule set -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FormatterConfig { /// Maximum line length where formatter will try to wrap the line pub line_length: usize, @@ -19,6 +19,8 @@ pub struct FormatterConfig { pub quote_style: QuoteStyle, /// Style of underscores in number literals pub number_underscore: NumberUnderscore, + /// Style of underscores in hex literals + pub hex_underscore: HexUnderscore, /// Style of single line blocks in statements pub single_line_statement_blocks: SingleLineBlockStyle, /// Print space in state variable, function and modifier `override` attribute @@ -29,10 +31,12 @@ pub struct FormatterConfig { pub ignore: Vec, /// Add new line at start and end of contract declarations pub contract_new_lines: bool, + /// Sort import statements alphabetically in groups (a group is separated by a newline). + pub sort_imports: bool, } /// Style of uint/int256 types -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum IntTypes { /// Print the explicit uint256 or int256 @@ -44,20 +48,74 @@ pub enum IntTypes { } /// Style of underscores in number literals -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum NumberUnderscore { + /// Use the underscores defined in the source code + Preserve, /// Remove all underscores + #[default] Remove, /// Add an underscore every thousand, if greater than 9999 /// e.g. 1000 -> 1000 and 10000 -> 10_000 Thousands, +} + +impl NumberUnderscore { + /// Returns true if the option is `Preserve` + #[inline] + pub fn is_preserve(self) -> bool { + matches!(self, Self::Preserve) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_remove(self) -> bool { + matches!(self, Self::Remove) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_thousands(self) -> bool { + matches!(self, Self::Thousands) + } +} + +/// Style of underscores in hex literals +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HexUnderscore { /// Use the underscores defined in the source code Preserve, + /// Remove all underscores + #[default] + Remove, + /// Add underscore as separator between byte boundaries + Bytes, +} + +impl HexUnderscore { + /// Returns true if the option is `Preserve` + #[inline] + pub fn is_preserve(self) -> bool { + matches!(self, Self::Preserve) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_remove(self) -> bool { + matches!(self, Self::Remove) + } + + /// Returns true if the option is `Remove` + #[inline] + pub fn is_bytes(self) -> bool { + matches!(self, Self::Bytes) + } } /// Style of string quotes -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum QuoteStyle { /// Use double quotes where possible @@ -72,15 +130,15 @@ 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, } } } /// Style of single line blocks in statements -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum SingleLineBlockStyle { /// Prefer single line block when possible @@ -92,21 +150,25 @@ pub enum SingleLineBlockStyle { } /// Style of function header in case it doesn't fit -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MultilineFuncHeaderStyle { - /// Write function parameters multiline first + /// Write function parameters multiline first. ParamsFirst, - /// Write function attributes multiline first + /// Write function parameters multiline first when there is more than one param. + ParamsFirstMulti, + /// Write function attributes multiline first. AttributesFirst, - /// If function params or attrs are multiline + /// If function params or attrs are multiline. /// split the rest All, + /// Same as `All` but writes function params multiline even when there is a single param. + AllParams, } impl Default for FormatterConfig { fn default() -> Self { - FormatterConfig { + Self { line_length: 120, tab_width: 4, bracket_spacing: false, @@ -114,11 +176,13 @@ impl Default for FormatterConfig { multiline_func_header: MultilineFuncHeaderStyle::AttributesFirst, quote_style: QuoteStyle::Double, number_underscore: NumberUnderscore::Preserve, + hex_underscore: HexUnderscore::Remove, single_line_statement_blocks: SingleLineBlockStyle::Preserve, override_spacing: false, wrap_comments: false, ignore: vec![], contract_new_lines: false, + sort_imports: false, } } } diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index 3128cd6014c22..1d2c35ff33bf9 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -10,21 +10,24 @@ use std::{ /// Configures file system access /// /// E.g. for cheat codes (`vm.writeFile`) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[serde(transparent)] pub struct FsPermissions { /// what kind of access is allowed pub permissions: Vec, } -// === impl FsPermissions === - impl FsPermissions { /// Creates anew instance with the given `permissions` pub fn new(permissions: impl IntoIterator) -> Self { Self { permissions: permissions.into_iter().collect() } } + /// Adds a new permission + pub fn add(&mut self, permission: PathPermission) { + self.permissions.push(permission) + } + /// Returns true if access to the specified path is allowed with the specified. /// /// This first checks permission, and only if it is granted, whether the path is allowed. @@ -37,28 +40,47 @@ impl FsPermissions { self.find_permission(path).map(|perm| perm.is_granted(kind)).unwrap_or_default() } - /// Returns the permission for the matching path + /// Returns the permission for the matching path. + /// + /// This finds the longest matching path with resolved sym links, e.g. if we have the following + /// permissions: + /// + /// `./out` = `read` + /// `./out/contracts` = `read-write` + /// + /// And we check for `./out/contracts/MyContract.sol` we will get `read-write` as permission. pub fn find_permission(&self, path: &Path) -> Option { - self.permissions.iter().find(|perm| path.starts_with(&perm.path)).map(|perm| perm.access) + let mut permission: Option<&PathPermission> = None; + for perm in &self.permissions { + let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone()); + if path.starts_with(permission_path) { + if let Some(active_perm) = permission.as_ref() { + // the longest path takes precedence + if perm.path < active_perm.path { + continue; + } + } + permission = Some(perm); + } + } + permission.map(|perm| perm.access) } /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries - pub fn join_all(&mut self, root: impl AsRef) { - let root = root.as_ref(); + pub fn join_all(&mut self, root: &Path) { self.permissions.iter_mut().for_each(|perm| { perm.path = root.join(&perm.path); }) } /// Same as [`Self::join_all`] but consumes the type - pub fn joined(mut self, root: impl AsRef) -> Self { + pub fn joined(mut self, root: &Path) -> Self { self.join_all(root); self } /// Removes all existing permissions for the given path - pub fn remove(&mut self, path: impl AsRef) { - let path = path.as_ref(); + pub fn remove(&mut self, path: &Path) { self.permissions.retain(|permission| permission.path != path) } @@ -74,7 +96,7 @@ impl FsPermissions { } /// Represents an access permission to a single path -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct PathPermission { /// Permission level to access the `path` pub access: FsAccessPermission, @@ -82,8 +104,6 @@ pub struct PathPermission { pub path: PathBuf, } -// === impl PathPermission === - impl PathPermission { /// Returns a new permission for the path and the given access pub fn new(path: impl Into, access: FsAccessPermission) -> Self { @@ -117,7 +137,7 @@ impl PathPermission { } /// Represents the operation on the fs -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum FsAccessKind { /// read from fs (`vm.readFile`) Read, @@ -128,14 +148,14 @@ 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"), } } } /// Determines the status of file system access -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum FsAccessPermission { /// FS access is _not_ allowed #[default] @@ -148,16 +168,14 @@ pub enum FsAccessPermission { Write, } -// === impl FsAccessPermission === - 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, } } @@ -168,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}")), } } @@ -180,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"), } } } @@ -194,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"), } } } @@ -215,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), @@ -238,4 +255,19 @@ mod tests { assert_eq!(FsAccessPermission::Read, "read".parse().unwrap()); assert_eq!(FsAccessPermission::Write, "write".parse().unwrap()); } + + #[test] + fn nested_permissions() { + let permissions = FsPermissions::new(vec![ + PathPermission::read("./"), + PathPermission::write("./out"), + PathPermission::read_write("./out/contracts"), + ]); + + let permission = + permissions.find_permission(Path::new("./out/contracts/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::ReadWrite, permission); + let permission = permissions.find_permission(Path::new("./out/MyContract.sol")).unwrap(); + assert_eq!(FsAccessPermission::Write, permission); + } } diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index 5601c10b5c32b..26e1c080cbc8d 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -1,14 +1,11 @@ -//! Configuration for fuzz testing +//! Configuration for fuzz testing. -use ethers_core::types::U256; +use alloy_primitives::U256; use serde::{Deserialize, Serialize}; - -use crate::inline::{ - parse_config_u32, InlineConfigParser, InlineConfigParserError, INLINE_CONFIG_FUZZ_KEY, -}; +use std::path::PathBuf; /// Contains for fuzz testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, @@ -19,59 +16,51 @@ pub struct FuzzConfig { /// `prop_filter`. pub max_test_rejects: u32, /// Optional seed for the fuzzing RNG algorithm - #[serde( - deserialize_with = "ethers_core::types::serde_helpers::deserialize_stringified_numeric_opt" - )] pub seed: Option, /// The fuzz dictionary configuration #[serde(flatten)] pub dictionary: FuzzDictionaryConfig, + /// Number of runs to execute and include in the gas report. + pub gas_report_samples: u32, + /// Path where fuzz failures are recorded and replayed. + pub failure_persist_dir: Option, + /// Name of the file to record fuzz failures, defaults to `failures`. + pub failure_persist_file: Option, + /// show `console.log` in fuzz test, defaults to `false` + pub show_logs: bool, + /// Optional timeout (in seconds) for each property test + pub timeout: Option, } impl Default for FuzzConfig { fn default() -> Self { - FuzzConfig { + Self { runs: 256, max_test_rejects: 65536, seed: None, dictionary: FuzzDictionaryConfig::default(), + gas_report_samples: 256, + failure_persist_dir: None, + failure_persist_file: None, + show_logs: false, + timeout: None, } } } -impl InlineConfigParser for FuzzConfig { - fn config_key() -> String { - INLINE_CONFIG_FUZZ_KEY.into() - } - - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { - let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); - - if overrides.is_empty() { - return Ok(None) - } - - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; - - for pair in overrides { - let key = pair.0; - let value = pair.1; - match key.as_str() { - "runs" => conf_clone.runs = parse_config_u32(key, value)?, - "max-test-rejects" => conf_clone.max_test_rejects = parse_config_u32(key, value)?, - "dictionary-weight" => { - conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)? - } - _ => Err(InlineConfigParserError::InvalidConfigProperty(key))?, - } +impl FuzzConfig { + /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. + pub fn new(cache_dir: PathBuf) -> Self { + Self { + failure_persist_dir: Some(cache_dir), + failure_persist_file: Some("failures".to_string()), + ..Default::default() } - Ok(Some(conf_clone)) } } /// Contains for fuzz testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FuzzDictionaryConfig { /// The weight of the dictionary #[serde(deserialize_with = "crate::deserialize_stringified_percent")] @@ -94,7 +83,7 @@ pub struct FuzzDictionaryConfig { impl Default for FuzzDictionaryConfig { fn default() -> Self { - FuzzDictionaryConfig { + Self { dictionary_weight: 40, include_storage: true, include_push_bytes: true, @@ -105,66 +94,3 @@ impl Default for FuzzDictionaryConfig { } } } - -#[cfg(test)] -mod tests { - use crate::{inline::InlineConfigParser, FuzzConfig}; - - #[test] - fn unrecognized_property() { - let configs = &["forge-config: default.fuzz.unknownprop = 200".to_string()]; - let base_config = FuzzConfig::default(); - if let Err(e) = base_config.try_merge(configs) { - assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); - } else { - unreachable!() - } - } - - #[test] - fn successful_merge() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: default.fuzz.dictionary-weight = 42".to_string(), - ]; - let base_config = FuzzConfig::default(); - let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap(); - assert_eq!(merged.runs, 42424242); - assert_eq!(merged.dictionary.dictionary_weight, 42); - } - - #[test] - fn merge_is_none() { - let empty_config = &[]; - let base_config = FuzzConfig::default(); - let merged = base_config.try_merge(empty_config).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn merge_is_none_unrelated_property() { - let unrelated_configs = &["forge-config: default.invariant.runs = 2".to_string()]; - let base_config = FuzzConfig::default(); - let merged = base_config.try_merge(unrelated_configs).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn override_detection() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: ci.fuzz.runs = 666666".to_string(), - "forge-config: default.invariant.runs = 2".to_string(), - "forge-config: default.fuzz.dictionary-weight = 42".to_string(), - ]; - let variables = FuzzConfig::get_config_overrides(configs); - assert_eq!( - variables, - vec![ - ("runs".into(), "42424242".into()), - ("runs".into(), "666666".into()), - ("dictionary-weight".into(), "42".into()) - ] - ); - } -} diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs deleted file mode 100644 index a64cd67299184..0000000000000 --- a/crates/config/src/inline/conf_parser.rs +++ /dev/null @@ -1,206 +0,0 @@ -use regex::Regex; - -use crate::{InlineConfigError, NatSpec}; - -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX}; - -/// Errors returned by the [`InlineConfigParser`] trait. -#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] -pub enum InlineConfigParserError { - /// An invalid configuration property has been provided. - /// The property cannot be mapped to the configuration object - #[error("'{0}' is an invalid config property")] - InvalidConfigProperty(String), - /// An invalid profile has been provided - #[error("'{0}' specifies an invalid profile. Available profiles are: {1}")] - InvalidProfile(String, String), - /// An error occurred while trying to parse an integer configuration value - #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into an integer value")] - ParseInt(String, String), - /// An error occurred while trying to parse a boolean configuration value - #[error("Invalid config value for key '{0}'. Unable to parse '{1}' into a boolean value")] - ParseBool(String, String), -} - -/// This trait is intended to parse configurations from -/// structured text. Foundry users can annotate Solidity test functions, -/// providing special configs just for the execution of a specific test. -/// -/// An example: -/// -/// ```solidity -/// contract MyTest is Test { -/// /// forge-config: default.fuzz.runs = 100 -/// /// forge-config: ci.fuzz.runs = 500 -/// function test_SimpleFuzzTest(uint256 x) public {...} -/// -/// /// forge-config: default.fuzz.runs = 500 -/// /// forge-config: ci.fuzz.runs = 10000 -/// function test_ImportantFuzzTest(uint256 x) public {...} -/// } -/// ``` -pub trait InlineConfigParser -where - Self: Clone + Default + Sized + 'static, -{ - /// Returns a config key that is common to all valid configuration lines - /// for the current impl. This helps to extract correct values out of a text. - /// - /// An example key would be `fuzz` of `invariant`. - fn config_key() -> String; - - /// Tries to override `self` properties with values specified in the `configs` parameter. - /// - /// Returns - /// - `Some(Self)` in case some configurations are merged into self. - /// - `None` in case there are no configurations that can be applied to self. - /// - `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> { - let config_key = Self::config_key(); - - let configs = natspec - .config_lines() - .into_iter() - .filter(|l| l.contains(&config_key)) - .collect::>(); - - Self::default().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 config key - /// - /// i.e. Given the `invariant` config key and a vector of config lines - /// ```rust - /// let _config_lines = vec![ - /// "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 return the whole set of `invariant` configs. - /// ```rust - /// let _result = vec![ - /// ("runs", "500"), - /// ("depth", "500"), - /// ("depth", "500"), - /// ]; - /// ``` - fn get_config_overrides(config_lines: &[String]) -> Vec<(String, String)> { - let mut result: Vec<(String, String)> = vec![]; - let config_key = Self::config_key(); - let profile = ".*"; - let prefix = format!("^{INLINE_CONFIG_PREFIX}:{profile}{config_key}\\."); - let re = Regex::new(&prefix).unwrap(); - - config_lines - .iter() - .map(|l| remove_whitespaces(l)) - .filter(|l| re.is_match(l)) - .map(|l| re.replace(&l, "").to_string()) - .for_each(|line| { - let key_value = line.split('=').collect::>(); // i.e. "['runs', '500']" - if let Some(key) = key_value.first() { - if let Some(value) = key_value.last() { - result.push((key.to_string(), value.to_string())); - } - } - }); - - result - } -} - -/// Checks if all configuration lines specified in `natspec` use a valid profile. -/// -/// i.e. Given available profiles -/// ```rust -/// let _profiles = vec!["ci", "default"]; -/// ``` -/// A configuration like `forge-config: ciii.invariant.depth = 1` would result -/// in an error. -pub fn validate_profiles(natspec: &NatSpec, profiles: &[String]) -> Result<(), InlineConfigError> { - for config in natspec.config_lines() { - if !profiles.iter().any(|p| config.starts_with(&format!("{INLINE_CONFIG_PREFIX}:{p}."))) { - let err_line: String = natspec.debug_context(); - let profiles = format!("{profiles:?}"); - Err(InlineConfigError { - source: InlineConfigParserError::InvalidProfile(config, profiles), - line: err_line, - })? - } - } - Ok(()) -} - -/// Tries to parse a `u32` from `value`. The `key` argument is used to give details -/// in the case of an error. -pub fn parse_config_u32(key: String, value: String) -> Result { - value.parse().map_err(|_| InlineConfigParserError::ParseInt(key, value)) -} - -/// Tries to parse a `bool` from `value`. The `key` argument is used to give details -/// in the case of an error. -pub fn parse_config_bool(key: String, value: String) -> Result { - value.parse().map_err(|_| InlineConfigParserError::ParseBool(key, value)) -} - -#[cfg(test)] -mod tests { - use crate::{inline::conf_parser::validate_profiles, NatSpec}; - - #[test] - fn can_reject_invalid_profiles() { - let profiles = ["ci".to_string(), "default".to_string()]; - let natspec = NatSpec { - contract: Default::default(), - function: Default::default(), - line: Default::default(), - docs: r#" - forge-config: ciii.invariant.depth = 1 - forge-config: default.invariant.depth = 1 - "# - .into(), - }; - - let result = validate_profiles(&natspec, &profiles); - assert!(result.is_err()); - } - - #[test] - fn can_accept_valid_profiles() { - let profiles = ["ci".to_string(), "default".to_string()]; - let natspec = NatSpec { - contract: Default::default(), - function: Default::default(), - line: Default::default(), - docs: r#" - forge-config: ci.invariant.depth = 1 - forge-config: default.invariant.depth = 1 - "# - .into(), - }; - - let result = validate_profiles(&natspec, &profiles); - assert!(result.is_ok()); - } -} diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index 99a314c961a74..fa67b2426cf00 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,81 +1,186 @@ -mod conf_parser; -pub use conf_parser::{ - parse_config_bool, parse_config_u32, validate_profiles, InlineConfigParser, - InlineConfigParserError, +use crate::Config; +use alloy_primitives::map::HashMap; +use figment::{ + value::{Dict, Map, Value}, + Figment, Profile, Provider, }; -use once_cell::sync::Lazy; -use std::collections::HashMap; +use foundry_compilers::ProjectCompileOutput; +use itertools::Itertools; mod natspec; -pub use natspec::NatSpec; +pub use natspec::*; -use crate::Config; +const INLINE_CONFIG_PREFIX: &str = "forge-config:"; -pub const INLINE_CONFIG_FUZZ_KEY: &str = "fuzz"; -pub const INLINE_CONFIG_INVARIANT_KEY: &str = "invariant"; -const INLINE_CONFIG_PREFIX: &str = "forge-config"; +type DataMap = Map; -static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: Lazy = Lazy::new(|| { - let selected_profile = Config::selected_profile().to_string(); - format!("{INLINE_CONFIG_PREFIX}:{selected_profile}.") -}); +/// Errors returned when parsing inline config. +#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] +pub enum InlineConfigErrorKind { + /// Failed to parse inline config as TOML. + #[error(transparent)] + Parse(#[from] toml::de::Error), + /// An invalid profile has been provided. + #[error("invalid profile `{0}`; valid profiles: {1}")] + InvalidProfile(String, String), +} -/// Wrapper error struct that catches config parsing -/// errors [`InlineConfigParserError`], enriching them with context information +/// Wrapper error struct that catches config parsing errors, enriching them with context information /// reporting the misconfigured line. -#[derive(thiserror::Error, Debug)] -#[error("Inline config error detected at {line}")] +#[derive(Debug, thiserror::Error)] +#[error("Inline config error at {location}: {kind}")] pub struct InlineConfigError { - /// Specifies the misconfigured line. This is something of the form + /// The span of the error in the format: /// `dir/TestContract.t.sol:FuzzContract:10:12:111` - pub line: String, + pub location: String, /// The inner error - pub source: InlineConfigParserError, + pub kind: InlineConfigErrorKind, } -/// Represents a (test-contract, test-function) pair -type InlineConfigKey = (String, String); - /// Represents per-test configurations, declared inline /// as structured comments in Solidity test files. This allows /// to create configs directly bound to a solidity test. -#[derive(Default, Debug, Clone)] -pub struct InlineConfig { - /// Maps a (test-contract, test-function) pair - /// to a specific configuration provided by the user. - configs: HashMap, +#[derive(Clone, Debug, Default)] +pub struct InlineConfig { + /// Contract-level configuration. + contract_level: HashMap, + /// Function-level configuration. + fn_level: HashMap<(String, String), DataMap>, } -impl InlineConfig { - /// Returns an inline configuration, if any, for a test function. - /// Configuration is identified by the pair "contract", "function". - pub fn get>(&self, contract_id: S, fn_name: S) -> Option<&T> { - self.configs.get(&(contract_id.into(), fn_name.into())) +impl InlineConfig { + /// Creates a new, empty [`InlineConfig`]. + pub fn new() -> Self { + Self::default() + } + + /// Tries to create a new instance by detecting inline configurations from the project compile + /// output. + pub fn new_parsed(output: &ProjectCompileOutput, config: &Config) -> eyre::Result { + let natspecs: Vec = NatSpec::parse(output, &config.root); + let profiles = &config.profiles; + let mut inline = Self::new(); + for natspec in &natspecs { + inline.insert(natspec)?; + // Validate after parsing as TOML. + natspec.validate_profiles(profiles)?; + } + Ok(inline) + } + + /// Inserts a new [`NatSpec`] into the [`InlineConfig`]. + pub fn insert(&mut self, natspec: &NatSpec) -> Result<(), InlineConfigError> { + let map = if let Some(function) = &natspec.function { + self.fn_level.entry((natspec.contract.clone(), function.clone())).or_default() + } else { + self.contract_level.entry(natspec.contract.clone()).or_default() + }; + let joined = natspec + .config_values() + .map(|s| { + // Replace `-` with `_` for backwards compatibility with the old parser. + if let Some(idx) = s.find('=') { + s[..idx].replace('-', "_") + &s[idx..] + } else { + s.to_string() + } + }) + .format("\n") + .to_string(); + let data = toml::from_str::(&joined).map_err(|e| InlineConfigError { + location: natspec.location_string(), + kind: InlineConfigErrorKind::Parse(e), + })?; + extend_data_map(map, &data); + Ok(()) + } + + /// Returns a [`figment::Provider`] for this [`InlineConfig`] at the given contract and function + /// level. + pub fn provide<'a>(&'a self, contract: &'a str, function: &'a str) -> InlineConfigProvider<'a> { + InlineConfigProvider { inline: self, contract, function } + } + + /// Merges the inline configuration at the given contract and function level with the provided + /// base configuration. + pub fn merge(&self, contract: &str, function: &str, base: &Config) -> Figment { + Figment::from(base).merge(self.provide(contract, function)) + } + + /// Returns `true` if a configuration is present at the given contract level. + pub fn contains_contract(&self, contract: &str) -> bool { + self.get_contract(contract).is_some_and(|map| !map.is_empty()) } - /// Inserts an inline configuration, for a test function. - /// Configuration is identified by the pair "contract", "function". - pub fn insert>(&mut self, contract_id: S, fn_name: S, config: T) { - self.configs.insert((contract_id.into(), fn_name.into()), config); + /// Returns `true` if a configuration is present at the function level. + /// + /// Does not include contract-level configurations. + pub fn contains_function(&self, contract: &str, function: &str) -> bool { + self.get_function(contract, function).is_some_and(|map| !map.is_empty()) + } + + fn get_contract(&self, contract: &str) -> Option<&DataMap> { + self.contract_level.get(contract) + } + + fn get_function(&self, contract: &str, function: &str) -> Option<&DataMap> { + let key = (contract.to_string(), function.to_string()); + self.fn_level.get(&key) } } -fn remove_whitespaces(s: &str) -> String { - s.chars().filter(|c| !c.is_whitespace()).collect() +/// [`figment::Provider`] for [`InlineConfig`] at a given contract and function level. +/// +/// Created by [`InlineConfig::provide`]. +#[derive(Clone, Debug)] +pub struct InlineConfigProvider<'a> { + inline: &'a InlineConfig, + contract: &'a str, + function: &'a str, } -#[cfg(test)] -mod tests { - use super::InlineConfigParserError; - use crate::InlineConfigError; +impl Provider for InlineConfigProvider<'_> { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("inline config") + } - #[test] - fn can_format_inline_config_errors() { - let source = InlineConfigParserError::ParseBool("key".into(), "invalid-bool-value".into()); - let line = "dir/TestContract.t.sol:FuzzContract".to_string(); - let error = InlineConfigError { line: line.clone(), source }; + fn data(&self) -> figment::Result { + let mut map = DataMap::new(); + if let Some(new) = self.inline.get_contract(self.contract) { + extend_data_map(&mut map, new); + } + if let Some(new) = self.inline.get_function(self.contract, self.function) { + extend_data_map(&mut map, new); + } + Ok(map) + } +} + +fn extend_data_map(map: &mut DataMap, new: &DataMap) { + for (profile, data) in new { + extend_dict(map.entry(profile.clone()).or_default(), data); + } +} + +fn extend_dict(dict: &mut Dict, new: &Dict) { + for (k, v) in new { + match dict.entry(k.clone()) { + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(v.clone()); + } + std::collections::btree_map::Entry::Occupied(entry) => { + extend_value(entry.into_mut(), v); + } + } + } +} - let expected = format!("Inline config error detected at {line}"); - assert_eq!(error.to_string(), expected); +fn extend_value(value: &mut Value, new: &Value) { + match (value, new) { + (Value::Dict(tag, dict), Value::Dict(new_tag, new_dict)) => { + *tag = *new_tag; + extend_dict(dict, new_dict); + } + (value, new) => *value = new.clone(), } } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 36d8cefeca2b6..2cb605f033d98 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -1,24 +1,30 @@ -use std::{collections::BTreeMap, path::Path}; - -use ethers_solc::{ +use super::{InlineConfigError, InlineConfigErrorKind, INLINE_CONFIG_PREFIX}; +use figment::Profile; +use foundry_compilers::{ artifacts::{ast::NodeType, Node}, ProjectCompileOutput, }; +use itertools::Itertools; use serde_json::Value; - -use super::{remove_whitespaces, INLINE_CONFIG_PREFIX, INLINE_CONFIG_PREFIX_SELECTED_PROFILE}; +use solar_parse::{ + ast::{ + interface::{self, Session}, + Arena, CommentKind, Item, ItemKind, + }, + Parser, +}; +use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations +#[derive(Clone, Debug, PartialEq, Eq)] pub struct NatSpec { - /// The parent contract of the natspec + /// The parent contract of the natspec. pub contract: String, - /// The function annotated with the natspec - pub function: String, - /// The line the natspec appears, in the form - /// `row:col:length` i.e. `10:21:122` + /// 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, - /// The actual natspec comment, without slashes or block - /// punctuation + /// The actual natspec comment, without slashes or block punctuation. pub docs: String, } @@ -26,172 +32,427 @@ impl NatSpec { /// Factory function that extracts a vector of [`NatSpec`] instances from /// a solc compiler output. The root path is to express contract base dirs. /// That is essential to match per-test configs at runtime. - pub fn parse

(output: &ProjectCompileOutput, root: &P) -> Vec - where - P: AsRef, - { + pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { let mut natspecs: Vec = vec![]; - let output = output.clone(); - for artifact in output.with_stripped_file_prefixes(root).into_artifacts() { - if let Some(ast) = artifact.1.ast.as_ref() { - let contract: String = artifact.0.identifier(); - if let Some(node) = contract_root_node(&ast.nodes, &contract) { - apply(&mut natspecs, &contract, node) + let solc = SolcParser::new(); + let solar = SolarParser::new(); + for (id, artifact) in output.artifact_ids() { + let abs_path = id.source.as_path(); + let path = abs_path.strip_prefix(root).unwrap_or(abs_path); + let contract_name = id.name.split('.').next().unwrap(); + // `id.identifier` but with the stripped path. + let contract = format!("{}:{}", path.display(), id.name); + + let mut used_solc_ast = false; + if let Some(ast) = &artifact.ast { + if let Some(node) = solc.contract_root_node(&ast.nodes, &contract) { + solc.parse(&mut natspecs, &contract, node, true); + used_solc_ast = true; + } + } + + if !used_solc_ast { + if let Ok(src) = std::fs::read_to_string(abs_path) { + solar.parse(&mut natspecs, &src, &contract, contract_name); } } } + natspecs } - /// Returns a string describing the 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) + /// Checks if all configuration lines use a valid profile. + /// + /// i.e. Given available profiles + /// ```rust + /// let _profiles = vec!["ci", "default"]; + /// ``` + /// A configuration like `forge-config: ciii.invariant.depth = 1` would result + /// in an error. + pub fn validate_profiles(&self, profiles: &[Profile]) -> eyre::Result<()> { + for config in self.config_values() { + if !profiles.iter().any(|p| { + config + .strip_prefix(p.as_str().as_str()) + .is_some_and(|rest| rest.trim_start().starts_with('.')) + }) { + Err(InlineConfigError { + location: self.location_string(), + kind: InlineConfigErrorKind::InvalidProfile( + config.to_string(), + profiles.iter().format(", ").to_string(), + ), + })? + } + } + Ok(()) } - /// Returns a list of configuration lines that match the current profile - pub fn current_profile_configs(&self) -> Vec { - let prefix: &str = INLINE_CONFIG_PREFIX_SELECTED_PROFILE.as_ref(); - self.config_lines_with_prefix(prefix) + /// Returns the path of the contract. + pub fn path(&self) -> &str { + match self.contract.split_once(':') { + Some((path, _)) => path, + None => self.contract.as_str(), + } } - /// Returns a list of configuration lines that match a specific string prefix - pub fn config_lines_with_prefix>(&self, prefix: S) -> Vec { - let prefix: String = prefix.into(); - self.config_lines().into_iter().filter(|l| l.starts_with(&prefix)).collect() + /// Returns the location of the natspec as a string. + pub fn location_string(&self) -> String { + format!("{}:{}", self.path(), self.line) } - /// Returns a list of all the configuration lines available in the natspec - pub fn config_lines(&self) -> Vec { - self.docs - .split('\n') - .map(remove_whitespaces) - .filter(|line| line.contains(INLINE_CONFIG_PREFIX)) - .collect::>() + /// Returns a list of all the configuration values available in the natspec. + pub fn config_values(&self) -> impl Iterator { + self.docs.lines().filter_map(|line| { + line.find(INLINE_CONFIG_PREFIX) + .map(|idx| line[idx + INLINE_CONFIG_PREFIX.len()..].trim()) + }) } } -/// Given a list of nodes, find a "ContractDefinition" node that matches -/// the provided contract_id. -fn contract_root_node<'a>(nodes: &'a [Node], contract_id: &'a str) -> Option<&'a Node> { - for n in nodes.iter() { - if let NodeType::ContractDefinition = n.node_type { - let contract_data = &n.other; - if let Value::String(contract_name) = contract_data.get("name")? { - if contract_id.ends_with(contract_name) { - return Some(n) +struct SolcParser { + _private: (), +} + +impl SolcParser { + fn new() -> Self { + Self { _private: () } + } + + /// Given a list of nodes, find a "ContractDefinition" node that matches + /// 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 n.node_type == NodeType::ContractDefinition { + let contract_data = &n.other; + if let Value::String(contract_name) = contract_data.get("name")? { + if contract_id.ends_with(contract_name) { + return Some(n) + } } } } + None + } + + /// 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, 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: Some(function), + line, + docs, + }) + } + self.parse(natspecs, contract, n, false); + } } - None -} -/// Implements a DFS over a compiler output node and its children. -/// If a natspec is found it is added to `natspecs` -fn apply(natspecs: &mut Vec, contract: &str, node: &Node) { - for n in node.nodes.iter() { - if let Some((function, docs, line)) = get_fn_data(n) { - natspecs.push(NatSpec { contract: contract.into(), function, line, docs }) + /// Given a compilation output node, if it is a function definition + /// that also contains a natspec then return a tuple of: + /// - Function name + /// - Natspec text + /// - Natspec position with format "row:col:length" + /// + /// Return None otherwise. + fn get_fn_data(&self, node: &Node) -> Option<(String, String, String)> { + 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) = self.get_node_docs(fn_data)?; + return Some((fn_name, fn_docs, docs_src_line)) } - apply(natspecs, contract, n); + + None + } + + /// Given a dictionary of function data returns the name of the function. + fn get_fn_name(&self, fn_data: &BTreeMap) -> Option { + match fn_data.get("name")? { + Value::String(fn_name) => Some(fn_name.into()), + _ => None, + } + } + + /// Inspects Solc compiler output for documentation comments. Returns: + /// - `Some((String, String))` in case the function has natspec comments. First item is a + /// 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_node_docs(&self, data: &BTreeMap) -> Option<(String, String)> { + if let Value::Object(fn_docs) = data.get("documentation")? { + if let Value::String(comment) = fn_docs.get("text")? { + if comment.contains(INLINE_CONFIG_PREFIX) { + let mut src_line = fn_docs + .get("src") + .map(|src| src.to_string()) + .unwrap_or_else(|| String::from("")); + + src_line.retain(|c| c != '"'); + return Some((comment.into(), src_line)) + } + } + } + None } } -/// Given a compilation output node, if it is a function definition -/// that also contains a natspec then return a tuple of: -/// - Function name -/// - Natspec text -/// - Natspec position with format "row:col:length" -/// -/// Return None otherwise. -fn get_fn_data(node: &Node) -> Option<(String, String, String)> { - if let NodeType::FunctionDefinition = node.node_type { - let fn_data = &node.other; - let fn_name: String = get_fn_name(fn_data)?; - let (fn_docs, docs_src_line): (String, String) = get_fn_docs(fn_data)?; - return Some((fn_name, fn_docs, docs_src_line)) - } - - None +struct SolarParser { + _private: (), } -/// Given a dictionary of function data returns the name of the function. -fn get_fn_name(fn_data: &BTreeMap) -> Option { - match fn_data.get("name")? { - Value::String(fn_name) => Some(fn_name.into()), - _ => None, +impl SolarParser { + fn new() -> Self { + Self { _private: () } } -} -/// Inspects Solc compiler output for documentation comments. Returns: -/// - `Some((String, String))` in case the function has natspec comments. First item is a 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(fn_data: &BTreeMap) -> Option<(String, String)> { - if let Value::Object(fn_docs) = fn_data.get("documentation")? { - if let Value::String(comment) = fn_docs.get("text")? { - if comment.contains(INLINE_CONFIG_PREFIX) { - let mut src_line = fn_docs - .get("src") - .map(|src| src.to_string()) - .unwrap_or(String::from("")); - - src_line.retain(|c| c != '"'); - return Some((comment.into(), src_line)) - } + fn parse( + &self, + natspecs: &mut Vec, + src: &str, + contract_id: &str, + contract_name: &str, + ) { + // Fast path to avoid parsing the file. + if !src.contains(INLINE_CONFIG_PREFIX) { + return; } + + let mut handle_docs = |item: &Item<'_>| { + if item.docs.is_empty() { + return; + } + let lines = item + .docs + .iter() + .filter_map(|d| { + let s = d.symbol.as_str(); + if !s.contains(INLINE_CONFIG_PREFIX) { + return None + } + match d.kind { + CommentKind::Line => Some(s.trim().to_string()), + CommentKind::Block => Some( + s.lines() + .filter(|line| line.contains(INLINE_CONFIG_PREFIX)) + .map(|line| line.trim_start().trim_start_matches('*').trim()) + .collect::>() + .join("\n"), + ), + } + }) + .join("\n"); + if lines.is_empty() { + return; + } + let span = + item.docs.iter().map(|doc| doc.span).reduce(|a, b| a.to(b)).unwrap_or_default(); + natspecs.push(NatSpec { + contract: contract_id.to_string(), + function: if let ItemKind::Function(f) = &item.kind { + Some( + f.header + .name + .map(|sym| sym.to_string()) + .unwrap_or_else(|| f.kind.to_string()), + ) + } else { + None + }, + line: format!("{}:{}:0", span.lo().0, span.hi().0), + docs: lines, + }); + }; + + let sess = Session::builder() + .with_silent_emitter(Some("Inline config parsing failed".to_string())) + .build(); + let _ = sess.enter(|| -> interface::Result<()> { + let arena = Arena::new(); + + let mut parser = Parser::from_source_code( + &sess, + &arena, + interface::source_map::FileName::Custom(contract_id.to_string()), + src.to_string(), + )?; + + let source_unit = parser.parse_file().map_err(|e| e.emit())?; + + for item in source_unit.items.iter() { + let ItemKind::Contract(c) = &item.kind else { continue }; + if c.name.as_str() != contract_name { + continue; + } + + // Handle contract level doc comments. + handle_docs(item); + + // Handle function level doc comments. + for item in c.body.iter() { + let ItemKind::Function(_) = &item.kind else { continue }; + handle_docs(item); + } + } + + Ok(()) + }); } - None } #[cfg(test)] mod tests { - use crate::{inline::natspec::get_fn_docs, NatSpec}; - use serde_json::{json, Value}; - use std::collections::BTreeMap; + use super::*; + use serde_json::json; #[test] - fn config_lines() { - let natspec = natspec(); - let config_lines = natspec.config_lines(); + fn can_reject_invalid_profiles() { + let profiles = ["ci".into(), "default".into()]; + let natspec = NatSpec { + contract: Default::default(), + function: Default::default(), + line: Default::default(), + docs: r" + forge-config: ciii.invariant.depth = 1 + forge-config: default.invariant.depth = 1 + " + .into(), + }; + + let result = natspec.validate_profiles(&profiles); + assert!(result.is_err()); + } + + #[test] + fn can_accept_valid_profiles() { + let profiles = ["ci".into(), "default".into()]; + let natspec = NatSpec { + contract: Default::default(), + function: Default::default(), + line: Default::default(), + docs: r" + forge-config: ci.invariant.depth = 1 + forge-config: default.invariant.depth = 1 + " + .into(), + }; + + let result = natspec.validate_profiles(&profiles); + assert!(result.is_ok()); + } + + #[test] + fn parse_solar() { + let src = " +contract C { /// forge-config: default.fuzz.runs = 600 + +\t\t\t\t /// forge-config: default.fuzz.runs = 601 + + function f1() {} + /** forge-config: default.fuzz.runs = 700 */ +function f2() {} /** forge-config: default.fuzz.runs = 800 */ function f3() {} + +/** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function f4() {} +} +"; + let mut natspecs = vec![]; + let id = || "path.sol:C".to_string(); + let solar_parser = SolarParser::new(); + solar_parser.parse(&mut natspecs, src, &id(), "C"); assert_eq!( - config_lines, - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:ci.fuzz.runs=500".to_string(), - "forge-config:default.invariant.runs=1".to_string() + natspecs, + [ + // f1 + NatSpec { + contract: id(), + function: Some("f1".to_string()), + line: "14:134:0".to_string(), + docs: "forge-config: default.fuzz.runs = 600\nforge-config: default.fuzz.runs = 601".to_string(), + }, + // f2 + NatSpec { + contract: id(), + function: Some("f2".to_string()), + line: "164:208:0".to_string(), + docs: "forge-config: default.fuzz.runs = 700".to_string(), + }, + // f3 + NatSpec { + contract: id(), + function: Some("f3".to_string()), + line: "226:270:0".to_string(), + docs: "forge-config: default.fuzz.runs = 800".to_string(), + }, + // f4 + NatSpec { + contract: id(), + function: Some("f4".to_string()), + line: "289:391:0".to_string(), + docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), + }, ] - ) + ); } #[test] - fn current_profile_configs() { - let natspec = natspec(); - let config_lines = natspec.current_profile_configs(); + fn parse_solar_2() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function testInlineConfFuzz(uint8 x) public { + require(true, "this is not going to revert"); + } +} + "#; + let mut natspecs = vec![]; + let solar = SolarParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); assert_eq!( - config_lines, - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:default.invariant.runs=1".to_string() + natspecs, + [ + NatSpec { + contract: id(), + function: Some("testInlineConfFuzz".to_string()), + line: "141:255:0".to_string(), + docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), + }, ] ); } #[test] - fn config_lines_with_prefix() { - use super::INLINE_CONFIG_PREFIX; + fn config_lines() { let natspec = natspec(); - let prefix = format!("{INLINE_CONFIG_PREFIX}:default"); - let config_lines = natspec.config_lines_with_prefix(prefix); + let config_lines = natspec.config_values(); assert_eq!( - config_lines, - vec![ - "forge-config:default.fuzz.runs=600".to_string(), - "forge-config:default.invariant.runs=1".to_string() + config_lines.collect::>(), + [ + "default.fuzz.runs = 600".to_string(), + "ci.fuzz.runs = 500".to_string(), + "default.invariant.runs = 1".to_string() ] ) } @@ -199,24 +460,24 @@ mod tests { #[test] fn can_handle_unavailable_src_line_with_fallback() { let mut fn_data: BTreeMap = BTreeMap::new(); - let doc_withouth_src_field = json!({ "text": "forge-config:default.fuzz.runs=600" }); - fn_data.insert("documentation".into(), doc_withouth_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + 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_node_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "".to_string()); } #[test] fn can_handle_available_src_line() { let mut fn_data: BTreeMap = BTreeMap::new(); - let doc_withouth_src_field = + let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600", "src": "73:21:12" }); - fn_data.insert("documentation".into(), doc_withouth_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + fn_data.insert("documentation".into(), doc_without_src_field); + let (_, src_line) = SolcParser::new().get_node_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "73:21:12".to_string()); } fn natspec() -> NatSpec { - let conf = r#" + let conf = r" forge-config: default.fuzz.runs = 600 forge-config: ci.fuzz.runs = 500 ========= SOME NOISY TEXT ============= @@ -226,13 +487,98 @@ mod tests { 醤㭊r􎜕󷾸𶚏 ܖ̹灱녗V*竅􋹲⒪苏贗񾦼=숽ؓ򗋲бݧ󫥛𛲍ʹ園Ьi ======================================= forge-config: default.invariant.runs = 1 - "#; + "; 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(), } } + + #[test] + fn parse_solar_multiple_contracts_from_same_file() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /// forge-config: default.fuzz.runs = 1 + function testInlineConfFuzz1() {} +} + +contract FuzzInlineConf2 is DSTest { + /// forge-config: default.fuzz.runs = 2 + function testInlineConfFuzz2() {} +} + "#; + let mut natspecs = vec![]; + let solar = SolarParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [NatSpec { + contract: id(), + function: Some("testInlineConfFuzz1".to_string()), + line: "142:181:0".to_string(), + docs: "forge-config: default.fuzz.runs = 1".to_string(), + },] + ); + + let mut natspecs = vec![]; + let id = || "inline/FuzzInlineConf2.t.sol:FuzzInlineConf2".to_string(); + solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf2"); + assert_eq!( + natspecs, + [NatSpec { + contract: id(), + function: Some("testInlineConfFuzz2".to_string()), + line: "264:303:0".to_string(), + // should not get config from previous contract + docs: "forge-config: default.fuzz.runs = 2".to_string(), + },] + ); + } + + #[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 solar = SolarParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + solar.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [ + NatSpec { + contract: id(), + function: None, + line: "101:140:0".to_string(), + docs: "forge-config: default.fuzz.runs = 1".to_string(), + }, + NatSpec { + contract: id(), + function: Some("testInlineConfFuzz1".to_string()), + line: "181:220:0".to_string(), + docs: "forge-config: default.fuzz.runs = 3".to_string(), + } + ] + ); + } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index 1b3cd6218a627..3fb3b8f7e1d49 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -1,16 +1,11 @@ //! Configuration for invariant testing -use crate::{ - fuzz::FuzzDictionaryConfig, - inline::{ - parse_config_bool, parse_config_u32, InlineConfigParser, InlineConfigParserError, - INLINE_CONFIG_INVARIANT_KEY, - }, -}; +use crate::fuzz::FuzzDictionaryConfig; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; /// Contains for invariant testing -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct InvariantConfig { /// The number of runs that must execute for each invariant test group. pub runs: u32, @@ -24,101 +19,66 @@ pub struct InvariantConfig { /// The fuzz dictionary configuration #[serde(flatten)] pub dictionary: FuzzDictionaryConfig, - /// Attempt to shrink the failure case to its smallest sequence of calls - pub shrink_sequence: bool, + /// The maximum number of attempts to shrink the sequence + pub shrink_run_limit: u32, + /// The maximum number of rejects via `vm.assume` which can be encountered during a single + /// invariant run. + pub max_assume_rejects: u32, + /// Number of runs to execute and include in the gas report. + pub gas_report_samples: u32, + /// Path where invariant failures are recorded and replayed. + pub failure_persist_dir: Option, + /// Whether to collect and display fuzzed selectors metrics. + pub show_metrics: bool, + /// Optional timeout (in seconds) for each invariant test. + pub timeout: Option, + /// Display counterexample as solidity calls. + pub show_solidity: bool, } impl Default for InvariantConfig { fn default() -> Self { - InvariantConfig { + Self { runs: 256, - depth: 15, + depth: 500, fail_on_revert: false, call_override: false, dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() }, - shrink_sequence: true, + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: None, + show_metrics: false, + timeout: None, + show_solidity: false, } } } -impl InlineConfigParser for InvariantConfig { - fn config_key() -> String { - INLINE_CONFIG_INVARIANT_KEY.into() - } - - fn try_merge(&self, configs: &[String]) -> Result, InlineConfigParserError> { - let overrides: Vec<(String, String)> = Self::get_config_overrides(configs); - - if overrides.is_empty() { - return Ok(None) - } - - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; - - for pair in overrides { - let key = pair.0; - let value = pair.1; - match key.as_str() { - "runs" => conf_clone.runs = parse_config_u32(key, value)?, - "depth" => conf_clone.depth = parse_config_u32(key, value)?, - "fail-on-revert" => conf_clone.fail_on_revert = parse_config_bool(key, value)?, - "call-override" => conf_clone.call_override = parse_config_bool(key, value)?, - "shrink-sequence" => conf_clone.shrink_sequence = parse_config_bool(key, value)?, - _ => Err(InlineConfigParserError::InvalidConfigProperty(key.to_string()))?, - } - } - Ok(Some(conf_clone)) - } -} - -#[cfg(test)] -mod tests { - use crate::{inline::InlineConfigParser, InvariantConfig}; - - #[test] - fn unrecognized_property() { - let configs = &["forge-config: default.invariant.unknownprop = 200".to_string()]; - let base_config = InvariantConfig::default(); - if let Err(e) = base_config.try_merge(configs) { - assert_eq!(e.to_string(), "'unknownprop' is an invalid config property"); - } else { - unreachable!() +impl InvariantConfig { + /// Creates invariant configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. + pub fn new(cache_dir: PathBuf) -> Self { + Self { + runs: 256, + depth: 500, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() }, + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: Some(cache_dir), + show_metrics: false, + timeout: None, + show_solidity: false, } } - #[test] - fn successful_merge() { - let configs = &["forge-config: default.invariant.runs = 42424242".to_string()]; - let base_config = InvariantConfig::default(); - let merged: InvariantConfig = base_config.try_merge(configs).expect("No errors").unwrap(); - assert_eq!(merged.runs, 42424242); - } - - #[test] - fn merge_is_none() { - let empty_config = &[]; - let base_config = InvariantConfig::default(); - let merged = base_config.try_merge(empty_config).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn can_merge_unrelated_properties_into_config() { - let unrelated_configs = &["forge-config: default.fuzz.runs = 2".to_string()]; - let base_config = InvariantConfig::default(); - let merged = base_config.try_merge(unrelated_configs).expect("No errors"); - assert!(merged.is_none()); - } - - #[test] - fn override_detection() { - let configs = &[ - "forge-config: default.fuzz.runs = 42424242".to_string(), - "forge-config: ci.fuzz.runs = 666666".to_string(), - "forge-config: default.invariant.runs = 2".to_string(), - ]; - let variables = InvariantConfig::get_config_overrides(configs); - assert_eq!(variables, vec![("runs".into(), "2".into())]); + /// Returns path to failure dir of given invariant test contract. + pub fn failure_dir(self, contract_name: &str) -> PathBuf { + self.failure_persist_dir + .unwrap() + .join("failures") + .join(contract_name.split(':').next_back().unwrap()) } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 80899815fd1c9..62df691ac2ef6 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,102 +1,128 @@ +//! # foundry-config +//! //! Foundry configuration. -#![warn(missing_docs, unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; use crate::cache::StorageCachingConfig; -use ethers_core::types::{Address, Chain::Mainnet, H160, H256, U256}; -pub use ethers_solc::{self, artifacts::OptimizerDetails}; -use ethers_solc::{ - artifacts::{ - output_selection::ContractOutputSelection, serde_helpers, BytecodeHash, DebuggingSettings, - Libraries, ModelCheckerSettings, ModelCheckerTarget, Optimizer, RevertStrings, Settings, - SettingsMetadata, Severity, - }, - cache::SOLIDITY_FILES_CACHE_FILENAME, - error::SolcError, - remappings::{RelativeRemapping, Remapping}, - ConfigurableArtifacts, EvmVersion, Project, ProjectPathsConfig, Solc, SolcConfig, -}; +use alloy_primitives::{address, Address, B256, U256}; use eyre::{ContextCompat, WrapErr}; use figment::{ providers::{Env, Format, Serialized, Toml}, value::{Dict, Map, Value}, Error, Figment, Metadata, Profile, Provider, }; -use inflector::Inflector; -use once_cell::sync::Lazy; +use filter::GlobMatcher; +use foundry_compilers::{ + artifacts::{ + output_selection::{ContractOutputSelection, OutputSelection}, + remappings::{RelativeRemapping, Remapping}, + serde_helpers, BytecodeHash, DebuggingSettings, EofVersion, EvmVersion, Libraries, + ModelCheckerSettings, ModelCheckerTarget, Optimizer, OptimizerDetails, RevertStrings, + Settings, SettingsMetadata, Severity, + }, + cache::SOLIDITY_FILES_CACHE_FILENAME, + compilers::{ + multi::{MultiCompiler, MultiCompilerSettings}, + solc::{Solc, SolcCompiler}, + vyper::{Vyper, VyperSettings}, + Compiler, + }, + error::SolcError, + multi::{MultiCompilerParsedSource, MultiCompilerRestrictions}, + solc::{CliSettings, SolcSettings}, + ArtifactOutput, ConfigurableArtifacts, Graph, Project, ProjectPathsConfig, + RestrictionsWithVersion, VyperLanguage, +}; use regex::Regex; +use revm_primitives::{map::AddressHashMap, FixedBytes, SpecId}; use semver::Version; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; use std::{ borrow::Cow, - collections::HashMap, + collections::BTreeMap, fs, path::{Path, PathBuf}, str::FromStr, + sync::mpsc::{self, RecvTimeoutError}, + time::Duration, }; -pub(crate) use tracing::trace; -// Macros useful for creating a figment. mod macros; -// Utilities for making it easier to handle tests. pub mod utils; -pub use crate::utils::*; +pub use utils::*; mod endpoints; -pub use endpoints::{ResolvedRpcEndpoints, RpcEndpoint, RpcEndpoints}; +pub use endpoints::{ + ResolvedRpcEndpoint, ResolvedRpcEndpoints, RpcEndpoint, RpcEndpointUrl, RpcEndpoints, +}; mod etherscan; +use etherscan::{ + EtherscanConfigError, EtherscanConfigs, EtherscanEnvProvider, ResolvedEtherscanConfig, +}; + mod resolve; pub use resolve::UnresolvedEnvVarError; pub mod cache; use cache::{Cache, ChainCache}; -mod chain; -pub use chain::Chain; - pub mod fmt; pub use fmt::FormatterConfig; pub mod fs_permissions; -pub use crate::fs_permissions::FsPermissions; +pub use fs_permissions::FsPermissions; +use fs_permissions::PathPermission; pub mod error; +use error::ExtractConfigError; pub use error::SolidityErrorCode; pub mod doc; pub use doc::DocConfig; +pub mod filter; +pub use filter::SkipBuildFilters; + mod warning; pub use warning::*; -// helpers for fixing configuration warnings pub mod fix; // reexport so cli types can implement `figment::Provider` to easily merge compiler arguments +pub use alloy_chains::{Chain, NamedChain}; pub use figment; -use tracing::warn; -/// config providers pub mod providers; - -use crate::{ - error::ExtractConfigError, - etherscan::{EtherscanConfigError, EtherscanConfigs, ResolvedEtherscanConfig}, -}; +pub use providers::Remappings; use providers::*; mod fuzz; pub use fuzz::{FuzzConfig, FuzzDictionaryConfig}; mod invariant; -use crate::fs_permissions::PathPermission; pub use invariant::InvariantConfig; -use providers::remappings::RemappingsProvider; mod inline; -pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; +pub use inline::{InlineConfig, InlineConfigError, NatSpec}; + +pub mod soldeer; +use soldeer::{SoldeerConfig, SoldeerDependencyConfig}; + +mod vyper; +use vyper::VyperConfig; + +mod bind_json; +use bind_json::BindJsonConfig; + +mod compilation; +pub use compilation::{CompilationRestrictions, SettingsOverrides}; /// Foundry configuration /// @@ -129,7 +155,7 @@ pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfi /// the "default" meta-profile. /// /// Note that these behaviors differ from those of [`Config::figment()`]. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Config { /// The selected profile. **(default: _default_ `default`)** /// @@ -139,6 +165,19 @@ pub struct Config { /// set to the extracting Figment's selected `Profile`. #[serde(skip)] pub profile: Profile, + /// The list of all profiles defined in the config. + /// + /// See `profile`. + #[serde(skip)] + pub profiles: Vec, + + /// The root path where the config detection started from, [`Config::with_root`]. + // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] + // representation, but will be deserialized from the `Figment` so that forge commands can + // override it. + #[serde(default = "root_default", skip_serializing)] + pub root: PathBuf, + /// path of the source contracts dir, like `src` or `contracts` pub src: PathBuf, /// path of the test dir @@ -159,12 +198,20 @@ pub struct Config { pub cache: bool, /// where the cache is stored if enabled pub cache_path: PathBuf, + /// where the gas snapshots are stored + pub snapshots: PathBuf, + /// whether to check for differences against previously stored gas snapshots + pub gas_snapshot_check: bool, + /// whether to emit gas snapshots to disk + pub gas_snapshot_emit: bool, /// where the broadcast logs are stored pub broadcast: PathBuf, /// additional solc allow paths for `--allow-paths` pub allow_paths: Vec, /// additional solc include paths for `--include-path` pub include_paths: Vec, + /// glob patterns to skip + pub skip: Vec, /// whether to force a `project.clean()` pub force: bool, /// evm version to use @@ -174,15 +221,20 @@ pub struct Config { pub gas_reports: Vec, /// list of contracts to ignore for gas reports pub gas_reports_ignore: Vec, + /// Whether to include gas reports for tests. + pub gas_reports_include_tests: bool, /// The Solc instance to use if any. /// /// This takes precedence over `auto_detect_solc`, if a version is set then this overrides /// auto-detection. /// /// **Note** for backwards compatibility reasons this also accepts solc_version from the toml - /// file, see [`BackwardsCompatProvider`] + /// file, see `BackwardsCompatTomlProvider`. + /// + /// Avoid using this field directly; call the related `solc` methods instead. + #[doc(hidden)] pub solc: Option, - /// whether to autodetect the solc compiler version to use + /// Whether to autodetect the solc compiler version to use. pub auto_detect_solc: bool, /// Offline mode, if set, network access (downloading solc) is disallowed. /// @@ -192,9 +244,18 @@ pub struct Config { /// install it pub offline: bool, /// Whether to activate optimizer - pub optimizer: bool, - /// Sets the optimizer runs - pub optimizer_runs: usize, + pub optimizer: Option, + /// The number of runs specifies roughly how often each opcode of the deployed code will be + /// executed across the life-time of the contract. This means it is a trade-off parameter + /// between code size (deploy cost) and code execution cost (cost after deployment). + /// An `optimizer_runs` parameter of `1` will produce short but expensive code. In contrast, a + /// larger `optimizer_runs` parameter will produce longer but more gas efficient code. The + /// maximum value of the parameter is `2**32-1`. + /// + /// A common misconception is that this parameter specifies the number of iterations of the + /// optimizer. This is not true: The optimizer will always run as many times as it can + /// still improve the code. + pub optimizer_runs: Option, /// Switch optimizer components on or off in detail. /// The "enabled" switch above provides two defaults which can be /// tweaked here. If "details" is given, "enabled" can be omitted. @@ -205,6 +266,19 @@ pub struct Config { pub verbosity: u8, /// url of the rpc server that should be used for any rpc calls pub eth_rpc_url: Option, + /// JWT secret that should be used for any rpc calls + pub eth_rpc_jwt: Option, + /// Timeout that should be used for any rpc calls + pub eth_rpc_timeout: Option, + /// Headers that should be used for any rpc calls + /// + /// # Example + /// + /// rpc_headers = ["x-custom-header:value", "x-another-header:another-value"] + /// + /// You can also the ETH_RPC_HEADERS env variable like so: + /// `ETH_RPC_HEADERS="x-custom-header:value x-another-header:another-value"` + pub eth_rpc_headers: Option>, /// etherscan API key, or alias for an `EtherscanConfig` in `etherscan` table pub etherscan_api_key: Option, /// Multiple etherscan api configs and their aliases @@ -212,6 +286,9 @@ pub struct Config { pub etherscan: EtherscanConfigs, /// list of solidity error codes to always silence in the compiler output pub ignored_error_codes: Vec, + /// list of file paths to ignore + #[serde(rename = "ignored_warnings_from")] + pub ignored_file_paths: Vec, /// When true, compiler warnings are treated as errors pub deny_warnings: bool, /// Only run test functions matching the specified regex pattern. @@ -232,12 +309,27 @@ 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 pub invariant: InvariantConfig, /// Whether to allow ffi cheatcodes in test pub ffi: bool, + /// Whether to allow `expectRevert` for internal functions. + pub allow_internal_expect_revert: bool, + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + pub always_use_create_2_factory: bool, + /// Sets a timeout in seconds for vm.prompt cheatcodes + pub prompt_timeout: u64, /// The address which will be executing all tests pub sender: Address, /// The tx.origin value during EVM execution @@ -248,50 +340,58 @@ pub struct Config { pub block_number: u64, /// pins the block number for the state fork pub fork_block_number: Option, - /// The chain id to use - pub chain_id: Option, - /// Block gas limit + /// The chain name or EIP-155 chain ID. + #[serde(rename = "chain_id", alias = "chain")] + pub chain: Option, + /// Block gas limit. pub gas_limit: GasLimit, /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. pub code_size_limit: Option, - /// `tx.gasprice` value during EVM execution" + /// `tx.gasprice` value during EVM execution. /// /// This is an Option, so we can determine in fork mode whether to use the config's gas price - /// (if set by user) or the remote client's gas price + /// (if set by user) or the remote client's gas price. pub gas_price: Option, - /// the base fee in a block + /// The base fee in a block. pub block_base_fee_per_gas: u64, - /// the `block.coinbase` value during EVM execution + /// The `block.coinbase` value during EVM execution. pub block_coinbase: Address, - /// the `block.timestamp` value during EVM execution + /// The `block.timestamp` value during EVM execution. pub block_timestamp: u64, - /// the `block.difficulty` value during EVM execution + /// The `block.difficulty` value during EVM execution. pub block_difficulty: u64, - /// Before merge the `block.max_hash` after merge it is `block.prevrandao` - pub block_prevrandao: H256, - /// the `block.gaslimit` value during EVM execution + /// Before merge the `block.max_hash`, after merge it is `block.prevrandao`. + pub block_prevrandao: B256, + /// The `block.gaslimit` value during EVM execution. pub block_gas_limit: Option, - /// The memory limit of the EVM (32 MB by default) + /// The memory limit per EVM execution in bytes. + /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. + /// + /// The default is 128MiB. pub memory_limit: u64, - /// Additional output selection for all contracts - /// such as "ir", "devdoc", "storageLayout", etc. - /// See [Solc Compiler Api](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-api) + /// Additional output selection for all contracts, such as "ir", "devdoc", "storageLayout", + /// etc. + /// + /// See the [Solc Compiler Api](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-api) for more information. /// - /// The following values are always set because they're required by `forge` - //{ - // "*": [ - // "abi", - // "evm.bytecode", - // "evm.deployedBytecode", - // "evm.methodIdentifiers" - // ] - // } - // "# + /// The following values are always set because they're required by `forge`: + /// ```json + /// { + /// "*": [ + /// "abi", + /// "evm.bytecode", + /// "evm.deployedBytecode", + /// "evm.methodIdentifiers" + /// ] + /// } + /// ``` #[serde(default)] pub extra_output: Vec, - /// If set , a separate `json` file will be emitted for every contract depending on the + /// If set, a separate JSON file will be emitted for every contract depending on the /// selection, eg. `extra_output_files = ["metadata"]` will create a `metadata.json` for - /// each contract in the project. See [Contract Metadata](https://docs.soliditylang.org/en/latest/metadata.html) + /// each contract in the project. + /// + /// See [Contract Metadata](https://docs.soliditylang.org/en/latest/metadata.html) for more information. /// /// The difference between `extra_output = ["metadata"]` and /// `extra_output_files = ["metadata"]` is that the former will include the @@ -299,13 +399,15 @@ pub struct Config { /// output selection as separate files. #[serde(default)] pub extra_output_files: Vec, - /// Print the names of the compiled contracts + /// Whether to print the names of the compiled contracts. pub names: bool, - /// Print the sizes of the compiled contracts + /// Whether to print the sizes of the compiled contracts. pub sizes: bool, /// If set to true, changes compilation pipeline to go through the Yul intermediate /// representation. pub via_ir: bool, + /// Whether to include the AST as JSON in the compiler output. + pub ast: bool, /// RPC storage caching settings determines what chains and endpoints to cache pub rpc_storage_caching: StorageCachingConfig, /// Disables storage caching entirely. This overrides any settings made in @@ -335,13 +437,10 @@ pub struct Config { /// Whether to compile in sparse mode /// /// If this option is enabled, only the required contracts/files will be selected to be - /// included in solc's output selection, see also - /// [OutputSelection](ethers_solc::artifacts::output_selection::OutputSelection) + /// included in solc's output selection, see also [`OutputSelection`]. pub sparse_mode: bool, - /// Whether to emit additional build info files - /// - /// If set to `true`, `ethers-solc` will generate additional build info json files for every - /// new build, containing the `CompilerInput` and `CompilerOutput` + /// Generates additional build info json files for every new build, containing the + /// `CompilerInput` and `CompilerOutput`. pub build_info: bool, /// The path to the `build-info` directory that contains the build info json files. pub build_info_path: Option, @@ -349,46 +448,105 @@ 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) pub fs_permissions: FsPermissions, - /// The root path where the config detection started from, `Config::with_root` - #[doc(hidden)] - // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] - // representation, but will be deserialized from the `Figment` so that forge commands can - // override it. - #[serde(rename = "root", default, skip_serializing)] - pub __root: RootPath, + + /// Whether to enable call isolation. + /// + /// Useful for more correct gas accounting and EVM behavior in general. + pub isolate: bool, + + /// Whether to disable the block gas limit. + pub disable_block_gas_limit: bool, + + /// Address labels + pub labels: AddressHashMap, + + /// Whether to enable safety checks for `vm.getCode` and `vm.getDeployedCode` invocations. + /// If disabled, it is possible to access artifacts which were not recompiled or cached. + pub unchecked_cheatcode_artifacts: bool, + + /// CREATE2 salt to use for the library deployment in scripts. + pub create2_library_salt: B256, + + /// The CREATE2 deployer address to use. + pub create2_deployer: Address, + + /// Configuration for Vyper compiler + pub vyper: VyperConfig, + + /// Soldeer dependencies + pub dependencies: Option, + + /// Soldeer custom configs + pub soldeer: Option, + + /// 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, + + /// Whether to enable Odyssey features. + #[serde(alias = "alphanet")] + pub odyssey: bool, + + /// Timeout for transactions in seconds. + pub transaction_timeout: u64, + + /// Use EOF-enabled solc for compilation. + pub eof: bool, + + /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information. + #[serde(rename = "__warnings", default, skip_serializing)] + pub warnings: Vec, + + /// Additional settings profiles to use when compiling. + #[serde(default)] + pub additional_compiler_profiles: Vec, + + /// Restrictions on compilation of certain files. + #[serde(default)] + pub compilation_restrictions: Vec, + /// PRIVATE: This structure may grow, As such, constructing this structure should /// _always_ be done using a public constructor or update syntax: /// - /// ```rust + /// ```ignore /// use foundry_config::Config; /// - /// let config = Config { - /// src: "other".into(), - /// ..Default::default() - /// }; + /// let config = Config { src: "other".into(), ..Default::default() }; /// ``` #[doc(hidden)] #[serde(skip)] - pub __non_exhaustive: (), - /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information - #[serde(default, skip_serializing)] - pub __warnings: Vec, + pub _non_exhaustive: (), } -/// Mapping of fallback standalone sections. See [`FallbackProfileProvider`] -pub static STANDALONE_FALLBACK_SECTIONS: Lazy> = - Lazy::new(|| HashMap::from([("invariant", "fuzz")])); +/// Mapping of fallback standalone sections. See [`FallbackProfileProvider`]. +pub const STANDALONE_FALLBACK_SECTIONS: &[(&str, &str)] = &[("invariant", "fuzz")]; -/// Deprecated keys. -pub static DEPRECATIONS: Lazy> = Lazy::new(|| HashMap::from([])); +/// Deprecated keys and their replacements. +/// +/// See [Warning::DeprecatedKey] +pub const DEPRECATIONS: &[(&str, &str)] = &[("cancun", "evm_version = Cancun")]; impl Config { /// The default profile: "default" - pub const DEFAULT_PROFILE: Profile = Profile::const_new("default"); + pub const DEFAULT_PROFILE: Profile = Profile::Default; /// The hardhat profile: "hardhat" pub const HARDHAT_PROFILE: Profile = Profile::const_new("hardhat"); @@ -397,8 +555,19 @@ impl Config { pub const PROFILE_SECTION: &'static str = "profile"; /// Standalone sections in the config which get integrated into the selected profile - pub const STANDALONE_SECTIONS: &'static [&'static str] = - &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant"]; + pub const STANDALONE_SECTIONS: &'static [&'static str] = &[ + "rpc_endpoints", + "etherscan", + "fmt", + "doc", + "fuzz", + "invariant", + "labels", + "dependencies", + "soldeer", + "vyper", + "bind_json", + ]; /// File name of config toml file pub const FILE_NAME: &'static str = "foundry.toml"; @@ -409,51 +578,38 @@ impl Config { /// Default address for tx.origin /// /// `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38` - pub const DEFAULT_SENDER: H160 = H160([ - 0x18, 0x04, 0xc8, 0xAB, 0x1F, 0x12, 0xE6, 0xbb, 0xF3, 0x89, 0x4D, 0x40, 0x83, 0xF3, 0x3E, - 0x07, 0x30, 0x9D, 0x1F, 0x38, - ]); + pub const DEFAULT_SENDER: Address = address!("1804c8AB1F12E6bbf3894d4083f33e07309d1f38"); + + /// Default salt for create2 library deployments + pub const DEFAULT_CREATE2_LIBRARY_SALT: FixedBytes<32> = FixedBytes::<32>::ZERO; - /// Returns the current `Config` + /// Default create2 deployer + pub const DEFAULT_CREATE2_DEPLOYER: Address = + address!("4e59b44847b379578588920ca78fbf26c0b4956c"); + + /// Docker image with eof-enabled solc binary + pub const EOF_SOLC_IMAGE: &'static str = "ghcr.io/paradigmxyz/forge-eof@sha256:46f868ce5264e1190881a3a335d41d7f42d6f26ed20b0c823609c715e38d603f"; + + /// Loads the `Config` from the current directory. /// - /// See `Config::figment` - #[track_caller] - pub fn load() -> Self { - Config::from_provider(Config::figment()) + /// See [`figment`](Self::figment) for more details. + pub fn load() -> Result { + Self::from_provider(Self::figment()) } - /// Returns the current `Config` + /// Loads the `Config` with the given `providers` preset. /// - /// See `Config::figment_with_root` - #[track_caller] - pub fn load_with_root(root: impl Into) -> Self { - Config::from_provider(Config::figment_with_root(root)) + /// See [`figment`](Self::figment) for more details. + pub fn load_with_providers(providers: FigmentProviders) -> Result { + Self::from_provider(Self::default().to_figment(providers)) } - /// Extract a `Config` from `provider`, panicking if extraction fails. - /// - /// # Panics - /// - /// If extraction fails, prints an error message indicating the failure and - /// panics. For a version that doesn't panic, use [`Config::try_from()`]. - /// - /// # Example + /// Loads the `Config` from the given root directory. /// - /// ```no_run - /// use foundry_config::Config; - /// use figment::providers::{Toml, Format, Env}; - /// - /// // Use foundry's default `Figment`, but allow values from `other.toml` - /// // to supersede its values. - /// let figment = Config::figment() - /// .merge(Toml::file("other.toml").nested()); - /// - /// let config = Config::from_provider(figment); - /// ``` + /// See [`figment_with_root`](Self::figment_with_root) for more details. #[track_caller] - pub fn from_provider(provider: T) -> Self { - trace!("load config with provider: {:?}", provider.metadata()); - Self::try_from(provider).unwrap_or_else(|err| panic!("{}", err)) + pub fn load_with_root(root: impl AsRef) -> Result { + Self::from_provider(Self::figment_with_root(root.as_ref())) } /// Attempts to extract a `Config` from `provider`, returning the result. @@ -461,30 +617,146 @@ impl Config { /// # Example /// /// ```rust + /// use figment::providers::{Env, Format, Toml}; /// use foundry_config::Config; - /// use figment::providers::{Toml, Format, Env}; /// /// // Use foundry's default `Figment`, but allow values from `other.toml` /// // to supersede its values. - /// let figment = Config::figment() - /// .merge(Toml::file("other.toml").nested()); + /// let figment = Config::figment().merge(Toml::file("other.toml").nested()); /// - /// let config = Config::try_from(figment); + /// let config = Config::from_provider(figment); /// ``` + #[doc(alias = "try_from")] + pub fn from_provider(provider: T) -> Result { + trace!("load config with provider: {:?}", provider.metadata()); + Self::from_figment(Figment::from(provider)) + } + + #[doc(hidden)] + #[deprecated(note = "use `Config::from_provider` instead")] pub fn try_from(provider: T) -> Result { - let figment = Figment::from(provider); + Self::from_provider(provider) + } + + fn from_figment(figment: Figment) -> Result { let mut config = figment.extract::().map_err(ExtractConfigError::new)?; config.profile = figment.profile().clone(); + + // The `"profile"` profile contains all the profiles as keys. + let mut add_profile = |profile: &Profile| { + if !config.profiles.contains(profile) { + config.profiles.push(profile.clone()); + } + }; + let figment = figment.select(Self::PROFILE_SECTION); + if let Ok(data) = figment.data() { + if let Some(profiles) = data.get(&Profile::new(Self::PROFILE_SECTION)) { + for profile in profiles.keys() { + add_profile(&Profile::new(profile)); + } + } + } + add_profile(&Self::DEFAULT_PROFILE); + add_profile(&config.profile); + + config.normalize_optimizer_settings(); + Ok(config) } + /// Returns the populated [Figment] using the requested [FigmentProviders] preset. + /// + /// This will merge various providers, such as env,toml,remappings into the figment if + /// requested. + pub fn to_figment(&self, providers: FigmentProviders) -> Figment { + // Note that `Figment::from` here is a method on `Figment` rather than the `From` impl below + + if providers.is_none() { + return Figment::from(self); + } + + let root = self.root.as_path(); + let profile = Self::selected_profile(); + let mut figment = Figment::default().merge(DappHardhatDirProvider(root)); + + // merge global foundry.toml file + 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 = Self::merge_toml_provider( + figment, + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), root.join(Self::FILE_NAME)).cached(), + profile.clone(), + ); + + // merge environment variables + figment = figment + .merge( + Env::prefixed("DAPP_") + .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge( + Env::prefixed("DAPP_TEST_") + .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge(DappEnvCompatProvider) + .merge(EtherscanEnvProvider::default()) + .merge( + Env::prefixed("FOUNDRY_") + .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .map(|key| { + let key = key.as_str(); + if Self::STANDALONE_SECTIONS.iter().any(|section| { + key.starts_with(&format!("{}_", section.to_ascii_uppercase())) + }) { + key.replacen('_', ".", 1).into() + } else { + key.into() + } + }) + .global(), + ) + .select(profile.clone()); + + // only resolve remappings if all providers are requested + if providers.is_all() { + // we try to merge remappings after we've merged all other providers, this prevents + // redundant fs lookups to determine the default remappings that are eventually updated + // by other providers, like the toml file + let remappings = RemappingsProvider { + auto_detect_remappings: figment + .extract_inner::("auto_detect_remappings") + .unwrap_or(true), + lib_paths: figment + .extract_inner::>("libs") + .map(Cow::Owned) + .unwrap_or_else(|_| Cow::Borrowed(&self.libs)), + root, + remappings: figment.extract_inner::>("remappings"), + }; + figment = figment.merge(remappings); + } + + // normalize defaults + figment = self.normalize_defaults(figment); + + Figment::from(self).merge(figment).select(profile) + } + /// The config supports relative paths and tracks the root path separately see /// `Config::with_root` /// /// This joins all relative paths with the current root and attempts to make them canonic #[must_use] pub fn canonic(self) -> Self { - let root = self.__root.0.clone(); + let root = self.root.clone(); self.canonic_at(root) } @@ -519,6 +791,7 @@ impl Config { self.out = p(&root, &self.out); self.broadcast = p(&root, &self.broadcast); self.cache_path = p(&root, &self.cache_path); + self.snapshots = p(&root, &self.snapshots); if let Some(build_info_path) = self.build_info_path { self.build_info_path = Some(p(&root, &build_info_path)); @@ -535,7 +808,7 @@ impl Config { self.fs_permissions.join_all(&root); - if let Some(ref mut model_checker) = self.model_checker { + if let Some(model_checker) = &mut self.model_checker { model_checker.contracts = std::mem::take(&mut model_checker.contracts) .into_iter() .map(|(path, contracts)| { @@ -547,6 +820,53 @@ impl Config { self } + /// Normalizes the evm version if a [SolcReq] is set + pub fn normalized_evm_version(mut self) -> Self { + self.normalize_evm_version(); + self + } + + /// Normalizes optimizer settings. + /// See + pub fn normalized_optimizer_settings(mut self) -> Self { + self.normalize_optimizer_settings(); + self + } + + /// Normalizes the evm version if a [SolcReq] is set to a valid version. + pub fn normalize_evm_version(&mut self) { + self.evm_version = self.get_normalized_evm_version(); + } + + /// Normalizes optimizer settings: + /// - with default settings, optimizer is set to false and optimizer runs to 200 + /// - if optimizer is set and optimizer runs not specified, then optimizer runs is set to 200 + /// - enable optimizer if not explicitly set and optimizer runs set to a value greater than 0 + pub fn normalize_optimizer_settings(&mut self) { + match (self.optimizer, self.optimizer_runs) { + // Default: set the optimizer to false and optimizer runs to 200. + (None, None) => { + self.optimizer = Some(false); + self.optimizer_runs = Some(200); + } + // Set the optimizer runs to 200 if the `optimizer` config set. + (Some(_), None) => self.optimizer_runs = Some(200), + // Enables optimizer if the `optimizer_runs` has been set with a value greater than 0. + (None, Some(runs)) => self.optimizer = Some(runs > 0), + _ => {} + } + } + + /// Returns the normalized [EvmVersion] for the current solc version, or the configured one. + pub fn get_normalized_evm_version(&self) -> EvmVersion { + if let Some(version) = self.solc_version() { + if let Some(evm_version) = self.evm_version.normalize_version_solc(&version) { + return evm_version; + } + } + self.evm_version + } + /// Returns a sanitized version of the Config where are paths are set correctly and potential /// duplicates are resolved /// @@ -560,6 +880,8 @@ impl Config { config.libs.sort_unstable(); config.libs.dedup(); + config.sanitize_eof_settings(); + config } @@ -577,6 +899,22 @@ impl Config { } } + /// Adjusts settings if EOF compilation is enabled. + /// + /// This includes enabling via_ir, eof_version and ensuring that evm_version is not lower than + /// Prague. + pub fn sanitize_eof_settings(&mut self) { + if self.eof { + self.via_ir = true; + if self.eof_version.is_none() { + self.eof_version = Some(EofVersion::V1); + } + if self.evm_version < EvmVersion::Prague { + self.evm_version = EvmVersion::Prague; + } + } + } + /// Returns the directory in which dependencies should be installed /// /// Returns the first dir from `libs` that is not `node_modules` or `lib` if `libs` is empty @@ -598,98 +936,238 @@ impl Config { /// /// ``` /// use foundry_config::Config; - /// let config = Config::load_with_root(".").sanitized(); - /// let project = config.project(); + /// let config = Config::load_with_root(".")?.sanitized(); + /// let project = config.project()?; + /// # Ok::<_, eyre::Error>(()) /// ``` - pub fn project(&self) -> Result { - self.create_project(true, false) + pub fn project(&self) -> Result, SolcError> { + self.create_project(self.cache, false) } /// Same as [`Self::project()`] but sets configures the project to not emit artifacts and ignore - /// cache, caching causes no output until https://github.com/gakonst/ethers-rs/issues/727 - pub fn ephemeral_no_artifacts_project(&self) -> Result { + /// cache. + pub fn ephemeral_project(&self) -> Result, SolcError> { self.create_project(false, true) } - fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { - let mut project = Project::builder() + /// Builds mapping with additional settings profiles. + fn additional_settings( + &self, + base: &MultiCompilerSettings, + ) -> BTreeMap { + let mut map = BTreeMap::new(); + + for profile in &self.additional_compiler_profiles { + let mut settings = base.clone(); + profile.apply(&mut settings); + map.insert(profile.name.clone(), settings); + } + + map + } + + /// Resolves globs and builds a mapping from individual source files to their restrictions + #[expect(clippy::disallowed_macros)] + fn restrictions( + &self, + paths: &ProjectPathsConfig, + ) -> Result>, SolcError> + { + let mut map = BTreeMap::new(); + if self.compilation_restrictions.is_empty() { + return Ok(BTreeMap::new()); + } + + let graph = Graph::::resolve(paths)?; + let (sources, _) = graph.into_sources(); + + for res in &self.compilation_restrictions { + for source in sources.keys().filter(|path| { + if res.paths.is_match(path) { + true + } else if let Ok(path) = path.strip_prefix(&paths.root) { + res.paths.is_match(path) + } else { + false + } + }) { + let res: RestrictionsWithVersion<_> = + res.clone().try_into().map_err(SolcError::msg)?; + if !map.contains_key(source) { + map.insert(source.clone(), res); + } else { + let value = map.remove(source.as_path()).unwrap(); + if let Some(merged) = value.clone().merge(res) { + map.insert(source.clone(), merged); + } else { + // `sh_warn!` is a circular dependency, preventing us from using it here. + eprintln!( + "{}", + yansi::Paint::yellow(&format!( + "Failed to merge compilation restrictions for {}", + source.display() + )) + ); + map.insert(source.clone(), value); + } + } + } + } + + Ok(map) + } + + /// Creates a [`Project`] with the given `cached` and `no_artifacts` flags. + /// + /// Prefer using [`Self::project`] or [`Self::ephemeral_project`] instead. + pub fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { + let settings = self.compiler_settings()?; + let paths = self.project_paths(); + let mut builder = Project::builder() .artifacts(self.configured_artifacts_handler()) - .paths(self.project_paths()) - .allowed_path(&self.__root.0) - .allowed_paths(&self.libs) - .allowed_paths(&self.allow_paths) - .include_paths(&self.include_paths) - .solc_config(SolcConfig::builder().settings(self.solc_settings()?).build()) + .additional_settings(self.additional_settings(&settings)) + .restrictions(self.restrictions(&paths)?) + .settings(settings) + .paths(paths) .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) + .ignore_paths(self.ignored_file_paths.clone()) .set_compiler_severity_filter(if self.deny_warnings { Severity::Warning } else { Severity::Error }) - .set_auto_detect(self.is_auto_detect()) .set_offline(self.offline) .set_cached(cached) - .set_build_info(cached & self.build_info) - .set_no_artifacts(no_artifacts) - .build()?; + .set_build_info(!no_artifacts && self.build_info) + .set_no_artifacts(no_artifacts); - if self.force { - project.cleanup()?; + if !self.skip.is_empty() { + let filter = SkipBuildFilters::new(self.skip.clone(), self.root.clone()); + builder = builder.sparse_output(filter); } - if let Some(solc) = self.ensure_solc()? { - project.solc = solc; + let project = builder.build(self.compiler()?)?; + + if self.force { + self.cleanup(&project)?; } Ok(project) } + /// Cleans the project. + 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 { + let path = project.root().join(test_dir); + if path.exists() { + let _ = fs::remove_dir_all(&path); + } + } + }; + remove_test_dir(&self.fuzz.failure_persist_dir); + remove_test_dir(&self.invariant.failure_persist_dir); + + Ok(()) + } + /// Ensures that the configured version is installed if explicitly set /// /// If `solc` is [`SolcReq::Version`] then this will download and install the solc version if /// it's missing, unless the `offline` flag is enabled, in which case an error is thrown. /// /// If `solc` is [`SolcReq::Local`] then this will ensure that the path exists. + #[allow(clippy::disallowed_macros)] fn ensure_solc(&self) -> Result, SolcError> { - if let Some(ref solc) = self.solc { + if self.eof { + let (tx, rx) = mpsc::channel(); + let root = self.root.clone(); + std::thread::spawn(move || { + tx.send( + Solc::new_with_args( + "docker", + [ + "run", + "--rm", + "-i", + "-v", + &format!("{}:/app/root", root.display()), + Self::EOF_SOLC_IMAGE, + ], + ) + .map(Some), + ) + }); + // If it takes more than 1 second, this likely means we are pulling the image. + return match rx.recv_timeout(Duration::from_secs(1)) { + Ok(res) => res, + Err(RecvTimeoutError::Timeout) => { + // `sh_warn!` is a circular dependency, preventing us from using it here. + eprintln!( + "{}", + yansi::Paint::yellow( + "Pulling Docker image for eof-solc, this might take some time..." + ) + ); + + rx.recv().expect("sender dropped") + } + Err(RecvTimeoutError::Disconnected) => panic!("sender dropped"), + }; + } + + if let Some(solc) = &self.solc { let solc = match solc { SolcReq::Version(version) => { - let v = version.to_string(); - let mut solc = Solc::find_svm_installed_version(&v)?; - if solc.is_none() { + if let Some(solc) = Solc::find_svm_installed_version(version)? { + solc + } else { if self.offline { return Err(SolcError::msg(format!( "can't install missing solc {version} in offline mode" - ))) + ))); } - Solc::blocking_install(version)?; - solc = Solc::find_svm_installed_version(&v)?; + Solc::blocking_install(version)? } - solc } SolcReq::Local(solc) => { if !solc.is_file() { return Err(SolcError::msg(format!( "`solc` {} does not exist", solc.display() - ))) + ))); } - Some(Solc::new(solc)) + Solc::new(solc)? } }; - return Ok(solc) + return Ok(Some(solc)); } Ok(None) } + /// Returns the [SpecId] derived from the configured [EvmVersion] + #[inline] + pub fn evm_spec_id(&self) -> SpecId { + evm_spec_id(self.evm_version, self.odyssey) + } + /// Returns whether the compiler version should be auto-detected /// /// Returns `false` if `solc_version` is explicitly set, otherwise returns the value of /// `auto_detect_solc` pub fn is_auto_detect(&self) -> bool { if self.solc.is_some() { - return false + return false; } self.auto_detect_solc } @@ -701,7 +1179,7 @@ impl Config { self.rpc_storage_caching.enable_for_endpoint(endpoint) } - /// Returns the `ProjectPathsConfig` sub set of the config. + /// Returns the `ProjectPathsConfig` sub set of the config. /// /// **NOTE**: this uses the paths as they are and does __not__ modify them, see /// `[Self::sanitized]` @@ -709,47 +1187,93 @@ impl Config { /// # Example /// /// ``` + /// use foundry_compilers::solc::Solc; /// use foundry_config::Config; - /// let config = Config::load_with_root(".").sanitized(); - /// let paths = config.project_paths(); + /// let config = Config::load_with_root(".")?.sanitized(); + /// let paths = config.project_paths::(); + /// # Ok::<_, eyre::Error>(()) /// ``` - pub fn project_paths(&self) -> ProjectPathsConfig { + pub fn project_paths(&self) -> ProjectPathsConfig { let mut builder = ProjectPathsConfig::builder() .cache(self.cache_path.join(SOLIDITY_FILES_CACHE_FILENAME)) .sources(&self.src) .tests(&self.test) .scripts(&self.script) .artifacts(&self.out) - .libs(self.libs.clone()) - .remappings(self.get_all_remappings()); + .libs(self.libs.iter()) + .remappings(self.get_all_remappings()) + .allowed_path(&self.root) + .allowed_paths(&self.libs) + .allowed_paths(&self.allow_paths) + .include_paths(&self.include_paths); if let Some(build_info_path) = &self.build_info_path { builder = builder.build_infos(build_info_path); } - builder.build_with_root(&self.__root.0) + builder.build_with_root(&self.root) + } + + /// Returns configuration for a compiler to use when setting up a [Project]. + pub fn solc_compiler(&self) -> Result { + if let Some(solc) = self.ensure_solc()? { + Ok(SolcCompiler::Specific(solc)) + } else { + Ok(SolcCompiler::AutoDetect) + } + } + + /// Returns the solc version, if any. + pub fn solc_version(&self) -> Option { + self.solc.as_ref().and_then(|solc| solc.try_version().ok()) + } + + /// Returns configured [Vyper] compiler. + pub fn vyper_compiler(&self) -> Result, SolcError> { + // Only instantiate Vyper if there are any Vyper files in the project. + 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 { + Vyper::new("vyper").ok() + }; + + Ok(vyper) + } + + /// Returns configuration for a compiler to use when setting up a [Project]. + pub fn compiler(&self) -> Result { + Ok(MultiCompiler { solc: Some(self.solc_compiler()?), vyper: self.vyper_compiler()? }) + } + + /// Returns configured [MultiCompilerSettings]. + pub fn compiler_settings(&self) -> Result { + Ok(MultiCompilerSettings { solc: self.solc_settings()?, vyper: self.vyper_settings()? }) + } + + /// Returns all configured remappings. + pub fn get_all_remappings(&self) -> impl Iterator + '_ { + self.remappings.iter().map(|m| m.clone().into()) } - /// Returns all configured [`Remappings`] + /// Returns the configured rpc jwt secret /// - /// **Note:** this will add an additional `/=` remapping here, see - /// [Self::get_source_dir_remapping()] + /// Returns: + /// - The jwt secret, if configured /// - /// So that + /// # Example /// - /// ```solidity - /// import "./math/math.sol"; - /// import "contracts/tokens/token.sol"; /// ``` - /// - /// in `contracts/contract.sol` are resolved to - /// - /// ```text - /// contracts/tokens/token.sol - /// contracts/math/math.sol + /// use foundry_config::Config; + /// # fn t() { + /// let config = Config::with_root("./"); + /// let rpc_jwt = config.get_rpc_jwt_secret().unwrap().unwrap(); + /// # } /// ``` - pub fn get_all_remappings(&self) -> Vec { - self.remappings.iter().map(|m| m.clone().into()).collect() + pub fn get_rpc_jwt_secret(&self) -> Result>, UnresolvedEnvVarError> { + Ok(self.eth_rpc_jwt.as_ref().map(|jwt| Cow::Borrowed(jwt.as_str()))) } /// Returns the configured rpc url @@ -761,14 +1285,13 @@ impl Config { /// # Example /// /// ``` - /// /// use foundry_config::Config; /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url().unwrap().unwrap(); + /// let config = Config::with_root("./"); + /// 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) @@ -779,44 +1302,59 @@ impl Config { /// Resolves the given alias to a matching rpc url /// - /// Returns: - /// - the matching, resolved url of `rpc_endpoints` if `maybe_alias` is an alias - /// - None otherwise + /// # Returns + /// + /// In order of resolution: + /// + /// - the matching, resolved url of `rpc_endpoints` if `maybe_alias` is an alias + /// - a mesc resolved url if `maybe_alias` is a known alias in mesc + /// - `None` otherwise + /// + /// # Note on mesc + /// + /// The endpoint is queried for in mesc under the `foundry` profile, allowing users to customize + /// endpoints for Foundry specifically. /// /// # Example /// /// ``` - /// /// use foundry_config::Config; /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url_with_alias("mainnet").unwrap().unwrap(); + /// let config = Config::with_root("./"); + /// let rpc_url = config.get_rpc_url_with_alias("mainnet").unwrap().unwrap(); /// # } /// ``` 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)) - } + if let Some(endpoint) = endpoints.remove(maybe_alias) { + return Some(endpoint.url().map(Cow::Owned)); + } - /// Returns the configured rpc, or the fallback url + if let Ok(Some(endpoint)) = mesc::get_endpoint_by_query(maybe_alias, Some("foundry")) { + return Some(Ok(Cow::Owned(endpoint.url))); + } + + None + } + + /// Returns the configured rpc, or the fallback url /// /// # Example /// /// ``` - /// /// use foundry_config::Config; /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url_or("http://localhost:8545").unwrap(); + /// let config = Config::with_root("./"); + /// let rpc_url = config.get_rpc_url_or("http://localhost:8545").unwrap(); /// # } /// ``` 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 { @@ -829,14 +1367,13 @@ impl Config { /// # Example /// /// ``` - /// /// use foundry_config::Config; /// # fn t() { - /// let config = Config::with_root("./"); - /// let rpc_url = config.get_rpc_url_or_localhost_http().unwrap(); + /// let config = Config::with_root("./"); + /// 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") } @@ -845,34 +1382,24 @@ impl Config { /// Returns /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if `etherscan_api_key` is /// an alias + /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if a `chain` is + /// configured. an alias /// - the Mainnet `ResolvedEtherscanConfig` if `etherscan_api_key` is set, `None` otherwise /// /// # Example /// /// ``` - /// /// use foundry_config::Config; /// # fn t() { - /// let config = Config::with_root("./"); - /// let etherscan_config = config.get_etherscan_config().unwrap().unwrap(); - /// let client = etherscan_config.into_client().unwrap(); + /// let config = Config::with_root("./"); + /// let etherscan_config = config.get_etherscan_config().unwrap().unwrap(); + /// let client = etherscan_config.into_client().unwrap(); /// # } /// ``` pub fn get_etherscan_config( &self, ) -> Option> { - let maybe_alias = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref())?; - if self.etherscan.contains_key(maybe_alias) { - // etherscan points to an alias in the `etherscan` table, so we try to resolve that - let mut resolved = self.etherscan.clone().resolved(); - return resolved.remove(maybe_alias) - } - - // we treat the `etherscan_api_key` as actual API key - // if no chain provided, we assume mainnet - let chain = self.chain_id.unwrap_or(Chain::Named(Mainnet)); - let api_key = self.etherscan_api_key.as_ref()?; - ResolvedEtherscanConfig::create(api_key, chain).map(Ok) + self.get_etherscan_config_with_chain(None).transpose() } /// Same as [`Self::get_etherscan_config()`] but optionally updates the config with the given @@ -883,25 +1410,25 @@ impl Config { /// over the chain's entry in the table. pub fn get_etherscan_config_with_chain( &self, - chain: Option>, + chain: Option, ) -> Result, EtherscanConfigError> { - let chain = chain.map(Into::into); if let Some(maybe_alias) = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref()) { if self.etherscan.contains_key(maybe_alias) { - return self.etherscan.clone().resolved().remove(maybe_alias).transpose() + return self.etherscan.clone().resolved().remove(maybe_alias).transpose(); } } // try to find by comparing chain IDs after resolving - if let Some(res) = - chain.and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) + if let Some(res) = chain + .or(self.chain) + .and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) { match (res, self.etherscan_api_key.as_ref()) { (Ok(mut config), Some(key)) => { // we update the key, because if an etherscan_api_key is set, it should take // precedence over the entry, since this is usually set via env var or CLI args. - config.key = key.clone(); - return Ok(Some(config)) + config.key.clone_from(key); + return Ok(Some(config)); } (Ok(config), None) => return Ok(Some(config)), (Err(err), None) => return Err(err), @@ -913,15 +1440,19 @@ impl Config { // etherscan fallback via API key if let Some(key) = self.etherscan_api_key.as_ref() { - let chain = chain.or(self.chain_id).unwrap_or_default(); - return Ok(ResolvedEtherscanConfig::create(key, chain)) + let chain = chain.or(self.chain).unwrap_or_default(); + return Ok(ResolvedEtherscanConfig::create(key, chain)); } Ok(None) } /// Helper function to just get the API key - pub fn get_etherscan_api_key(&self, chain: Option>) -> Option { + /// + /// Optionally updates the config with the given `chain`. + /// + /// See also [Self::get_etherscan_config_with_chain] + pub fn get_etherscan_api_key(&self, chain: Option) -> Option { self.get_etherscan_config_with_chain(chain).ok().flatten().map(|c| c.key) } @@ -937,7 +1468,7 @@ impl Config { /// Returns the remapping for the project's _test_ directory, but only if it exists pub fn get_test_dir_remapping(&self) -> Option { - if self.__root.0.join(&self.test).exists() { + if self.root.join(&self.test).exists() { get_dir_remapping(&self.test) } else { None @@ -946,7 +1477,7 @@ impl Config { /// Returns the remapping for the project's _script_ directory, but only if it exists pub fn get_script_dir_remapping(&self) -> Option { - if self.__root.0.join(&self.script).exists() { + if self.root.join(&self.script).exists() { get_dir_remapping(&self.script) } else { None @@ -954,17 +1485,25 @@ impl Config { } /// Returns the `Optimizer` based on the configured settings + /// + /// Note: optimizer details can be set independently of `enabled` + /// See also: + /// and pub fn optimizer(&self) -> Optimizer { - // only configure optimizer settings if optimizer is enabled - let details = if self.optimizer { self.optimizer_details.clone() } else { None }; - - Optimizer { enabled: Some(self.optimizer), runs: Some(self.optimizer_runs), details } + Optimizer { + enabled: self.optimizer, + runs: self.optimizer_runs, + // we always set the details because `enabled` is effectively a specific details profile + // that can still be modified + details: self.optimizer_details.clone(), + } } - /// returns the [`ethers_solc::ConfigurableArtifacts`] for this config, that includes the + /// returns the [`foundry_compilers::ConfigurableArtifacts`] for this config, that includes the /// `extra_output` fields pub fn configured_artifacts_handler(&self) -> ConfigurableArtifacts { let mut extra_output = self.extra_output.clone(); + // Sourcify verification requires solc metadata output. Since, it doesn't // affect the UX & performance of the compiler, output the metadata files // by default. @@ -974,7 +1513,7 @@ impl Config { extra_output.push(ContractOutputSelection::Metadata); } - ConfigurableArtifacts::new(extra_output, self.extra_output_files.clone()) + ConfigurableArtifacts::new(extra_output, self.extra_output_files.iter().cloned()) } /// Parses all libraries in the form of @@ -983,28 +1522,31 @@ impl Config { Libraries::parse(&self.libraries) } - /// Returns the configured `solc` `Settings` that includes: - /// - all libraries - /// - the optimizer (including details, if configured) - /// - evm version - pub fn solc_settings(&self) -> Result { - let libraries = self.parsed_libraries()?.with_applied_remappings(&self.project_paths()); - let optimizer = self.optimizer(); + /// Returns all libraries with applied remappings. Same as `self.solc_settings()?.libraries`. + pub fn libraries_with_remappings(&self) -> Result { + let paths: ProjectPathsConfig = self.project_paths(); + Ok(self.parsed_libraries()?.apply(|libs| paths.apply_lib_remappings(libs))) + } + /// Returns the configured `solc` `Settings` that includes: + /// - all libraries + /// - the optimizer (including details, if configured) + /// - evm version + 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. let mut model_checker = self.model_checker.clone(); - if let Some(ref mut model_checker_settings) = model_checker { + if let Some(model_checker_settings) = &mut model_checker { if model_checker_settings.targets.is_none() { model_checker_settings.targets = Some(vec![ModelCheckerTarget::Assert]); } } let mut settings = Settings { - optimizer, + libraries: self.libraries_with_remappings()?, + optimizer: self.optimizer(), evm_version: Some(self.evm_version), - libraries, metadata: Some(SettingsMetadata { use_literal_content: Some(self.use_literal_content), bytecode_hash: Some(self.bytecode_hash), @@ -1012,19 +1554,49 @@ impl Config { }), debug: self.revert_strings.map(|revert_strings| DebuggingSettings { revert_strings: Some(revert_strings), + // Not used. debug_info: Vec::new(), }), model_checker, - ..Default::default() + via_ir: Some(self.via_ir), + // Not used. + stop_after: None, + // Set in project paths. + 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()) - .with_ast(); + .with_extra_output(self.configured_artifacts_handler().output_selection()); - if self.via_ir { - settings = settings.with_via_ir(); + // We're keeping AST in `--build-info` for backwards compatibility with HardHat. + if self.ast || self.build_info { + settings = settings.with_ast(); } - Ok(settings) + let cli_settings = + CliSettings { extra_args: self.extra_args.clone(), ..Default::default() }; + + Ok(SolcSettings { settings, cli_settings }) + } + + /// Returns the configured [VyperSettings] that includes: + /// - evm version + pub fn vyper_settings(&self) -> Result { + Ok(VyperSettings { + evm_version: Some(self.evm_version), + optimize: self.vyper.optimize, + bytecode_metadata: None, + // TODO: We don't yet have a way to deserialize other outputs correctly, so request only + // those for now. It should be enough to run tests and deploy contracts. + output_selection: OutputSelection::common_output_selection([ + "abi".to_string(), + "evm.bytecode".to_string(), + "evm.deployedBytecode".to_string(), + ]), + search_paths: None, + experimental_codegen: self.vyper.experimental_codegen, + }) } /// Returns the default figment @@ -1048,7 +1620,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 @@ -1062,8 +1634,18 @@ impl Config { /// /// let my_config = Config::figment_with_root(".").extract::(); /// ``` - pub fn figment_with_root(root: impl Into) -> Figment { - Self::with_root(root).into() + pub fn figment_with_root(root: impl AsRef) -> Figment { + Self::with_root(root.as_ref()).into() + } + + #[doc(hidden)] + #[track_caller] + pub fn figment_with_root_opt(root: Option<&Path>) -> Figment { + let root = match root { + Some(root) => root, + None => &find_project_root(None).expect("could not determine project root"), + }; + Self::figment_with_root(root) } /// Creates a new Config that adds additional context extracted from the provided root. @@ -1074,43 +1656,46 @@ impl Config { /// use foundry_config::Config; /// let my_config = Config::with_root("."); /// ``` - pub fn with_root(root: impl Into) -> Self { + pub fn with_root(root: impl AsRef) -> Self { + Self::_with_root(root.as_ref()) + } + + fn _with_root(root: &Path) -> Self { // autodetect paths - let root = root.into(); - let paths = ProjectPathsConfig::builder().build_with_root(&root); + let paths = ProjectPathsConfig::builder().build_with_root::<()>(root); let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); - Config { - __root: paths.root.into(), + Self { + root: paths.root, src: paths.sources.file_name().unwrap().into(), out: artifacts.clone(), libs: paths.libraries.into_iter().map(|lib| lib.file_name().unwrap().into()).collect(), remappings: paths .remappings .into_iter() - .map(|r| RelativeRemapping::new(r, &root)) + .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 { - chain_id: Some(Chain::Id(99)), + Self { + chain: Some(Chain::from_id(99)), block_timestamp: 0, block_number: 0, - ..Config::default() + ..Self::default() } } @@ -1136,11 +1721,11 @@ impl Config { /// /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See /// [Self::get_config_path()] and if the closure returns `true`. - pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> + pub fn update_at(root: &Path, f: F) -> eyre::Result<()> where - F: FnOnce(&Config, &mut toml_edit::Document) -> bool, + F: FnOnce(&Self, &mut toml_edit::DocumentMut) -> bool, { - let config = Self::load_with_root(root).sanitized(); + let config = Self::load_with_root(root)?.sanitized(); config.update(|doc| f(&config, doc)) } @@ -1150,14 +1735,14 @@ impl Config { /// [Self::get_config_path()] and if the closure returns `true` pub fn update(&self, f: F) -> eyre::Result<()> where - F: FnOnce(&mut toml_edit::Document) -> bool, + F: FnOnce(&mut toml_edit::DocumentMut) -> bool, { let file_path = self.get_config_path(); if !file_path.exists() { - return Ok(()) + return Ok(()); } let contents = fs::read_to_string(&file_path)?; - let mut doc = contents.parse::()?; + let mut doc = contents.parse::()?; if f(&mut doc) { fs::write(file_path, doc.to_string())?; } @@ -1172,7 +1757,7 @@ impl Config { pub fn update_libs(&self) -> eyre::Result<()> { self.update(|doc| { let profile = self.profile.as_str().as_str(); - let root = &self.__root.0; + let root = &self.root; let libs: toml_edit::Value = self .libs .iter() @@ -1183,7 +1768,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 }) } @@ -1205,7 +1790,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(); @@ -1214,7 +1799,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() @@ -1227,79 +1812,92 @@ impl Config { toml::to_string_pretty(&toml::Value::Table(wrapping_table)) } - /// Returns the path to the `foundry.toml` of this `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.join(Self::FILE_NAME) } - /// Returns the selected profile + /// Returns the selected profile. /// - /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_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) + // Can't cache in tests because the env var can change. + #[cfg(test)] + { + Self::force_selected_profile() + } + #[cfg(not(test))] + { + static CACHE: std::sync::OnceLock = std::sync::OnceLock::new(); + CACHE.get_or_init(Self::force_selected_profile).clone() + } + } + + fn force_selected_profile() -> Profile { + Profile::from_env_or("FOUNDRY_PROFILE", Self::DEFAULT_PROFILE) } - /// Returns the path to foundry's global toml file that's stored at `~/.foundry/foundry.toml` + /// 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/` + /// 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::home_dir().map(|p| p.join(Self::FOUNDRY_DIR_NAME)) } - /// Returns the path to foundry's cache dir `~/.foundry/cache` + /// Returns the path to foundry's cache dir: `~/.foundry/cache`. pub fn foundry_cache_dir() -> Option { Self::foundry_dir().map(|p| p.join("cache")) } - /// Returns the path to foundry rpc cache dir `~/.foundry/cache/rpc` + /// Returns the path to foundry rpc cache dir: `~/.foundry/cache/rpc`. pub fn foundry_rpc_cache_dir() -> Option { Some(Self::foundry_cache_dir()?.join("rpc")) } - /// Returns the path to foundry chain's cache dir `~/.foundry/cache/rpc/` + /// Returns the path to foundry chain's cache dir: `~/.foundry/cache/rpc/` pub fn foundry_chain_cache_dir(chain_id: impl Into) -> Option { Some(Self::foundry_rpc_cache_dir()?.join(chain_id.into().to_string())) } - /// Returns the path to foundry's etherscan cache dir `~/.foundry/cache/etherscan` + /// Returns the path to foundry's etherscan cache dir: `~/.foundry/cache/etherscan`. pub fn foundry_etherscan_cache_dir() -> Option { Some(Self::foundry_cache_dir()?.join("etherscan")) } - /// Returns the path to foundry's keystores dir `~/.foundry/keystores` + /// Returns the path to foundry's keystores dir: `~/.foundry/keystores`. pub fn foundry_keystores_dir() -> Option { Some(Self::foundry_dir()?.join("keystores")) } - /// Returns the path to foundry's etherscan cache dir for `chain_id` + /// Returns the path to foundry's etherscan cache dir for `chain_id`: /// `~/.foundry/cache/etherscan/` pub fn foundry_etherscan_chain_cache_dir(chain_id: impl Into) -> Option { Some(Self::foundry_etherscan_cache_dir()?.join(chain_id.into().to_string())) } - /// Returns the path to the cache dir of the `block` on the `chain` - /// `~/.foundry/cache/rpc// + /// Returns the path to the cache dir of the `block` on the `chain`: + /// `~/.foundry/cache/rpc//` pub fn foundry_block_cache_dir(chain_id: impl Into, block: u64) -> Option { Some(Self::foundry_chain_cache_dir(chain_id)?.join(format!("{block}"))) } - /// Returns the path to the cache file of the `block` on the `chain` + /// Returns the path to the cache file of the `block` on the `chain`: /// `~/.foundry/cache/rpc///storage.json` pub fn foundry_block_cache_file(chain_id: impl Into, block: u64) -> Option { Some(Self::foundry_block_cache_dir(chain_id, block)?.join("storage.json")) } - #[doc = r#"Returns the path to `foundry`'s data directory inside the user's data directory - |Platform | Value | Example | - | ------- | ------------------------------------- | -------------------------------- | - | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/foundry | /home/alice/.config/foundry| - | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | - | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | - "#] + /// Returns the path to `foundry`'s data directory inside the user's data directory. + /// + /// | Platform | Value | Example | + /// | ------- | --------------------------------------------- | ------------------------------------------------ | + /// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/foundry | /home/alice/.config/foundry | + /// | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | + /// | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | pub fn data_dir() -> eyre::Result { - let path = dirs_next::data_dir().wrap_err("Failed to find data directory")?.join("foundry"); + let path = dirs::data_dir().wrap_err("Failed to find data directory")?.join("foundry"); std::fs::create_dir_all(&path).wrap_err("Failed to create module directory")?; Ok(path) } @@ -1309,32 +1907,32 @@ impl Config { /// and the first hit is used. /// /// If this search comes up empty, then it checks if a global `foundry.toml` exists at - /// `~/.foundry/foundry.tol`, see [`Self::foundry_dir_toml()`] + /// `~/.foundry/foundry.toml`, see [`Self::foundry_dir_toml`]. pub fn find_config_file() -> Option { fn find(path: &Path) -> Option { if path.is_absolute() { return match path.is_file() { true => Some(path.to_path_buf()), false => None, - } + }; } let cwd = std::env::current_dir().ok()?; let mut cwd = cwd.as_path(); loop { let file_path = cwd.join(path); if file_path.is_file() { - return Some(file_path) + return Some(file_path); } 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 + /// 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 { @@ -1344,9 +1942,9 @@ impl Config { Ok(()) } - /// Clears the foundry cache for `chain` + /// 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 { @@ -1356,9 +1954,9 @@ impl Config { Ok(()) } - /// Clears the foundry cache for `chain` and `block` + /// 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 { @@ -1368,9 +1966,9 @@ impl Config { Ok(()) } - /// Clears the foundry etherscan cache + /// 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 { @@ -1380,9 +1978,9 @@ impl Config { Ok(()) } - /// Clears the foundry etherscan cache for `chain` + /// 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 { @@ -1392,12 +1990,12 @@ impl Config { Ok(()) } - /// List the data in the foundry cache + /// 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) + return Ok(cache); } if let Ok(entries) = cache_dir.as_path().read_dir() { for entry in entries.flatten().filter(|x| x.path().is_dir()) { @@ -1415,9 +2013,9 @@ impl Config { } } - /// List the cached data for `chain` + /// 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"); @@ -1425,7 +2023,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(), @@ -1437,26 +2035,33 @@ impl Config { } } - //The path provided to this function should point to a cached chain folder + /// The path provided to this function should point to a cached chain folder. fn get_cached_blocks(chain_path: &Path) -> eyre::Result> { let mut blocks = vec![]; if !chain_path.exists() { - return Ok(blocks) + return Ok(blocks); } - for block in chain_path.read_dir()?.flatten().filter(|x| x.file_type().unwrap().is_dir()) { - let filepath = block.path().join("storage.json"); - blocks.push(( - block.file_name().to_string_lossy().into_owned(), - fs::metadata(filepath)?.len(), - )); + for block in chain_path.read_dir()?.flatten() { + let file_type = block.file_type()?; + let file_name = block.file_name(); + let filepath = if file_type.is_dir() { + block.path().join("storage.json") + } else if file_type.is_file() && + file_name.to_string_lossy().chars().all(char::is_numeric) + { + block.path() + } else { + continue; + }; + blocks.push((file_name.to_string_lossy().into_owned(), fs::metadata(filepath)?.len())); } Ok(blocks) } - //The path provided to this function should point to the etherscan cache for a chain + /// The path provided to this function should point to the etherscan cache for a chain. fn get_cached_block_explorer_data(chain_path: &Path) -> eyre::Result { if !chain_path.exists() { - return Ok(0) + return Ok(0); } fn dir_size_recursive(mut dir: fs::ReadDir) -> eyre::Result { @@ -1487,22 +2092,24 @@ 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); // apply any key fixes - let provider = BackwardsCompatTomlProvider(ForcedSnakeCaseData(provider)); + 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 { - if let Some(fallback) = STANDALONE_FALLBACK_SECTIONS.get(standalone_key) { + for standalone_key in Self::STANDALONE_SECTIONS { + if let Some((_, fallback)) = + STANDALONE_FALLBACK_SECTIONS.iter().find(|(key, _)| standalone_key == key) + { figment = figment.merge( provider .fallback(standalone_key, fallback) @@ -1516,82 +2123,86 @@ impl Config { figment = figment.merge(provider); figment } + + /// Check if any defaults need to be normalized. + /// + /// This normalizes the default `evm_version` if a `solc` was provided in the config. + /// + /// See also + fn normalize_defaults(&self, mut figment: Figment) -> Figment { + // TODO: add a warning if evm_version is provided but incompatible + if figment.contains("evm_version") { + return figment; + } + + // Normalize `evm_version` based on the provided solc version. + if let Ok(solc) = figment.extract_inner::("solc") { + if let Some(version) = solc + .try_version() + .ok() + .and_then(|version| self.evm_version.normalize_version_solc(&version)) + { + figment = figment.merge(("evm_version", version)); + } + } + + figment + } } impl From for Figment { - fn from(c: Config) -> Figment { - let profile = Config::selected_profile(); - let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.__root.0)); + fn from(c: Config) -> Self { + (&c).into() + } +} +impl From<&Config> for Figment { + fn from(c: &Config) -> Self { + c.to_figment(FigmentProviders::All) + } +} - // merge global foundry.toml file - if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(None, global_toml).cached(), - profile.clone(), - ); - } - // merge local foundry.toml file - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.__root.0.join(Config::FILE_NAME)) - .cached(), - profile.clone(), - ); +/// Determines what providers should be used when loading the [`Figment`] for a [`Config`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum FigmentProviders { + /// Include all providers. + #[default] + All, + /// Only include necessary providers that are useful for cast commands. + /// + /// This will exclude more expensive providers such as remappings. + Cast, + /// Only include necessary providers that are useful for anvil. + /// + /// This will exclude more expensive providers such as remappings. + Anvil, + /// Don't include any providers. + None, +} - // merge environment variables - figment = figment - .merge( - Env::prefixed("DAPP_") - .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge( - Env::prefixed("DAPP_TEST_") - .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge(DappEnvCompatProvider) - .merge(Env::raw().only(&["ETHERSCAN_API_KEY"])) - .merge( - Env::prefixed("FOUNDRY_") - .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .map(|key| { - let key = key.as_str(); - if Config::STANDALONE_SECTIONS.iter().any(|section| { - key.starts_with(&format!("{}_", section.to_ascii_uppercase())) - }) { - key.replacen('_', ".", 1).into() - } else { - key.into() - } - }) - .global(), - ) - .select(profile.clone()); +impl FigmentProviders { + /// Returns true if all providers should be included. + pub const fn is_all(&self) -> bool { + matches!(self, Self::All) + } - // we try to merge remappings after we've merged all other providers, this prevents - // redundant fs lookups to determine the default remappings that are eventually updated by - // other providers, like the toml file - let remappings = RemappingsProvider { - auto_detect_remappings: figment - .extract_inner::("auto_detect_remappings") - .unwrap_or(true), - lib_paths: figment - .extract_inner::>("libs") - .map(Cow::Owned) - .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), - root: &c.__root.0, - remappings: figment.extract_inner::>("remappings"), - }; - let merge = figment.merge(remappings); + /// Returns true if this is the cast preset. + pub const fn is_cast(&self) -> bool { + matches!(self, Self::Cast) + } + + /// Returns true if this is the anvil preset. + pub const fn is_anvil(&self) -> bool { + matches!(self, Self::Anvil) + } - Figment::from(c).merge(merge).select(profile) + /// Returns true if no providers should be included. + pub const fn is_none(&self) -> bool { + matches!(self, Self::None) } } /// Wrapper type for `regex::Regex` that implements `PartialEq` -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(transparent)] pub struct RegexWrapper { #[serde(with = "serde_regex")] @@ -1620,7 +2231,7 @@ impl From for regex::Regex { impl From for RegexWrapper { fn from(re: Regex) -> Self { - RegexWrapper { inner: re } + Self { inner: re } } } @@ -1644,35 +2255,12 @@ pub(crate) mod from_opt_glob { { let s: Option = Option::deserialize(deserializer)?; if let Some(s) = s { - return Ok(Some(globset::Glob::new(&s).map_err(serde::de::Error::custom)?)) + return Ok(Some(globset::Glob::new(&s).map_err(serde::de::Error::custom)?)); } Ok(None) } } -/// A helper wrapper around the root path used during Config detection -#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord, Deserialize, Serialize)] -#[serde(transparent)] -pub struct RootPath(pub PathBuf); - -impl Default for RootPath { - fn default() -> Self { - ".".into() - } -} - -impl> From

for RootPath { - fn from(p: P) -> Self { - RootPath(p.into()) - } -} - -impl AsRef for RootPath { - fn as_ref(&self) -> &Path { - &self.0 - } -} - /// Parses a config profile /// /// All `Profile` date is ignored by serde, however the `Config::to_string_pretty` includes it and @@ -1707,7 +2295,7 @@ impl Provider for Config { fn data(&self) -> Result, figment::Error> { let mut data = Serialized::defaults(self).data()?; if let Some(entry) = data.get_mut(&self.profile) { - entry.insert("root".to_string(), Value::serialize(self.__root.clone())?); + entry.insert("root".to_string(), Value::serialize(self.root.clone())?); } Ok(data) } @@ -1721,8 +2309,10 @@ impl Default for Config { fn default() -> Self { Self { profile: Self::DEFAULT_PROFILE, + profiles: vec![Self::DEFAULT_PROFILE], fs_permissions: FsPermissions::new([PathPermission::read("out")]), - __root: Default::default(), + isolate: cfg!(feature = "isolate-by-default"), + root: root_default(), src: "src".into(), test: "test".into(), script: "script".into(), @@ -1731,17 +2321,22 @@ impl Default for Config { cache: true, cache_path: "cache".into(), broadcast: "broadcast".into(), + snapshots: "snapshots".into(), + gas_snapshot_check: false, + gas_snapshot_emit: true, allow_paths: vec![], include_paths: vec![], force: false, - evm_version: EvmVersion::Paris, + evm_version: EvmVersion::Cancun, gas_reports: vec!["*".to_string()], gas_reports_ignore: vec![], + gas_reports_include_tests: false, solc: None, + vyper: Default::default(), auto_detect_solc: true, offline: false, - optimizer: true, - optimizer_runs: 200, + optimizer: None, + optimizer_runs: None, optimizer_details: None, model_checker: None, extra_output: Default::default(), @@ -1754,26 +2349,37 @@ impl Default for Config { contract_pattern_inverse: None, path_pattern: None, path_pattern_inverse: None, - fuzz: Default::default(), - invariant: Default::default(), + 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, - sender: Config::DEFAULT_SENDER, - tx_origin: Config::DEFAULT_SENDER, - initial_balance: U256::from(0xffffffffffffffffffffffffu128), + allow_internal_expect_revert: false, + prompt_timeout: 120, + sender: Self::DEFAULT_SENDER, + tx_origin: Self::DEFAULT_SENDER, + initial_balance: U256::from((1u128 << 96) - 1), block_number: 1, fork_block_number: None, - chain_id: None, - gas_limit: i64::MAX.into(), + chain: None, + gas_limit: (1u64 << 30).into(), // ~1B code_size_limit: None, gas_price: None, block_base_fee_per_gas: 0, - block_coinbase: Address::zero(), + block_coinbase: Address::ZERO, block_timestamp: 1, block_difficulty: 0, block_prevrandao: Default::default(), block_gas_limit: None, - memory_limit: 2u64.pow(25), + disable_block_gas_limit: false, + memory_limit: 1 << 27, // 2**27 = 128MiB = 134_217_728 bytes eth_rpc_url: None, + eth_rpc_jwt: None, + eth_rpc_timeout: None, + eth_rpc_headers: None, etherscan_api_key: None, verbosity: 0, remappings: vec![], @@ -1783,9 +2389,12 @@ impl Default for Config { SolidityErrorCode::SpdxLicenseNotProvided, SolidityErrorCode::ContractExceeds24576Bytes, SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, + SolidityErrorCode::TransientStorageUsed, ], + ignored_file_paths: vec![], deny_warnings: false, via_ir: false, + ast: false, rpc_storage_caching: Default::default(), rpc_endpoints: Default::default(), etherscan: Default::default(), @@ -1800,39 +2409,42 @@ impl Default for Config { build_info_path: None, fmt: Default::default(), doc: Default::default(), - __non_exhaustive: (), - __warnings: vec![], + bind_json: Default::default(), + labels: Default::default(), + unchecked_cheatcode_artifacts: false, + create2_library_salt: Self::DEFAULT_CREATE2_LIBRARY_SALT, + create2_deployer: Self::DEFAULT_CREATE2_DEPLOYER, + skip: vec![], + dependencies: Default::default(), + soldeer: Default::default(), + assertions_revert: true, + legacy_assertions: false, + warnings: vec![], + extra_args: vec![], + eof_version: None, + odyssey: false, + transaction_timeout: 120, + additional_compiler_profiles: Default::default(), + compilation_restrictions: Default::default(), + eof: false, + _non_exhaustive: (), } } } -/// Wrapper for the config's `gas_limit` value necessary because toml-rs can't handle larger number because integers are stored signed: +/// Wrapper for the config's `gas_limit` value necessary because toml-rs can't handle larger number +/// because integers are stored signed: /// /// Due to this limitation this type will be serialized/deserialized as String if it's larger than /// `i64` -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct GasLimit(pub u64); +#[derive(Clone, Copy, Debug, Default, 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 { @@ -1845,7 +2457,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) @@ -1853,34 +2467,8 @@ 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(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(untagged)] pub enum SolcReq { /// Requires a specific solc version, that's either already installed (via `svm`) or will be @@ -1890,531 +2478,42 @@ pub enum SolcReq { Local(PathBuf), } -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) - } else { - SolcReq::Local(s.into()) - } - } -} - -/// A convenience provider to retrieve a toml file. -/// This will return an error if the env var is set but the file does not exist -struct TomlFileProvider { - pub env_var: Option<&'static str>, - pub default: PathBuf, - pub cache: Option, Error>>, -} - -impl TomlFileProvider { - fn new(env_var: Option<&'static str>, default: impl Into) -> Self { - Self { env_var, default: default.into(), cache: None } - } - - fn env_val(&self) -> Option { - self.env_var.and_then(Env::var) - } - - fn file(&self) -> PathBuf { - self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone()) - } - - fn is_missing(&self) -> bool { - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { - return true - } - } - false - } - - pub fn cached(mut self) -> Self { - self.cache = Some(self.read()); - self - } - - fn read(&self) -> Result, Error> { - use serde::de::Error as _; - if let Some(file) = self.env_val() { - let path = Path::new(&file); - if !path.exists() { - return Err(Error::custom(format!( - "Config file `{}` set in env var `{}` does not exist", - file, - self.env_var.unwrap() - ))) - } - Toml::file(file) - } else { - Toml::file(&self.default) - } - .nested() - .data() - } -} - -impl Provider for TomlFileProvider { - fn metadata(&self) -> Metadata { - if self.is_missing() { - Metadata::named("TOML file provider") - } else { - Toml::file(self.file()).nested().metadata() - } - } - - fn data(&self) -> Result, Error> { - if let Some(cache) = self.cache.as_ref() { - cache.clone() - } else { - self.read() - } - } -} - -/// A Provider that ensures all keys are snake case if they're not standalone sections, See -/// `Config::STANDALONE_SECTIONS` -struct ForcedSnakeCaseData

(P); - -impl Provider for ForcedSnakeCaseData

{ - fn metadata(&self) -> Metadata { - self.0.metadata() - } - - fn data(&self) -> Result, Error> { - let mut map = Map::new(); - for (profile, dict) in self.0.data()? { - if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { - // don't force snake case for keys in standalone sections - map.insert(profile, dict); - continue - } - map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect()); - } - Ok(map) - } -} - -/// A Provider that handles breaking changes in toml files -struct BackwardsCompatTomlProvider

(P); - -impl Provider for BackwardsCompatTomlProvider

{ - fn metadata(&self) -> Metadata { - self.0.metadata() - } - - fn data(&self) -> Result, Error> { - let mut map = Map::new(); - let solc_env = std::env::var("FOUNDRY_SOLC_VERSION") - .or_else(|_| std::env::var("DAPP_SOLC_VERSION")) - .map(Value::from) - .ok(); - for (profile, mut dict) in self.0.data()? { - if let Some(v) = solc_env.clone().or_else(|| dict.remove("solc_version")) { - dict.insert("solc".to_string(), v); - } - map.insert(profile, dict); - } - Ok(map) - } -} - -/// A provider that sets the `src` and `output` path depending on their existence. -struct DappHardhatDirProvider<'a>(&'a Path); - -impl<'a> Provider for DappHardhatDirProvider<'a> { - fn metadata(&self) -> Metadata { - Metadata::named("Dapp Hardhat dir compat") - } - - fn data(&self) -> Result, Error> { - let mut dict = Dict::new(); - dict.insert( - "src".to_string(), - ProjectPathsConfig::find_source_dir(self.0) - .file_name() - .unwrap() - .to_string_lossy() - .to_string() - .into(), - ); - dict.insert( - "out".to_string(), - ProjectPathsConfig::find_artifacts_dir(self.0) - .file_name() - .unwrap() - .to_string_lossy() - .to_string() - .into(), - ); - - // detect libs folders: - // if `lib` _and_ `node_modules` exists: include both - // if only `node_modules` exists: include `node_modules` - // include `lib` otherwise - let mut libs = vec![]; - let node_modules = self.0.join("node_modules"); - let lib = self.0.join("lib"); - if node_modules.exists() { - if lib.exists() { - libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); - } - libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string()); - } else { - libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); - } - - dict.insert("libs".to_string(), libs.into()); - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_ -struct DappEnvCompatProvider; - -impl Provider for DappEnvCompatProvider { - fn metadata(&self) -> Metadata { - Metadata::named("Dapp env compat") - } - - fn data(&self) -> Result, Error> { - use serde::de::Error as _; - use std::env; - - let mut dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_NUMBER") { - dict.insert( - "block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_TEST_ADDRESS") { - dict.insert("sender".to_string(), val.into()); - } - if let Ok(val) = env::var("DAPP_FORK_BLOCK") { - dict.insert( - "fork_block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") { - dict.insert( - "fork_block_number".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") { - dict.insert( - "block_timestamp".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") { - dict.insert( - "optimizer_runs".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") { - // Activate Solidity optimizer (0 or 1) - let val = val.parse::().map_err(figment::Error::custom)?; - if val > 1 { - return Err( - format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into() - ) - } - dict.insert("optimizer".to_string(), (val == 1).into()); - } - - // libraries in env vars either as `[..]` or single string separated by comma - if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) { - dict.insert("libraries".to_string(), utils::to_array_value(&val)?); - } - - let mut fuzz_dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") { - fuzz_dict.insert( - "runs".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - dict.insert("fuzz".to_string(), fuzz_dict.into()); - - let mut invariant_dict = Dict::new(); - if let Ok(val) = env::var("DAPP_TEST_DEPTH") { - invariant_dict.insert( - "depth".to_string(), - val.parse::().map_err(figment::Error::custom)?.into(), - ); - } - dict.insert("invariant".to_string(), invariant_dict.into()); - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// Renames a profile from `from` to `to -/// -/// For example given: -/// -/// ```toml -/// [from] -/// key = "value" -/// ``` -/// -/// RenameProfileProvider will output -/// -/// ```toml -/// [to] -/// key = "value" -/// ``` -struct RenameProfileProvider

{ - provider: P, - from: Profile, - to: Profile, -} - -impl

RenameProfileProvider

{ - pub fn new(provider: P, from: impl Into, to: impl Into) -> Self { - Self { provider, from: from.into(), to: to.into() } - } -} - -impl Provider for RenameProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - let mut data = self.provider.data()?; - if let Some(data) = data.remove(&self.from) { - return Ok(Map::from([(self.to.clone(), data)])) +impl SolcReq { + /// Tries to get the solc version from the `SolcReq` + /// + /// If the `SolcReq` is a `Version` it will return the version, if it's a path to a binary it + /// will try to get the version from the binary. + fn try_version(&self) -> Result { + match self { + Self::Version(version) => Ok(version.clone()), + Self::Local(path) => Solc::new(path).map(|solc| solc.version), } - Ok(Default::default()) - } - fn profile(&self) -> Option { - Some(self.to.clone()) - } -} - -/// Unwraps a profile reducing the key depth -/// -/// For example given: -/// -/// ```toml -/// [wrapping_key.profile] -/// key = "value" -/// ``` -/// -/// UnwrapProfileProvider will output: -/// -/// ```toml -/// [profile] -/// key = "value" -/// ``` -struct UnwrapProfileProvider

{ - provider: P, - wrapping_key: Profile, - profile: Profile, -} - -impl

UnwrapProfileProvider

{ - pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { - Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } - } -} - -impl Provider for UnwrapProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - self.provider.data().and_then(|mut data| { - if let Some(profiles) = data.remove(&self.wrapping_key) { - for (profile_str, profile_val) in profiles { - let profile = Profile::new(&profile_str); - if profile != self.profile { - continue - } - match profile_val { - Value::Dict(_, dict) => return Ok(profile.collect(dict)), - bad_val => { - let mut err = Error::from(figment::error::Kind::InvalidType( - bad_val.to_actual(), - "dict".into(), - )); - err.metadata = Some(self.provider.metadata()); - err.profile = Some(self.profile.clone()); - return Err(err) - } - } - } - } - Ok(Default::default()) - }) - } - fn profile(&self) -> Option { - Some(self.profile.clone()) } } -/// Wraps a profile in another profile -/// -/// For example given: -/// -/// ```toml -/// [profile] -/// key = "value" -/// ``` -/// -/// WrapProfileProvider will output: -/// -/// ```toml -/// [wrapping_key.profile] -/// key = "value" -/// ``` -struct WrapProfileProvider

{ - provider: P, - wrapping_key: Profile, - profile: Profile, -} - -impl

WrapProfileProvider

{ - pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { - Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } - } -} - -impl Provider for WrapProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - if let Some(inner) = self.provider.data()?.remove(&self.profile) { - let value = Value::from(inner); - let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect(); - Ok(self.wrapping_key.collect(dict)) +impl> From for SolcReq { + fn from(s: T) -> Self { + let s = s.as_ref(); + if let Ok(v) = Version::from_str(s) { + Self::Version(v) } else { - Ok(Default::default()) + Self::Local(s.into()) } } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Extracts the profile from the `profile` key and using the original key as backup, merging -/// values where necessary -/// -/// For example given: -/// -/// ```toml -/// [profile.cool] -/// key = "value" -/// -/// [cool] -/// key2 = "value2" -/// ``` -/// -/// OptionalStrictProfileProvider will output: -/// -/// ```toml -/// [cool] -/// key = "value" -/// key2 = "value2" -/// ``` -/// -/// And emit a deprecation warning -struct OptionalStrictProfileProvider

{ - provider: P, - profiles: Vec, -} - -impl

OptionalStrictProfileProvider

{ - pub const PROFILE_PROFILE: Profile = Profile::const_new("profile"); - - pub fn new(provider: P, profiles: impl IntoIterator>) -> Self { - Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() } - } -} - -impl Provider for OptionalStrictProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - fn data(&self) -> Result, Error> { - let mut figment = Figment::from(&self.provider); - for profile in &self.profiles { - figment = figment.merge(UnwrapProfileProvider::new( - &self.provider, - Self::PROFILE_PROFILE, - profile.clone(), - )); - } - figment.data().map_err(|err| { - // figment does tag metadata and tries to map metadata to an error, since we use a new - // figment in this provider this new figment does not know about the metadata of the - // provider and can't map the metadata to the error. Therefor we return the root error - // if this error originated in the provider's data. - if let Err(root_err) = self.provider.data() { - return root_err - } - err - }) - } - fn profile(&self) -> Option { - self.profiles.last().cloned() - } } -trait ProviderExt: Provider { - fn rename( - &self, - from: impl Into, - to: impl Into, - ) -> RenameProfileProvider<&Self> { - RenameProfileProvider::new(self, from, to) - } - - fn wrap( - &self, - wrapping_key: impl Into, - profile: impl Into, - ) -> WrapProfileProvider<&Self> { - WrapProfileProvider::new(self, wrapping_key, profile) - } - - fn strict_select( - &self, - profiles: impl IntoIterator>, - ) -> OptionalStrictProfileProvider<&Self> { - OptionalStrictProfileProvider::new(self, profiles) - } - - fn fallback( - &self, - profile: impl Into, - fallback: impl Into, - ) -> FallbackProfileProvider<&Self> { - FallbackProfileProvider::new(self, profile, fallback) - } -} -impl ProviderExt for P {} - /// A subset of the foundry `Config` /// used to initialize a `foundry.toml` file /// /// # Example /// /// ```rust -/// use foundry_config::{Config, BasicConfig}; +/// use foundry_config::{BasicConfig, Config}; /// use serde::Deserialize; /// /// let my_config = Config::figment().extract::(); /// ``` -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BasicConfig { /// the profile tag: `[profile.default]` #[serde(skip)] @@ -2447,9 +2546,8 @@ impl BasicConfig { } pub(crate) mod from_str_lowercase { - use std::str::FromStr; - use serde::{Deserialize, Deserializer, Serializer}; + use std::str::FromStr; pub fn serialize(value: &T, serializer: S) -> Result where @@ -2471,7 +2569,11 @@ pub(crate) mod from_str_lowercase { fn canonic(path: impl Into) -> PathBuf { let path = path.into(); - ethers_solc::utils::canonicalize(&path).unwrap_or(path) + foundry_compilers::utils::canonicalize(&path).unwrap_or(path) +} + +fn root_default() -> PathBuf { + ".".into() } #[cfg(test)] @@ -2479,35 +2581,38 @@ mod tests { use super::*; use crate::{ cache::{CachedChains, CachedEndpoints}, - endpoints::RpcEndpoint, + endpoints::{RpcEndpoint, RpcEndpointType}, etherscan::ResolvedEtherscanConfigs, - fs_permissions::PathPermission, }; - use ethers_core::types::Chain::Moonbeam; - use ethers_solc::artifacts::{ModelCheckerEngine, YulDetails}; - use figment::{error::Kind::InvalidType, value::Value, Figment}; - use pretty_assertions::assert_eq; - use std::{collections::BTreeMap, fs::File, io::Write, str::FromStr}; + use endpoints::{RpcAuth, RpcEndpointConfig}; + use figment::error::Kind::InvalidType; + use foundry_compilers::artifacts::{ + vyper::VyperOptimizationMode, ModelCheckerEngine, YulDetails, + }; + use similar_asserts::assert_eq; + use soldeer::RemappingsLocation; + use std::{collections::BTreeMap, fs::File, io::Write}; use tempfile::tempdir; + use NamedChain::Moonbeam; // Helper function to clear `__warnings` in config, since it will be populated during loading // from file, causing testing problem when comparing to those created from `default()`, etc. fn clear_warning(config: &mut Config) { - config.__warnings = vec![]; + config.warnings = vec![]; } #[test] fn default_sender() { assert_eq!( Config::DEFAULT_SENDER, - "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38".parse().unwrap() + Address::from_str("0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38").unwrap() ); } #[test] fn test_caching() { let mut config = Config::default(); - let chain_id = ethers_core::types::Chain::Mainnet; + let chain_id = NamedChain::Mainnet; let url = "https://eth-mainnet.alchemyapi"; assert!(config.enable_caching(url, chain_id)); @@ -2515,32 +2620,32 @@ mod tests { assert!(!config.enable_caching(url, chain_id)); config.no_storage_caching = false; - assert!(!config.enable_caching(url, ethers_core::types::Chain::Dev)); + assert!(!config.enable_caching(url, NamedChain::Dev)); } #[test] fn test_install_dir() { figment::Jail::expect_with(|jail| { - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); jail.create_file( "foundry.toml", - r#" + r" [profile.default] libs = ['node_modules', 'lib'] - "#, + ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.install_lib_dir(), PathBuf::from("lib")); jail.create_file( "foundry.toml", - r#" + r" [profile.default] libs = ['custom', 'node_modules', 'lib'] - "#, + ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.install_lib_dir(), PathBuf::from("custom")); Ok(()) @@ -2550,9 +2655,39 @@ mod tests { #[test] fn test_figment_is_default() { figment::Jail::expect_with(|_| { - let mut default: Config = Config::figment().extract().unwrap(); - default.profile = Config::default().profile; - assert_eq!(default, Config::default()); + let mut default: Config = Config::figment().extract()?; + let default2 = Config::default(); + default.profile = default2.profile.clone(); + default.profiles = default2.profiles.clone(); + assert_eq!(default, default2); + Ok(()) + }); + } + + #[test] + fn figment_profiles() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [foo.baz] + libs = ['node_modules', 'lib'] + + [profile.default] + libs = ['node_modules', 'lib'] + + [profile.ci] + libs = ['node_modules', 'lib'] + + [profile.local] + libs = ['node_modules', 'lib'] + ", + )?; + + let config = crate::Config::load().unwrap(); + let expected: &[figment::Profile] = &["ci".into(), "default".into(), "local".into()]; + assert_eq!(config.profiles, expected); + Ok(()) }); } @@ -2561,10 +2696,10 @@ mod tests { fn test_default_round_trip() { figment::Jail::expect_with(|_| { let original = Config::figment(); - let roundtrip = Figment::from(Config::from_provider(&original)); + let roundtrip = Figment::from(Config::from_provider(&original).unwrap()); for figment in &[original, roundtrip] { - let config = Config::from_provider(figment); - assert_eq!(config, Config::default()); + let config = Config::from_provider(figment).unwrap(); + assert_eq!(config, Config::default().normalized_optimizer_settings()); } Ok(()) }); @@ -2576,7 +2711,7 @@ mod tests { jail.set_env("FOUNDRY_FFI", "true"); jail.set_env("FFI", "true"); jail.set_env("DAPP_FFI", "true"); - let config = Config::load(); + let config = Config::load().unwrap(); assert!(!config.ffi); Ok(()) @@ -2596,15 +2731,15 @@ mod tests { jail.create_file( "foundry.toml", - r#" + r" [profile.default] libs = ['lib'] [profile.local] libs = ['modules'] - "#, + ", )?; jail.set_env("FOUNDRY_PROFILE", "local"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("modules")]); Ok(()) @@ -2615,7 +2750,7 @@ mod tests { fn test_default_test_path() { figment::Jail::expect_with(|_| { let config = Config::default(); - let paths_config = config.project_paths(); + let paths_config = config.project_paths::(); assert_eq!(paths_config.tests, PathBuf::from(r"test")); Ok(()) }); @@ -2624,15 +2759,15 @@ mod tests { #[test] fn test_default_libs() { figment::Jail::expect_with(|jail| { - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("lib")]); fs::create_dir_all(jail.directory().join("node_modules")).unwrap(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("node_modules")]); fs::create_dir_all(jail.directory().join("lib")).unwrap(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); Ok(()) @@ -2649,19 +2784,18 @@ mod tests { test = "defaulttest" src = "defaultsrc" libs = ['lib', 'node_modules'] - + [profile.custom] src = "customsrc" "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.src, PathBuf::from("defaultsrc")); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); jail.set_env("FOUNDRY_PROFILE", "custom"); - let config = Config::load(); - + let config = Config::load().unwrap(); assert_eq!(config.src, PathBuf::from("customsrc")); assert_eq!(config.test, PathBuf::from("defaulttest")); assert_eq!(config.libs, vec![PathBuf::from("lib"), PathBuf::from("node_modules")]); @@ -2681,8 +2815,8 @@ mod tests { "#, )?; - let config = Config::load(); - let paths_config = config.project_paths(); + let config = Config::load().unwrap(); + let paths_config = config.project_paths::(); assert_eq!(paths_config.tests, PathBuf::from(r"mytest")); Ok(()) }); @@ -2700,18 +2834,18 @@ mod tests { cache = true "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config.remappings.is_empty()); jail.create_file( "remappings.txt", - r#" + r" file-ds-test/=lib/ds-test/ file-other/=lib/other/ - "#, + ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.remappings, vec![ @@ -2721,7 +2855,7 @@ mod tests { ); jail.set_env("DAPP_REMAPPINGS", "ds-test=lib/ds-test/\nother/=lib/other/"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.remappings, @@ -2751,18 +2885,18 @@ mod tests { cache = true "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config.remappings.is_empty()); jail.create_file( "remappings.txt", - r#" + r" ds-test/=lib/ds-test/ other/=lib/other/ - "#, + ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.remappings, vec![ @@ -2772,7 +2906,7 @@ mod tests { ); jail.set_env("DAPP_REMAPPINGS", "ds-test/=lib/ds-test/src/\nenv-lib/=lib/env-lib/"); - let config = Config::load(); + let config = Config::load().unwrap(); // Remappings should now be: // - ds-test from environment (lib/ds-test/src/) @@ -2789,7 +2923,7 @@ mod tests { // contains additional remapping to the source dir assert_eq!( - config.get_all_remappings(), + config.get_all_remappings().collect::>(), vec![ Remapping::from_str("ds-test/=lib/ds-test/src/").unwrap(), Remapping::from_str("env-lib/=lib/env-lib/").unwrap(), @@ -2812,11 +2946,11 @@ mod tests { "#, )?; - let mut config = Config::load(); + let mut config = Config::load().unwrap(); config.libs.push("libs".into()); config.update_libs().unwrap(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.libs, vec![PathBuf::from("node_modules"), PathBuf::from("libs"),]); Ok(()) }); @@ -2836,8 +2970,14 @@ mod tests { ), )?; - let config = Config::load(); - assert_eq!(config, Config { gas_limit: gas.into(), ..Config::default() }); + let config = Config::load().unwrap(); + assert_eq!( + config, + Config { + gas_limit: gas.into(), + ..Config::default().normalized_optimizer_settings() + } + ); Ok(()) }); @@ -2855,7 +2995,7 @@ mod tests { "#, )?; - let _config = Config::load(); + let _config = Config::load().unwrap(); Ok(()) }); @@ -2867,7 +3007,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.set_env("FOUNDRY_CONFIG", "this config does not exist"); - let _config = Config::load(); + let _config = Config::load().unwrap(); Ok(()) }); @@ -2888,19 +3028,16 @@ mod tests { "#, )?; - let config = Config::load(); - assert!(config.get_etherscan_config_with_chain(None::).unwrap().is_none()); + let config = Config::load().unwrap(); assert!(config - .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::BinanceSmartChain)) + .get_etherscan_config_with_chain(Some(NamedChain::BinanceSmartChain.into())) .is_err()); std::env::set_var(env_key, env_value); assert_eq!( config - .get_etherscan_config_with_chain(Some( - ethers_core::types::Chain::BinanceSmartChain - )) + .get_etherscan_config_with_chain(Some(NamedChain::BinanceSmartChain.into())) .unwrap() .unwrap() .key, @@ -2912,9 +3049,7 @@ mod tests { assert_eq!( with_key - .get_etherscan_config_with_chain(Some( - ethers_core::types::Chain::BinanceSmartChain - )) + .get_etherscan_config_with_chain(Some(NamedChain::BinanceSmartChain.into())) .unwrap() .unwrap() .key, @@ -2940,7 +3075,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert!(config.etherscan.clone().resolved().has_unresolved()); @@ -2950,7 +3085,7 @@ mod tests { assert!(!configs.has_unresolved()); let mb_urls = Moonbeam.etherscan_urls().unwrap(); - let mainnet_urls = Mainnet.etherscan_urls().unwrap(); + let mainnet_urls = NamedChain::Mainnet.etherscan_urls().unwrap(); assert_eq!( configs, ResolvedEtherscanConfigs::new([ @@ -2958,7 +3093,7 @@ mod tests { "mainnet", ResolvedEtherscanConfig { api_url: mainnet_urls.0.to_string(), - chain: Some(Mainnet.into()), + chain: Some(NamedChain::Mainnet.into()), browser_url: Some(mainnet_urls.1.to_string()), key: "FX42Z3BBJJEWXWGYV2X1CIPRSCN".to_string(), } @@ -2979,6 +3114,29 @@ mod tests { }); } + #[test] + fn test_resolve_etherscan_chain_id() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + chain_id = "sepolia" + + [etherscan] + sepolia = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } + "#, + )?; + + let config = Config::load().unwrap(); + let etherscan = config.get_etherscan_config().unwrap().unwrap(); + assert_eq!(etherscan.chain, Some(NamedChain::Sepolia.into())); + assert_eq!(etherscan.key, "FX42Z3BBJJEWXWGYV2X1CIPRSCN"); + + Ok(()) + }); + } + #[test] fn test_resolve_rpc_url() { figment::Jail::expect_with(|jail| { @@ -2993,7 +3151,7 @@ mod tests { )?; jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); - let mut config = Config::load(); + let mut config = Config::load().unwrap(); assert_eq!("http://localhost:8545", config.get_rpc_url_or_localhost_http().unwrap()); config.eth_rpc_url = Some("mainnet".to_string()); @@ -3022,7 +3180,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!("http://localhost:8545", config.get_rpc_url_or_localhost_http().unwrap()); Ok(()) @@ -3040,13 +3198,13 @@ mod tests { polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_RESOLVE_RPC_ALIAS}" "#, )?; - let mut config = Config::load(); + let mut config = Config::load().unwrap(); config.eth_rpc_url = Some("polygonMumbai".to_string()); assert!(config.get_rpc_url().unwrap().is_err()); jail.set_env("_RESOLVE_RPC_ALIAS", "123455"); - let mut config = Config::load(); + let mut config = Config::load().unwrap(); config.eth_rpc_url = Some("polygonMumbai".to_string()); assert_eq!( "https://polygon-mumbai.g.alchemy.com/v2/123455", @@ -3057,6 +3215,174 @@ mod tests { }) } + #[test] + fn test_resolve_rpc_aliases() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + [etherscan] + arbitrum_alias = { key = "${TEST_RESOLVE_RPC_ALIAS_ARBISCAN}" } + [rpc_endpoints] + arbitrum_alias = "https://arb-mainnet.g.alchemy.com/v2/${TEST_RESOLVE_RPC_ALIAS_ARB_ONE}" + "#, + )?; + + jail.set_env("TEST_RESOLVE_RPC_ALIAS_ARB_ONE", "123455"); + jail.set_env("TEST_RESOLVE_RPC_ALIAS_ARBISCAN", "123455"); + + let config = Config::load().unwrap(); + + let config = config.get_etherscan_config_with_chain(Some(NamedChain::Arbitrum.into())); + assert!(config.is_err()); + assert_eq!(config.unwrap_err().to_string(), "At least one of `url` or `chain` must be present for Etherscan config with unknown alias `arbitrum_alias`"); + + Ok(()) + }); + } + + #[test] + fn test_resolve_rpc_config() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = { endpoint = "${_CONFIG_MAINNET}", retries = 3, retry_backoff = 1000, compute_units_per_second = 1000 } + "#, + )?; + jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); + + let config = Config::load().unwrap(); + assert_eq!( + RpcEndpoints::new([ + ( + "optimism", + RpcEndpointType::String(RpcEndpointUrl::Url( + "https://example.com/".to_string() + )) + ), + ( + "mainnet", + RpcEndpointType::Config(RpcEndpoint { + endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + config: RpcEndpointConfig { + retries: Some(3), + retry_backoff: Some(1000), + compute_units_per_second: Some(1000), + }, + auth: None, + }) + ), + ]), + config.rpc_endpoints + ); + + let resolved = config.rpc_endpoints.resolved(); + assert_eq!( + RpcEndpoints::new([ + ( + "optimism", + RpcEndpointType::String(RpcEndpointUrl::Url( + "https://example.com/".to_string() + )) + ), + ( + "mainnet", + RpcEndpointType::Config(RpcEndpoint { + endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + config: RpcEndpointConfig { + retries: Some(3), + retry_backoff: Some(1000), + compute_units_per_second: Some(1000), + }, + auth: None, + }) + ), + ]) + .resolved(), + resolved + ); + Ok(()) + }) + } + + #[test] + fn test_resolve_auth() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + eth_rpc_url = "optimism" + [rpc_endpoints] + optimism = "https://example.com/" + mainnet = { endpoint = "${_CONFIG_MAINNET}", retries = 3, retry_backoff = 1000, compute_units_per_second = 1000, auth = "Bearer ${_CONFIG_AUTH}" } + "#, + )?; + + let config = Config::load().unwrap(); + + jail.set_env("_CONFIG_AUTH", "123456"); + jail.set_env("_CONFIG_MAINNET", "https://eth-mainnet.alchemyapi.io/v2/123455"); + + assert_eq!( + RpcEndpoints::new([ + ( + "optimism", + RpcEndpointType::String(RpcEndpointUrl::Url( + "https://example.com/".to_string() + )) + ), + ( + "mainnet", + RpcEndpointType::Config(RpcEndpoint { + endpoint: RpcEndpointUrl::Env("${_CONFIG_MAINNET}".to_string()), + config: RpcEndpointConfig { + retries: Some(3), + retry_backoff: Some(1000), + compute_units_per_second: Some(1000) + }, + auth: Some(RpcAuth::Env("Bearer ${_CONFIG_AUTH}".to_string())), + }) + ), + ]), + config.rpc_endpoints + ); + let resolved = config.rpc_endpoints.resolved(); + assert_eq!( + RpcEndpoints::new([ + ( + "optimism", + RpcEndpointType::String(RpcEndpointUrl::Url( + "https://example.com/".to_string() + )) + ), + ( + "mainnet", + RpcEndpointType::Config(RpcEndpoint { + endpoint: RpcEndpointUrl::Url( + "https://eth-mainnet.alchemyapi.io/v2/123455".to_string() + ), + config: RpcEndpointConfig { + retries: Some(3), + retry_backoff: Some(1000), + compute_units_per_second: Some(1000) + }, + auth: Some(RpcAuth::Raw("Bearer 123456".to_string())), + }) + ), + ]) + .resolved(), + resolved + ); + + Ok(()) + }); + } + #[test] fn test_resolve_endpoints() { figment::Jail::expect_with(|jail| { @@ -3073,7 +3399,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.get_rpc_url().unwrap().unwrap(), "https://example.com/"); @@ -3090,18 +3416,22 @@ mod tests { assert_eq!( endpoints, RpcEndpoints::new([ - ("optimism", RpcEndpoint::Url("https://example.com/".to_string())), + ("optimism", RpcEndpointUrl::Url("https://example.com/".to_string())), ( "mainnet", - RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123455".to_string()) + RpcEndpointUrl::Url( + "https://eth-mainnet.alchemyapi.io/v2/123455".to_string() + ) ), ( "mainnet_2", - RpcEndpoint::Url("https://eth-mainnet.alchemyapi.io/v2/123456".to_string()) + RpcEndpointUrl::Url( + "https://eth-mainnet.alchemyapi.io/v2/123456".to_string() + ) ), ( "mainnet_3", - RpcEndpoint::Url( + RpcEndpointUrl::Url( "https://eth-mainnet.alchemyapi.io/v2/123456/98765".to_string() ) ), @@ -3128,15 +3458,14 @@ mod tests { "#, )?; - let mut config = Config::load(); + let mut config = Config::load().unwrap(); - let optimism = config.get_etherscan_api_key(Some(ethers_core::types::Chain::Optimism)); + let optimism = config.get_etherscan_api_key(Some(NamedChain::Optimism.into())); assert_eq!(optimism, Some("https://etherscan-optimism.com/".to_string())); config.etherscan_api_key = Some("mumbai".to_string()); - let mumbai = - config.get_etherscan_api_key(Some(ethers_core::types::Chain::PolygonMumbai)); + let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into())); assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); Ok(()) @@ -3156,10 +3485,10 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); let mumbai = config - .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::PolygonMumbai)) + .get_etherscan_config_with_chain(Some(NamedChain::PolygonMumbai.into())) .unwrap() .unwrap(); assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); @@ -3181,10 +3510,10 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); let mumbai = config - .get_etherscan_config_with_chain(Some(ethers_core::types::Chain::PolygonMumbai)) + .get_etherscan_config_with_chain(Some(NamedChain::PolygonMumbai.into())) .unwrap() .unwrap(); assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); @@ -3211,10 +3540,9 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); - let mumbai = - config.get_etherscan_config_with_chain(Option::::None).unwrap().unwrap(); + let mumbai = config.get_etherscan_config_with_chain(None).unwrap().unwrap(); assert_eq!(mumbai.key, "https://etherscan-mumbai.com/".to_string()); let mumbai_rpc = config.get_rpc_url().unwrap().unwrap(); @@ -3244,6 +3572,7 @@ mod tests { revert_strings = "strip" allow_paths = ["allow", "paths"] build_info_path = "build-info" + always_use_create_2_factory = true [rpc_endpoints] optimism = "https://example.com/" @@ -3253,7 +3582,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { @@ -3266,11 +3595,11 @@ mod tests { via_ir: true, rpc_storage_caching: StorageCachingConfig { chains: CachedChains::Chains(vec![ - Chain::Named(ethers_core::types::Chain::Mainnet), - Chain::Named(ethers_core::types::Chain::Optimism), - Chain::Id(999999) + Chain::mainnet(), + Chain::optimism_mainnet(), + Chain::from_id(999999) ]), - endpoints: CachedEndpoints::All + endpoints: CachedEndpoints::All, }, use_literal_content: false, bytecode_hash: BytecodeHash::Ipfs, @@ -3278,24 +3607,25 @@ mod tests { revert_strings: Some(RevertStrings::Strip), allow_paths: vec![PathBuf::from("allow"), PathBuf::from("paths")], rpc_endpoints: RpcEndpoints::new([ - ("optimism", RpcEndpoint::Url("https://example.com/".to_string())), - ("mainnet", RpcEndpoint::Env("${RPC_MAINNET}".to_string())), + ("optimism", RpcEndpointUrl::Url("https://example.com/".to_string())), + ("mainnet", RpcEndpointUrl::Env("${RPC_MAINNET}".to_string())), ( "mainnet_2", - RpcEndpoint::Env( + RpcEndpointUrl::Env( "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}".to_string() ) ), ( "mainnet_3", - RpcEndpoint::Env( + RpcEndpointUrl::Env( "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}/${ANOTHER_KEY}" .to_string() ) ), ]), build_info_path: Some("build-info".into()), - ..Config::default() + always_use_create_2_factory: true, + ..Config::default().normalized_optimizer_settings() } ); @@ -3308,13 +3638,13 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [profile.default] remappings = ['nested/=lib/nested/'] - "#, + ", )?; - let config = Config::load_with_root(jail.directory()); + let config = Config::load_with_root(jail.directory()).unwrap(); assert_eq!( config.remappings, vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] @@ -3346,17 +3676,19 @@ mod tests { evm_version = 'london' extra_output = [] extra_output_files = [] + always_use_create_2_factory = false ffi = false force = false gas_limit = 9223372036854775807 gas_price = 0 gas_reports = ['*'] ignored_error_codes = [1878] + ignored_warnings_from = ["something"] deny_warnings = false initial_balance = '0xffffffffffffffffffffffff' libraries = [] libs = ['lib'] - memory_limit = 33554432 + memory_limit = 134217728 names = false no_storage_caching = false no_rpc_rate_limit = false @@ -3373,7 +3705,7 @@ mod tests { tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' verbosity = 0 via_ir = false - + [profile.default.rpc_storage_caching] chains = 'all' endpoints = 'all' @@ -3390,16 +3722,17 @@ mod tests { [invariant] runs = 256 - depth = 15 + depth = 500 fail_on_revert = false call_override = false - shrink_sequence = true + shrink_run_limit = 5000 "#, )?; - let config = Config::load_with_root(jail.directory()); + let config = Config::load_with_root(jail.directory()).unwrap(); - assert_eq!(config.fuzz.seed, Some(1000.into())); + assert_eq!(config.ignored_file_paths, vec![PathBuf::from("something")]); + assert_eq!(config.fuzz.seed, Some(U256::from(1000))); assert_eq!( config.remappings, vec![Remapping::from_str("nested/=lib/nested/").unwrap().into()] @@ -3408,11 +3741,11 @@ mod tests { assert_eq!( config.rpc_endpoints, RpcEndpoints::new([ - ("optimism", RpcEndpoint::Url("https://example.com/".to_string())), - ("mainnet", RpcEndpoint::Env("${RPC_MAINNET}".to_string())), + ("optimism", RpcEndpointUrl::Url("https://example.com/".to_string())), + ("mainnet", RpcEndpointUrl::Env("${RPC_MAINNET}".to_string())), ( "mainnet_2", - RpcEndpoint::Env( + RpcEndpointUrl::Env( "https://eth-mainnet.alchemyapi.io/v2/${API_KEY}".to_string() ) ), @@ -3434,8 +3767,8 @@ mod tests { "#, )?; - let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.8.12".parse().unwrap()))); + let config = Config::load().unwrap(); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); jail.create_file( "foundry.toml", @@ -3445,8 +3778,8 @@ mod tests { "#, )?; - let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.8.12".parse().unwrap()))); + let config = Config::load().unwrap(); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); jail.create_file( "foundry.toml", @@ -3456,12 +3789,47 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.solc, Some(SolcReq::Local("path/to/local/solc".into()))); jail.set_env("FOUNDRY_SOLC_VERSION", "0.6.6"); - let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.6.6".parse().unwrap()))); + let config = Config::load().unwrap(); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 6, 6)))); + Ok(()) + }); + } + + // ensures the newer `solc` takes precedence over `solc_version` + #[test] + fn test_backwards_solc_version() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + solc = "0.8.12" + solc_version = "0.8.20" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); + + Ok(()) + }); + + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [default] + solc_version = "0.8.20" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 20)))); + Ok(()) }); } @@ -3482,7 +3850,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { @@ -3492,7 +3860,7 @@ mod tests { eth_rpc_url: Some("https://example.com/".to_string()), auto_detect_solc: false, evm_version: EvmVersion::Berlin, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); @@ -3512,7 +3880,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.extra_output, @@ -3537,26 +3905,26 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config, Config { src: "mysrc".into(), out: "myout".into(), verbosity: 3, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); - jail.set_env("FOUNDRY_SRC", r#"other-src"#); - let config = Config::load(); + jail.set_env("FOUNDRY_SRC", r"other-src"); + let config = Config::load().unwrap(); assert_eq!( config, Config { src: "other-src".into(), out: "myout".into(), verbosity: 3, - ..Config::default() + ..Config::default().normalized_optimizer_settings() } ); @@ -3584,7 +3952,7 @@ mod tests { src = "other-src" "#, )?; - let loaded = Config::load(); + let loaded = Config::load().unwrap(); assert_eq!(loaded.evm_version, EvmVersion::Berlin); let base = loaded.into_basic(); let default = Config::default(); @@ -3598,7 +3966,7 @@ mod tests { remappings: default.remappings.clone(), } ); - jail.set_env("FOUNDRY_PROFILE", r#"other"#); + jail.set_env("FOUNDRY_PROFILE", r"other"); let base = Config::figment().extract::().unwrap(); assert_eq!( base, @@ -3620,12 +3988,12 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [fuzz] dictionary_weight = 101 - "#, + ", )?; - let _config = Config::load(); + let _config = Config::load().unwrap(); Ok(()) }); } @@ -3635,7 +4003,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [fuzz] runs = 1 include_storage = false @@ -3649,11 +4017,11 @@ mod tests { [profile.ci.invariant] runs = 400 - "#, + ", )?; let invariant_default = InvariantConfig::default(); - let config = Config::load(); + let config = Config::load().unwrap(); assert_ne!(config.invariant.runs, config.fuzz.runs); assert_eq!(config.invariant.runs, 420); @@ -3677,7 +4045,7 @@ mod tests { ); jail.set_env("FOUNDRY_PROFILE", "ci"); - let ci_config = Config::load(); + let ci_config = Config::load().unwrap(); assert_eq!(ci_config.fuzz.runs, 1); assert_eq!(ci_config.invariant.runs, 400); assert_eq!(ci_config.fuzz.dictionary.dictionary_weight, 5); @@ -3695,7 +4063,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [fuzz] runs = 100 @@ -3707,15 +4075,15 @@ mod tests { [profile.ci.invariant] runs = 500 - "#, + ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.fuzz.runs, 100); assert_eq!(config.invariant.runs, 120); jail.set_env("FOUNDRY_PROFILE", "ci"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.fuzz.runs, 420); assert_eq!(config.invariant.runs, 500); @@ -3726,7 +4094,7 @@ mod tests { #[test] fn can_handle_deviating_dapp_aliases() { figment::Jail::expect_with(|jail| { - let addr = Address::random(); + let addr = Address::ZERO; jail.set_env("DAPP_TEST_NUMBER", 1337); jail.set_env("DAPP_TEST_ADDRESS", format!("{addr:?}")); jail.set_env("DAPP_TEST_FUZZ_RUNS", 420); @@ -3735,15 +4103,15 @@ mod tests { jail.set_env("DAPP_BUILD_OPTIMIZE_RUNS", 999); jail.set_env("DAPP_BUILD_OPTIMIZE", 0); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.block_number, 1337); assert_eq!(config.sender, addr); assert_eq!(config.fuzz.runs, 420); assert_eq!(config.invariant.depth, 20); assert_eq!(config.fork_block_number, Some(100)); - assert_eq!(config.optimizer_runs, 999); - assert!(!config.optimizer); + assert_eq!(config.optimizer_runs, Some(999)); + assert!(!config.optimizer.unwrap()); Ok(()) }); @@ -3756,7 +4124,7 @@ mod tests { "DAPP_LIBRARIES", "[src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6]", ); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.libraries, vec!["src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" @@ -3767,7 +4135,7 @@ mod tests { "DAPP_LIBRARIES", "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", ); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.libraries, vec!["src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6" @@ -3778,7 +4146,7 @@ mod tests { "DAPP_LIBRARIES", "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6,src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", ); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.libraries, vec![ @@ -3798,7 +4166,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [profile.default] libraries= [ './src/SizeAuctionDiscount.sol:Chainlink:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', @@ -3806,14 +4174,14 @@ mod tests { './src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', './src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', './src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', - ] - "#, + ] + ", )?; - let config = Config::load(); + let config = Config::load().unwrap(); let libs = config.parsed_libraries().unwrap().libs; - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( libs, BTreeMap::from([ ( @@ -3859,11 +4227,11 @@ mod tests { #[test] fn config_roundtrip() { figment::Jail::expect_with(|jail| { - let default = Config::default(); + let default = Config::default().normalized_optimizer_settings(); let basic = default.clone().into_basic(); jail.create_file("foundry.toml", &basic.to_string_pretty().unwrap())?; - let mut other = Config::load(); + let mut other = Config::load().unwrap(); clear_warning(&mut other); assert_eq!(default, other); @@ -3871,7 +4239,7 @@ mod tests { assert_eq!(basic, other); jail.create_file("foundry.toml", &default.to_string_pretty().unwrap())?; - let mut other = Config::load(); + let mut other = Config::load().unwrap(); clear_warning(&mut other); assert_eq!(default, other); @@ -3889,7 +4257,7 @@ mod tests { fs_permissions = [{ access = "read-write", path = "./"}] "#, )?; - let loaded = Config::load(); + let loaded = Config::load().unwrap(); assert_eq!( loaded.fs_permissions, @@ -3903,7 +4271,7 @@ mod tests { fs_permissions = [{ access = "none", path = "./"}] "#, )?; - let loaded = Config::load(); + let loaded = Config::load().unwrap(); assert_eq!(loaded.fs_permissions, FsPermissions::new(vec![PathPermission::none("./")])); Ok(()) @@ -3915,7 +4283,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [profile.default] optimizer = true @@ -3924,9 +4292,9 @@ mod tests { [profile.default.optimizer_details.yulDetails] stackAllocation = true - "#, + ", )?; - let mut loaded = Config::load(); + let mut loaded = Config::load().unwrap(); clear_warning(&mut loaded); assert_eq!( loaded.optimizer_details, @@ -3943,7 +4311,7 @@ mod tests { let s = loaded.to_string_pretty().unwrap(); jail.create_file("foundry.toml", &s)?; - let mut reloaded = Config::load(); + let mut reloaded = Config::load().unwrap(); clear_warning(&mut reloaded); assert_eq!(loaded, reloaded); @@ -3956,7 +4324,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [profile.default] [profile.default.model_checker] @@ -3964,9 +4332,9 @@ mod tests { engine = 'chc' targets = [ 'assert', 'outOfBounds' ] timeout = 10000 - "#, + ", )?; - let mut loaded = Config::load(); + let mut loaded = Config::load().unwrap(); clear_warning(&mut loaded); assert_eq!( loaded.model_checker, @@ -3993,7 +4361,7 @@ mod tests { let s = loaded.to_string_pretty().unwrap(); jail.create_file("foundry.toml", &s)?; - let mut reloaded = Config::load(); + let mut reloaded = Config::load().unwrap(); clear_warning(&mut reloaded); assert_eq!(loaded, reloaded); @@ -4006,7 +4374,7 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [profile.default] [profile.default.model_checker] @@ -4014,15 +4382,15 @@ mod tests { engine = 'chc' targets = [ 'assert', 'outOfBounds' ] timeout = 10000 - "#, + ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); // NOTE(onbjerg): We have to canonicalize the path here using dunce because figment will // canonicalize the jail path using the standard library. The standard library *always* // transforms Windows paths to some weird extended format, which none of our code base // does. - let dir = ethers_solc::utils::canonicalize(jail.directory()) + let dir = foundry_compilers::utils::canonicalize(jail.directory()) .expect("Could not canonicalize jail path"); assert_eq!( loaded.model_checker, @@ -4061,14 +4429,14 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [fmt] line_length = 100 tab_width = 2 bracket_spacing = true - "#, + ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!( loaded.fmt, FormatterConfig { @@ -4088,17 +4456,22 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [invariant] runs = 512 depth = 10 - "#, + ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!( loaded.invariant, - InvariantConfig { runs: 512, depth: 10, ..Default::default() } + InvariantConfig { + runs: 512, + depth: 10, + failure_persist_dir: Some(PathBuf::from("cache/invariant")), + ..Default::default() + } ); Ok(()) @@ -4110,20 +4483,20 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [fuzz] runs = 100 [invariant] depth = 1 - "#, + ", )?; jail.set_env("FOUNDRY_FMT_LINE_LENGTH", "95"); jail.set_env("FOUNDRY_FUZZ_DICTIONARY_WEIGHT", "99"); jail.set_env("FOUNDRY_INVARIANT_DEPTH", "5"); - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!(config.fmt.line_length, 95); assert_eq!(config.fuzz.dictionary.dictionary_weight, 99); assert_eq!(config.invariant.depth, 5); @@ -4134,14 +4507,14 @@ mod tests { #[test] fn test_parse_with_profile() { - let foundry_str = r#" + let foundry_str = r" [profile.default] src = 'src' out = 'out' libs = ['lib'] # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options - "#; + "; assert_eq!( parse_with_profile::(foundry_str).unwrap().unwrap(), ( @@ -4162,17 +4535,17 @@ mod tests { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", - r#" + r" [default] src = 'my-src' out = 'my-out' - "#, + ", )?; - let loaded = Config::load().sanitized(); + let loaded = Config::load().unwrap().sanitized(); assert_eq!(loaded.src.file_name().unwrap(), "my-src"); assert_eq!(loaded.out.file_name().unwrap(), "my-out"); assert_eq!( - loaded.__warnings, + loaded.warnings, vec![Warning::UnknownSection { unknown_section: Profile::new("default"), source: Some("foundry.toml".into()) @@ -4183,7 +4556,68 @@ mod tests { }); } + #[test] + fn test_etherscan_api_key() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + ", + )?; + jail.set_env("ETHERSCAN_API_KEY", ""); + let loaded = Config::load().unwrap().sanitized(); + assert!(loaded.etherscan_api_key.is_none()); + + jail.set_env("ETHERSCAN_API_KEY", "DUMMY"); + let loaded = Config::load().unwrap().sanitized(); + assert_eq!(loaded.etherscan_api_key, Some("DUMMY".into())); + + Ok(()) + }); + } + + #[test] + fn test_etherscan_api_key_figment() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + etherscan_api_key = 'DUMMY' + ", + )?; + jail.set_env("ETHERSCAN_API_KEY", "ETHER"); + + let figment = Config::figment_with_root(jail.directory()) + .merge(("etherscan_api_key", "USER_KEY")); + + let loaded = Config::from_provider(figment).unwrap(); + assert_eq!(loaded.etherscan_api_key, Some("USER_KEY".into())); + + Ok(()) + }); + } + + #[test] + fn test_normalize_defaults() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + solc = '0.8.13' + ", + )?; + + let loaded = Config::load().unwrap().sanitized(); + assert_eq!(loaded.evm_version, EvmVersion::London); + Ok(()) + }); + } + // a test to print the config, mainly used to update the example config in the README + #[allow(clippy::disallowed_macros)] #[test] #[ignore] fn print_config() { @@ -4201,6 +4635,7 @@ mod tests { stack_allocation: None, optimizer_steps: Some("dhfoDgvulfnTUtnIf".to_string()), }), + simple_counter_for_loop_unchecked_increment: None, }), ..Default::default() }; @@ -4208,6 +4643,7 @@ mod tests { } #[test] + #[allow(unknown_lints, non_local_definitions)] fn can_use_impl_figment_macro() { #[derive(Default, Serialize)] struct MyArgs { @@ -4230,7 +4666,6 @@ mod tests { } let _figment: Figment = From::from(&MyArgs::default()); - let _config: Config = From::from(&MyArgs::default()); #[derive(Default)] struct Outer { @@ -4241,7 +4676,6 @@ mod tests { impl_figment_convert!(Outer, start, other, another); let _figment: Figment = From::from(&Outer::default()); - let _config: Config = From::from(&Outer::default()); } #[test] @@ -4254,23 +4688,38 @@ mod tests { writeln!(file, "{}", vec![' '; size_bytes - 1].iter().collect::()).unwrap(); } + fn fake_block_cache_block_path_as_file( + chain_path: &Path, + block_number: &str, + size_bytes: usize, + ) { + let block_path = chain_path.join(block_number); + let mut file = File::create(block_path).unwrap(); + writeln!(file, "{}", vec![' '; size_bytes - 1].iter().collect::()).unwrap(); + } + let chain_dir = tempdir()?; fake_block_cache(chain_dir.path(), "1", 100); fake_block_cache(chain_dir.path(), "2", 500); + fake_block_cache_block_path_as_file(chain_dir.path(), "3", 900); // Pollution file that should not show up in the cached block let mut pol_file = File::create(chain_dir.path().join("pol.txt")).unwrap(); writeln!(pol_file, "{}", [' '; 10].iter().collect::()).unwrap(); let result = Config::get_cached_blocks(chain_dir.path())?; - assert_eq!(result.len(), 2); + assert_eq!(result.len(), 3); let block1 = &result.iter().find(|x| x.0 == "1").unwrap(); let block2 = &result.iter().find(|x| x.0 == "2").unwrap(); + let block3 = &result.iter().find(|x| x.0 == "3").unwrap(); + assert_eq!(block1.0, "1"); assert_eq!(block1.1, 100); assert_eq!(block2.0, "2"); assert_eq!(block2.1, 500); + assert_eq!(block3.0, "3"); + assert_eq!(block3.1, 900); chain_dir.close()?; Ok(()) @@ -4319,7 +4768,7 @@ mod tests { "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); assert_eq!( config.ignored_error_codes, vec![ @@ -4334,20 +4783,130 @@ mod tests { } #[test] - fn test_parse_optimizer_settings() { + fn test_parse_file_paths() { figment::Jail::expect_with(|jail| { jail.create_file( "foundry.toml", r#" [default] - [profile.default.optimizer_details] + ignored_warnings_from = ["something"] "#, )?; - let config = Config::load(); + let config = Config::load().unwrap(); + assert_eq!(config.ignored_file_paths, vec![Path::new("something").to_path_buf()]); + + Ok(()) + }); + } + + #[test] + fn test_parse_optimizer_settings() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r" + [default] + [profile.default.optimizer_details] + ", + )?; + + let config = Config::load().unwrap(); assert_eq!(config.optimizer_details, Some(OptimizerDetails::default())); Ok(()) }); } + + #[test] + fn test_parse_labels() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [labels] + 0x1F98431c8aD98523631AE4a59f267346ea31F984 = "Uniswap V3: Factory" + 0xC36442b4a4522E871399CD717aBDD847Ab11FE88 = "Uniswap V3: Positions NFT" + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!( + config.labels, + AddressHashMap::from_iter(vec![ + ( + Address::from_str("0x1F98431c8aD98523631AE4a59f267346ea31F984").unwrap(), + "Uniswap V3: Factory".to_string() + ), + ( + Address::from_str("0xC36442b4a4522E871399CD717aBDD847Ab11FE88").unwrap(), + "Uniswap V3: Positions NFT".to_string() + ), + ]) + ); + + Ok(()) + }); + } + + #[test] + fn test_parse_vyper() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [vyper] + optimize = "codesize" + path = "/path/to/vyper" + experimental_codegen = true + "#, + )?; + + let config = Config::load().unwrap(); + assert_eq!( + config.vyper, + VyperConfig { + optimize: Some(VyperOptimizationMode::Codesize), + path: Some("/path/to/vyper".into()), + experimental_codegen: Some(true), + } + ); + + Ok(()) + }); + } + + #[test] + fn test_parse_soldeer() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [soldeer] + remappings_generate = true + remappings_regenerate = false + remappings_version = true + remappings_prefix = "@" + remappings_location = "txt" + recursive_deps = true + "#, + )?; + + let config = Config::load().unwrap(); + + assert_eq!( + config.soldeer, + Some(SoldeerConfig { + remappings_generate: true, + remappings_regenerate: false, + remappings_version: true, + remappings_prefix: "@".to_string(), + remappings_location: RemappingsLocation::Txt, + recursive_deps: true, + }) + ); + + Ok(()) + }); + } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index eaf7ae7a52950..cb5dc9771abc9 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -1,7 +1,8 @@ -/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +/// A macro to implement converters from a type to [`Config`](crate::Config) and +/// [`figment::Figment`]. /// /// This can be used to remove some boilerplate code that's necessary to add additional layer(s) to -/// the [`Config`]'s default `Figment`. +/// the `Config`'s default `Figment`. /// /// `impl_figment` takes the default `Config` and merges additional `Provider`, therefore the /// targeted type, requires an implementation of `figment::Profile`. @@ -9,7 +10,7 @@ /// # Example /// /// Use `impl_figment` on a type with a `root: Option` field, which will be used for -/// [`Config::figment_with_root()`] +/// [`Config::figment_with_root()`](crate::Config::figment_with_root). /// /// ```rust /// use std::path::PathBuf; @@ -30,7 +31,7 @@ /// Metadata::default() /// } /// -/// fn data(&self) -> Result, Error> { +/// fn data(&self) -> std::result::Result, Error> { /// let value = Value::serialize(self)?; /// let error = InvalidType(value.to_actual(), "map".into()); /// let mut dict = value.into_dict().ok_or(error)?; @@ -39,7 +40,6 @@ /// } /// /// let figment: Figment = From::from(&MyArgs::default()); -/// let config: Config = From::from(&MyArgs::default()); /// /// // Use `impl_figment` on a type that has several nested `Provider` as fields but is _not_ a `Provider` itself /// @@ -52,26 +52,13 @@ /// impl_figment_convert!(Outer, start, second, third); /// /// let figment: Figment = From::from(&Outer::default()); -/// let config: Config = From::from(&Outer::default()); /// ``` #[macro_export] macro_rules! impl_figment_convert { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - if let Some(root) = args.root.clone() { - $crate::Config::figment_with_root(root) - } else { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) - } - .merge(args) - } - } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() + $crate::Config::figment_with_root_opt(args.root.as_deref()).merge(args) } } }; @@ -79,38 +66,24 @@ macro_rules! impl_figment_convert { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); + $( + figment = figment.merge(&args.$more); )* figment } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; ($name:ty, self, $start:ident $(, $more:ident)*) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); + $( + figment = figment.merge(&args.$more); )* figment = figment.merge(args); figment } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; } @@ -121,10 +94,11 @@ macro_rules! impl_figment_convert { /// Merge several nested `Provider` together with the type itself /// /// ```rust +/// use foundry_config::{ +/// figment::{value::*, *}, +/// impl_figment_convert, merge_impl_figment_convert, Config, +/// }; /// use std::path::PathBuf; -/// use foundry_config::{Config, merge_impl_figment_convert, impl_figment_convert}; -/// use foundry_config::figment::*; -/// use foundry_config::figment::value::*; /// /// #[derive(Default)] /// struct MyArgs { @@ -136,8 +110,8 @@ macro_rules! impl_figment_convert { /// Metadata::default() /// } /// -/// fn data(&self) -> Result, Error> { -/// todo!() +/// fn data(&self) -> std::result::Result, Error> { +/// todo!() /// } /// } /// @@ -146,7 +120,7 @@ macro_rules! impl_figment_convert { /// #[derive(Default)] /// struct OuterArgs { /// value: u64, -/// inner: MyArgs +/// inner: MyArgs, /// } /// /// impl Provider for OuterArgs { @@ -154,8 +128,8 @@ macro_rules! impl_figment_convert { /// Metadata::default() /// } /// -/// fn data(&self) -> Result, Error> { -/// todo!() +/// fn data(&self) -> std::result::Result, Error> { +/// todo!() /// } /// } /// @@ -174,33 +148,26 @@ macro_rules! merge_impl_figment_convert { figment } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; } -/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +/// A macro to implement converters from a type to [`Config`](crate::Config) and +/// [`figment::Figment`]. +/// +/// Via [Config::to_figment](crate::Config::to_figment) and the +/// [Cast](crate::FigmentProviders::Cast) profile. #[macro_export] macro_rules! impl_figment_convert_cast { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) + let root = + $crate::find_project_root(None).expect("could not determine project root"); + $crate::Config::with_root(&root) + .to_figment($crate::FigmentProviders::Cast) .merge(args) } } - - impl<'a> From<&'a $name> for $crate::Config { - fn from(args: &'a $name) -> Self { - let figment: $crate::figment::Figment = args.into(); - $crate::Config::from_provider(figment).sanitized() - } - } }; } diff --git a/crates/config/src/providers/ext.rs b/crates/config/src/providers/ext.rs new file mode 100644 index 0000000000000..58f4184690b46 --- /dev/null +++ b/crates/config/src/providers/ext.rs @@ -0,0 +1,562 @@ +use crate::{utils, Config}; +use figment::{ + providers::{Env, Format, Toml}, + value::{Dict, Map, Value}, + Error, Figment, Metadata, Profile, Provider, +}; +use foundry_compilers::ProjectPathsConfig; +use inflector::Inflector; +use std::path::{Path, PathBuf}; + +pub(crate) trait ProviderExt: Provider + Sized { + fn rename( + self, + from: impl Into, + to: impl Into, + ) -> RenameProfileProvider { + RenameProfileProvider::new(self, from, to) + } + + fn wrap( + self, + wrapping_key: impl Into, + profile: impl Into, + ) -> WrapProfileProvider { + WrapProfileProvider::new(self, wrapping_key, profile) + } + + fn strict_select( + self, + profiles: impl IntoIterator>, + ) -> OptionalStrictProfileProvider { + OptionalStrictProfileProvider::new(self, profiles) + } + + fn fallback( + self, + profile: impl Into, + fallback: impl Into, + ) -> FallbackProfileProvider { + FallbackProfileProvider::new(self, profile, fallback) + } +} + +impl ProviderExt for P {} + +/// A convenience provider to retrieve a toml file. +/// This will return an error if the env var is set but the file does not exist +pub(crate) struct TomlFileProvider { + pub env_var: Option<&'static str>, + pub default: PathBuf, + pub cache: Option, Error>>, +} + +impl TomlFileProvider { + pub(crate) fn new(env_var: Option<&'static str>, default: impl Into) -> Self { + Self { env_var, default: default.into(), cache: None } + } + + fn env_val(&self) -> Option { + self.env_var.and_then(Env::var) + } + + fn file(&self) -> PathBuf { + self.env_val().map(PathBuf::from).unwrap_or_else(|| self.default.clone()) + } + + fn is_missing(&self) -> bool { + if let Some(file) = self.env_val() { + let path = Path::new(&file); + if !path.exists() { + return true; + } + } + false + } + + pub(crate) fn cached(mut self) -> Self { + self.cache = Some(self.read()); + self + } + + fn read(&self) -> Result, Error> { + use serde::de::Error as _; + if let Some(file) = self.env_val() { + let path = Path::new(&file); + if !path.exists() { + return Err(Error::custom(format!( + "Config file `{}` set in env var `{}` does not exist", + file, + self.env_var.unwrap() + ))); + } + Toml::file(file) + } else { + Toml::file(&self.default) + } + .nested() + .data() + } +} + +impl Provider for TomlFileProvider { + fn metadata(&self) -> Metadata { + if self.is_missing() { + Metadata::named("TOML file provider") + } else { + Toml::file(self.file()).nested().metadata() + } + } + + fn data(&self) -> Result, Error> { + if let Some(cache) = self.cache.as_ref() { + cache.clone() + } else { + self.read() + } + } +} + +/// A Provider that ensures all keys are snake case if they're not standalone sections, See +/// `Config::STANDALONE_SECTIONS` +pub(crate) struct ForcedSnakeCaseData

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

{ + fn metadata(&self) -> Metadata { + self.0.metadata() + } + + fn data(&self) -> Result, Error> { + let mut map = Map::new(); + for (profile, dict) in self.0.data()? { + if Config::STANDALONE_SECTIONS.contains(&profile.as_ref()) { + // don't force snake case for keys in standalone sections + map.insert(profile, dict); + continue; + } + map.insert(profile, dict.into_iter().map(|(k, v)| (k.to_snake_case(), v)).collect()); + } + Ok(map) + } +} + +/// A Provider that handles breaking changes in toml files +pub(crate) struct BackwardsCompatTomlProvider

(pub(crate) P); + +impl Provider for BackwardsCompatTomlProvider

{ + fn metadata(&self) -> Metadata { + self.0.metadata() + } + + fn data(&self) -> Result, Error> { + let mut map = Map::new(); + let solc_env = std::env::var("FOUNDRY_SOLC_VERSION") + .or_else(|_| std::env::var("DAPP_SOLC_VERSION")) + .map(Value::from) + .ok(); + for (profile, mut dict) in self.0.data()? { + if let Some(v) = solc_env.clone() { + // ENV var takes precedence over config file + dict.insert("solc".to_string(), v); + } else if let Some(v) = dict.remove("solc_version") { + // only insert older variant if not already included + if !dict.contains_key("solc") { + dict.insert("solc".to_string(), v); + } + } + + if let Some(v) = dict.remove("odyssey") { + dict.insert("odyssey".to_string(), v); + } + map.insert(profile, dict); + } + Ok(map) + } +} + +/// A provider that sets the `src` and `output` path depending on their existence. +pub(crate) struct DappHardhatDirProvider<'a>(pub(crate) &'a Path); + +impl Provider for DappHardhatDirProvider<'_> { + fn metadata(&self) -> Metadata { + Metadata::named("Dapp Hardhat dir compat") + } + + fn data(&self) -> Result, Error> { + let mut dict = Dict::new(); + dict.insert( + "src".to_string(), + ProjectPathsConfig::find_source_dir(self.0) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() + .into(), + ); + dict.insert( + "out".to_string(), + ProjectPathsConfig::find_artifacts_dir(self.0) + .file_name() + .unwrap() + .to_string_lossy() + .to_string() + .into(), + ); + + // detect libs folders: + // if `lib` _and_ `node_modules` exists: include both + // if only `node_modules` exists: include `node_modules` + // include `lib` otherwise + let mut libs = vec![]; + let node_modules = self.0.join("node_modules"); + let lib = self.0.join("lib"); + if node_modules.exists() { + if lib.exists() { + libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); + } + libs.push(node_modules.file_name().unwrap().to_string_lossy().to_string()); + } else { + libs.push(lib.file_name().unwrap().to_string_lossy().to_string()); + } + + dict.insert("libs".to_string(), libs.into()); + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// A provider that checks for DAPP_ env vars that are named differently than FOUNDRY_ +pub(crate) struct DappEnvCompatProvider; + +impl Provider for DappEnvCompatProvider { + fn metadata(&self) -> Metadata { + Metadata::named("Dapp env compat") + } + + fn data(&self) -> Result, Error> { + use serde::de::Error as _; + use std::env; + + let mut dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_NUMBER") { + dict.insert( + "block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_TEST_ADDRESS") { + dict.insert("sender".to_string(), val.into()); + } + if let Ok(val) = env::var("DAPP_FORK_BLOCK") { + dict.insert( + "fork_block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } else if let Ok(val) = env::var("DAPP_TEST_NUMBER") { + dict.insert( + "fork_block_number".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_TEST_TIMESTAMP") { + dict.insert( + "block_timestamp".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE_RUNS") { + dict.insert( + "optimizer_runs".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + if let Ok(val) = env::var("DAPP_BUILD_OPTIMIZE") { + // Activate Solidity optimizer (0 or 1) + let val = val.parse::().map_err(figment::Error::custom)?; + if val > 1 { + return Err( + format!("Invalid $DAPP_BUILD_OPTIMIZE value `{val}`, expected 0 or 1").into() + ); + } + dict.insert("optimizer".to_string(), (val == 1).into()); + } + + // libraries in env vars either as `[..]` or single string separated by comma + if let Ok(val) = env::var("DAPP_LIBRARIES").or_else(|_| env::var("FOUNDRY_LIBRARIES")) { + dict.insert("libraries".to_string(), utils::to_array_value(&val)?); + } + + let mut fuzz_dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_FUZZ_RUNS") { + fuzz_dict.insert( + "runs".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + dict.insert("fuzz".to_string(), fuzz_dict.into()); + + let mut invariant_dict = Dict::new(); + if let Ok(val) = env::var("DAPP_TEST_DEPTH") { + invariant_dict.insert( + "depth".to_string(), + val.parse::().map_err(figment::Error::custom)?.into(), + ); + } + dict.insert("invariant".to_string(), invariant_dict.into()); + + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// Renames a profile from `from` to `to`. +/// +/// For example given: +/// +/// ```toml +/// [from] +/// key = "value" +/// ``` +/// +/// RenameProfileProvider will output +/// +/// ```toml +/// [to] +/// key = "value" +/// ``` +pub(crate) struct RenameProfileProvider

{ + provider: P, + from: Profile, + to: Profile, +} + +impl

RenameProfileProvider

{ + pub(crate) fn new(provider: P, from: impl Into, to: impl Into) -> Self { + Self { provider, from: from.into(), to: to.into() } + } +} + +impl Provider for RenameProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + let mut data = self.provider.data()?; + if let Some(data) = data.remove(&self.from) { + return Ok(Map::from([(self.to.clone(), data)])); + } + Ok(Default::default()) + } + fn profile(&self) -> Option { + Some(self.to.clone()) + } +} + +/// Unwraps a profile reducing the key depth +/// +/// For example given: +/// +/// ```toml +/// [wrapping_key.profile] +/// key = "value" +/// ``` +/// +/// UnwrapProfileProvider will output: +/// +/// ```toml +/// [profile] +/// key = "value" +/// ``` +struct UnwrapProfileProvider

{ + provider: P, + wrapping_key: Profile, + profile: Profile, +} + +impl

UnwrapProfileProvider

{ + pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { + Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } + } +} + +impl Provider for UnwrapProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + self.provider.data().and_then(|mut data| { + if let Some(profiles) = data.remove(&self.wrapping_key) { + for (profile_str, profile_val) in profiles { + let profile = Profile::new(&profile_str); + if profile != self.profile { + continue; + } + match profile_val { + Value::Dict(_, dict) => return Ok(profile.collect(dict)), + bad_val => { + let mut err = Error::from(figment::error::Kind::InvalidType( + bad_val.to_actual(), + "dict".into(), + )); + err.metadata = Some(self.provider.metadata()); + err.profile = Some(self.profile.clone()); + return Err(err); + } + } + } + } + Ok(Default::default()) + }) + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Wraps a profile in another profile +/// +/// For example given: +/// +/// ```toml +/// [profile] +/// key = "value" +/// ``` +/// +/// WrapProfileProvider will output: +/// +/// ```toml +/// [wrapping_key.profile] +/// key = "value" +/// ``` +pub(crate) struct WrapProfileProvider

{ + provider: P, + wrapping_key: Profile, + profile: Profile, +} + +impl

WrapProfileProvider

{ + pub fn new(provider: P, wrapping_key: impl Into, profile: impl Into) -> Self { + Self { provider, wrapping_key: wrapping_key.into(), profile: profile.into() } + } +} + +impl Provider for WrapProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + if let Some(inner) = self.provider.data()?.remove(&self.profile) { + let value = Value::from(inner); + let dict = [(self.profile.to_string().to_snake_case(), value)].into_iter().collect(); + Ok(self.wrapping_key.collect(dict)) + } else { + Ok(Default::default()) + } + } + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} + +/// Extracts the profile from the `profile` key and using the original key as backup, merging +/// values where necessary +/// +/// For example given: +/// +/// ```toml +/// [profile.cool] +/// key = "value" +/// +/// [cool] +/// key2 = "value2" +/// ``` +/// +/// OptionalStrictProfileProvider will output: +/// +/// ```toml +/// [cool] +/// key = "value" +/// key2 = "value2" +/// ``` +/// +/// And emit a deprecation warning +pub(crate) struct OptionalStrictProfileProvider

{ + provider: P, + profiles: Vec, +} + +impl

OptionalStrictProfileProvider

{ + pub const PROFILE_PROFILE: Profile = Profile::const_new("profile"); + + pub fn new(provider: P, profiles: impl IntoIterator>) -> Self { + Self { provider, profiles: profiles.into_iter().map(|profile| profile.into()).collect() } + } +} + +impl Provider for OptionalStrictProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + fn data(&self) -> Result, Error> { + let mut figment = Figment::from(&self.provider); + for profile in &self.profiles { + figment = figment.merge(UnwrapProfileProvider::new( + &self.provider, + Self::PROFILE_PROFILE, + profile.clone(), + )); + } + figment.data().map_err(|err| { + // figment does tag metadata and tries to map metadata to an error, since we use a new + // figment in this provider this new figment does not know about the metadata of the + // provider and can't map the metadata to the error. Therefore we return the root error + // if this error originated in the provider's data. + if let Err(root_err) = self.provider.data() { + return root_err; + } + err + }) + } + fn profile(&self) -> Option { + self.profiles.last().cloned() + } +} + +/// Extracts the profile from the `profile` key and sets unset values according to the fallback +/// provider +pub struct FallbackProfileProvider

{ + provider: P, + profile: Profile, + fallback: Profile, +} + +impl

FallbackProfileProvider

{ + /// Creates a new fallback profile provider. + pub fn new(provider: P, profile: impl Into, fallback: impl Into) -> Self { + Self { provider, profile: profile.into(), fallback: fallback.into() } + } +} + +impl Provider for FallbackProfileProvider

{ + fn metadata(&self) -> Metadata { + self.provider.metadata() + } + + fn data(&self) -> Result, Error> { + let data = self.provider.data()?; + if let Some(fallback) = data.get(&self.fallback) { + let mut inner = data.get(&self.profile).cloned().unwrap_or_default(); + for (k, v) in fallback.iter() { + if !inner.contains_key(k) { + inner.insert(k.to_owned(), v.clone()); + } + } + Ok(self.profile.collect(inner)) + } else { + Ok(data) + } + } + + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} diff --git a/crates/config/src/providers/mod.rs b/crates/config/src/providers/mod.rs index a7e567788ba2c..9fec7d290ca34 100644 --- a/crates/config/src/providers/mod.rs +++ b/crates/config/src/providers/mod.rs @@ -1,138 +1,10 @@ -use crate::{Config, Warning, DEPRECATIONS}; -use figment::{ - value::{Dict, Map, Value}, - Error, Figment, Metadata, Profile, Provider, -}; +//! Config providers. -/// Remappings provider -pub mod remappings; +mod ext; +pub use ext::*; -/// Generate warnings for unknown sections and deprecated keys -pub struct WarningsProvider

{ - provider: P, - profile: Profile, - old_warnings: Result, Error>, -} +mod remappings; +pub use remappings::*; -impl

WarningsProvider

{ - const WARNINGS_KEY: &'static str = "__warnings"; - - /// Creates a new warnings provider. - pub fn new( - provider: P, - profile: impl Into, - old_warnings: Result, Error>, - ) -> Self { - Self { provider, profile: profile.into(), old_warnings } - } - - /// Creates a new figment warnings provider. - pub fn for_figment(provider: P, figment: &Figment) -> Self { - let old_warnings = { - let warnings_res = figment.extract_inner(Self::WARNINGS_KEY); - if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) { - Ok(vec![]) - } else { - warnings_res - } - }; - Self::new(provider, figment.profile().clone(), old_warnings) - } -} - -impl WarningsProvider

{ - /// Collects all warnings. - pub fn collect_warnings(&self) -> Result, Error> { - let mut out = self.old_warnings.clone()?; - // add warning for unknown sections - out.extend( - self.provider - .data() - .unwrap_or_default() - .keys() - .filter(|k| { - k != &Config::PROFILE_SECTION && - !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) - }) - .map(|unknown_section| { - let source = self.provider.metadata().source.map(|s| s.to_string()); - Warning::UnknownSection { unknown_section: unknown_section.clone(), source } - }), - ); - // add warning for deprecated keys - out.extend( - self.provider - .data() - .unwrap_or_default() - .iter() - .flat_map(|(profile, dict)| dict.keys().map(move |key| format!("{profile}.{key}"))) - .filter(|k| DEPRECATIONS.contains_key(k)) - .map(|deprecated_key| Warning::DeprecatedKey { - old: deprecated_key.clone(), - new: DEPRECATIONS.get(&deprecated_key).unwrap().to_string(), - }), - ); - Ok(out) - } -} - -impl Provider for WarningsProvider

{ - fn metadata(&self) -> Metadata { - if let Some(source) = self.provider.metadata().source { - Metadata::from("Warnings", source) - } else { - Metadata::named("Warnings") - } - } - fn data(&self) -> Result, Error> { - Ok(Map::from([( - self.profile.clone(), - Dict::from([( - Self::WARNINGS_KEY.to_string(), - Value::serialize(self.collect_warnings()?)?, - )]), - )])) - } - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} - -/// Extracts the profile from the `profile` key and sets unset values according to the fallback -/// provider -pub struct FallbackProfileProvider

{ - provider: P, - profile: Profile, - fallback: Profile, -} - -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() } - } -} - -impl Provider for FallbackProfileProvider

{ - fn metadata(&self) -> Metadata { - self.provider.metadata() - } - - fn data(&self) -> Result, Error> { - if let Some(fallback) = self.provider.data()?.get(&self.fallback) { - let mut inner = self.provider.data()?.remove(&self.profile).unwrap_or_default(); - for (k, v) in fallback.iter() { - if !inner.contains_key(k) { - inner.insert(k.to_owned(), v.clone()); - } - } - Ok(self.profile.collect(inner)) - } else { - self.provider.data() - } - } - - fn profile(&self) -> Option { - Some(self.profile.clone()) - } -} +mod warnings; +pub use warnings::*; diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index f6c84eb1fce4e..c6eb55fe99f43 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -1,55 +1,120 @@ use crate::{foundry_toml_dirs, remappings_from_env_var, remappings_from_newline, Config}; -use ethers_solc::remappings::{RelativeRemapping, Remapping}; use figment::{ value::{Dict, Map}, - Error, Metadata, Profile, Provider, + Error, Figment, Metadata, Profile, Provider, }; +use foundry_compilers::artifacts::remappings::{RelativeRemapping, Remapping}; use std::{ borrow::Cow, collections::{btree_map::Entry, BTreeMap, HashSet}, fs, path::{Path, PathBuf}, }; -use tracing::trace; /// Wrapper types over a `Vec` that only appends unique remappings. -#[derive(Debug, Clone, Default)] +#[derive(Clone, Debug, Default)] pub struct Remappings { /// Remappings. remappings: Vec, + /// Source, test and script configured project dirs. + /// Remappings of these dirs from libs are ignored. + project_paths: Vec, } impl Remappings { /// Create a new `Remappings` wrapper with an empty vector. pub fn new() -> Self { - Self { remappings: Vec::new() } + Self { remappings: Vec::new(), project_paths: Vec::new() } } /// Create a new `Remappings` wrapper with a vector of remappings. pub fn new_with_remappings(remappings: Vec) -> Self { - Self { remappings } + Self { remappings, project_paths: Vec::new() } + } + + /// Extract project paths that cannot be remapped by dependencies. + pub fn with_figment(mut self, figment: &Figment) -> Self { + let mut add_project_remapping = |path: &str| { + if let Ok(path) = figment.find_value(path) { + if let Some(path) = path.into_string() { + let remapping = Remapping { + context: None, + name: format!("{path}/"), + path: format!("{path}/"), + }; + self.project_paths.push(remapping); + } + } + }; + add_project_remapping("src"); + add_project_remapping("test"); + add_project_remapping("script"); + self + } + + /// Filters the remappings vector by name and context. + fn filter_key(r: &Remapping) -> String { + match &r.context { + Some(str) => str.clone() + &r.name.clone(), + None => r.name.clone(), + } } /// Consumes the wrapper and returns the inner remappings vector. pub fn into_inner(self) -> Vec { - let mut tmp = HashSet::new(); + let mut seen = HashSet::new(); let remappings = - self.remappings.iter().filter(|r| tmp.insert(r.name.clone())).cloned().collect(); + self.remappings.iter().filter(|r| seen.insert(Self::filter_key(r))).cloned().collect(); remappings } - /// Push an element ot the remappings vector, but only if it's not already present. - pub fn push(&mut self, remapping: Remapping) { - if !self.remappings.iter().any(|existing| { + /// Push an element to the remappings vector, but only if it's not already present. + fn push(&mut self, remapping: Remapping) { + // Special handling for .sol file remappings, only allow one remapping per source file. + if remapping.name.ends_with(".sol") && !remapping.path.ends_with(".sol") { + return; + } + + if self.remappings.iter().any(|existing| { + if remapping.name.ends_with(".sol") { + // For .sol files, only prevent duplicate source names in the same context + return existing.name == remapping.name && + existing.context == remapping.context && + existing.path == remapping.path + } + // What we're doing here is filtering for ambiguous paths. For example, if we have // @prb/math/=node_modules/@prb/math/src/ as existing, and - // @prb/=node_modules/@prb/ as the one being checked, + // @prb/=node_modules/@prb/ as the one being checked, // we want to keep the already existing one, which is the first one. This way we avoid // having to deal with ambiguous paths which is unwanted when autodetecting remappings. - existing.name.starts_with(&remapping.name) && existing.context == remapping.context + // Remappings are added from root of the project down to libraries, so + // we also want to exclude any conflicting remappings added from libraries. For example, + // if we have `@utils/=src/` added in project remappings and `@utils/libraries/=src/` + // added in a dependency, we don't want to add the new one as it conflicts with project + // existing remapping. + let mut existing_name_path = existing.name.clone(); + if !existing_name_path.ends_with('/') { + existing_name_path.push('/') + } + let is_conflicting = remapping.name.starts_with(&existing_name_path) || + existing.name.starts_with(&remapping.name); + is_conflicting && existing.context == remapping.context }) { - self.remappings.push(remapping) - } + return; + }; + + // Ignore remappings of root project src, test or script dir. + // See . + if self + .project_paths + .iter() + .any(|project_path| remapping.name.eq_ignore_ascii_case(&project_path.name)) + { + return; + }; + + self.remappings.push(remapping); } /// Extend the remappings vector, leaving out the remappings that are already present. @@ -72,7 +137,7 @@ pub struct RemappingsProvider<'a> { pub lib_paths: Cow<'a, Vec>, /// the root path used to turn an absolute `Remapping`, as we're getting it from /// `Remapping::find_many` into a relative one. - pub root: &'a PathBuf, + pub root: &'a Path, /// This contains either: /// - previously set remappings /// - a `MissingField` error, which means previous provider didn't set the "remappings" field @@ -80,7 +145,7 @@ pub struct RemappingsProvider<'a> { pub remappings: Result, Error>, } -impl<'a> RemappingsProvider<'a> { +impl RemappingsProvider<'_> { /// Find and parse remappings for the projects /// /// **Order** @@ -95,6 +160,7 @@ impl<'a> RemappingsProvider<'a> { trace!("get all remappings from {:?}", self.root); /// prioritizes remappings that are closer: shorter `path` /// - ("a", "1/2") over ("a", "1/2/3") + /// /// grouped by remapping context fn insert_closest( mappings: &mut BTreeMap, BTreeMap>, @@ -143,7 +209,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(); @@ -156,14 +222,12 @@ 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()) { - println!("- skipping the remapping"); + trace!(target: "forge", "- skipping the remapping"); continue } insert_closest(&mut lib_remappings, r.context, r.name, r.path.into()); @@ -190,14 +254,15 @@ impl<'a> RemappingsProvider<'a> { fn lib_foundry_toml_remappings(&self) -> impl Iterator + '_ { self.lib_paths .iter() - .map(|p| self.root.join(p)) + .map(|p| if p.is_absolute() { self.root.join("lib") } else { self.root.join(p) }) .flat_map(foundry_toml_dirs) .inspect(|lib| { trace!("find all remappings of nested foundry.toml lib: {:?}", lib); }) .flat_map(|lib: PathBuf| { // load config, of the nested lib if it exists - let config = Config::load_with_root(&lib).sanitized(); + let Ok(config) = Config::load_with_root(&lib) else { return vec![] }; + let config = config.sanitized(); // if the configured _src_ directory is set to something that // [Remapping::find_many()] doesn't classify as a src directory (src, contracts, @@ -234,7 +299,7 @@ impl<'a> RemappingsProvider<'a> { } } -impl<'a> Provider for RemappingsProvider<'a> { +impl Provider for RemappingsProvider<'_> { fn metadata(&self) -> Metadata { Metadata::named("Remapping Provider") } @@ -267,3 +332,132 @@ impl<'a> Provider for RemappingsProvider<'a> { Some(Config::selected_profile()) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sol_file_remappings() { + let mut remappings = Remappings::new(); + + // First valid remapping + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "implementations/Contract1.sol".to_string(), + }); + + // Same source to different target (should be rejected) + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "implementations/Contract2.sol".to_string(), + }); + + // Different source to same target (should be allowed) + remappings.push(Remapping { + context: None, + name: "OtherContract.sol".to_string(), + path: "implementations/Contract1.sol".to_string(), + }); + + // Exact duplicate (should be silently ignored) + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "implementations/Contract1.sol".to_string(), + }); + + // Invalid .sol remapping (target not .sol) + remappings.push(Remapping { + context: None, + name: "Invalid.sol".to_string(), + path: "implementations/Contract1.txt".to_string(), + }); + + let result = remappings.into_inner(); + assert_eq!(result.len(), 2, "Should only have 2 valid remappings"); + + // Verify the correct remappings exist + assert!( + result + .iter() + .any(|r| r.name == "MyContract.sol" && r.path == "implementations/Contract1.sol"), + "Should keep first mapping of MyContract.sol" + ); + assert!( + !result + .iter() + .any(|r| r.name == "MyContract.sol" && r.path == "implementations/Contract2.sol"), + "Should keep first mapping of MyContract.sol" + ); + assert!(result.iter().any(|r| r.name == "OtherContract.sol" && r.path == "implementations/Contract1.sol"), + "Should allow different source to same target"); + + // Verify the rejected remapping doesn't exist + assert!( + !result + .iter() + .any(|r| r.name == "MyContract.sol" && r.path == "implementations/Contract2.sol"), + "Should reject same source to different target" + ); + } + + #[test] + fn test_mixed_remappings() { + let mut remappings = Remappings::new(); + + remappings.push(Remapping { + context: None, + name: "@openzeppelin-contracts/".to_string(), + path: "lib/openzeppelin-contracts/".to_string(), + }); + remappings.push(Remapping { + context: None, + name: "@openzeppelin/contracts/".to_string(), + path: "lib/openzeppelin/contracts/".to_string(), + }); + + remappings.push(Remapping { + context: None, + name: "MyContract.sol".to_string(), + path: "os/Contract.sol".to_string(), + }); + + let result = remappings.into_inner(); + assert_eq!(result.len(), 3, "Should have 3 remappings"); + assert_eq!(result.first().unwrap().name, "@openzeppelin-contracts/"); + assert_eq!(result.first().unwrap().path, "lib/openzeppelin-contracts/"); + assert_eq!(result.get(1).unwrap().name, "@openzeppelin/contracts/"); + assert_eq!(result.get(1).unwrap().path, "lib/openzeppelin/contracts/"); + assert_eq!(result.get(2).unwrap().name, "MyContract.sol"); + assert_eq!(result.get(2).unwrap().path, "os/Contract.sol"); + } + + #[test] + fn test_remappings_with_context() { + let mut remappings = Remappings::new(); + + // Same name but different contexts + remappings.push(Remapping { + context: Some("test/".to_string()), + name: "MyContract.sol".to_string(), + path: "test/Contract.sol".to_string(), + }); + remappings.push(Remapping { + context: Some("prod/".to_string()), + name: "MyContract.sol".to_string(), + path: "prod/Contract.sol".to_string(), + }); + + let result = remappings.into_inner(); + assert_eq!(result.len(), 2, "Should allow same name with different contexts"); + assert!(result + .iter() + .any(|r| r.context == Some("test/".to_string()) && r.path == "test/Contract.sol")); + assert!(result + .iter() + .any(|r| r.context == Some("prod/".to_string()) && r.path == "prod/Contract.sol")); + } +} diff --git a/crates/config/src/providers/warnings.rs b/crates/config/src/providers/warnings.rs new file mode 100644 index 0000000000000..944225be18c2c --- /dev/null +++ b/crates/config/src/providers/warnings.rs @@ -0,0 +1,109 @@ +use crate::{Config, Warning, DEPRECATIONS}; +use figment::{ + value::{Dict, Map, Value}, + Error, Figment, Metadata, Profile, Provider, +}; +use std::collections::BTreeMap; + +/// Generate warnings for unknown sections and deprecated keys +pub struct WarningsProvider

{ + provider: P, + profile: Profile, + old_warnings: Result, Error>, +} + +impl WarningsProvider

{ + const WARNINGS_KEY: &'static str = "__warnings"; + + /// Creates a new warnings provider. + pub fn new( + provider: P, + profile: impl Into, + old_warnings: Result, Error>, + ) -> Self { + Self { provider, profile: profile.into(), old_warnings } + } + + /// Creates a new figment warnings provider. + pub fn for_figment(provider: P, figment: &Figment) -> Self { + let old_warnings = { + let warnings_res = figment.extract_inner(Self::WARNINGS_KEY); + if warnings_res.as_ref().err().map(|err| err.missing()).unwrap_or(false) { + Ok(vec![]) + } else { + warnings_res + } + }; + Self::new(provider, figment.profile().clone(), old_warnings) + } + + /// Collects all warnings. + pub fn collect_warnings(&self) -> Result, Error> { + let data = self.provider.data().unwrap_or_default(); + + let mut out = self.old_warnings.clone()?; + + // Add warning for unknown sections. + out.extend( + data.keys() + .filter(|k| { + **k != Config::PROFILE_SECTION && + !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) + }) + .map(|unknown_section| { + let source = self.provider.metadata().source.map(|s| s.to_string()); + Warning::UnknownSection { unknown_section: unknown_section.clone(), source } + }), + ); + + // Add warning for deprecated keys. + let deprecated_key_warning = |key| { + DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { + if key == *deprecated_key { + Some(Warning::DeprecatedKey { + old: deprecated_key.to_string(), + new: new_value.to_string(), + }) + } else { + None + } + }) + }; + let profiles = data + .iter() + .filter(|(profile, _)| **profile == Config::PROFILE_SECTION) + .map(|(_, dict)| dict); + out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning)); + out.extend( + profiles + .filter_map(|dict| dict.get(self.profile.as_str().as_str())) + .filter_map(Value::as_dict) + .flat_map(BTreeMap::keys) + .filter_map(deprecated_key_warning), + ); + + Ok(out) + } +} + +impl Provider for WarningsProvider

{ + fn metadata(&self) -> Metadata { + if let Some(source) = self.provider.metadata().source { + Metadata::from("Warnings", source) + } else { + Metadata::named("Warnings") + } + } + + fn data(&self) -> Result, Error> { + let warnings = self.collect_warnings()?; + Ok(Map::from([( + self.profile.clone(), + Dict::from([(Self::WARNINGS_KEY.to_string(), Value::serialize(warnings)?)]), + )])) + } + + fn profile(&self) -> Option { + Some(self.profile.clone()) + } +} diff --git a/crates/config/src/resolve.rs b/crates/config/src/resolve.rs index b062558b353d3..20606cb11aa57 100644 --- a/crates/config/src/resolve.rs +++ b/crates/config/src/resolve.rs @@ -1,15 +1,14 @@ //! Helper for resolving env vars -use once_cell::sync::Lazy; use regex::Regex; -use std::{env, env::VarError, fmt}; +use std::{env, env::VarError, fmt, sync::LazyLock}; /// A regex that matches `${val}` placeholders -pub static RE_PLACEHOLDER: Lazy = - Lazy::new(|| Regex::new(r"(?m)(?P\$\{\s*(?P.*?)\s*})").unwrap()); +pub static RE_PLACEHOLDER: LazyLock = + LazyLock::new(|| Regex::new(r"(?m)(?P\$\{\s*(?P.*?)\s*})").unwrap()); /// Error when we failed to resolve an env var -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct UnresolvedEnvVarError { /// The unresolved input string pub unresolved: String, @@ -19,22 +18,28 @@ pub struct UnresolvedEnvVarError { pub source: VarError, } -// === impl UnresolvedEnvVarError === - impl UnresolvedEnvVarError { /// Tries to resolve a value - pub fn try_resolve(&self) -> Result { + pub fn try_resolve(&self) -> Result { interpolate(&self.unresolved) } + + fn is_simple(&self) -> bool { + RE_PLACEHOLDER.captures_iter(&self.unresolved).count() <= 1 + } } impl fmt::Display for UnresolvedEnvVarError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Failed to resolve env var `{}` in `{}`: {}", - self.var, self.unresolved, self.source - ) + write!(f, "environment variable `{}` ", self.var)?; + f.write_str(match self.source { + VarError::NotPresent => "not found", + VarError::NotUnicode(_) => "is not valid unicode", + })?; + if !self.is_simple() { + write!(f, " in `{}`", self.unresolved)?; + } + Ok(()) } } diff --git a/crates/config/src/soldeer.rs b/crates/config/src/soldeer.rs new file mode 100644 index 0000000000000..75ee96c2c0fee --- /dev/null +++ b/crates/config/src/soldeer.rs @@ -0,0 +1,94 @@ +//! Configuration specific to the `forge soldeer` command and the `forge_soldeer` package + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +/// Soldeer dependencies config structure when it's defined as a map +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +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, Eq, Serialize, Deserialize)] +pub struct SoldeerDependencyConfig(BTreeMap); + +impl AsRef for SoldeerDependencyConfig { + 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), +} + +/// Location where to store the remappings, either in `remappings.txt` or in the `foundry.toml` +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Default)] +#[serde(rename_all = "lowercase")] +pub enum RemappingsLocation { + #[default] + Txt, + Config, +} + +fn default_true() -> bool { + true +} + +/// Type for Soldeer configs, under soldeer tag in the foundry.toml +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct SoldeerConfig { + #[serde(default = "default_true")] + pub remappings_generate: bool, + + #[serde(default)] + pub remappings_regenerate: bool, + + #[serde(default = "default_true")] + pub remappings_version: bool, + + #[serde(default)] + pub remappings_prefix: String, + + #[serde(default)] + pub remappings_location: RemappingsLocation, + + #[serde(default)] + pub recursive_deps: bool, +} + +impl AsRef for SoldeerConfig { + fn as_ref(&self) -> &Self { + self + } +} +impl Default for SoldeerConfig { + fn default() -> Self { + Self { + remappings_generate: true, + remappings_regenerate: false, + remappings_version: true, + remappings_prefix: String::new(), + remappings_location: Default::default(), + recursive_deps: false, + } + } +} diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index d71419481bf25..94f4823dc7cb5 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -1,47 +1,47 @@ //! Utility functions use crate::Config; -use ethers_core::types::{serde_helpers::Numeric, U256}; -use ethers_solc::remappings::{Remapping, RemappingError}; +use alloy_primitives::U256; use figment::value::Value; +use foundry_compilers::artifacts::{ + remappings::{Remapping, RemappingError}, + EvmVersion, +}; +use revm_primitives::SpecId; use serde::{de::Error, Deserialize, Deserializer}; use std::{ + io, path::{Path, PathBuf}, str::FromStr, }; -use toml_edit::{Document, Item}; -/// Loads the config for the current project workspace -pub fn load_config() -> Config { +// TODO: Why do these exist separately from `Config::load`? + +/// Loads the config for the current project workspace. +pub fn load_config() -> eyre::Result { load_config_with_root(None) } -/// Loads the config for the current project workspace or the provided root path -pub fn load_config_with_root(root: Option) -> Config { - if let Some(root) = root { - Config::load_with_root(root) - } else { - Config::load_with_root(find_project_root_path(None).unwrap()) - } - .sanitized() +/// Loads the config for the current project workspace or the provided root path. +pub fn load_config_with_root(root: Option<&Path>) -> eyre::Result { + let root = match root { + Some(root) => root, + None => &find_project_root(None)?, + }; + Ok(Config::load_with_root(root)?.sanitized()) } -/// Returns the path of the top-level directory of the working git tree. If there is no working -/// tree, an error is returned. -pub fn find_git_root_path(relative_to: impl AsRef) -> eyre::Result { - let path = std::process::Command::new("git") - .args(["rev-parse", "--show-toplevel"]) - .current_dir(relative_to.as_ref()) - .output()? - .stdout; - let path = std::str::from_utf8(&path)?.trim_end_matches('\n'); - Ok(PathBuf::from(path)) +/// Returns the path of the top-level directory of the working git tree. +pub fn find_git_root(relative_to: &Path) -> io::Result> { + let root = + if relative_to.is_absolute() { relative_to } else { &dunce::canonicalize(relative_to)? }; + Ok(root.ancestors().find(|p| p.join(".git").is_dir()).map(Path::to_path_buf)) } -/// Returns the root path to set for the project root +/// Returns the root path to set for the project root. /// -/// traverse the dir tree up and look for a `foundry.toml` file starting at the given path or cwd, -/// but only until the root dir of the current repo so that +/// Traverse the dir tree up and look for a `foundry.toml` file starting at the given path or cwd, +/// but only until the root dir of the current repo so that: /// /// ```text /// -- foundry.toml @@ -51,29 +51,27 @@ pub fn find_git_root_path(relative_to: impl AsRef) -> eyre::Result) -> std::io::Result { - let cwd = &std::env::current_dir()?; - let cwd = path.unwrap_or(cwd); - let boundary = find_git_root_path(cwd) - .ok() - .filter(|p| !p.as_os_str().is_empty()) - .unwrap_or_else(|| cwd.clone()); - let mut cwd = cwd.as_path(); - // traverse as long as we're in the current git repo cwd - while cwd.starts_with(&boundary) { - let file_path = cwd.join(Config::FILE_NAME); - if file_path.is_file() { - return Ok(cwd.to_path_buf()) - } - if let Some(parent) = cwd.parent() { - cwd = parent; - } else { - break - } - } - // no foundry.toml found - Ok(boundary) +/// +/// will still detect `repo` as root. +/// +/// Returns `repo` or `cwd` if no `foundry.toml` is found in the tree. +/// +/// Returns an error if: +/// - `cwd` is `Some` and is not a valid directory; +/// - `cwd` is `None` and the [`std::env::current_dir`] call fails. +pub fn find_project_root(cwd: Option<&Path>) -> io::Result { + let cwd = match cwd { + Some(path) => path, + None => &std::env::current_dir()?, + }; + let boundary = find_git_root(cwd)?; + let found = cwd + .ancestors() + // Don't look outside of the git repo if it exists. + .take_while(|p| if let Some(boundary) = &boundary { p.starts_with(boundary) } else { true }) + .find(|p| p.join(Config::FILE_NAME).is_file()) + .map(Path::to_path_buf); + Ok(found.or(boundary).unwrap_or_else(|| cwd.to_path_buf())) } /// Returns all [`Remapping`]s contained in the `remappings` str separated by newlines @@ -151,7 +149,7 @@ pub fn foundry_toml_dirs(root: impl AsRef) -> Vec { .into_iter() .filter_map(Result::ok) .filter(|e| e.file_type().is_dir()) - .filter_map(|e| ethers_solc::utils::canonicalize(e.path()).ok()) + .filter_map(|e| dunce::canonicalize(e.path()).ok()) .filter(|p| p.join(Config::FILE_NAME).exists()) .collect() } @@ -174,52 +172,12 @@ pub(crate) fn get_dir_remapping(dir: impl AsRef) -> Option { } } -/// Returns all available `profile` keys in a given `.toml` file -/// -/// i.e. The toml below would return would return `["default", "ci", "local"]` -/// ```toml -/// [profile.default] -/// ... -/// [profile.ci] -/// ... -/// [profile.local] -/// ``` -pub fn get_available_profiles(toml_path: impl AsRef) -> eyre::Result> { - let mut result = vec![Config::DEFAULT_PROFILE.to_string()]; - - if !toml_path.as_ref().exists() { - return Ok(result) - } - - let doc = read_toml(toml_path)?; - - if let Some(Item::Table(profiles)) = doc.as_table().get(Config::PROFILE_SECTION) { - for (_, (profile, _)) in profiles.iter().enumerate() { - let p = profile.to_string(); - if !result.contains(&p) { - result.push(p); - } - } - } - - Ok(result) -} - -/// Returns a [`toml_edit::Document`] loaded from the provided `path`. -/// Can raise an error in case of I/O or parsing errors. -fn read_toml(path: impl AsRef) -> eyre::Result { - let path = path.as_ref().to_owned(); - let doc: Document = std::fs::read_to_string(path)?.parse()?; - Ok(doc) -} - /// Deserialize stringified percent. The value must be between 0 and 100 inclusive. pub(crate) fn deserialize_stringified_percent<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { - let num: U256 = - Numeric::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom)?; + let num: U256 = Numeric::deserialize(deserializer)?.into(); let num: u64 = num.try_into().map_err(serde::de::Error::custom)?; if num <= 100 { num.try_into().map_err(serde::de::Error::custom) @@ -228,67 +186,83 @@ 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), + } } -#[cfg(test)] -mod tests { - use crate::get_available_profiles; - use std::path::Path; - - #[test] - fn get_profiles_from_toml() { - figment::Jail::expect_with(|jail| { - jail.create_file( - "foundry.toml", - r#" - [foo.baz] - libs = ['node_modules', 'lib'] - - [profile.default] - libs = ['node_modules', 'lib'] +/// 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) +} - [profile.ci] - libs = ['node_modules', 'lib'] +/// Helper type to parse both `u64` and `U256` +#[derive(Clone, Copy, Deserialize)] +#[serde(untagged)] +pub enum Numeric { + /// A [U256] value. + U256(U256), + /// A `u64` value. + Num(u64), +} - [profile.local] - libs = ['node_modules', 'lib'] - "#, - )?; +impl From for U256 { + fn from(n: Numeric) -> Self { + match n { + Numeric::U256(n) => n, + Numeric::Num(n) => Self::from(n), + } + } +} - let path = Path::new("./foundry.toml"); - let profiles = get_available_profiles(path).unwrap(); +impl FromStr for Numeric { + type Err = String; - assert_eq!( - profiles, - vec!["default".to_string(), "ci".to_string(), "local".to_string()] - ); + fn from_str(s: &str) -> Result { + if s.starts_with("0x") { + U256::from_str_radix(s, 16).map(Numeric::U256).map_err(|err| err.to_string()) + } else { + U256::from_str(s).map(Numeric::U256).map_err(|err| err.to_string()) + } + } +} - Ok(()) - }); +/// Returns the [SpecId] derived from [EvmVersion] +#[inline] +pub fn evm_spec_id(evm_version: EvmVersion, odyssey: bool) -> SpecId { + if odyssey { + return SpecId::OSAKA; + } + match evm_version { + EvmVersion::Homestead => SpecId::HOMESTEAD, + EvmVersion::TangerineWhistle => SpecId::TANGERINE, + EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON, + EvmVersion::Byzantium => SpecId::BYZANTIUM, + EvmVersion::Constantinople => SpecId::CONSTANTINOPLE, + EvmVersion::Petersburg => SpecId::PETERSBURG, + EvmVersion::Istanbul => SpecId::ISTANBUL, + EvmVersion::Berlin => SpecId::BERLIN, + EvmVersion::London => SpecId::LONDON, + EvmVersion::Paris => SpecId::MERGE, + EvmVersion::Shanghai => SpecId::SHANGHAI, + EvmVersion::Cancun => SpecId::CANCUN, + EvmVersion::Prague => SpecId::OSAKA, // Osaka enables EOF } } diff --git a/crates/config/src/vyper.rs b/crates/config/src/vyper.rs new file mode 100644 index 0000000000000..dbd47faec208d --- /dev/null +++ b/crates/config/src/vyper.rs @@ -0,0 +1,18 @@ +//! Vyper specific configuration types. + +use foundry_compilers::artifacts::vyper::VyperOptimizationMode; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[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")] + pub optimize: Option, + /// The Vyper instance to use if any. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, + /// Optionally enables experimental Venom pipeline + #[serde(default, skip_serializing_if = "Option::is_none")] + pub experimental_codegen: Option, +} diff --git a/crates/config/src/warning.rs b/crates/config/src/warning.rs index fc98be3d4fa25..a19104eaf7b4d 100644 --- a/crates/config/src/warning.rs +++ b/crates/config/src/warning.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use std::{fmt, path::PathBuf}; /// Warnings emitted during loading or managing Configuration -#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Warning { /// An unknown section was encountered in a TOML file @@ -53,30 +53,37 @@ impl fmt::Display for Warning { match self { Self::UnknownSection { unknown_section, source } => { let source = source.as_ref().map(|src| format!(" in {src}")).unwrap_or_default(); - f.write_fmt(format_args!("Unknown section [{unknown_section}] found{source}. This notation for profiles has been deprecated and may result in the profile not being registered in future versions. Please use [profile.{unknown_section}] instead or run `forge config --fix`.")) - } - Self::NoLocalToml(tried) => { - let path = tried.display(); - f.write_fmt(format_args!("No local TOML found to fix at {path}. Change the current directory to a project path or set the foundry.toml path with the FOUNDRY_CONFIG environment variable")) + write!( + f, + "Found unknown config section{source}: [{unknown_section}]\n\ + This notation for profiles has been deprecated and may result in the profile \ + not being registered in future versions.\n\ + Please use [profile.{unknown_section}] instead or run `forge config --fix`." + ) } + Self::NoLocalToml(path) => write!( + f, + "No local TOML found to fix at {}.\n\ + Change the current directory to a project path or set the foundry.toml path with \ + the `FOUNDRY_CONFIG` environment variable", + path.display() + ), + Self::CouldNotReadToml { path, err } => { - f.write_fmt(format_args!("Could not read TOML at {}: {err}", path.display())) + write!(f, "Could not read TOML at {}: {err}", path.display()) } Self::CouldNotWriteToml { path, err } => { - f.write_fmt(format_args!("Could not write TOML to {}: {err}", path.display())) + write!(f, "Could not write TOML to {}: {err}", path.display()) + } + Self::CouldNotFixProfile { path, profile, err } => { + write!(f, "Could not fix [{profile}] in TOML at {}: {err}", path.display()) + } + Self::DeprecatedKey { old, new } if new.is_empty() => { + write!(f, "Key `{old}` is being deprecated and will be removed in future versions.") + } + Self::DeprecatedKey { old, new } => { + write!(f, "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.") } - Self::CouldNotFixProfile { path, profile, err } => f.write_fmt(format_args!( - "Could not fix [{}] in TOML at {}: {}", - profile, - path.display(), - err - )), - Self::DeprecatedKey { old, new } if new.is_empty() => f.write_fmt(format_args!( - "Key `{old}` is being deprecated and will be removed in future versions.", - )), - Self::DeprecatedKey { old, new } => f.write_fmt(format_args!( - "Key `{old}` is being deprecated in favor of `{new}`. It will be removed in future versions.", - )), } } } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml new file mode 100644 index 0000000000000..4cf86e20a5045 --- /dev/null +++ b/crates/debugger/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "foundry-debugger" + +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.workspace = true +foundry-compilers.workspace = true +foundry-evm-traces.workspace = true +foundry-evm-core.workspace = true +revm-inspectors.workspace = true + +alloy-primitives.workspace = true + +crossterm = "0.28" +eyre.workspace = true +ratatui = { version = "0.29", default-features = false, features = [ + "crossterm", +] } +revm.workspace = true +tracing.workspace = true +serde.workspace = true diff --git a/crates/debugger/src/builder.rs b/crates/debugger/src/builder.rs new file mode 100644 index 0000000000000..fd2dce3dfe099 --- /dev/null +++ b/crates/debugger/src/builder.rs @@ -0,0 +1,90 @@ +//! Debugger builder. + +use crate::{node::flatten_call_trace, DebugNode, Debugger}; +use alloy_primitives::{map::AddressHashMap, Address}; +use foundry_common::{evm::Breakpoints, get_contract_name}; +use foundry_evm_traces::{debug::ContractSources, CallTraceArena, CallTraceDecoder, Traces}; +/// Debugger builder. +#[derive(Debug, Default)] +#[must_use = "builders do nothing unless you call `build` on them"] +pub struct DebuggerBuilder { + /// Debug traces returned from the EVM execution. + debug_arena: Vec, + /// Identified contracts. + identified_contracts: AddressHashMap, + /// Map of source files. + sources: ContractSources, + /// Map of the debugger breakpoints. + breakpoints: Breakpoints, +} + +impl DebuggerBuilder { + /// Creates a new debugger builder. + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Extends the debug arena. + #[inline] + pub fn traces(mut self, traces: Traces) -> Self { + for (_, arena) in traces { + self = self.trace_arena(arena.arena); + } + self + } + + /// Extends the debug arena. + #[inline] + pub fn trace_arena(mut self, arena: CallTraceArena) -> Self { + flatten_call_trace(arena, &mut self.debug_arena); + self + } + + /// Extends the identified contracts from multiple decoders. + #[inline] + pub fn decoders(mut self, decoders: &[CallTraceDecoder]) -> Self { + for decoder in decoders { + self = self.decoder(decoder); + } + self + } + + /// Extends the identified contracts from a decoder. + #[inline] + pub fn decoder(self, decoder: &CallTraceDecoder) -> Self { + let c = decoder.contracts.iter().map(|(k, v)| (*k, get_contract_name(v).to_string())); + self.identified_contracts(c) + } + + /// Extends the identified contracts. + #[inline] + pub fn identified_contracts( + mut self, + identified_contracts: impl IntoIterator, + ) -> Self { + self.identified_contracts.extend(identified_contracts); + self + } + + /// Sets the sources for the debugger. + #[inline] + pub fn sources(mut self, sources: ContractSources) -> Self { + self.sources = sources; + self + } + + /// Sets the breakpoints for the debugger. + #[inline] + pub fn breakpoints(mut self, breakpoints: Breakpoints) -> Self { + self.breakpoints = breakpoints; + self + } + + /// Builds the debugger. + #[inline] + pub fn build(self) -> Debugger { + let Self { debug_arena, identified_contracts, sources, breakpoints } = self; + Debugger::new(debug_arena, identified_contracts, sources, breakpoints) + } +} diff --git a/crates/debugger/src/debugger.rs b/crates/debugger/src/debugger.rs new file mode 100644 index 0000000000000..907232cad7e98 --- /dev/null +++ b/crates/debugger/src/debugger.rs @@ -0,0 +1,71 @@ +//! Debugger implementation. + +use crate::{tui::TUI, DebugNode, DebuggerBuilder, ExitReason}; +use alloy_primitives::map::AddressHashMap; +use eyre::Result; +use foundry_common::evm::Breakpoints; +use foundry_evm_traces::debug::ContractSources; +use std::path::Path; + +pub struct DebuggerContext { + pub debug_arena: Vec, + pub identified_contracts: AddressHashMap, + /// Source map of contract sources + pub contracts_sources: ContractSources, + pub breakpoints: Breakpoints, +} + +pub struct Debugger { + context: DebuggerContext, +} + +impl Debugger { + /// Creates a new debugger builder. + #[inline] + pub fn builder() -> DebuggerBuilder { + DebuggerBuilder::new() + } + + /// Creates a new debugger. + pub fn new( + debug_arena: Vec, + identified_contracts: AddressHashMap, + contracts_sources: ContractSources, + breakpoints: Breakpoints, + ) -> Self { + Self { + context: DebuggerContext { + debug_arena, + identified_contracts, + contracts_sources, + breakpoints, + }, + } + } + + /// Starts the debugger TUI. Terminates the current process on failure or user exit. + pub fn run_tui_exit(mut self) -> ! { + let code = match self.try_run_tui() { + Ok(ExitReason::CharExit) => 0, + Err(e) => { + let _ = sh_eprintln!("{e}"); + 1 + } + }; + std::process::exit(code) + } + + /// Starts the debugger TUI. + pub fn try_run_tui(&mut self) -> Result { + eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty"); + + let mut tui = TUI::new(&mut self.context); + tui.try_run() + } + + /// Dumps debugger data to file. + pub fn dump_to_file(&mut self, path: &Path) -> Result<()> { + eyre::ensure!(!self.context.debug_arena.is_empty(), "debug arena is empty"); + crate::dump::dump(path, &self.context) + } +} diff --git a/crates/debugger/src/dump.rs b/crates/debugger/src/dump.rs new file mode 100644 index 0000000000000..83af7b0e777f7 --- /dev/null +++ b/crates/debugger/src/dump.rs @@ -0,0 +1,148 @@ +use crate::{debugger::DebuggerContext, DebugNode}; +use alloy_primitives::map::AddressMap; +use foundry_common::fs::write_json_file; +use foundry_compilers::{ + artifacts::sourcemap::{Jump, SourceElement}, + multi::MultiCompilerLanguage, +}; +use foundry_evm_core::utils::PcIcMap; +use foundry_evm_traces::debug::{ArtifactData, ContractSources, SourceData}; +use serde::Serialize; +use std::{collections::HashMap, path::Path}; + +/// Dumps debugger data to a JSON file. +pub(crate) fn dump(path: &Path, context: &DebuggerContext) -> eyre::Result<()> { + write_json_file(path, &DebuggerDump::new(context))?; + Ok(()) +} + +/// Holds info of debugger dump. +#[derive(Serialize)] +struct DebuggerDump<'a> { + contracts: ContractsDump<'a>, + debug_arena: &'a [DebugNode], +} + +impl<'a> DebuggerDump<'a> { + fn new(debugger_context: &'a DebuggerContext) -> Self { + Self { + contracts: ContractsDump::new(debugger_context), + debug_arena: &debugger_context.debug_arena, + } + } +} + +#[derive(Serialize)] +struct SourceElementDump { + offset: u32, + length: u32, + index: i32, + jump: u32, + modifier_depth: u32, +} + +impl SourceElementDump { + fn new(v: &SourceElement) -> Self { + Self { + offset: v.offset(), + length: v.length(), + index: v.index_i32(), + jump: match v.jump() { + Jump::In => 0, + Jump::Out => 1, + Jump::Regular => 2, + }, + modifier_depth: v.modifier_depth(), + } + } +} + +#[derive(Serialize)] +struct ContractsDump<'a> { + identified_contracts: &'a AddressMap, + sources: ContractsSourcesDump<'a>, +} + +impl<'a> ContractsDump<'a> { + fn new(debugger_context: &'a DebuggerContext) -> Self { + Self { + identified_contracts: &debugger_context.identified_contracts, + sources: ContractsSourcesDump::new(&debugger_context.contracts_sources), + } + } +} + +#[derive(Serialize)] +struct ContractsSourcesDump<'a> { + sources_by_id: HashMap<&'a str, HashMap>>, + artifacts_by_name: HashMap<&'a str, Vec>>, +} + +impl<'a> ContractsSourcesDump<'a> { + fn new(contracts_sources: &'a ContractSources) -> Self { + Self { + sources_by_id: contracts_sources + .sources_by_id + .iter() + .map(|(name, inner_map)| { + ( + name.as_str(), + inner_map + .iter() + .map(|(id, source_data)| (*id, SourceDataDump::new(source_data))) + .collect(), + ) + }) + .collect(), + artifacts_by_name: contracts_sources + .artifacts_by_name + .iter() + .map(|(name, data)| { + (name.as_str(), data.iter().map(ArtifactDataDump::new).collect()) + }) + .collect(), + } + } +} + +#[derive(Serialize)] +struct SourceDataDump<'a> { + source: &'a str, + language: MultiCompilerLanguage, + path: &'a Path, +} + +impl<'a> SourceDataDump<'a> { + fn new(v: &'a SourceData) -> Self { + Self { source: &v.source, language: v.language, path: &v.path } + } +} + +#[derive(Serialize)] +struct ArtifactDataDump<'a> { + source_map: Option>, + source_map_runtime: Option>, + pc_ic_map: Option<&'a PcIcMap>, + pc_ic_map_runtime: Option<&'a PcIcMap>, + build_id: &'a str, + file_id: u32, +} + +impl<'a> ArtifactDataDump<'a> { + fn new(v: &'a ArtifactData) -> Self { + Self { + source_map: v + .source_map + .as_ref() + .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), + source_map_runtime: v + .source_map_runtime + .as_ref() + .map(|source_map| source_map.iter().map(SourceElementDump::new).collect()), + pc_ic_map: v.pc_ic_map.as_ref(), + pc_ic_map_runtime: v.pc_ic_map_runtime.as_ref(), + build_id: &v.build_id, + file_id: v.file_id, + } + } +} diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs new file mode 100644 index 0000000000000..1c1bf9614ee2e --- /dev/null +++ b/crates/debugger/src/lib.rs @@ -0,0 +1,27 @@ +//! # foundry-debugger +//! +//! Interactive Solidity TUI debugger and debugger data file dumper + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; + +mod op; + +mod builder; +mod debugger; +mod dump; +mod tui; + +mod node; + +pub use node::DebugNode; + +pub use builder::DebuggerBuilder; +pub use debugger::Debugger; +pub use tui::{ExitReason, TUI}; diff --git a/crates/debugger/src/node.rs b/crates/debugger/src/node.rs new file mode 100644 index 0000000000000..83477f006c8c0 --- /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 new file mode 100644 index 0000000000000..bc8e96ccb3997 --- /dev/null +++ b/crates/debugger/src/op.rs @@ -0,0 +1,346 @@ +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 { + /// The name of the parameter. + pub(crate) name: &'static str, + /// The index of the parameter on the stack. This is relative to the top of the stack. + pub(crate) index: usize, +} + +impl OpcodeParam { + /// Returns the list of named parameters for the given opcode, accounts for special opcodes + /// requiring immediate bytes to determine stack items. + #[inline] + pub(crate) fn of(op: u8, immediate: Option<&Bytes>) -> Option> { + match op { + // Handle special cases requiring immediate bytes + opcode::DUPN => immediate + .and_then(|i| i.first().copied()) + .map(|i| vec![Self { name: "dup_value", index: i as usize }]), + opcode::SWAPN => immediate.and_then(|i| { + i.first().map(|i| { + vec![ + Self { name: "a", index: 1 }, + Self { name: "swap_value", index: *i as usize }, + ] + }) + }), + opcode::EXCHANGE => immediate.and_then(|i| { + i.first().map(|imm| { + let n = (imm >> 4) + 1; + let m = (imm & 0xf) + 1; + vec![ + Self { name: "value1", index: n as usize }, + Self { name: "value2", index: m as usize }, + ] + }) + }), + _ => Some(MAP[op as usize].to_vec()), + } + } +} + +static MAP: [&[OpcodeParam]; 256] = { + let mut table = [[].as_slice(); 256]; + let mut i = 0; + while i < 256 { + table[i] = map_opcode(i as u8); + i += 1; + } + table +}; + +const fn map_opcode(op: u8) -> &'static [OpcodeParam] { + macro_rules! map { + ($($op:literal($($idx:literal : $name:literal),* $(,)?)),* $(,)?) => { + match op { + $($op => &[ + $(OpcodeParam { + name: $name, + index: $idx, + }),* + ]),* + } + }; + } + + // https://www.evm.codes + // https://github.com/smlxl/evm.codes + // https://github.com/klkvr/evm.codes + // https://github.com/klkvr/evm.codes/blob/HEAD/opcodes.json + // jq -rf opcodes.jq opcodes.json + /* + def mkargs(input): + input | split(" | ") | to_entries | map("\(.key): \"\(.value)\"") | join(", "); + + to_entries[] | "0x\(.key)(\(mkargs(.value.input)))," + */ + map! { + 0x00(), + 0x01(0: "a", 1: "b"), + 0x02(0: "a", 1: "b"), + 0x03(0: "a", 1: "b"), + 0x04(0: "a", 1: "b"), + 0x05(0: "a", 1: "b"), + 0x06(0: "a", 1: "b"), + 0x07(0: "a", 1: "b"), + 0x08(0: "a", 1: "b", 2: "N"), + 0x09(0: "a", 1: "b", 2: "N"), + 0x0a(0: "a", 1: "exponent"), + 0x0b(0: "b", 1: "x"), + 0x0c(), + 0x0d(), + 0x0e(), + 0x0f(), + 0x10(0: "a", 1: "b"), + 0x11(0: "a", 1: "b"), + 0x12(0: "a", 1: "b"), + 0x13(0: "a", 1: "b"), + 0x14(0: "a", 1: "b"), + 0x15(0: "a"), + 0x16(0: "a", 1: "b"), + 0x17(0: "a", 1: "b"), + 0x18(0: "a", 1: "b"), + 0x19(0: "a"), + 0x1a(0: "i", 1: "x"), + 0x1b(0: "shift", 1: "value"), + 0x1c(0: "shift", 1: "value"), + 0x1d(0: "shift", 1: "value"), + 0x1e(), + 0x1f(), + 0x20(0: "offset", 1: "size"), + 0x21(), + 0x22(), + 0x23(), + 0x24(), + 0x25(), + 0x26(), + 0x27(), + 0x28(), + 0x29(), + 0x2a(), + 0x2b(), + 0x2c(), + 0x2d(), + 0x2e(), + 0x2f(), + 0x30(), + 0x31(0: "address"), + 0x32(), + 0x33(), + 0x34(), + 0x35(0: "i"), + 0x36(), + 0x37(0: "destOffset", 1: "offset", 2: "size"), + 0x38(), + 0x39(0: "destOffset", 1: "offset", 2: "size"), + 0x3a(), + 0x3b(0: "address"), + 0x3c(0: "address", 1: "destOffset", 2: "offset", 3: "size"), + 0x3d(), + 0x3e(0: "destOffset", 1: "offset", 2: "size"), + 0x3f(0: "address"), + 0x40(0: "blockNumber"), + 0x41(), + 0x42(), + 0x43(), + 0x44(), + 0x45(), + 0x46(), + 0x47(), + 0x48(), + 0x49(), + 0x4a(), + 0x4b(), + 0x4c(), + 0x4d(), + 0x4e(), + 0x4f(), + 0x50(0: "y"), + 0x51(0: "offset"), + 0x52(0: "offset", 1: "value"), + 0x53(0: "offset", 1: "value"), + 0x54(0: "key"), + 0x55(0: "key", 1: "value"), + 0x56(0: "counter"), + 0x57(0: "counter", 1: "b"), + 0x58(), + 0x59(), + 0x5a(), + 0x5b(), + 0x5c(), + 0x5d(), + 0x5e(), + + // PUSHN + 0x5f(), + 0x60(), + 0x61(), + 0x62(), + 0x63(), + 0x64(), + 0x65(), + 0x66(), + 0x67(), + 0x68(), + 0x69(), + 0x6a(), + 0x6b(), + 0x6c(), + 0x6d(), + 0x6e(), + 0x6f(), + 0x70(), + 0x71(), + 0x72(), + 0x73(), + 0x74(), + 0x75(), + 0x76(), + 0x77(), + 0x78(), + 0x79(), + 0x7a(), + 0x7b(), + 0x7c(), + 0x7d(), + 0x7e(), + 0x7f(), + + // DUPN + 0x80(0x00: "dup_value"), + 0x81(0x01: "dup_value"), + 0x82(0x02: "dup_value"), + 0x83(0x03: "dup_value"), + 0x84(0x04: "dup_value"), + 0x85(0x05: "dup_value"), + 0x86(0x06: "dup_value"), + 0x87(0x07: "dup_value"), + 0x88(0x08: "dup_value"), + 0x89(0x09: "dup_value"), + 0x8a(0x0a: "dup_value"), + 0x8b(0x0b: "dup_value"), + 0x8c(0x0c: "dup_value"), + 0x8d(0x0d: "dup_value"), + 0x8e(0x0e: "dup_value"), + 0x8f(0x0f: "dup_value"), + + // SWAPN + 0x90(0: "a", 0x01: "swap_value"), + 0x91(0: "a", 0x02: "swap_value"), + 0x92(0: "a", 0x03: "swap_value"), + 0x93(0: "a", 0x04: "swap_value"), + 0x94(0: "a", 0x05: "swap_value"), + 0x95(0: "a", 0x06: "swap_value"), + 0x96(0: "a", 0x07: "swap_value"), + 0x97(0: "a", 0x08: "swap_value"), + 0x98(0: "a", 0x09: "swap_value"), + 0x99(0: "a", 0x0a: "swap_value"), + 0x9a(0: "a", 0x0b: "swap_value"), + 0x9b(0: "a", 0x0c: "swap_value"), + 0x9c(0: "a", 0x0d: "swap_value"), + 0x9d(0: "a", 0x0e: "swap_value"), + 0x9e(0: "a", 0x0f: "swap_value"), + 0x9f(0: "a", 0x10: "swap_value"), + + 0xa0(0: "offset", 1: "size"), + 0xa1(0: "offset", 1: "size", 2: "topic"), + 0xa2(0: "offset", 1: "size", 2: "topic1", 3: "topic2"), + 0xa3(0: "offset", 1: "size", 2: "topic1", 3: "topic2", 4: "topic3"), + 0xa4(0: "offset", 1: "size", 2: "topic1", 3: "topic2", 4: "topic3", 5: "topic4"), + 0xa5(), + 0xa6(), + 0xa7(), + 0xa8(), + 0xa9(), + 0xaa(), + 0xab(), + 0xac(), + 0xad(), + 0xae(), + 0xaf(), + 0xb0(), + 0xb1(), + 0xb2(), + 0xb3(), + 0xb4(), + 0xb5(), + 0xb6(), + 0xb7(), + 0xb8(), + 0xb9(), + 0xba(), + 0xbb(), + 0xbc(), + 0xbd(), + 0xbe(), + 0xbf(), + 0xc0(), + 0xc1(), + 0xc2(), + 0xc3(), + 0xc4(), + 0xc5(), + 0xc6(), + 0xc7(), + 0xc8(), + 0xc9(), + 0xca(), + 0xcb(), + 0xcc(), + 0xcd(), + 0xce(), + 0xcf(), + 0xd0(0: "offset"), + 0xd1(), + 0xd2(), + 0xd3(0: "memOffset", 1: "offset", 2: "size"), + 0xd4(), + 0xd5(), + 0xd6(), + 0xd7(), + 0xd8(), + 0xd9(), + 0xda(), + 0xdb(), + 0xdc(), + 0xdd(), + 0xde(), + 0xdf(), + 0xe0(), + 0xe1(0: "condition"), + 0xe2(0: "case"), + 0xe3(), + 0xe4(), + 0xe5(), + 0xe6(), + 0xe7(), + 0xe8(), + 0xe9(), + 0xea(), + 0xeb(), + 0xec(0: "value", 1: "salt", 2: "offset", 3: "size"), + 0xed(), + 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"), + 0xf2(0: "gas", 1: "address", 2: "value", 3: "argsOffset", 4: "argsSize", 5: "retOffset", 6: "retSize"), + 0xf3(0: "offset", 1: "size"), + 0xf4(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), + 0xf5(0: "value", 1: "offset", 2: "size", 3: "salt"), + 0xf6(), + 0xf7(0: "offset"), + 0xf8(0: "address", 1: "argsOffset", 2: "argsSize", 3: "value"), + 0xf9(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xfa(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), + 0xfb(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xfc(), + 0xfd(0: "offset", 1: "size"), + 0xfe(), + 0xff(0: "address"), + } +} diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs new file mode 100644 index 0000000000000..0c61a1fcd41c4 --- /dev/null +++ b/crates/debugger/src/tui/context.rs @@ -0,0 +1,354 @@ +//! Debugger context and event handler implementation. + +use crate::{debugger::DebuggerContext, DebugNode, ExitReason}; +use alloy_primitives::{hex, Address}; +use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; +use foundry_evm_core::buffer::BufferKind; +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. +#[derive(Default)] +pub(crate) struct DrawMemory { + pub(crate) inner_call_index: usize, + pub(crate) current_buf_startline: usize, + pub(crate) current_stack_startline: usize, +} + +pub(crate) struct TUIContext<'a> { + pub(crate) debugger_context: &'a mut DebuggerContext, + + /// Buffer for keys prior to execution, i.e. '10' + 'k' => move up 10 operations. + pub(crate) key_buffer: String, + /// Current step in the debug steps. + pub(crate) current_step: usize, + pub(crate) draw_memory: DrawMemory, + pub(crate) opcode_list: Vec, + pub(crate) last_index: usize, + + pub(crate) stack_labels: bool, + /// Whether to decode active buffer as utf8 or not. + pub(crate) buf_utf: bool, + pub(crate) show_shortcuts: bool, + /// The currently active buffer (memory, calldata, returndata) to be drawn. + pub(crate) active_buffer: BufferKind, +} + +impl<'a> TUIContext<'a> { + pub(crate) fn new(debugger_context: &'a mut DebuggerContext) -> Self { + TUIContext { + debugger_context, + + key_buffer: String::with_capacity(64), + current_step: 0, + draw_memory: DrawMemory::default(), + opcode_list: Vec::new(), + last_index: 0, + + stack_labels: false, + buf_utf: false, + show_shortcuts: true, + active_buffer: BufferKind::Memory, + } + } + + pub(crate) fn init(&mut self) { + self.gen_opcode_list(); + } + + pub(crate) fn debug_arena(&self) -> &[DebugNode] { + &self.debugger_context.debug_arena + } + + pub(crate) fn debug_call(&self) -> &DebugNode { + &self.debug_arena()[self.draw_memory.inner_call_index] + } + + /// Returns the current call address. + pub(crate) fn address(&self) -> &Address { + &self.debug_call().address + } + + /// Returns the current call kind. + pub(crate) fn call_kind(&self) -> CallKind { + self.debug_call().kind + } + + /// Returns the current debug steps. + pub(crate) fn debug_steps(&self) -> &[CallTraceStep] { + &self.debug_call().steps + } + + /// Returns the current debug step. + 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_context.debug_arena[self.draw_memory.inner_call_index].steps; + for step in debug_steps { + self.opcode_list.push(pretty_opcode(step)); + } + } + + fn gen_opcode_list_if_necessary(&mut self) { + if self.last_index != self.draw_memory.inner_call_index { + self.gen_opcode_list(); + self.last_index = self.draw_memory.inner_call_index; + } + } + + fn active_buffer(&self) -> &[u8] { + match self.active_buffer { + BufferKind::Memory => self.current_step().memory.as_ref().unwrap().as_bytes(), + BufferKind::Calldata => &self.debug_call().calldata, + BufferKind::Returndata => &self.current_step().returndata, + } + } +} + +impl TUIContext<'_> { + pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow { + let ret = match event { + Event::Key(event) => self.handle_key_event(event), + Event::Mouse(event) => self.handle_mouse_event(event), + _ => ControlFlow::Continue(()), + }; + // Generate the list after the event has been handled. + self.gen_opcode_list_if_necessary(); + ret + } + + fn handle_key_event(&mut self, event: KeyEvent) -> ControlFlow { + // Breakpoints + if let KeyCode::Char(c) = event.code { + if c.is_alphabetic() && self.key_buffer.starts_with('\'') { + self.handle_breakpoint(c); + return ControlFlow::Continue(()); + } + } + + let control = event.modifiers.contains(KeyModifiers::CONTROL); + + match event.code { + // Exit + KeyCode::Char('q') => return ControlFlow::Break(ExitReason::CharExit), + + // Scroll up the memory buffer + KeyCode::Char('k') | KeyCode::Up if control => self.repeat(|this| { + this.draw_memory.current_buf_startline = + this.draw_memory.current_buf_startline.saturating_sub(1); + }), + // Scroll down the memory buffer + KeyCode::Char('j') | KeyCode::Down if control => self.repeat(|this| { + let max_buf = (this.active_buffer().len() / 32).saturating_sub(1); + if this.draw_memory.current_buf_startline < max_buf { + this.draw_memory.current_buf_startline += 1; + } + }), + + // Move up + KeyCode::Char('k') | KeyCode::Up => self.repeat(Self::step_back), + // Move down + KeyCode::Char('j') | KeyCode::Down => self.repeat(Self::step), + + // Scroll up the stack + KeyCode::Char('K') => self.repeat(|this| { + this.draw_memory.current_stack_startline = + this.draw_memory.current_stack_startline.saturating_sub(1); + }), + // Scroll down the stack + KeyCode::Char('J') => self.repeat(|this| { + 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; + } + }), + + // Cycle buffers + KeyCode::Char('b') => { + self.active_buffer = self.active_buffer.next(); + self.draw_memory.current_buf_startline = 0; + } + + // Go to top of file + KeyCode::Char('g') => { + self.draw_memory.inner_call_index = 0; + self.current_step = 0; + } + + // Go to bottom of file + KeyCode::Char('G') => { + self.draw_memory.inner_call_index = self.debug_arena().len() - 1; + self.current_step = self.n_steps() - 1; + } + + // Go to previous call + KeyCode::Char('c') => { + self.draw_memory.inner_call_index = + self.draw_memory.inner_call_index.saturating_sub(1); + self.current_step = self.n_steps() - 1; + } + + // Go to next call + KeyCode::Char('C') => { + if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; + } + } + + // Step forward + KeyCode::Char('s') => self.repeat(|this| { + 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.debug_steps()[..this.current_step]; + this.current_step = ops + .iter() + .enumerate() + .skip(1) + .rev() + .find(|&(i, op)| { + let prev = &ops[i - 1]; + is_jump(op, prev) + }) + .map(|(i, _)| i) + .unwrap_or_default(); + }), + + // Toggle stack labels + KeyCode::Char('t') => self.stack_labels = !self.stack_labels, + + // Toggle memory UTF-8 decoding + KeyCode::Char('m') => self.buf_utf = !self.buf_utf, + + // Toggle help notice + KeyCode::Char('h') => self.show_shortcuts = !self.show_shortcuts, + + // Numbers for repeating commands or breakpoints + KeyCode::Char( + other @ ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '\''), + ) => { + // Early return to not clear the buffer. + self.key_buffer.push(other); + return ControlFlow::Continue(()); + } + + // Unknown/unhandled key code + _ => {} + }; + + self.key_buffer.clear(); + ControlFlow::Continue(()) + } + + fn handle_breakpoint(&mut self, c: char) { + // Find the location of the called breakpoint in the whole debug arena (at this address with + // this pc) + if let Some((caller, pc)) = self.debugger_context.breakpoints.get(&c) { + for (i, node) in self.debug_arena().iter().enumerate() { + if node.address == *caller { + if let Some(step) = node.steps.iter().position(|step| step.pc == *pc) { + self.draw_memory.inner_call_index = i; + self.current_step = step; + break; + } + } + } + } + self.key_buffer.clear(); + } + + fn handle_mouse_event(&mut self, event: MouseEvent) -> ControlFlow { + match event.kind { + MouseEventKind::ScrollUp => self.step_back(), + MouseEventKind::ScrollDown => self.step(), + _ => {} + } + + ControlFlow::Continue(()) + } + + fn step_back(&mut self) { + if self.current_step > 0 { + self.current_step -= 1; + } else if self.draw_memory.inner_call_index > 0 { + self.draw_memory.inner_call_index -= 1; + self.current_step = self.n_steps() - 1; + } + } + + fn step(&mut self) { + if self.current_step < self.n_steps() - 1 { + self.current_step += 1; + } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; + } + } + + /// Calls a closure `f` the number of times specified in the key buffer, and at least once. + fn repeat(&mut self, mut f: impl FnMut(&mut Self)) { + for _ in 0..buffer_as_number(&self.key_buffer) { + f(self); + } + } + + fn n_steps(&self) -> usize { + self.debug_steps().len() + } +} + +/// Grab number from buffer. Used for something like '10k' to move up 10 operations +fn buffer_as_number(s: &str) -> usize { + const MIN: usize = 1; + 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 new file mode 100644 index 0000000000000..a918bc89ea733 --- /dev/null +++ b/crates/debugger/src/tui/draw.rs @@ -0,0 +1,684 @@ +//! TUI draw implementation. + +use super::context::TUIContext; +use crate::op::OpcodeParam; +use foundry_compilers::artifacts::sourcemap::SourceElement; +use foundry_evm_core::buffer::{get_buffer_accesses, BufferKind}; +use foundry_evm_traces::debug::SourceData; +use ratatui::{ + layout::{Alignment, Constraint, Direction, Layout, Rect}, + style::{Color, Modifier, Style}, + text::{Line, Span, Text}, + widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}, + Frame, +}; +use revm_inspectors::tracing::types::CallKind; +use std::{collections::VecDeque, fmt::Write, io}; + +impl TUIContext<'_> { + /// Draws the TUI layout and subcomponents to the given terminal. + pub(crate) fn draw(&self, terminal: &mut super::DebuggerTerminal) -> io::Result<()> { + terminal.draw(|f| self.draw_layout(f)).map(drop) + } + + #[inline] + fn draw_layout(&self, f: &mut Frame<'_>) { + // We need 100 columns to display a 32 byte word in the memory and stack panes. + let area = f.area(); + let min_width = 100; + let min_height = 16; + if area.width < min_width || area.height < min_height { + self.size_too_small(f, min_width, min_height); + return; + } + + // The horizontal layout draws these panes at 50% width. + let min_column_width_for_horizontal = 200; + if area.width >= min_column_width_for_horizontal { + self.horizontal_layout(f); + } else { + self.vertical_layout(f); + } + } + + fn size_too_small(&self, f: &mut Frame<'_>, min_width: u16, min_height: u16) { + let mut lines = Vec::with_capacity(4); + + let l1 = "Terminal size too small:"; + lines.push(Line::from(l1)); + + let area = f.area(); + let width_color = if area.width >= min_width { Color::Green } else { Color::Red }; + let height_color = if area.height >= min_height { Color::Green } else { Color::Red }; + let l2 = vec![ + Span::raw("Width = "), + Span::styled(area.width.to_string(), Style::new().fg(width_color)), + Span::raw(" Height = "), + Span::styled(area.height.to_string(), Style::new().fg(height_color)), + ]; + lines.push(Line::from(l2)); + + let l3 = "Needed for current config:"; + lines.push(Line::from(l3)); + let l4 = format!("Width = {min_width} Height = {min_height}"); + lines.push(Line::from(l4)); + + let paragraph = + Paragraph::new(lines).alignment(Alignment::Center).wrap(Wrap { trim: true }); + f.render_widget(paragraph, area) + } + + /// Draws the layout in vertical mode. + /// + /// ```text + /// |-----------------------------| + /// | op | + /// |-----------------------------| + /// | stack | + /// |-----------------------------| + /// | buf | + /// |-----------------------------| + /// | | + /// | src | + /// | | + /// |-----------------------------| + /// ``` + fn vertical_layout(&self, f: &mut Frame<'_>) { + let area = f.area(); + let h_height = if self.show_shortcuts { 4 } else { 0 }; + + // NOTE: `Layout::split` always returns a slice of the same length as the number of + // constraints, so the `else` branch is unreachable. + + // Split off footer. + let [app, footer] = Layout::new( + Direction::Vertical, + [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)], + ) + .split(area)[..] else { + unreachable!() + }; + + // Split the app in 4 vertically to construct all the panes. + let [op_pane, stack_pane, memory_pane, src_pane] = Layout::new( + Direction::Vertical, + [ + Constraint::Ratio(1, 6), + Constraint::Ratio(1, 6), + Constraint::Ratio(1, 6), + Constraint::Ratio(3, 6), + ], + ) + .split(app)[..] else { + unreachable!() + }; + + if self.show_shortcuts { + self.draw_footer(f, footer); + } + self.draw_src(f, src_pane); + self.draw_op_list(f, op_pane); + self.draw_stack(f, stack_pane); + self.draw_buffer(f, memory_pane); + } + + /// Draws the layout in horizontal mode. + /// + /// ```text + /// |-----------------|-----------| + /// | op | stack | + /// |-----------------|-----------| + /// | | | + /// | src | buf | + /// | | | + /// |-----------------|-----------| + /// ``` + fn horizontal_layout(&self, f: &mut Frame<'_>) { + let area = f.area(); + let h_height = if self.show_shortcuts { 4 } else { 0 }; + + // Split off footer. + let [app, footer] = Layout::new( + Direction::Vertical, + [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)], + ) + .split(area)[..] else { + unreachable!() + }; + + // Split app in 2 horizontally. + let [app_left, app_right] = + Layout::new(Direction::Horizontal, [Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) + .split(app)[..] + else { + unreachable!() + }; + + // Split left pane in 2 vertically to opcode list and source. + let [op_pane, src_pane] = + Layout::new(Direction::Vertical, [Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) + .split(app_left)[..] + else { + unreachable!() + }; + + // Split right pane horizontally to construct stack and memory. + let [stack_pane, memory_pane] = + Layout::new(Direction::Vertical, [Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) + .split(app_right)[..] + else { + unreachable!() + }; + + if self.show_shortcuts { + self.draw_footer(f, footer); + } + self.draw_src(f, src_pane); + self.draw_op_list(f, op_pane); + self.draw_stack(f, stack_pane); + self.draw_buffer(f, memory_pane); + } + + fn draw_footer(&self, f: &mut Frame<'_>, area: Rect) { + let l1 = "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end | [b]: cycle memory/calldata/returndata buffers"; + let l2 = "[t]: stack labels | [m]: buffer decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll buffer | [']: goto breakpoint | [h] toggle help"; + let dimmed = Style::new().add_modifier(Modifier::DIM); + let lines = + vec![Line::from(Span::styled(l1, dimmed)), Line::from(Span::styled(l2, dimmed))]; + let paragraph = + Paragraph::new(lines).alignment(Alignment::Center).wrap(Wrap { trim: false }); + f.render_widget(paragraph, area); + } + + fn draw_src(&self, f: &mut Frame<'_>, area: Rect) { + let (text_output, source_name) = self.src_text(area); + let call_kind_text = match self.call_kind() { + CallKind::Create | CallKind::Create2 => "Contract creation", + CallKind::Call => "Contract call", + CallKind::StaticCall => "Contract staticcall", + CallKind::CallCode => "Contract callcode", + CallKind::DelegateCall => "Contract delegatecall", + CallKind::AuthCall => "Contract authcall", + CallKind::EOFCreate => "EOF contract creation", + }; + let title = format!( + "{} {} ", + call_kind_text, + source_name.map(|s| format!("| {s}")).unwrap_or_default() + ); + let block = Block::default().title(title).borders(Borders::ALL); + let paragraph = Paragraph::new(text_output).block(block).wrap(Wrap { trim: false }); + f.render_widget(paragraph, area); + } + + fn src_text(&self, area: Rect) -> (Text<'_>, Option<&str>) { + let (source_element, source) = match self.src_map() { + Ok(r) => r, + Err(e) => return (Text::from(e), None), + }; + + // We are handed a vector of SourceElements that give us a span of sourcecode that is + // currently being executed. This includes an offset and length. + // This vector is in instruction pointer order, meaning the location of the instruction + // minus `sum(push_bytes[..pc])`. + let offset = source_element.offset() as usize; + let len = source_element.length() as usize; + 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.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; + let needed_highlight = actual.len(); + let mid_len = before.len() + actual.len(); + + // adjust what text we show of the source code + let (start_line, end_line) = if needed_highlight > height { + // highlighted section is more lines than we have available + let start_line = before.len().saturating_sub(1); + (start_line, before.len() + needed_highlight) + } else if height > num_lines { + // we can fit entire source + (0, num_lines) + } else { + let remaining = height - needed_highlight; + let mut above = remaining / 2; + let mut below = remaining / 2; + if below > after.len() { + // unused space below the highlight + above += below - after.len(); + } else if above > before.len() { + // we have unused space above the highlight + below += above - before.len(); + } else { + // no unused space + } + + // since above is subtracted from before.len(), and the resulting + // start_line is used to index into before, above must be at least + // 1 to avoid out-of-range accesses. + if above == 0 { + above = 1; + } + (before.len().saturating_sub(above), mid_len + below) + }; + + // Unhighlighted line number: gray. + let u_num = Style::new().fg(Color::Gray); + // Unhighlighted text: default, dimmed. + let u_text = Style::new().add_modifier(Modifier::DIM); + // Highlighted line number: cyan. + let h_num = Style::new().fg(Color::Cyan); + // Highlighted text: cyan, bold. + let h_text = Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD); + + 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() { + let last_has_nl = last.ends_with('\n'); + + if last_has_nl { + before.push(last); + } + for line in &before[start_line..] { + lines.push(u_num, line, u_text); + } + + let first = if !last_has_nl { + lines.push_raw(h_num, &[Span::raw(last), Span::styled(actual[0], h_text)]); + 1 + } else { + 0 + }; + + // Skip the first line if it has already been handled above. + for line in &actual[first..] { + lines.push(h_num, line, h_text); + } + } else { + // No text before the current line. + for line in &actual { + lines.push(h_num, line, h_text); + } + } + + // Fill in the rest of the line as unhighlighted. + if let Some(last) = actual.last() { + if !last.ends_with('\n') { + if let Some(post) = after.pop_front() { + if let Some(last) = lines.lines.last_mut() { + last.spans.push(Span::raw(post)); + } + } + } + } + + // Add after highlighted text. + while mid_len + after.len() > end_line { + after.pop_back(); + } + for line in after { + lines.push(u_num, line, u_text); + } + + // pad with empty to each line to ensure the previous text is cleared + for line in &mut lines.lines { + // note that the \n is not included in the line length + if area.width as usize > line.width() + 1 { + line.push_span(Span::raw(" ".repeat(area.width as usize - line.width() - 1))); + } + } + + (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, &SourceData), String> { + let address = self.address(); + let Some(contract_name) = self.debugger_context.identified_contracts.get(address) else { + return Err(format!("Unknown contract at address {address}")); + }; + + self.debugger_context + .contracts_sources + .find_source_mapping( + contract_name, + self.current_step().pc as u32, + 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) { + let debug_steps = self.debug_steps(); + let max_pc = debug_steps.iter().map(|step| step.pc).max().unwrap_or(0); + let max_pc_len = hex_digits(max_pc); + + let items = debug_steps + .iter() + .enumerate() + .map(|(i, step)| { + let mut content = String::with_capacity(64); + write!(content, "{:0>max_pc_len$x}|", step.pc).unwrap(); + if let Some(op) = self.opcode_list.get(i) { + content.push_str(op); + } + ListItem::new(Span::styled(content, Style::new().fg(Color::White))) + }) + .collect::>(); + + let title = format!( + "Address: {} | PC: {} | Gas used in call: {} | Code section: {}", + self.address(), + self.current_step().pc, + 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) + .block(block) + .highlight_symbol("▶") + .highlight_style(Style::new().fg(Color::White).bg(Color::DarkGray)) + .scroll_padding(1); + let mut state = ListState::default().with_selected(Some(self.current_step)); + f.render_stateful_widget(list, area, &mut state); + } + + fn draw_stack(&self, f: &mut Frame<'_>, area: Rect) { + let step = self.current_step(); + 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 params = OpcodeParam::of(step.op.get(), step.immediate_bytes.as_ref()); + + let text: Vec> = stack + .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)); + } + } + + spans.push(Span::raw("\n")); + + Line::from(spans) + }) + .collect() + }) + .unwrap_or_default(); + + 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().unwrap().as_ref(), + BufferKind::Calldata => call.calldata.as_ref(), + BufferKind::Returndata => step.returndata.as_ref(), + }; + + let min_len = hex_digits(buf.len()); + + // Color memory region based on read/write. + let mut offset = None; + let mut len = None; + let mut write_offset = None; + let mut write_size = None; + let mut color = None; + let stack_len = step.stack.as_ref().map_or(0, |s| s.len()); + if stack_len > 0 { + if let Some(stack) = step.stack.as_ref() { + if let Some(accesses) = get_buffer_accesses(step.op.get(), stack) { + if let Some(read_access) = accesses.read { + offset = Some(read_access.1.offset); + len = Some(read_access.1.len); + color = Some(Color::Cyan); + } + if let Some(write_access) = accesses.write { + if self.active_buffer == BufferKind::Memory { + write_offset = Some(write_access.offset); + write_size = Some(write_access.len); + } + } + } + } + } + + // color word on previous write op + // TODO: technically it's possible for this to conflict with the current op, ie, with + // subsequent MCOPYs, but solc can't seem to generate that code even with high optimizer + // settings + if self.current_step > 0 { + let prev_step = self.current_step - 1; + let prev_step = &self.debug_steps()[prev_step]; + if let Some(stack) = prev_step.stack.as_ref() { + if let Some(write_access) = + get_buffer_accesses(prev_step.op.get(), stack).and_then(|a| a.write) + { + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + len = Some(write_access.len); + color = Some(Color::Green); + } + } + } + } + + let height = area.height as usize; + let end_line = self.draw_memory.current_buf_startline + height; + + let text: Vec> = buf + .chunks(32) + .enumerate() + .skip(self.draw_memory.current_buf_startline) + .take_while(|(i, _)| *i < end_line) + .map(|(i, buf_word)| { + let mut spans = Vec::with_capacity(1 + 32 * 2 + 1 + 32 / 4 + 1); + + // Buffer index. + spans.push(Span::styled( + format!("{:0min_len$x}| ", i * 32), + Style::new().fg(Color::White), + )); + + // Word hex bytes. + hex_bytes_spans(buf_word, &mut spans, |j, _| { + let mut byte_color = Color::White; + let mut end = None; + let idx = i * 32 + j; + if let (Some(offset), Some(len), Some(color)) = (offset, len, color) { + end = Some(offset + len); + if (offset..offset + len).contains(&idx) { + // [offset, offset + len] is the memory region to be colored. + // If a byte at row i and column j in the memory panel + // falls in this region, set the color. + byte_color = color; + } + } + if let (Some(write_offset), Some(write_size)) = (write_offset, write_size) { + // check for overlap with read region + let write_end = write_offset + write_size; + if let Some(read_end) = end { + let read_start = offset.unwrap(); + if (write_offset..write_end).contains(&read_end) { + // if it contains end, start from write_start up to read_end + if (write_offset..read_end).contains(&idx) { + return Style::new().fg(Color::Yellow); + } + } else if (write_offset..write_end).contains(&read_start) { + // otherwise if it contains read start, start from read_start up to + // write_end + if (read_start..write_end).contains(&idx) { + return Style::new().fg(Color::Yellow); + } + } + } + if (write_offset..write_end).contains(&idx) { + byte_color = Color::Red; + } + } + + Style::new().fg(byte_color) + }); + + if self.buf_utf { + spans.push(Span::raw("|")); + for utf in buf_word.chunks(4) { + if let Ok(utf_str) = std::str::from_utf8(utf) { + spans.push(Span::raw(utf_str.replace('\0', "."))); + } else { + spans.push(Span::raw(".")); + } + } + } + + spans.push(Span::raw("\n")); + + Line::from(spans) + }) + .collect(); + + let title = self.active_buffer.title(buf.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); + } +} + +/// 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(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) { + self.push_raw(line_number_style, &[Span::styled(line, line_style)]); + } + + 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.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. + line_spans.push(Span::raw(" ")); + + line_spans.extend_from_slice(spans); + + self.lines.push(Line::from(line_spans)); + } +} + +fn hex_bytes_spans(bytes: &[u8], spans: &mut Vec>, f: impl Fn(usize, u8) -> Style) { + for (i, &byte) in bytes.iter().enumerate() { + if i > 0 { + spans.push(Span::raw(" ")); + } + spans.push(Span::styled(alloy_primitives::hex::encode([byte]), f(i, byte))); + } +} + +/// Returns the number of decimal digits in the given number. +/// +/// This is the same as `n.to_string().len()`. +fn decimal_digits(n: usize) -> usize { + n.checked_ilog10().unwrap_or(0) as usize + 1 +} + +/// Returns the number of hexadecimal digits in the given number. +/// +/// This is the same as `format!("{n:x}").len()`. +fn hex_digits(n: usize) -> usize { + n.checked_ilog(16).unwrap_or(0) as usize + 1 +} + +#[cfg(test)] +mod tests { + #[test] + fn decimal_digits() { + assert_eq!(super::decimal_digits(0), 1); + assert_eq!(super::decimal_digits(1), 1); + assert_eq!(super::decimal_digits(2), 1); + assert_eq!(super::decimal_digits(9), 1); + assert_eq!(super::decimal_digits(10), 2); + assert_eq!(super::decimal_digits(11), 2); + assert_eq!(super::decimal_digits(50), 2); + assert_eq!(super::decimal_digits(99), 2); + assert_eq!(super::decimal_digits(100), 3); + assert_eq!(super::decimal_digits(101), 3); + assert_eq!(super::decimal_digits(201), 3); + assert_eq!(super::decimal_digits(999), 3); + assert_eq!(super::decimal_digits(1000), 4); + assert_eq!(super::decimal_digits(1001), 4); + } + + #[test] + fn hex_digits() { + assert_eq!(super::hex_digits(0), 1); + assert_eq!(super::hex_digits(1), 1); + assert_eq!(super::hex_digits(2), 1); + assert_eq!(super::hex_digits(9), 1); + assert_eq!(super::hex_digits(10), 1); + assert_eq!(super::hex_digits(11), 1); + assert_eq!(super::hex_digits(15), 1); + assert_eq!(super::hex_digits(16), 2); + assert_eq!(super::hex_digits(17), 2); + assert_eq!(super::hex_digits(0xff), 2); + assert_eq!(super::hex_digits(0x100), 3); + assert_eq!(super::hex_digits(0x101), 3); + } +} diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs new file mode 100644 index 0000000000000..abc7c4a9706e6 --- /dev/null +++ b/crates/debugger/src/tui/mod.rs @@ -0,0 +1,167 @@ +//! The TUI implementation. + +use crossterm::{ + event::{self, DisableMouseCapture, EnableMouseCapture, Event}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use eyre::Result; +use ratatui::{ + backend::{Backend, CrosstermBackend}, + Terminal, +}; +use std::{ + io, + ops::ControlFlow, + sync::{mpsc, Arc}, + thread, + time::{Duration, Instant}, +}; + +mod context; +use crate::debugger::DebuggerContext; +use context::TUIContext; + +mod draw; + +type DebuggerTerminal = Terminal>; + +/// Debugger exit reason. +#[derive(Debug)] +pub enum ExitReason { + /// Exit using 'q'. + CharExit, +} + +/// The TUI debugger. +pub struct TUI<'a> { + debugger_context: &'a mut DebuggerContext, +} + +impl<'a> TUI<'a> { + /// Creates a new debugger. + pub fn new(debugger_context: &'a mut DebuggerContext) -> Self { + Self { debugger_context } + } + + /// Starts the debugger TUI. + pub fn try_run(&mut self) -> Result { + let backend = CrosstermBackend::new(io::stdout()); + let terminal = Terminal::new(backend)?; + TerminalGuard::with(terminal, |terminal| self.try_run_real(terminal)) + } + + #[instrument(target = "debugger", name = "run", skip_all, ret)] + fn try_run_real(&mut self, terminal: &mut DebuggerTerminal) -> Result { + // Create the context. + let mut cx = TUIContext::new(self.debugger_context); + + cx.init(); + + // Create an event listener in a different thread. + let (tx, rx) = mpsc::channel(); + thread::Builder::new() + .name("event-listener".into()) + .spawn(move || Self::event_listener(tx)) + .expect("failed to spawn thread"); + + // Start the event loop. + loop { + cx.draw(terminal)?; + match cx.handle_event(rx.recv()?) { + ControlFlow::Continue(()) => {} + ControlFlow::Break(reason) => return Ok(reason), + } + } + } + + fn event_listener(tx: mpsc::Sender) { + // This is the recommend tick rate from `ratatui`, based on their examples + let tick_rate = Duration::from_millis(200); + + let mut last_tick = Instant::now(); + loop { + // Poll events since last tick - if last tick is greater than tick_rate, we + // demand immediate availability of the event. This may affect interactivity, + // but I'm not sure as it is hard to test. + if event::poll(tick_rate.saturating_sub(last_tick.elapsed())).unwrap() { + let event = event::read().unwrap(); + if tx.send(event).is_err() { + return; + } + } + + // Force update if time has passed + if last_tick.elapsed() > tick_rate { + last_tick = Instant::now(); + } + } + } +} + +// TODO: Update once on 1.82 +#[allow(deprecated)] +type PanicHandler = Box) + 'static + Sync + Send>; + +/// Handles terminal state. +#[must_use] +struct TerminalGuard { + terminal: Terminal, + hook: Option>, +} + +impl TerminalGuard { + fn with(terminal: Terminal, mut f: impl FnMut(&mut Terminal) -> T) -> T { + let mut guard = Self { terminal, hook: None }; + guard.setup(); + f(&mut guard.terminal) + } + + fn setup(&mut self) { + let previous = Arc::new(std::panic::take_hook()); + self.hook = Some(previous.clone()); + // We need to restore the terminal state before displaying the panic message. + // TODO: Use `std::panic::update_hook` when it's stable + std::panic::set_hook(Box::new(move |info| { + Self::half_restore(&mut std::io::stdout()); + (previous)(info) + })); + + let _ = enable_raw_mode(); + let _ = execute!(*self.terminal.backend_mut(), EnterAlternateScreen, EnableMouseCapture); + let _ = self.terminal.hide_cursor(); + let _ = self.terminal.clear(); + } + + fn restore(&mut self) { + if !std::thread::panicking() { + // Drop the current hook to guarantee that `self.hook` is the only reference to it. + let _ = std::panic::take_hook(); + // Restore the previous panic hook. + let prev = self.hook.take().unwrap(); + let prev = match Arc::try_unwrap(prev) { + Ok(prev) => prev, + Err(_) => unreachable!("`self.hook` is not the only reference to the panic hook"), + }; + std::panic::set_hook(prev); + + // NOTE: Our panic handler calls this function, so we only have to call it here if we're + // not panicking. + Self::half_restore(self.terminal.backend_mut()); + } + + let _ = self.terminal.show_cursor(); + } + + fn half_restore(w: &mut impl io::Write) { + let _ = disable_raw_mode(); + let _ = execute!(*w, LeaveAlternateScreen, DisableMouseCapture); + } +} + +impl Drop for TerminalGuard { + #[inline] + fn drop(&mut self) { + self.restore(); + } +} diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index b189f3732f69e..95e26df179cc4 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -10,34 +10,26 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -# foundry internal forge-fmt.workspace = true +foundry-common.workspace = true +foundry-compilers.workspace = true foundry-config.workspace = true -foundry-utils.workspace = true - -# ethers -ethers-solc = { workspace = true, features = ["async"] } -ethers-core.workspace = true -# tracing -tracing = "0.1" +alloy-primitives.workspace = true -# mdbook +derive_more.workspace = true +eyre.workspace = true +itertools.workspace = true mdbook = { version = "0.4", default-features = false, features = ["search"] } -warp = { version = "0.3", default-features = false, features = ["websocket"] } -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -futures-util = "0.3" - -# misc +rayon.workspace = true +serde_json.workspace = true +serde.workspace = true solang-parser.workspace = true -eyre = "0.6" -thiserror = "1" -rayon = "1" -itertools.workspace = true -toml = "0.7" -auto_impl = "1" -derive_more = "0.99" -once_cell = "1" -serde = "1.0.163" -serde_json = "1.0.96" +thiserror.workspace = true +toml.workspace = true +tracing.workspace = true +regex.workspace = true diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index d015c21d379b9..6c2bec99d19f8 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -2,16 +2,15 @@ use crate::{ document::DocumentContent, helpers::merge_toml_table, AsDoc, BufWriter, Document, ParseItem, ParseSource, Parser, Preprocessor, }; -use ethers_solc::utils::source_files_iter; +use alloy_primitives::map::HashMap; use forge_fmt::{FormatterConfig, Visitable}; -use foundry_config::DocConfig; -use foundry_utils::glob::expand_globs; +use foundry_compilers::{compilers::solc::SOLC_EXTENSIONS, utils::source_files_iter}; +use foundry_config::{filter::expand_globs, DocConfig}; use itertools::Itertools; use mdbook::MDBook; use rayon::prelude::*; use std::{ cmp::Ordering, - collections::HashMap, fs, path::{Path, PathBuf}, }; @@ -26,6 +25,8 @@ pub struct DocBuilder { pub root: PathBuf, /// Path to Solidity source files. pub sources: PathBuf, + /// Paths to external libraries. + pub libraries: Vec, /// Flag whether to build mdbook. pub should_build: bool, /// Documentation configuration. @@ -34,20 +35,29 @@ pub struct DocBuilder { pub preprocessors: Vec>, /// The formatter config. pub fmt: FormatterConfig, + /// Whether to include libraries to the output. + pub include_libraries: bool, } // TODO: consider using `tfio` impl DocBuilder { - const SRC: &'static str = "src"; + pub(crate) const SRC: &'static str = "src"; const SOL_EXT: &'static str = "sol"; const README: &'static str = "README.md"; const SUMMARY: &'static str = "SUMMARY.md"; /// Create new instance of builder. - pub fn new(root: PathBuf, sources: PathBuf) -> Self { + pub fn new( + root: PathBuf, + sources: PathBuf, + libraries: Vec, + include_libraries: bool, + ) -> Self { Self { root, sources, + libraries, + include_libraries, should_build: false, config: DocConfig::default(), preprocessors: Default::default(), @@ -55,7 +65,7 @@ impl DocBuilder { } } - /// Set `shoul_build` flag on the builder + /// Set `should_build` flag on the builder pub fn with_should_build(mut self, should_build: bool) -> Self { self.should_build = should_build; self @@ -90,29 +100,52 @@ impl DocBuilder { let ignored = expand_globs(&self.root, self.config.ignore.iter())?; // Collect and parse source files - let sources = source_files_iter(&self.sources) + let sources = source_files_iter(&self.sources, SOLC_EXTENSIONS) .filter(|file| !ignored.contains(file)) .collect::>(); if sources.is_empty() { - println!("No sources detected at {}", self.sources.display()); + sh_println!("No sources detected at {}", self.sources.display())?; return Ok(()) } - let documents = sources + let library_sources = self + .libraries + .iter() + .flat_map(|lib| source_files_iter(lib, SOLC_EXTENSIONS)) + .collect::>(); + + let combined_sources = sources + .iter() + .map(|path| (path, false)) + .chain(library_sources.iter().map(|path| (path, true))) + .collect::>(); + + let documents = combined_sources .par_iter() .enumerate() - .map(|(i, path)| { + .map(|(i, (path, from_library))| { + let path = *path; + let from_library = *from_library; + // Read and parse source file let source = fs::read_to_string(path)?; - let (mut source_unit, comments) = - solang_parser::parse(&source, i).map_err(|diags| { - eyre::eyre!( - "Failed to parse Solidity code for {}\nDebug info: {:?}", - path.display(), - diags - ) - })?; + + let (mut source_unit, comments) = match solang_parser::parse(&source, i) { + Ok(res) => res, + Err(err) => { + if from_library { + // Ignore failures for library files + return Ok(Vec::new()); + } else { + return Err(eyre::eyre!( + "Failed to parse Solidity code for {}\nDebug info: {:?}", + path.display(), + err + )); + } + } + }; // Visit the parse tree let mut doc = Parser::new(comments, source).with_fmt(self.fmt.clone()); @@ -150,8 +183,13 @@ impl DocBuilder { let relative_path = path.strip_prefix(&self.root)?.join(item.filename()); let target_path = self.config.out.join(Self::SRC).join(relative_path); let ident = item.source.ident(); - Ok(Document::new(path.clone(), target_path) - .with_content(DocumentContent::Single(item), ident)) + Ok(Document::new( + path.clone(), + target_path, + from_library, + self.config.out.clone(), + ) + .with_content(DocumentContent::Single(item), ident)) }) .collect::>>()?; @@ -177,8 +215,13 @@ impl DocBuilder { }; files.push( - Document::new(path.clone(), target_path) - .with_content(DocumentContent::Constants(consts), identity), + Document::new( + path.clone(), + target_path, + from_library, + self.config.out.clone(), + ) + .with_content(DocumentContent::Constants(consts), identity), ) } @@ -189,8 +232,13 @@ impl DocBuilder { let relative_path = path.strip_prefix(&self.root)?.join(filename); let target_path = self.config.out.join(Self::SRC).join(relative_path); files.push( - Document::new(path.clone(), target_path) - .with_content(DocumentContent::OverloadedFunctions(funcs), ident), + Document::new( + path.clone(), + target_path, + from_library, + self.config.out.clone(), + ) + .with_content(DocumentContent::OverloadedFunctions(funcs), ident), ); } } @@ -213,7 +261,9 @@ impl DocBuilder { }); // Write mdbook related files - self.write_mdbook(documents.collect_vec())?; + self.write_mdbook( + documents.filter(|d| !d.from_library || self.include_libraries).collect_vec(), + )?; // Build the book if requested if self.should_build { @@ -255,7 +305,7 @@ impl DocBuilder { }; let readme_path = out_dir_src.join(Self::README); - fs::write(&readme_path, homepage_content)?; + fs::write(readme_path, homepage_content)?; // Write summary and section readmes let mut summary = BufWriter::default(); @@ -283,7 +333,7 @@ impl DocBuilder { document .target_path .parent() - .ok_or(eyre::format_err!("empty target path; noop"))?, + .ok_or_else(|| eyre::format_err!("empty target path; noop"))?, )?; fs::write(&document.target_path, document.as_doc()?)?; } @@ -339,7 +389,7 @@ impl DocBuilder { } if let Some(path) = base_path { - let title = path.iter().last().unwrap().to_string_lossy(); + let title = path.iter().next_back().unwrap().to_string_lossy(); if depth == 1 { summary.write_title(&title)?; } else { @@ -361,8 +411,8 @@ impl DocBuilder { } // Sort entries by path depth let grouped = grouped.into_iter().sorted_by(|(lhs, _), (rhs, _)| { - let lhs_at_end = lhs.extension().map(|ext| ext.eq(Self::SOL_EXT)).unwrap_or_default(); - let rhs_at_end = rhs.extension().map(|ext| ext.eq(Self::SOL_EXT)).unwrap_or_default(); + let lhs_at_end = lhs.extension().map(|ext| ext == Self::SOL_EXT).unwrap_or_default(); + let rhs_at_end = rhs.extension().map(|ext| ext == Self::SOL_EXT).unwrap_or_default(); if lhs_at_end == rhs_at_end { lhs.cmp(rhs) } else if lhs_at_end { @@ -374,7 +424,7 @@ impl DocBuilder { let mut readme = BufWriter::new("\n\n# Contents\n"); for (path, files) in grouped { - if path.extension().map(|ext| ext.eq(Self::SOL_EXT)).unwrap_or_default() { + if path.extension().map(|ext| ext == Self::SOL_EXT).unwrap_or_default() { for file in files { let ident = &file.identity; @@ -394,7 +444,7 @@ impl DocBuilder { readme.write_link_list_item(ident, &readme_path.display().to_string(), 0)?; } } else { - let name = path.iter().last().unwrap().to_string_lossy(); + let name = path.iter().next_back().unwrap().to_string_lossy(); let readme_path = Path::new("/").join(&path).display().to_string(); readme.write_link_list_item(&name, &readme_path, 0)?; self.write_summary_section(summary, &files, Some(&path), depth + 1)?; diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index 158b013124abd..10f72a672c256 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -1,6 +1,10 @@ -use std::{collections::HashMap, path::PathBuf, sync::Mutex}; - -use crate::{ParseItem, PreprocessorId, PreprocessorOutput}; +use crate::{DocBuilder, ParseItem, PreprocessorId, PreprocessorOutput}; +use alloy_primitives::map::HashMap; +use std::{ + path::{Path, PathBuf}, + slice::IterMut, + sync::Mutex, +}; /// The wrapper around the [ParseItem] containing additional /// information the original item and extra context for outputting it. @@ -18,26 +22,28 @@ pub struct Document { pub identity: String, /// The preprocessors results. context: Mutex>, -} - -/// The content of the document. -#[derive(Debug)] -pub enum DocumentContent { - Empty, - Single(ParseItem), - Constants(Vec), - OverloadedFunctions(Vec), + /// Whether the document is from external library. + pub from_library: bool, + /// The target directory for the doc output. + pub out_target_dir: PathBuf, } impl Document { /// Create new instance of [Document]. - pub fn new(item_path: PathBuf, target_path: PathBuf) -> Self { + pub fn new( + item_path: PathBuf, + target_path: PathBuf, + from_library: bool, + out_target_dir: PathBuf, + ) -> Self { Self { item_path, target_path, + from_library, item_content: String::default(), identity: String::default(), content: DocumentContent::Empty, + out_target_dir, context: Mutex::new(HashMap::default()), } } @@ -61,6 +67,116 @@ impl Document { let context = self.context.lock().expect("failed to lock context"); context.get(&id).cloned() } + + fn try_relative_output_path(&self) -> Option<&Path> { + self.target_path.strip_prefix(&self.out_target_dir).ok()?.strip_prefix(DocBuilder::SRC).ok() + } + + /// Returns the relative path of the document output. + pub fn relative_output_path(&self) -> &Path { + self.try_relative_output_path().unwrap_or(self.target_path.as_path()) + } +} + +/// The content of the document. +#[derive(Debug)] +pub enum DocumentContent { + Empty, + Single(ParseItem), + Constants(Vec), + OverloadedFunctions(Vec), +} + +impl DocumentContent { + pub(crate) fn len(&self) -> usize { + match self { + Self::Empty => 0, + Self::Single(_) => 1, + Self::Constants(items) => items.len(), + Self::OverloadedFunctions(items) => items.len(), + } + } + + pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut ParseItem> { + match self { + Self::Empty => None, + Self::Single(item) => { + if index == 0 { + Some(item) + } else { + None + } + } + Self::Constants(items) => items.get_mut(index), + Self::OverloadedFunctions(items) => items.get_mut(index), + } + } + + pub fn iter_items(&self) -> ParseItemIter<'_> { + match self { + Self::Empty => ParseItemIter { next: None, other: None }, + Self::Single(item) => ParseItemIter { next: Some(item), other: None }, + Self::Constants(items) => ParseItemIter { next: None, other: Some(items.iter()) }, + Self::OverloadedFunctions(items) => { + ParseItemIter { next: None, other: Some(items.iter()) } + } + } + } + + pub fn iter_items_mut(&mut self) -> ParseItemIterMut<'_> { + match self { + Self::Empty => ParseItemIterMut { next: None, other: None }, + Self::Single(item) => ParseItemIterMut { next: Some(item), other: None }, + Self::Constants(items) => { + ParseItemIterMut { next: None, other: Some(items.iter_mut()) } + } + Self::OverloadedFunctions(items) => { + ParseItemIterMut { next: None, other: Some(items.iter_mut()) } + } + } + } +} + +#[derive(Debug)] +pub struct ParseItemIter<'a> { + next: Option<&'a ParseItem>, + other: Option>, +} + +impl<'a> Iterator for ParseItemIter<'a> { + type Item = &'a ParseItem; + + fn next(&mut self) -> Option { + if let Some(next) = self.next.take() { + return Some(next) + } + if let Some(other) = self.other.as_mut() { + return other.next() + } + + None + } +} + +#[derive(Debug)] +pub struct ParseItemIterMut<'a> { + next: Option<&'a mut ParseItem>, + other: Option>, +} + +impl<'a> Iterator for ParseItemIterMut<'a> { + type Item = &'a mut ParseItem; + + fn next(&mut self) -> Option { + if let Some(next) = self.next.take() { + return Some(next) + } + if let Some(other) = self.other.as_mut() { + return other.next() + } + + None + } } /// Read the preprocessor output variant from document context. diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index d629283b55723..174c76600d8a0 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -1,38 +1,33 @@ -#![warn(missing_debug_implementations, missing_docs, unreachable_pub, unused_crate_dependencies)] -#![deny(unused_must_use, rust_2018_idioms)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] - //! The module for generating Solidity documentation. //! -//! See [DocBuilder] +//! See [`DocBuilder`]. -mod builder; -mod document; -mod helpers; -mod parser; -mod preprocessor; -mod server; -mod writer; +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -/// The documentation builder. -pub use builder::DocBuilder; +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; -/// The documentation server. -pub use server::Server; +mod builder; +pub use builder::DocBuilder; -/// The document output. +mod document; pub use document::Document; -/// Solidity parser and related output items. +mod helpers; + +mod parser; pub use parser::{ error, Comment, CommentTag, Comments, CommentsRef, ParseItem, ParseSource, Parser, }; -/// Preprocessors. +mod preprocessor; pub use preprocessor::*; -/// Traits for formatting items into doc output. +mod writer; pub use writer::{AsDoc, AsDocResult, BufWriter, Markdown}; + +pub use mdbook; diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index a897804496be1..bf2b0ad7b4f0d 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -1,10 +1,10 @@ -use derive_more::{Deref, DerefMut}; +use alloy_primitives::map::HashMap; +use derive_more::{derive::Display, Deref, DerefMut}; use solang_parser::doccomment::DocCommentTag; -use std::collections::HashMap; /// The natspec comment tag explaining the purpose of the comment. -/// See: https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags. -#[derive(PartialEq, Clone, Debug)] +/// See: . +#[derive(Clone, Debug, Display, PartialEq, Eq)] pub enum CommentTag { /// A title that should describe the contract/interface Title, @@ -28,24 +28,24 @@ impl CommentTag { fn from_str(s: &str) -> Option { let trimmed = s.trim(); let tag = match trimmed { - "title" => CommentTag::Title, - "author" => CommentTag::Author, - "notice" => CommentTag::Notice, - "dev" => CommentTag::Dev, - "param" => CommentTag::Param, - "return" => CommentTag::Return, - "inheritdoc" => CommentTag::Inheritdoc, + "title" => Self::Title, + "author" => Self::Author, + "notice" => Self::Notice, + "dev" => Self::Dev, + "param" => Self::Param, + "return" => Self::Return, + "inheritdoc" => Self::Inheritdoc, _ if trimmed.starts_with("custom:") => { // `@custom:param` tag will be parsed as `CommentTag::Param` due to a limitation // on specifying parameter docs for unnamed function arguments. let custom_tag = trimmed.trim_start_matches("custom:").trim(); match custom_tag { - "param" => CommentTag::Param, - _ => CommentTag::Custom(custom_tag.to_owned()), + "param" => Self::Param, + _ => Self::Custom(custom_tag.to_owned()), } } _ => { - tracing::warn!(target: "forge::doc", tag = trimmed, "unknown comment tag. custom tags must be preceded by `custom:`"); + warn!(target: "forge::doc", tag=trimmed, "unknown comment tag. custom tags must be preceded by `custom:`"); return None } }; @@ -54,8 +54,9 @@ impl CommentTag { } /// The natspec documentation comment. -/// https://docs.soliditylang.org/en/v0.8.17/natspec-format.html -#[derive(PartialEq, Clone, Debug)] +/// +/// Ref: +#[derive(Clone, Debug, PartialEq, Eq)] pub struct Comment { /// The doc comment tag. pub tag: CommentTag, @@ -87,7 +88,7 @@ impl Comment { pub fn match_first_word(&self, expected: &str) -> Option<&str> { self.split_first_word().and_then( |(word, rest)| { - if word.eq(expected) { + if word == expected { Some(rest) } else { None @@ -95,10 +96,15 @@ impl Comment { }, ) } + + /// Check if this comment is a custom tag. + pub fn is_custom(&self) -> bool { + matches!(self.tag, CommentTag::Custom(_)) + } } /// The collection of natspec [Comment] items. -#[derive(Deref, DerefMut, PartialEq, Default, Clone, Debug)] +#[derive(Clone, Debug, Default, PartialEq, Deref, DerefMut)] pub struct Comments(Vec); /// Forward the [Comments] function implementation to the [CommentsRef] @@ -126,9 +132,9 @@ impl Comments { pub fn merge_inheritdoc( &self, ident: &str, - inheritdocs: Option>, - ) -> Comments { - let mut result = Comments(Vec::from_iter(self.iter().cloned())); + inheritdocs: Option>, + ) -> Self { + let mut result = Self(Vec::from_iter(self.iter().cloned())); if let (Some(inheritdocs), Some(base)) = (inheritdocs, self.find_inheritdoc_base()) { let key = format!("{base}.{ident}"); @@ -152,23 +158,23 @@ impl From> for Comments { } /// The collection of references to natspec [Comment] items. -#[derive(Deref, PartialEq, Default, Debug)] +#[derive(Debug, Default, PartialEq, Deref)] pub struct CommentsRef<'a>(Vec<&'a Comment>); impl<'a> CommentsRef<'a> { - /// Filter a collection of comments and return only those that match a provided tag - pub fn include_tag(&self, tag: CommentTag) -> CommentsRef<'a> { + /// Filter a collection of comments and return only those that match a provided tag. + pub fn include_tag(&self, tag: CommentTag) -> Self { self.include_tags(&[tag]) } - /// Filter a collection of comments and return only those that match provided tags - pub fn include_tags(&self, tags: &[CommentTag]) -> CommentsRef<'a> { + /// Filter a collection of comments and return only those that match provided tags. + pub fn include_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here CommentsRef(self.iter().cloned().filter(|c| tags.contains(&c.tag)).collect()) } - /// Filter a collection of comments and return only those that do not match provided tags - pub fn exclude_tags(&self, tags: &[CommentTag]) -> CommentsRef<'a> { + /// Filter a collection of comments and return only those that do not match provided tags. + pub fn exclude_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here CommentsRef(self.iter().cloned().filter(|c| !tags.contains(&c.tag)).collect()) } @@ -191,6 +197,11 @@ impl<'a> CommentsRef<'a> { .find(|c| matches!(c.tag, CommentTag::Inheritdoc)) .and_then(|c| c.value.split_whitespace().next()) } + + /// Filter a collection of comments and only return the custom tags. + pub fn get_custom_tags(&self) -> Self { + CommentsRef(self.iter().cloned().filter(|c| c.is_custom()).collect()) + } } impl<'a> From<&'a Comments> for CommentsRef<'a> { @@ -213,7 +224,7 @@ mod tests { assert_eq!(CommentTag::from_str("param"), Some(CommentTag::Param)); assert_eq!(CommentTag::from_str("return"), Some(CommentTag::Return)); assert_eq!(CommentTag::from_str("inheritdoc"), Some(CommentTag::Inheritdoc)); - assert_eq!(CommentTag::from_str("custom:"), Some(CommentTag::Custom("".to_owned()))); + assert_eq!(CommentTag::from_str("custom:"), Some(CommentTag::Custom(String::new()))); assert_eq!( CommentTag::from_str("custom:some"), Some(CommentTag::Custom("some".to_owned())) @@ -227,4 +238,32 @@ mod tests { assert_eq!(CommentTag::from_str("custom"), None); assert_eq!(CommentTag::from_str("sometag"), None); } + + #[test] + fn test_is_custom() { + // Test custom tag. + let custom_comment = Comment::new( + CommentTag::from_str("custom:test").unwrap(), + "dummy custom tag".to_owned(), + ); + assert!(custom_comment.is_custom(), "Custom tag should return true for is_custom"); + + // Test non-custom tags. + let non_custom_tags = [ + CommentTag::Title, + CommentTag::Author, + CommentTag::Notice, + CommentTag::Dev, + CommentTag::Param, + CommentTag::Return, + CommentTag::Inheritdoc, + ]; + for tag in non_custom_tags { + let comment = Comment::new(tag.clone(), "Non-custom comment".to_string()); + assert!( + !comment.is_custom(), + "Non-custom tag {tag:?} should return false for is_custom" + ); + } + } } diff --git a/crates/doc/src/parser/error.rs b/crates/doc/src/parser/error.rs index 26ffb6256fc60..770137f99d173 100644 --- a/crates/doc/src/parser/error.rs +++ b/crates/doc/src/parser/error.rs @@ -2,7 +2,7 @@ use forge_fmt::FormatterError; use thiserror::Error; /// The parser error. -#[derive(Error, Debug)] +#[derive(Debug, Error)] #[error(transparent)] pub enum ParserError { /// Formatter error. diff --git a/crates/doc/src/parser/item.rs b/crates/doc/src/parser/item.rs index b33068341daf3..999758cebc992 100644 --- a/crates/doc/src/parser/item.rs +++ b/crates/doc/src/parser/item.rs @@ -9,7 +9,7 @@ use solang_parser::pt::{ }; /// The parsed item. -#[derive(PartialEq, Debug)] +#[derive(Debug, PartialEq)] pub struct ParseItem { /// The parse tree source. pub source: ParseSource, @@ -27,7 +27,7 @@ pub struct ParseItem { macro_rules! filter_children_fn { ($vis:vis fn $name:ident(&self, $variant:ident) -> $ret:ty) => { /// Filter children items for [ParseSource::$variant] variants. - $vis fn $name(&self) -> Option> { + $vis fn $name(&self) -> Option> { let items = self.children.iter().filter_map(|item| match item.source { ParseSource::$variant(ref inner) => Some((inner, &item.comments, &item.code)), _ => None, @@ -75,7 +75,7 @@ impl ParseItem { } /// Set children on the [ParseItem]. - pub fn with_children(mut self, children: Vec) -> Self { + pub fn with_children(mut self, children: Vec) -> Self { self.children = children; self } @@ -147,7 +147,7 @@ impl ParseItem { } /// A wrapper type around pt token. -#[derive(PartialEq, Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ParseSource { /// Source contract definition. Contract(Box), @@ -171,16 +171,16 @@ impl ParseSource { /// Get the identity of the source pub fn ident(&self) -> String { match self { - ParseSource::Contract(contract) => contract.name.safe_unwrap().name.to_owned(), - ParseSource::Variable(var) => var.name.safe_unwrap().name.to_owned(), - ParseSource::Event(event) => event.name.safe_unwrap().name.to_owned(), - ParseSource::Error(error) => error.name.safe_unwrap().name.to_owned(), - ParseSource::Struct(structure) => structure.name.safe_unwrap().name.to_owned(), - ParseSource::Enum(enumerable) => enumerable.name.safe_unwrap().name.to_owned(), - ParseSource::Function(func) => { + Self::Contract(contract) => contract.name.safe_unwrap().name.to_owned(), + Self::Variable(var) => var.name.safe_unwrap().name.to_owned(), + Self::Event(event) => event.name.safe_unwrap().name.to_owned(), + Self::Error(error) => error.name.safe_unwrap().name.to_owned(), + Self::Struct(structure) => structure.name.safe_unwrap().name.to_owned(), + Self::Enum(enumerable) => enumerable.name.safe_unwrap().name.to_owned(), + Self::Function(func) => { func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()) } - ParseSource::Type(ty) => ty.name.name.to_owned(), + Self::Type(ty) => ty.name.name.to_owned(), } } } diff --git a/crates/doc/src/parser/mod.rs b/crates/doc/src/parser/mod.rs index ae1621aa777c5..9757fec1adc26 100644 --- a/crates/doc/src/parser/mod.rs +++ b/crates/doc/src/parser/mod.rs @@ -23,9 +23,10 @@ pub use item::{ParseItem, ParseSource}; mod comment; pub use comment::{Comment, CommentTag, Comments, CommentsRef}; -/// The documentation parser. This type implements a [Visitor] trait. While walking the parse tree, -/// [Parser] will collect relevant source items and corresponding doc comments. The resulting -/// [ParseItem]s can be accessed by calling [Parser::items]. +/// The documentation parser. This type implements a [Visitor] trait. +/// +/// While walking the parse tree, [Parser] will collect relevant source items and corresponding +/// doc comments. The resulting [ParseItem]s can be accessed by calling [Parser::items]. #[derive(Debug, Default)] pub struct Parser { /// Initial comments from solang parser. @@ -52,7 +53,7 @@ struct ParserContext { impl Parser { /// Create a new instance of [Parser]. pub fn new(comments: Vec, source: String) -> Self { - Parser { comments, source, ..Default::default() } + Self { comments, source, ..Default::default() } } /// Set formatter config on the [Parser] @@ -171,6 +172,14 @@ impl Visitor for Parser { Ok(()) } + fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> { + self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc) + } + + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> { + self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc) + } + fn visit_function(&mut self, func: &mut FunctionDefinition) -> ParserResult<()> { // If the function parameter doesn't have a name, try to set it with // `@custom:name` tag if any was provided @@ -195,8 +204,8 @@ impl Visitor for Parser { self.add_element_to_parent(ParseSource::Function(func.clone()), func.loc) } - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Variable(var.clone()), var.loc) + fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> { + self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc) } fn visit_event(&mut self, event: &mut EventDefinition) -> ParserResult<()> { @@ -207,14 +216,6 @@ impl Visitor for Parser { self.add_element_to_parent(ParseSource::Error(error.clone()), error.loc) } - fn visit_struct(&mut self, structure: &mut StructDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Struct(structure.clone()), structure.loc) - } - - fn visit_enum(&mut self, enumerable: &mut EnumDefinition) -> ParserResult<()> { - self.add_element_to_parent(ParseSource::Enum(enumerable.clone()), enumerable.loc) - } - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> ParserResult<()> { self.add_element_to_parent(ParseSource::Type(def.clone()), def.loc) } @@ -264,15 +265,15 @@ mod tests { #[test] fn multiple_shallow_contracts() { let items = parse_source( - r#" + r" contract A { } contract B { } contract C { } - "#, + ", ); assert_eq!(items.len(), 3); - let first_item = items.get(0).unwrap(); + let first_item = items.first().unwrap(); assert!(matches!(first_item.source, ParseSource::Contract(_))); assert_eq!(first_item.source.ident(), "A"); @@ -288,7 +289,7 @@ mod tests { #[test] fn contract_with_children_items() { let items = parse_source( - r#" + r" event TopLevelEvent(); contract Contract { @@ -304,12 +305,12 @@ mod tests { bool localVar; // must be ignored } } - "#, + ", ); assert_eq!(items.len(), 2); - let event = items.get(0).unwrap(); + let event = items.first().unwrap(); assert!(event.comments.is_empty()); assert!(event.children.is_empty()); assert_eq!(event.source.ident(), "TopLevelEvent"); @@ -327,11 +328,11 @@ mod tests { #[test] fn contract_with_fallback() { let items = parse_source( - r#" + r" contract Contract { fallback() external payable {} } - "#, + ", ); assert_eq!(items.len(), 1); diff --git a/crates/doc/src/preprocessor/contract_inheritance.rs b/crates/doc/src/preprocessor/contract_inheritance.rs index e5cd1e1747aad..ac229a4342f5e 100644 --- a/crates/doc/src/preprocessor/contract_inheritance.rs +++ b/crates/doc/src/preprocessor/contract_inheritance.rs @@ -1,21 +1,24 @@ -use forge_fmt::solang_ext::SafeUnwrap; - use super::{Preprocessor, PreprocessorId}; use crate::{document::DocumentContent, Document, ParseSource, PreprocessorOutput}; -use std::{collections::HashMap, path::PathBuf}; +use alloy_primitives::map::HashMap; +use forge_fmt::solang_ext::SafeUnwrap; +use std::path::PathBuf; /// [ContractInheritance] preprocessor id. pub const CONTRACT_INHERITANCE_ID: PreprocessorId = PreprocessorId("contract_inheritance"); /// The contract inheritance preprocessor. +/// /// It matches the documents with inner [`ParseSource::Contract`](crate::ParseSource) elements, /// iterates over their [Base](solang_parser::pt::Base)s and attempts /// to link them with the paths of the other contract documents. /// /// This preprocessor writes to [Document]'s context. -#[derive(Default, Debug)] -#[non_exhaustive] -pub struct ContractInheritance; +#[derive(Debug, Default)] +pub struct ContractInheritance { + /// Whether to capture inherited contracts from libraries. + pub include_libraries: bool, +} impl Preprocessor for ContractInheritance { fn id(&self) -> PreprocessorId { @@ -52,6 +55,9 @@ impl Preprocessor for ContractInheritance { impl ContractInheritance { fn try_link_base(&self, base: &str, documents: &Vec) -> Option { for candidate in documents { + if candidate.from_library && !self.include_libraries { + continue; + } if let DocumentContent::Single(ref item) = candidate.content { if let ParseSource::Contract(ref contract) = item.source { if base == contract.name.safe_unwrap().name { diff --git a/crates/doc/src/preprocessor/deployments.rs b/crates/doc/src/preprocessor/deployments.rs index f29b357e1db10..9ad360c57487f 100644 --- a/crates/doc/src/preprocessor/deployments.rs +++ b/crates/doc/src/preprocessor/deployments.rs @@ -1,8 +1,10 @@ -use ethers_core::types::Address; - use super::{Preprocessor, PreprocessorId}; use crate::{Document, PreprocessorOutput}; -use std::{fs, path::PathBuf}; +use alloy_primitives::Address; +use std::{ + fs, + path::{Path, PathBuf}, +}; /// [Deployments] preprocessor id. pub const DEPLOYMENTS_ID: PreprocessorId = PreprocessorId("deployments"); @@ -19,7 +21,7 @@ pub struct Deployments { } /// A contract deployment. -#[derive(serde::Deserialize, Debug, Clone)] +#[derive(Clone, Debug, serde::Deserialize)] pub struct Deployment { /// The contract address pub address: Address, @@ -34,20 +36,21 @@ impl Preprocessor for Deployments { fn preprocess(&self, documents: Vec) -> Result, eyre::Error> { let deployments_dir = - self.root.join(self.deployments.as_ref().unwrap_or(&PathBuf::from("deployments"))); + self.root.join(self.deployments.as_deref().unwrap_or(Path::new("deployments"))); // Gather all networks from the deployments directory. let networks = fs::read_dir(&deployments_dir)? - .map(|x| { - x.map(|y| { - if y.file_type()?.is_dir() { - Ok(y.file_name().into_string().map_err(|e| { - eyre::eyre!("Failed to extract directory name: {:?}", e) - })?) - } else { - eyre::bail!("Not a directory.") - } - })? + .map(|entry| { + let entry = entry?; + let path = entry.path(); + if entry.file_type()?.is_dir() { + entry + .file_name() + .into_string() + .map_err(|e| eyre::eyre!("failed to extract directory name: {e:?}")) + } else { + eyre::bail!("not a directory: {}", path.display()) + } }) .collect::, _>>()?; @@ -62,11 +65,10 @@ impl Preprocessor for Deployments { item_path_clone.set_extension("json"); // Determine the path of the deployment artifact relative to the root directory. - let deployment_path = deployments_dir.join(network).join( - item_path_clone - .file_name() - .ok_or(eyre::eyre!("Failed to extract file name from item path"))?, - ); + let deployment_path = + deployments_dir.join(network).join(item_path_clone.file_name().ok_or_else( + || eyre::eyre!("Failed to extract file name from item path"), + )?); // If the deployment file for the given contract is found, add the deployment // address to the document context. diff --git a/crates/doc/src/preprocessor/git_source.rs b/crates/doc/src/preprocessor/git_source.rs index dbacfd8c95e7c..eaa53e6268d5e 100644 --- a/crates/doc/src/preprocessor/git_source.rs +++ b/crates/doc/src/preprocessor/git_source.rs @@ -28,6 +28,9 @@ impl Preprocessor for GitSource { let repo = repo.trim_end_matches('/'); let commit = self.commit.clone().unwrap_or("master".to_owned()); for document in documents.iter() { + if document.from_library { + continue; + } let git_url = format!( "{repo}/blob/{commit}/{}", document.item_path.strip_prefix(&self.root)?.display() diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs new file mode 100644 index 0000000000000..25fafc0325b6b --- /dev/null +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -0,0 +1,324 @@ +use super::{Preprocessor, PreprocessorId}; +use crate::{Comments, Document, ParseItem, ParseSource}; +use forge_fmt::solang_ext::SafeUnwrap; +use regex::{Captures, Match, Regex}; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, + sync::LazyLock, +}; + +/// A regex that matches `{identifier-part}` placeholders +/// +/// Overloaded functions are referenced by including the exact function arguments in the `part` +/// section of the placeholder. +static RE_INLINE_LINK: LazyLock = LazyLock::new(|| { + Regex::new(r"(?m)(\{(?Pxref-)?(?P[a-zA-Z_][0-9a-zA-Z_]*)(-(?P[a-zA-Z_][0-9a-zA-Z_-]*))?}(\[(?P(.*?))\])?)").unwrap() +}); + +/// [InferInlineHyperlinks] preprocessor id. +pub const INFER_INLINE_HYPERLINKS_ID: PreprocessorId = PreprocessorId("infer inline hyperlinks"); + +/// The infer hyperlinks preprocessor tries to map @dev tags to referenced items +/// Traverses the documents and attempts to find referenced items +/// comments for dev comment tags. +/// +/// This preprocessor replaces inline links in comments with the links to the referenced items. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct InferInlineHyperlinks; + +impl Preprocessor for InferInlineHyperlinks { + fn id(&self) -> PreprocessorId { + INFER_INLINE_HYPERLINKS_ID + } + + fn preprocess(&self, mut documents: Vec) -> Result, eyre::Error> { + // traverse all comments and try to match inline links and replace with inline links for + // markdown + let mut docs = Vec::with_capacity(documents.len()); + while !documents.is_empty() { + let mut document = documents.remove(0); + let target_path = document.relative_output_path().to_path_buf(); + for idx in 0..document.content.len() { + let (mut comments, item_children_len) = { + let item = document.content.get_mut(idx).unwrap(); + let comments = std::mem::take(&mut item.comments); + let children = item.children.len(); + (comments, children) + }; + Self::inline_doc_links(&documents, &target_path, &mut comments, &document); + document.content.get_mut(idx).unwrap().comments = comments; + + // we also need to iterate over all child items + // This is a bit horrible but we need to traverse all items in all documents + for child_idx in 0..item_children_len { + let mut comments = { + let item = document.content.get_mut(idx).unwrap(); + + std::mem::take(&mut item.children[child_idx].comments) + }; + Self::inline_doc_links(&documents, &target_path, &mut comments, &document); + document.content.get_mut(idx).unwrap().children[child_idx].comments = comments; + } + } + + docs.push(document); + } + + Ok(docs) + } +} + +impl InferInlineHyperlinks { + /// Finds the first match for the given link. + /// + /// All items get their own section in the markdown file. + /// This section uses the identifier of the item: `#functionname` + /// + /// Note: the target path is the relative path to the markdown file. + fn find_match<'a>( + link: &InlineLink<'a>, + target_path: &Path, + items: impl Iterator, + ) -> Option> { + for item in items { + match &item.source { + ParseSource::Contract(contract) => { + let name = &contract.name.safe_unwrap().name; + if name == link.identifier { + if link.part.is_none() { + return Some(InlineLinkTarget::borrowed(name, target_path.to_path_buf())) + } + // try to find the referenced item in the contract's children + return Self::find_match(link, target_path, item.children.iter()) + } + } + ParseSource::Function(fun) => { + // TODO: handle overloaded functions + // functions can be overloaded so we need to keep track of how many matches we + // have so we can match the correct one + if let Some(id) = &fun.name { + // Note: constructors don't have a name + if id.name == link.ref_name() { + return Some(InlineLinkTarget::borrowed( + &id.name, + target_path.to_path_buf(), + )) + } + } else if link.ref_name() == "constructor" { + return Some(InlineLinkTarget::borrowed( + "constructor", + target_path.to_path_buf(), + )) + } + } + ParseSource::Variable(_) => {} + ParseSource::Event(ev) => { + let ev_name = &ev.name.safe_unwrap().name; + if ev_name == link.ref_name() { + return Some(InlineLinkTarget::borrowed(ev_name, target_path.to_path_buf())) + } + } + ParseSource::Error(err) => { + let err_name = &err.name.safe_unwrap().name; + if err_name == link.ref_name() { + return Some(InlineLinkTarget::borrowed(err_name, target_path.to_path_buf())) + } + } + ParseSource::Struct(structdef) => { + let struct_name = &structdef.name.safe_unwrap().name; + if struct_name == link.ref_name() { + return Some(InlineLinkTarget::borrowed( + struct_name, + target_path.to_path_buf(), + )) + } + } + ParseSource::Enum(_) => {} + ParseSource::Type(_) => {} + } + } + + None + } + + /// Attempts to convert inline links to markdown links. + fn inline_doc_links( + documents: &[Document], + target_path: &Path, + comments: &mut Comments, + parent: &Document, + ) { + // loop over all comments in the item + for comment in comments.iter_mut() { + let val = comment.value.clone(); + // replace all links with inline markdown links + for link in InlineLink::captures(val.as_str()) { + let target = if link.is_external() { + // find in all documents + documents.iter().find_map(|doc| { + Self::find_match( + &link, + doc.relative_output_path(), + doc.content.iter_items().flat_map(|item| { + Some(item).into_iter().chain(item.children.iter()) + }), + ) + }) + } else { + // find matches in the document + Self::find_match( + &link, + target_path, + parent + .content + .iter_items() + .flat_map(|item| Some(item).into_iter().chain(item.children.iter())), + ) + }; + if let Some(target) = target { + let display_value = link.markdown_link_display_value(); + let markdown_link = format!("[{display_value}]({target})"); + // replace the link with the markdown link + comment.value = + comment.value.as_str().replacen(link.as_str(), markdown_link.as_str(), 1); + } + } + } + } +} + +struct InlineLinkTarget<'a> { + section: Cow<'a, str>, + target_path: PathBuf, +} + +impl<'a> InlineLinkTarget<'a> { + fn borrowed(section: &'a str, target_path: PathBuf) -> Self { + Self { section: Cow::Borrowed(section), target_path } + } +} + +impl std::fmt::Display for InlineLinkTarget<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // NOTE: the url should be absolute for markdown and section names are lowercase + write!(f, "/{}#{}", self.target_path.display(), self.section.to_lowercase()) + } +} + +/// A parsed link to an item. +#[derive(Debug)] +struct InlineLink<'a> { + outer: Match<'a>, + identifier: &'a str, + part: Option<&'a str>, + link: Option<&'a str>, +} + +impl<'a> InlineLink<'a> { + fn from_capture(cap: Captures<'a>) -> Option { + Some(Self { + outer: cap.get(1)?, + identifier: cap.name("identifier")?.as_str(), + part: cap.name("part").map(|m| m.as_str()), + link: cap.name("link").map(|m| m.as_str()), + }) + } + + fn captures(s: &'a str) -> impl Iterator + 'a { + RE_INLINE_LINK.captures(s).map(Self::from_capture).into_iter().flatten() + } + + /// Parses the first inline link. + #[allow(unused)] + fn capture(s: &'a str) -> Option { + let cap = RE_INLINE_LINK.captures(s)?; + Self::from_capture(cap) + } + + /// Returns the name of the link + fn markdown_link_display_value(&self) -> Cow<'_, str> { + if let Some(link) = self.link { + Cow::Borrowed(link) + } else if let Some(part) = self.part { + Cow::Owned(format!("{}-{}", self.identifier, part)) + } else { + Cow::Borrowed(self.identifier) + } + } + + /// Returns the name of the referenced item. + fn ref_name(&self) -> &str { + self.exact_identifier().split('-').next().unwrap() + } + + fn exact_identifier(&self) -> &str { + let mut name = self.identifier; + if let Some(part) = self.part { + name = part; + } + name + } + + /// Returns the name of the referenced item and its arguments, if any. + /// + /// Eg: `safeMint-address-uint256-` returns `("safeMint", ["address", "uint256"])` + #[allow(unused)] + fn ref_name_exact(&self) -> (&str, impl Iterator + '_) { + let identifier = self.exact_identifier(); + let mut iter = identifier.split('-'); + (iter.next().unwrap(), iter.filter(|s| !s.is_empty())) + } + + /// Returns the content of the matched link. + fn as_str(&self) -> &str { + self.outer.as_str() + } + + /// Returns true if the link is external. + fn is_external(&self) -> bool { + self.part.is_some() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_inline_links() { + let s = " {IERC165-supportsInterface} "; + let cap = RE_INLINE_LINK.captures(s).unwrap(); + + let identifier = cap.name("identifier").unwrap().as_str(); + assert_eq!(identifier, "IERC165"); + let part = cap.name("part").unwrap().as_str(); + assert_eq!(part, "supportsInterface"); + + let s = " {supportsInterface} "; + let cap = RE_INLINE_LINK.captures(s).unwrap(); + + let identifier = cap.name("identifier").unwrap().as_str(); + assert_eq!(identifier, "supportsInterface"); + + let s = "{xref-ERC721-_safeMint-address-uint256-}"; + let cap = RE_INLINE_LINK.captures(s).unwrap(); + + let identifier = cap.name("identifier").unwrap().as_str(); + assert_eq!(identifier, "ERC721"); + let identifier = cap.name("xref").unwrap().as_str(); + assert_eq!(identifier, "xref-"); + let identifier = cap.name("part").unwrap().as_str(); + assert_eq!(identifier, "_safeMint-address-uint256-"); + + let link = InlineLink::capture(s).unwrap(); + assert_eq!(link.ref_name(), "_safeMint"); + assert_eq!(link.as_str(), "{xref-ERC721-_safeMint-address-uint256-}"); + + let s = "{xref-ERC721-_safeMint-address-uint256-}[`Named link`]"; + let link = InlineLink::capture(s).unwrap(); + assert_eq!(link.link, Some("`Named link`")); + assert_eq!(link.markdown_link_display_value(), "`Named link`"); + } +} diff --git a/crates/doc/src/preprocessor/inheritdoc.rs b/crates/doc/src/preprocessor/inheritdoc.rs index 4abbdf15448a2..65cbb688c443d 100644 --- a/crates/doc/src/preprocessor/inheritdoc.rs +++ b/crates/doc/src/preprocessor/inheritdoc.rs @@ -1,13 +1,11 @@ -use std::collections::HashMap; - -use forge_fmt::solang_ext::SafeUnwrap; - use super::{Preprocessor, PreprocessorId}; use crate::{ document::DocumentContent, Comments, Document, ParseItem, ParseSource, PreprocessorOutput, }; +use alloy_primitives::map::HashMap; +use forge_fmt::solang_ext::SafeUnwrap; -/// [ContractInheritance] preprocessor id. +/// [`Inheritdoc`] preprocessor ID. pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc"); /// The inheritdoc preprocessor. @@ -15,7 +13,7 @@ pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc"); /// comments for inheritdoc comment tags. /// /// This preprocessor writes to [Document]'s context. -#[derive(Default, Debug)] +#[derive(Debug, Default)] #[non_exhaustive] pub struct Inheritdoc; diff --git a/crates/doc/src/preprocessor/mod.rs b/crates/doc/src/preprocessor/mod.rs index 5874dab54cd16..5011b59a1b5e8 100644 --- a/crates/doc/src/preprocessor/mod.rs +++ b/crates/doc/src/preprocessor/mod.rs @@ -1,7 +1,8 @@ //! Module containing documentation preprocessors. use crate::{Comments, Document}; -use std::{collections::HashMap, fmt::Debug, path::PathBuf}; +use alloy_primitives::map::HashMap; +use std::{fmt::Debug, path::PathBuf}; mod contract_inheritance; pub use contract_inheritance::{ContractInheritance, CONTRACT_INHERITANCE_ID}; @@ -9,6 +10,9 @@ pub use contract_inheritance::{ContractInheritance, CONTRACT_INHERITANCE_ID}; mod inheritdoc; pub use inheritdoc::{Inheritdoc, INHERITDOC_ID}; +mod infer_hyperlinks; +pub use infer_hyperlinks::{InferInlineHyperlinks, INFER_INLINE_HYPERLINKS_ID}; + mod git_source; pub use git_source::{GitSource, GIT_SOURCE_ID}; @@ -16,13 +20,13 @@ mod deployments; pub use deployments::{Deployment, Deployments, DEPLOYMENTS_ID}; /// The preprocessor id. -#[derive(Debug, Eq, Hash, PartialEq)] +#[derive(Debug, PartialEq, Eq, Hash)] pub struct PreprocessorId(&'static str); /// Preprocessor output. -/// Wraps all exisiting preprocessor outputs +/// Wraps all existing preprocessor outputs /// in a single abstraction. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum PreprocessorOutput { /// The contract inheritance output. /// The map of contract base idents to the path of the base contract. diff --git a/crates/doc/src/server.rs b/crates/doc/src/server.rs deleted file mode 100644 index f4ac6567ccece..0000000000000 --- a/crates/doc/src/server.rs +++ /dev/null @@ -1,121 +0,0 @@ -use futures_util::{SinkExt, StreamExt}; -use mdbook::{utils::fs::get_404_output_file, MDBook}; -use std::{ - net::{SocketAddr, ToSocketAddrs}, - path::PathBuf, -}; -use tokio::sync::broadcast; -use warp::{ws::Message, Filter}; - -/// The HTTP endpoint for the websocket used to trigger reloads when a file changes. -const LIVE_RELOAD_ENDPOINT: &str = "__livereload"; - -/// Basic mdbook server. Given a path, hostname and port, serves the mdbook. -#[derive(Debug)] -pub struct Server { - path: PathBuf, - hostname: String, - port: usize, -} - -impl Default for Server { - fn default() -> Self { - Self { path: PathBuf::default(), hostname: "localhost".to_owned(), port: 3000 } - } -} - -impl Server { - /// Create new instance of [Server]. - pub fn new(path: PathBuf) -> Self { - Self { path, ..Default::default() } - } - - /// Set host on the [Server]. - pub fn with_hostname(mut self, hostname: String) -> Self { - self.hostname = hostname; - self - } - - /// Set port on the [Server]. - pub fn with_port(mut self, port: usize) -> Self { - self.port = port; - self - } - - /// Serve the mdbook. - pub fn serve(self) -> eyre::Result<()> { - let mut book = - MDBook::load(&self.path).map_err(|err| eyre::eyre!("failed to load book: {err:?}"))?; - - let address = format!("{}:{}", self.hostname, self.port); - - let update_config = |book: &mut MDBook| { - book.config - .set("output.html.live-reload-endpoint", LIVE_RELOAD_ENDPOINT) - .expect("live-reload-endpoint update failed"); - // Override site-url for local serving of the 404 file - book.config.set("output.html.site-url", "/").unwrap(); - }; - update_config(&mut book); - book.build().map_err(|err| eyre::eyre!("failed to build book: {err:?}"))?; - - let sockaddr: SocketAddr = address - .to_socket_addrs()? - .next() - .ok_or_else(|| eyre::eyre!("no address found for {}", address))?; - let build_dir = book.build_dir_for("html"); - let input_404 = book - .config - .get("output.html.input-404") - .and_then(|v| v.as_str()) - .map(ToString::to_string); - let file_404 = get_404_output_file(&input_404); - - // A channel used to broadcast to any websockets to reload when a file changes. - let (tx, _rx) = tokio::sync::broadcast::channel::(100); - - let thread_handle = std::thread::spawn(move || { - serve(build_dir, sockaddr, tx, &file_404); - }); - - println!("Serving on: http://{address}"); - - let _ = thread_handle.join(); - - Ok(()) - } -} - -// Adapted from https://github.com/rust-lang/mdBook/blob/41a6f0d43e1a2d9543877eacb4cd2a017f9fe8da/src/cmd/serve.rs#L124 -#[tokio::main] -async fn serve( - build_dir: PathBuf, - address: SocketAddr, - reload_tx: broadcast::Sender, - file_404: &str, -) { - // A warp Filter which captures `reload_tx` and provides an `rx` copy to - // receive reload messages. - let sender = warp::any().map(move || reload_tx.subscribe()); - - // A warp Filter to handle the livereload endpoint. This upgrades to a - // websocket, and then waits for any filesystem change notifications, and - // relays them over the websocket. - let livereload = warp::path(LIVE_RELOAD_ENDPOINT).and(warp::ws()).and(sender).map( - |ws: warp::ws::Ws, mut rx: broadcast::Receiver| { - ws.on_upgrade(move |ws| async move { - let (mut user_ws_tx, _user_ws_rx) = ws.split(); - if let Ok(m) = rx.recv().await { - let _ = user_ws_tx.send(m).await; - } - }) - }, - ); - // A warp Filter that serves from the filesystem. - let book_route = warp::fs::dir(build_dir.clone()); - // The fallback route for 404 errors - let fallback_route = warp::fs::file(build_dir.join(file_404)) - .map(|reply| warp::reply::with_status(reply, warp::http::StatusCode::NOT_FOUND)); - let routes = livereload.or(book_route).or(fallback_route); - warp::serve(routes).run(address).await; -} diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index ae501f379134f..56a0a4026c504 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use crate::{ document::{read_context, DocumentContent}, parser::ParseSource, @@ -9,13 +7,13 @@ use crate::{ }; use forge_fmt::solang_ext::SafeUnwrap; use itertools::Itertools; -use solang_parser::pt::Base; +use solang_parser::pt::{Base, FunctionDefinition}; +use std::path::{Path, PathBuf}; -/// The result of [Asdoc::as_doc] method. +/// The result of [`AsDoc::as_doc`]. pub type AsDocResult = Result; /// A trait for formatting a parse unit as documentation. -#[auto_impl::auto_impl(&)] pub trait AsDoc { /// Formats a parse tree item into a doc string. fn as_doc(&self) -> AsDocResult; @@ -33,7 +31,7 @@ impl AsDoc for Comments { } } -impl<'a> AsDoc for CommentsRef<'a> { +impl AsDoc for CommentsRef<'_> { // TODO: support other tags fn as_doc(&self) -> AsDocResult { let mut writer = BufWriter::default(); @@ -48,18 +46,33 @@ impl<'a> AsDoc for CommentsRef<'a> { // Write notice tags let notices = self.include_tag(CommentTag::Notice); - for notice in notices.iter() { - writer.writeln_raw(¬ice.value)?; + for n in notices.iter() { + writer.writeln_raw(&n.value)?; writer.writeln()?; } // Write dev tags let devs = self.include_tag(CommentTag::Dev); - for dev in devs.iter() { - writer.write_italic(&dev.value)?; + for d in devs.iter() { + writer.write_italic(&d.value)?; writer.writeln()?; } + // Write custom tags + let customs = self.get_custom_tags(); + if !customs.is_empty() { + writer.write_bold(&format!("Note{}:", if customs.len() == 1 { "" } else { "s" }))?; + for c in customs.iter() { + writer.writeln_raw(format!( + "{}{}: {}", + if customs.len() == 1 { "" } else { "- " }, + &c.tag, + &c.value + ))?; + writer.writeln()?; + } + } + Ok(writer.finish()) } } @@ -128,32 +141,33 @@ impl AsDoc for Document { if !contract.base.is_empty() { writer.write_bold("Inherits:")?; + // we need this to find the _relative_ paths + let src_target_dir = self.target_src_dir(); + let mut bases = vec![]; let linked = read_context!(self, CONTRACT_INHERITANCE_ID, ContractInheritance); for base in contract.base.iter() { let base_doc = base.as_doc()?; let base_ident = &base.name.identifiers.last().unwrap().name; - bases.push( - linked - .as_ref() - .and_then(|l| { - l.get(base_ident).map(|path| { - let path = Path::new("/").join( - path.strip_prefix("docs/src") - .ok() - .unwrap_or(path), - ); - Markdown::Link( - &base_doc, - &path.display().to_string(), - ) + + let link = linked + .as_ref() + .and_then(|link| { + link.get(base_ident).map(|path| { + let path = Path::new("/").join( + path.strip_prefix(&src_target_dir) + .ok() + .unwrap_or(path), + ); + Markdown::Link(&base_doc, &path.display().to_string()) .as_doc() - }) }) - .transpose()? - .unwrap_or(base_doc), - ) + }) + .transpose()? + .unwrap_or(base_doc); + + bases.push(link); } writer.writeln_raw(bases.join(", "))?; @@ -178,63 +192,18 @@ impl AsDoc for Document { if let Some(funcs) = item.functions() { writer.write_subtitle("Functions")?; - funcs.into_iter().try_for_each(|(func, comments, code)| { - let func_name = func - .name - .as_ref() - .map_or(func.ty.to_string(), |n| n.name.to_owned()); - let comments = comments.merge_inheritdoc( - &func_name, - read_context!(self, INHERITDOC_ID, Inheritdoc), - ); - // Write function name - writer.write_heading(&func_name)?; - writer.writeln()?; - - // Write function docs - writer.writeln_doc( - comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]), - )?; - - // Write function header - writer.write_code(code)?; - - // Write function parameter comments in a table - let params = func - .params - .iter() - .filter_map(|p| p.1.as_ref()) - .collect::>(); - writer.try_write_param_table( - CommentTag::Param, - ¶ms, - &comments, - )?; - - // Write function parameter comments in a table - let returns = func - .returns - .iter() - .filter_map(|p| p.1.as_ref()) - .collect::>(); - writer.try_write_param_table( - CommentTag::Return, - &returns, - &comments, - )?; - - writer.writeln()?; - - Ok::<(), std::fmt::Error>(()) - })?; + for (func, comments, code) in funcs.iter() { + self.write_function(&mut writer, func, comments, code)?; + } } if let Some(events) = item.events() { writer.write_subtitle("Events")?; events.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code) + writer.write_section(comments, code)?; + writer.try_write_events_table(&item.fields, comments) })?; } @@ -242,7 +211,8 @@ impl AsDoc for Document { writer.write_subtitle("Errors")?; errors.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code) + writer.write_section(comments, code)?; + writer.try_write_errors_table(&item.fields, comments) })?; } @@ -250,7 +220,8 @@ impl AsDoc for Document { writer.write_subtitle("Structs")?; structs.into_iter().try_for_each(|(item, comments, code)| { writer.write_heading(&item.name.safe_unwrap().name)?; - writer.write_section(comments, code) + writer.write_section(comments, code)?; + writer.try_write_properties_table(&item.fields, comments) })?; } @@ -267,7 +238,7 @@ impl AsDoc for Document { // TODO: cleanup // Write function docs writer.writeln_doc( - item.comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]), + &item.comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]), )?; // Write function header @@ -278,7 +249,7 @@ impl AsDoc for Document { func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); writer.try_write_param_table(CommentTag::Param, ¶ms, &item.comments)?; - // Write function parameter comments in a table + // Write function return parameter comments in a table let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); writer.try_write_param_table( @@ -290,12 +261,19 @@ impl AsDoc for Document { writer.writeln()?; } - ParseSource::Variable(_) | - ParseSource::Event(_) | - ParseSource::Error(_) | - ParseSource::Struct(_) | - ParseSource::Enum(_) | - ParseSource::Type(_) => { + ParseSource::Struct(ty) => { + writer.write_section(&item.comments, &item.code)?; + writer.try_write_properties_table(&ty.fields, &item.comments)?; + } + ParseSource::Event(ev) => { + writer.write_section(&item.comments, &item.code)?; + writer.try_write_events_table(&ev.fields, &item.comments)?; + } + ParseSource::Error(err) => { + writer.write_section(&item.comments, &item.code)?; + writer.try_write_errors_table(&err.fields, &item.comments)?; + } + ParseSource::Variable(_) | ParseSource::Enum(_) | ParseSource::Type(_) => { writer.write_section(&item.comments, &item.code)?; } } @@ -306,3 +284,45 @@ impl AsDoc for Document { Ok(writer.finish()) } } + +impl Document { + /// Where all the source files are written to + fn target_src_dir(&self) -> PathBuf { + self.out_target_dir.join("src") + } + + /// Writes a function to the buffer. + fn write_function( + &self, + writer: &mut BufWriter, + func: &FunctionDefinition, + comments: &Comments, + code: &str, + ) -> Result<(), std::fmt::Error> { + let func_name = func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()); + let comments = + comments.merge_inheritdoc(&func_name, read_context!(self, INHERITDOC_ID, Inheritdoc)); + + // Write function name + writer.write_heading(&func_name)?; + + writer.writeln()?; + + // Write function docs + writer.writeln_doc(&comments.exclude_tags(&[CommentTag::Param, CommentTag::Return]))?; + + // Write function header + writer.write_code(code)?; + + // Write function parameter comments in a table + let params = func.params.iter().filter_map(|p| p.1.as_ref()).collect::>(); + writer.try_write_param_table(CommentTag::Param, ¶ms, &comments)?; + + // Write function return parameter comments in a table + let returns = func.returns.iter().filter_map(|p| p.1.as_ref()).collect::>(); + writer.try_write_param_table(CommentTag::Return, &returns, &comments)?; + + writer.writeln()?; + Ok(()) + } +} diff --git a/crates/doc/src/writer/buf_writer.rs b/crates/doc/src/writer/buf_writer.rs index 5e75089ec9058..e6109c338c03f 100644 --- a/crates/doc/src/writer/buf_writer.rs +++ b/crates/doc/src/writer/buf_writer.rs @@ -1,27 +1,27 @@ -use ethers_core::utils::hex; +use crate::{writer::traits::ParamLike, AsDoc, CommentTag, Comments, Deployment, Markdown}; use itertools::Itertools; -use once_cell::sync::Lazy; -use solang_parser::pt::Parameter; -use std::fmt::{self, Display, Write}; - -use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown}; +use solang_parser::pt::{ErrorParameter, EventParameter, Parameter, VariableDeclaration}; +use std::{ + fmt::{self, Display, Write}, + sync::LazyLock, +}; /// Solidity language name. const SOLIDITY: &str = "solidity"; /// Headers and separator for rendering parameter table. const PARAM_TABLE_HEADERS: &[&str] = &["Name", "Type", "Description"]; -static PARAM_TABLE_SEPARATOR: Lazy = - Lazy::new(|| PARAM_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); +static PARAM_TABLE_SEPARATOR: LazyLock = + LazyLock::new(|| PARAM_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); /// Headers and separator for rendering the deployments table. const DEPLOYMENTS_TABLE_HEADERS: &[&str] = &["Network", "Address"]; -static DEPLOYMENTS_TABLE_SEPARATOR: Lazy = - Lazy::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); +static DEPLOYMENTS_TABLE_SEPARATOR: LazyLock = + LazyLock::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|")); /// The buffered writer. /// Writes various display items into the internal buffer. -#[derive(Default, Debug)] +#[derive(Debug, Default)] pub struct BufWriter { buf: String, } @@ -43,7 +43,7 @@ impl BufWriter { } /// Write [AsDoc] implementation to the buffer with newline. - pub fn writeln_doc(&mut self, doc: T) -> fmt::Result { + pub fn writeln_doc(&mut self, doc: &T) -> fmt::Result { writeln!(self.buf, "{}", doc.as_doc()?) } @@ -82,7 +82,7 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Italic(text)) } - /// Writes bold text to the bufffer formatted as [Markdown::Bold]. + /// Writes bold text to the buffer formatted as [Markdown::Bold]. pub fn write_bold(&mut self, text: &str) -> fmt::Result { writeln!(self.buf, "{}", Markdown::Bold(text)) } @@ -92,7 +92,7 @@ impl BufWriter { writeln!(self.buf, "{}", Markdown::Link(name, path)) } - /// Writes a list item to the bufffer indented by specified depth. + /// Writes a list item to the buffer indented by specified depth. pub fn write_list_item(&mut self, item: &str, depth: usize) -> fmt::Result { let indent = " ".repeat(depth * 2); writeln!(self.buf, "{indent}- {item}") @@ -116,14 +116,18 @@ impl BufWriter { self.writeln() } - /// Tries to write the parameters table to the buffer. + /// Tries to write the table to the buffer. /// Doesn't write anything if either params or comments are empty. - pub fn try_write_param_table( + fn try_write_table( &mut self, tag: CommentTag, - params: &[&Parameter], + params: &[T], comments: &Comments, - ) -> fmt::Result { + heading: &str, + ) -> fmt::Result + where + T: ParamLike, + { let comments = comments.include_tag(tag.clone()); // There is nothing to write. @@ -131,12 +135,6 @@ impl BufWriter { return Ok(()) } - let heading = match &tag { - CommentTag::Param => "Parameters", - CommentTag::Return => "Returns", - _ => return Err(fmt::Error), - }; - self.write_bold(heading)?; self.writeln()?; @@ -144,10 +142,10 @@ impl BufWriter { self.write_piped(&PARAM_TABLE_SEPARATOR)?; for (index, param) in params.iter().enumerate() { - let param_name = param.name.as_ref().map(|n| n.name.to_owned()); + let param_name = param.name(); let mut comment = param_name.as_ref().and_then(|name| { - comments.iter().find_map(|comment| comment.match_first_word(name.as_str())) + comments.iter().find_map(|comment| comment.match_first_word(name)) }); // If it's a return tag and couldn't match by first word, @@ -157,8 +155,8 @@ impl BufWriter { } let row = [ - Markdown::Code(¶m_name.unwrap_or_else(|| "".to_owned())).as_doc()?, - Markdown::Code(¶m.ty.to_string()).as_doc()?, + Markdown::Code(param_name.unwrap_or("")).as_doc()?, + Markdown::Code(¶m.type_name()).as_doc()?, comment.unwrap_or_default().replace('\n', " "), ]; self.write_piped(&row.join("|"))?; @@ -169,6 +167,53 @@ impl BufWriter { Ok(()) } + /// Tries to write the properties table to the buffer. + /// Doesn't write anything if either params or comments are empty. + pub fn try_write_properties_table( + &mut self, + params: &[VariableDeclaration], + comments: &Comments, + ) -> fmt::Result { + self.try_write_table(CommentTag::Param, params, comments, "Properties") + } + + /// Tries to write the parameters table to the buffer. + /// Doesn't write anything if either params or comments are empty. + pub fn try_write_events_table( + &mut self, + params: &[EventParameter], + comments: &Comments, + ) -> fmt::Result { + self.try_write_table(CommentTag::Param, params, comments, "Parameters") + } + + /// Tries to write the parameters table to the buffer. + /// Doesn't write anything if either params or comments are empty. + pub fn try_write_errors_table( + &mut self, + params: &[ErrorParameter], + comments: &Comments, + ) -> fmt::Result { + self.try_write_table(CommentTag::Param, params, comments, "Parameters") + } + + /// Tries to write the parameters table to the buffer. + /// Doesn't write anything if either params or comments are empty. + pub fn try_write_param_table( + &mut self, + tag: CommentTag, + params: &[&Parameter], + comments: &Comments, + ) -> fmt::Result { + let heading = match &tag { + CommentTag::Param => "Parameters", + CommentTag::Return => "Returns", + _ => return Err(fmt::Error), + }; + + self.try_write_table(tag, params, comments, heading) + } + /// Writes the deployment table to the buffer. pub fn write_deployments_table(&mut self, deployments: Vec) -> fmt::Result { self.write_bold("Deployments")?; @@ -183,7 +228,7 @@ impl BufWriter { let row = [ Markdown::Bold(&network).as_doc()?, - Markdown::Code(&format!("0x{}", hex::encode(deployment.address))).as_doc()?, + Markdown::Code(&format!("{:?}", deployment.address)).as_doc()?, ]; self.write_piped(&row.join("|"))?; } diff --git a/crates/doc/src/writer/markdown.rs b/crates/doc/src/writer/markdown.rs index 2e577ad9601c9..7583c35e79ef8 100644 --- a/crates/doc/src/writer/markdown.rs +++ b/crates/doc/src/writer/markdown.rs @@ -21,7 +21,7 @@ pub enum Markdown<'a> { CodeBlock(&'a str, &'a str), } -impl<'a> AsDoc for Markdown<'a> { +impl AsDoc for Markdown<'_> { fn as_doc(&self) -> AsDocResult { let doc = match self { Self::H1(val) => format!("# {val}"), @@ -37,7 +37,7 @@ impl<'a> AsDoc for Markdown<'a> { } } -impl<'a> std::fmt::Display for Markdown<'a> { +impl std::fmt::Display for Markdown<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!("{}", self.as_doc()?)) } diff --git a/crates/doc/src/writer/mod.rs b/crates/doc/src/writer/mod.rs index 47f497e0cc7d9..0d7c94205d294 100644 --- a/crates/doc/src/writer/mod.rs +++ b/crates/doc/src/writer/mod.rs @@ -7,3 +7,5 @@ mod markdown; pub use as_doc::{AsDoc, AsDocResult}; pub use buf_writer::BufWriter; pub use markdown::Markdown; + +mod traits; diff --git a/crates/doc/src/writer/traits.rs b/crates/doc/src/writer/traits.rs new file mode 100644 index 0000000000000..0b79718d5102f --- /dev/null +++ b/crates/doc/src/writer/traits.rs @@ -0,0 +1,70 @@ +//! Helper traits for writing documentation. + +use solang_parser::pt::Expression; + +/// Helper trait to abstract over a solang type that can be documented as parameter +pub(crate) trait ParamLike { + /// Returns the type of the parameter. + fn ty(&self) -> &Expression; + + /// Returns the type as a string. + fn type_name(&self) -> String { + self.ty().to_string() + } + + /// Returns the identifier of the parameter. + fn name(&self) -> Option<&str>; +} + +impl ParamLike for solang_parser::pt::Parameter { + fn ty(&self) -> &Expression { + &self.ty + } + + fn name(&self) -> Option<&str> { + self.name.as_ref().map(|id| id.name.as_str()) + } +} + +impl ParamLike for solang_parser::pt::VariableDeclaration { + fn ty(&self) -> &Expression { + &self.ty + } + + fn name(&self) -> Option<&str> { + self.name.as_ref().map(|id| id.name.as_str()) + } +} + +impl ParamLike for solang_parser::pt::EventParameter { + fn ty(&self) -> &Expression { + &self.ty + } + + fn name(&self) -> Option<&str> { + self.name.as_ref().map(|id| id.name.as_str()) + } +} + +impl ParamLike for solang_parser::pt::ErrorParameter { + fn ty(&self) -> &Expression { + &self.ty + } + + fn name(&self) -> Option<&str> { + self.name.as_ref().map(|id| id.name.as_str()) + } +} + +impl ParamLike for &T +where + T: ParamLike, +{ + fn ty(&self) -> &Expression { + T::ty(*self) + } + + fn name(&self) -> Option<&str> { + T::name(*self) + } +} diff --git a/crates/doc/static/book.toml b/crates/doc/static/book.toml index 11c8d042906b8..617db3095a4b9 100644 --- a/crates/doc/static/book.toml +++ b/crates/doc/static/book.toml @@ -6,6 +6,7 @@ src = "src" no-section-label = true additional-js = ["solidity.min.js"] additional-css = ["book.css"] +mathjax-support = true [output.html.fold] enable = true diff --git a/crates/evm/Cargo.toml b/crates/evm/Cargo.toml deleted file mode 100644 index 4ebb920539d46..0000000000000 --- a/crates/evm/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "foundry-evm" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -foundry-abi.workspace = true -foundry-utils.workspace = true -foundry-common.workspace = true -foundry-config.workspace = true -foundry-macros.workspace = true - -ethers = { workspace = true, features = ["ethers-solc"] } - -# Encoding/decoding -serde_json = "1" -serde = "1" -hex.workspace = true -jsonpath_lib = "0.3" - -# Error handling -eyre = "0.6" -thiserror = "1" - -# Logging -tracing = "0.1" - -# Threading/futures -tokio = { version = "1", features = ["time", "macros"] } -parking_lot = "0.12" -futures = "0.3" -once_cell = "1" - -# EVM -bytes = "1" -hashbrown = { version = "0.13", features = ["serde"] } -revm = { version = "3", default-features = false, features = [ - "std", - "serde", - "memory_limit", - "optional_eip3607", - "optional_block_gas_limit", - "optional_no_base_fee", -] } - -# Fuzzer -proptest = "1" - -# Display -yansi = "0.5" - -# Misc -url = "2" -auto_impl = "1" -itertools.workspace = true -ordered-float = "3" -walkdir = "2" - -# Coverage -semver = "1" - -[dev-dependencies] -foundry-utils.workspace = true -tempfile = "3" diff --git a/crates/evm/abi/Cargo.toml b/crates/evm/abi/Cargo.toml new file mode 100644 index 0000000000000..b3202c96799f1 --- /dev/null +++ b/crates/evm/abi/Cargo.toml @@ -0,0 +1,27 @@ +[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 + +[dev-dependencies] +foundry-test-utils.workspace = true diff --git a/crates/evm/abi/src/Console.json b/crates/evm/abi/src/Console.json new file mode 100644 index 0000000000000..54e6d46dff349 --- /dev/null +++ b/crates/evm/abi/src/Console.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 0000000000000..2e28fece21e42 --- /dev/null +++ b/crates/evm/abi/src/console.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +# Generates the JSON ABI for console.sol. + +import json +import re +import subprocess +import sys + + +def main(): + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + [console_file, abi_file] = sys.argv[1:3] + + # 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)) + + +if __name__ == "__main__": + main() diff --git a/crates/evm/abi/src/console/ds.rs b/crates/evm/abi/src/console/ds.rs new file mode 100644 index 0000000000000..444be0d77dce5 --- /dev/null +++ b/crates/evm/abi/src/console/ds.rs @@ -0,0 +1,83 @@ +//! DSTest log interface. + +use super::{format_units_int, format_units_uint}; +use alloy_primitives::hex; +use alloy_sol_types::sol; +use derive_more::Display; +use itertools::Itertools; + +// TODO: Use `UiFmt` + +sol! { +#[sol(abi)] +#[derive(Display)] +interface Console { + #[display("{val}")] + event log(string val); + + #[display("{}", hex::encode_prefixed(val))] + event logs(bytes val); + + #[display("{val}")] + event log_address(address val); + + #[display("{val}")] + event log_bytes32(bytes32 val); + + #[display("{val}")] + event log_int(int val); + + #[display("{val}")] + event log_uint(uint val); + + #[display("{}", hex::encode_prefixed(val))] + event log_bytes(bytes val); + + #[display("{val}")] + event log_string(string val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(uint256[] val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(int256[] val); + + #[display("[{}]", val.iter().format(", "))] + event log_array(address[] val); + + #[display("{key}: {val}")] + event log_named_address(string key, address val); + + #[display("{key}: {val}")] + event log_named_bytes32(string key, bytes32 val); + + #[display("{key}: {}", format_units_int(val, decimals))] + event log_named_decimal_int(string key, int val, uint decimals); + + #[display("{key}: {}", format_units_uint(val, decimals))] + event log_named_decimal_uint(string key, uint val, uint decimals); + + #[display("{key}: {val}")] + event log_named_int(string key, int val); + + #[display("{key}: {val}")] + event log_named_uint(string key, uint val); + + #[display("{key}: {}", hex::encode_prefixed(val))] + event log_named_bytes(string key, bytes val); + + #[display("{key}: {val}")] + event log_named_string(string key, string val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, uint256[] val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, int256[] val); + + #[display("{key}: [{}]", val.iter().format(", "))] + event log_named_array(string key, address[] val); +} +} + +pub use Console::*; diff --git a/crates/evm/abi/src/console/hh.rs b/crates/evm/abi/src/console/hh.rs new file mode 100644 index 0000000000000..3f4f6a240d182 --- /dev/null +++ b/crates/evm/abi/src/console/hh.rs @@ -0,0 +1,14 @@ +//! Hardhat `console.sol` interface. + +use alloy_sol_types::sol; +use foundry_common_fmt::*; +use foundry_macros::ConsoleFmt; + +sol!( + #[sol(abi)] + #[derive(ConsoleFmt)] + Console, + "src/Console.json" +); + +pub use Console::*; diff --git a/crates/evm/abi/src/console/mod.rs b/crates/evm/abi/src/console/mod.rs new file mode 100644 index 0000000000000..1e2b23d83ff7c --- /dev/null +++ b/crates/evm/abi/src/console/mod.rs @@ -0,0 +1,16 @@ +use alloy_primitives::{I256, U256}; + +pub mod ds; +pub mod hh; + +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)) +} + +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/evm/abi/src/lib.rs b/crates/evm/abi/src/lib.rs new file mode 100644 index 0000000000000..8e9313c2f516e --- /dev/null +++ b/crates/evm/abi/src/lib.rs @@ -0,0 +1,6 @@ +//! Solidity ABI-related utilities and [`sol!`](alloy_sol_types::sol) definitions. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +pub mod console; diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml new file mode 100644 index 0000000000000..5730e6fb3ab4a --- /dev/null +++ b/crates/evm/core/Cargo.toml @@ -0,0 +1,65 @@ +[package] +name = "foundry-evm-core" +description = "Core EVM abstractions" + +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-cheatcodes-spec.workspace = true +foundry-common.workspace = true +foundry-config.workspace = true +foundry-evm-abi.workspace = true + +alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-genesis.workspace = true +alloy-json-abi.workspace = true +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-provider.workspace = true +alloy-network.workspace = true +alloy-consensus.workspace = true +alloy-rpc-types.workspace = true +alloy-sol-types.workspace = true +foundry-fork-db.workspace = true + +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "optional_eip3607", + "optional_block_gas_limit", + "optional_no_base_fee", + "arbitrary", + "optimism", + "c-kzg", + "blst", +] } +revm-inspectors.workspace = true + +auto_impl.workspace = true +eyre.workspace = true +futures.workspace = true +itertools.workspace = true +parking_lot.workspace = true +serde.workspace = true +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/backend/cow.rs b/crates/evm/core/src/backend/cow.rs new file mode 100644 index 0000000000000..3ffb9fc84f3cd --- /dev/null +++ b/crates/evm/core/src/backend/cow.rs @@ -0,0 +1,318 @@ +//! A wrapper around `Backend` that is clone-on-write used for fuzzing. + +use super::BackendError; +use crate::{ + backend::{ + diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertStateSnapshotAction, + }, + fork::{CreateFork, ForkId}, + InspectorExt, +}; +use alloy_genesis::GenesisAccount; +use alloy_primitives::{Address, B256, U256}; +use alloy_rpc_types::TransactionRequest; +use eyre::WrapErr; +use foundry_fork_db::DatabaseError; +use revm::{ + db::DatabaseRef, + primitives::{ + Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, ResultAndState, + SpecId, + }, + Database, DatabaseCommit, JournaledState, +}; +use std::{borrow::Cow, collections::BTreeMap}; + +/// 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 +/// will result in a clone of the initial Database. Therefore, this backend type is basically +/// a clone-on-write `Backend`, where cloning is only necessary if cheatcodes will modify the +/// `Backend` +/// +/// Entire purpose of this type is for fuzzing. A test function fuzzer will repeatedly execute the +/// function via immutable raw (no state changes) calls. +/// +/// **N.B.**: we're assuming cheatcodes that alter the state (like multi fork swapping) are niche. +/// If they executed, it will require a clone of the initial input database. +/// This way we can support these cheatcodes cheaply without adding overhead for tests that +/// don't make use of them. Alternatively each test case would require its own `Backend` clone, +/// which would add significant overhead for large fuzz sets even if the Database is not big after +/// setup. +#[derive(Clone, Debug)] +pub struct CowBackend<'a> { + /// The underlying `Backend`. + /// + /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. + pub backend: Cow<'a, Backend>, + /// Keeps track of whether the backed is already initialized + is_initialized: bool, + /// The [SpecId] of the current backend. + spec_id: SpecId, +} + +impl<'a> CowBackend<'a> { + /// Creates a new `CowBackend` with the given `Backend`. + pub fn new_borrowed(backend: &'a Backend) -> Self { + Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } + } + + /// Executes the configured transaction of the `env` without committing state changes + /// + /// 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( + &mut self, + env: &mut EnvWithHandlerCfg, + inspector: &mut I, + ) -> eyre::Result { + // this is a new call to inspect with a new env, so even if we've cloned the backend + // already, we reset the initialized state + self.is_initialized = false; + self.spec_id = env.handler_cfg.spec_id; + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); + + let res = evm.transact().wrap_err("EVM error")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) + } + + /// Returns whether there was a state snapshot failure in the backend. + /// + /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. + pub fn has_state_snapshot_failure(&self) -> bool { + self.backend.has_state_snapshot_failure() + } + + /// Returns a mutable instance of the Backend. + /// + /// If this is the first time this is called, the backed is cloned and initialized. + fn backend_mut(&mut self, env: &Env) -> &mut Backend { + if !self.is_initialized { + let backend = self.backend.to_mut(); + let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), self.spec_id); + backend.initialize(&env); + self.is_initialized = true; + return backend + } + self.backend.to_mut() + } + + /// Returns a mutable instance of the Backend if it is initialized. + fn initialized_backend_mut(&mut self) -> Option<&mut Backend> { + if self.is_initialized { + return Some(self.backend.to_mut()) + } + None + } +} + +impl DatabaseExt for CowBackend<'_> { + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + self.backend_mut(env).snapshot_state(journaled_state, env) + } + + fn revert_state( + &mut self, + id: U256, + journaled_state: &JournaledState, + current: &mut Env, + action: RevertStateSnapshotAction, + ) -> Option { + self.backend_mut(current).revert_state(id, journaled_state, current, action) + } + + fn delete_state_snapshot(&mut self, id: U256) -> bool { + // delete state snapshot requires a previous snapshot to be initialized + if let Some(backend) = self.initialized_backend_mut() { + return backend.delete_state_snapshot(id) + } + false + } + + fn delete_state_snapshots(&mut self) { + if let Some(backend) = self.initialized_backend_mut() { + backend.delete_state_snapshots() + } + } + + fn create_fork(&mut self, fork: CreateFork) -> eyre::Result { + self.backend.to_mut().create_fork(fork) + } + + fn create_fork_at_transaction( + &mut self, + fork: CreateFork, + transaction: B256, + ) -> eyre::Result { + self.backend.to_mut().create_fork_at_transaction(fork, transaction) + } + + fn select_fork( + &mut self, + id: LocalForkId, + env: &mut Env, + journaled_state: &mut JournaledState, + ) -> eyre::Result<()> { + self.backend_mut(env).select_fork(id, env, journaled_state) + } + + fn roll_fork( + &mut self, + id: Option, + block_number: u64, + env: &mut Env, + journaled_state: &mut JournaledState, + ) -> eyre::Result<()> { + self.backend_mut(env).roll_fork(id, block_number, env, journaled_state) + } + + fn roll_fork_to_transaction( + &mut self, + id: Option, + transaction: B256, + env: &mut Env, + journaled_state: &mut JournaledState, + ) -> eyre::Result<()> { + self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) + } + + fn transact( + &mut self, + id: Option, + transaction: B256, + env: Env, + journaled_state: &mut JournaledState, + inspector: &mut dyn InspectorExt, + ) -> eyre::Result<()> { + self.backend_mut(&env).transact(id, transaction, env, journaled_state, inspector) + } + + fn transact_from_tx( + &mut self, + transaction: &TransactionRequest, + env: Env, + journaled_state: &mut JournaledState, + inspector: &mut dyn InspectorExt, + ) -> eyre::Result<()> { + self.backend_mut(&env).transact_from_tx(transaction, env, journaled_state, inspector) + } + + fn active_fork_id(&self) -> Option { + self.backend.active_fork_id() + } + + fn active_fork_url(&self) -> Option { + self.backend.active_fork_url() + } + + fn ensure_fork(&self, id: Option) -> eyre::Result { + self.backend.ensure_fork(id) + } + + fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { + self.backend.ensure_fork_id(id) + } + + fn diagnose_revert( + &self, + callee: Address, + journaled_state: &JournaledState, + ) -> Option { + self.backend.diagnose_revert(callee, journaled_state) + } + + fn load_allocs( + &mut self, + allocs: &BTreeMap, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError> { + self.backend_mut(&Env::default()).load_allocs(allocs, journaled_state) + } + + fn clone_account( + &mut self, + source: &GenesisAccount, + target: &Address, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError> { + self.backend_mut(&Env::default()).clone_account(source, target, journaled_state) + } + + fn is_persistent(&self, acc: &Address) -> bool { + self.backend.is_persistent(acc) + } + + fn remove_persistent_account(&mut self, account: &Address) -> bool { + self.backend.to_mut().remove_persistent_account(account) + } + + fn add_persistent_account(&mut self, account: Address) -> bool { + self.backend.to_mut().add_persistent_account(account) + } + + fn allow_cheatcode_access(&mut self, account: Address) -> bool { + self.backend.to_mut().allow_cheatcode_access(account) + } + + fn revoke_cheatcode_access(&mut self, account: &Address) -> bool { + self.backend.to_mut().revoke_cheatcode_access(account) + } + + fn has_cheatcode_access(&self, account: &Address) -> bool { + 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); + } +} + +impl DatabaseRef for CowBackend<'_> { + type Error = DatabaseError; + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + DatabaseRef::basic_ref(self.backend.as_ref(), address) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + DatabaseRef::code_by_hash_ref(self.backend.as_ref(), code_hash) + } + + fn storage_ref(&self, address: Address, index: U256) -> Result { + DatabaseRef::storage_ref(self.backend.as_ref(), address, index) + } + + fn block_hash_ref(&self, number: u64) -> Result { + DatabaseRef::block_hash_ref(self.backend.as_ref(), number) + } +} + +impl Database for CowBackend<'_> { + type Error = DatabaseError; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + DatabaseRef::basic_ref(self, address) + } + + fn code_by_hash(&mut self, code_hash: B256) -> Result { + DatabaseRef::code_by_hash_ref(self, code_hash) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + DatabaseRef::storage_ref(self, address, index) + } + + fn block_hash(&mut self, number: u64) -> Result { + DatabaseRef::block_hash_ref(self, number) + } +} + +impl DatabaseCommit for CowBackend<'_> { + fn commit(&mut self, changes: Map) { + self.backend.to_mut().commit(changes) + } +} diff --git a/crates/evm/src/executor/backend/diagnostic.rs b/crates/evm/core/src/backend/diagnostic.rs similarity index 58% rename from crates/evm/src/executor/backend/diagnostic.rs rename to crates/evm/core/src/backend/diagnostic.rs index 2baec89ecf97b..df215508da1c0 100644 --- a/crates/evm/src/executor/backend/diagnostic.rs +++ b/crates/evm/core/src/backend/diagnostic.rs @@ -1,11 +1,9 @@ -use crate::{ - executor::{backend::LocalForkId, inspector::Cheatcodes}, - Address, -}; -use foundry_common::fmt::UIfmt; +use crate::backend::LocalForkId; +use alloy_primitives::{map::AddressHashMap, Address}; +use itertools::Itertools; /// Represents possible diagnostic cases on revert -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum RevertDiagnostic { /// The `contract` does not exist on the `active` fork but exist on other fork(s) ContractExistsOnOtherForks { @@ -20,23 +18,25 @@ pub enum RevertDiagnostic { }, } -// === impl RevertDiagnostic === - impl RevertDiagnostic { /// Converts the diagnostic to a readable error message - pub fn to_error_msg(&self, cheats: &Cheatcodes) -> String { - let get_label = |addr| cheats.labels.get(addr).cloned().unwrap_or_else(|| addr.pretty()); + pub fn to_error_msg(&self, labels: &AddressHashMap) -> String { + let get_label = + |addr: &Address| labels.get(addr).cloned().unwrap_or_else(|| addr.to_string()); match self { - RevertDiagnostic::ContractExistsOnOtherForks { contract, active, available_on } => { + Self::ContractExistsOnOtherForks { contract, active, available_on } => { let contract_label = get_label(contract); format!( - r#"Contract {contract_label} does not exist on active fork with id `{active}` - But exists on non active forks: `{available_on:?}`"# + r#"Contract {} does not exist on active fork with id `{}` + But exists on non active forks: `[{}]`"#, + contract_label, + active, + available_on.iter().format(", ") ) } - RevertDiagnostic::ContractDoesNotExist { contract, persistent, .. } => { + Self::ContractDoesNotExist { contract, persistent, .. } => { let contract_label = get_label(contract); if *persistent { format!("Contract {contract_label} does not exist") diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs new file mode 100644 index 0000000000000..15649d9e1c7e8 --- /dev/null +++ b/crates/evm/core/src/backend/error.rs @@ -0,0 +1,66 @@ +use alloy_primitives::Address; +pub use foundry_fork_db::{DatabaseError, DatabaseResult}; +use revm::primitives::EVMError; +use std::convert::Infallible; + +pub type BackendResult = Result; + +/// Errors that can happen when working with [`revm::Database`] +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +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( + "CREATE2 Deployer (0x4e59b44847b379578588920ca78fbf26c0b4956c) not present on this chain.\n\ + For a production environment, you can deploy it using the pre-signed transaction from \ + https://github.com/Arachnid/deterministic-deployment-proxy.\n\ + For a test environment, you can use `etch` to place the required bytecode at that address." + )] + MissingCreate2Deployer, + #[error("{0}")] + Other(String), +} + +impl BackendError { + /// Create a new error with a message + pub fn msg(msg: impl Into) -> Self { + Self::Message(msg.into()) + } + + /// Create a new error with a message + pub fn display(msg: impl std::fmt::Display) -> Self { + Self::Message(msg.to_string()) + } +} + +impl From for BackendError { + fn from(value: tokio::task::JoinError) -> Self { + Self::display(value) + } +} + +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 BackendError { + fn from(err: EVMError) -> Self { + match err { + 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/src/executor/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs similarity index 50% rename from crates/evm/src/executor/backend/in_memory_db.rs rename to crates/evm/core/src/backend/in_memory_db.rs index 0340d2de770cf..4e90bfec0ae05 100644 --- a/crates/evm/src/executor/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -1,56 +1,58 @@ -//! The in memory DB -use crate::executor::backend::error::DatabaseError; +//! In-memory database. + +use crate::state_snapshot::StateSnapshots; +use alloy_primitives::{Address, B256, U256}; +use foundry_fork_db::DatabaseError; use revm::{ db::{CacheDB, DatabaseRef, EmptyDB}, - primitives::{Account, AccountInfo, Bytecode, HashMap as Map, B160, B256, U256}, + primitives::{Account, AccountInfo, Bytecode, HashMap as Map}, Database, DatabaseCommit, }; -use crate::executor::snapshot::Snapshots; - -/// Type alias for an in memory database +/// Type alias for an in-memory database. /// -/// See `EmptyDBWrapper` +/// See [`EmptyDBWrapper`]. pub type FoundryEvmInMemoryDB = CacheDB; -/// In memory Database for anvil +/// In-memory [`Database`] for Anvil. /// -/// This acts like a wrapper type for [InMemoryDB] but is capable of applying snapshots +/// This acts like a wrapper type for [`FoundryEvmInMemoryDB`] but is capable of applying snapshots. #[derive(Debug)] pub struct MemDb { pub inner: FoundryEvmInMemoryDB, - pub snapshots: Snapshots, + pub state_snapshots: StateSnapshots, } impl Default for MemDb { fn default() -> Self { - Self { inner: CacheDB::new(Default::default()), snapshots: Default::default() } + Self { inner: CacheDB::new(Default::default()), state_snapshots: Default::default() } } } impl DatabaseRef for MemDb { type Error = DatabaseError; - fn basic(&self, address: B160) -> Result, Self::Error> { - DatabaseRef::basic(&self.inner, address) + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + DatabaseRef::basic_ref(&self.inner, address) } - fn code_by_hash(&self, code_hash: B256) -> Result { - DatabaseRef::code_by_hash(&self.inner, code_hash) + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + DatabaseRef::code_by_hash_ref(&self.inner, code_hash) } - fn storage(&self, address: B160, index: U256) -> Result { - DatabaseRef::storage(&self.inner, address, index) + fn storage_ref(&self, address: Address, index: U256) -> Result { + DatabaseRef::storage_ref(&self.inner, address, index) } - fn block_hash(&self, number: U256) -> Result { - DatabaseRef::block_hash(&self.inner, number) + fn block_hash_ref(&self, number: u64) -> Result { + DatabaseRef::block_hash_ref(&self.inner, number) } } impl Database for MemDb { type Error = DatabaseError; - fn basic(&mut self, address: B160) -> Result, Self::Error> { + fn basic(&mut self, address: Address) -> Result, Self::Error> { // Note: this will always return `Some(AccountInfo)`, See `EmptyDBWrapper` Database::basic(&mut self.inner, address) } @@ -59,17 +61,17 @@ impl Database for MemDb { Database::code_by_hash(&mut self.inner, code_hash) } - fn storage(&mut self, address: B160, index: U256) -> Result { + fn storage(&mut self, address: Address, index: U256) -> Result { 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) } } impl DatabaseCommit for MemDb { - fn commit(&mut self, changes: Map) { + fn commit(&mut self, changes: Map) { DatabaseCommit::commit(&mut self.inner, changes) } } @@ -81,36 +83,43 @@ impl DatabaseCommit for MemDb { /// /// This will also _always_ return `Some(AccountInfo)`: /// -/// The [`Database`](revm::Database) implementation for `CacheDB` manages an `AccountState` for the `DbAccount`, this will be set to `AccountState::NotExisting` if the account does not exist yet. This is because there's a distinction between "non-existing" and "empty", See -/// If an account is `NotExisting`, `Database(Ref)::basic` will always return `None` for the -/// requested `AccountInfo`. To prevent +/// The [`Database`] implementation for `CacheDB` manages an `AccountState` for the +/// `DbAccount`, this will be set to `AccountState::NotExisting` if the account does not exist yet. +/// This is because there's a distinction between "non-existing" and "empty", +/// see . +/// If an account is `NotExisting`, `Database::basic_ref` will always return `None` for the +/// requested `AccountInfo`. /// -/// This will ensure that a missing account is never marked as `NotExisting` -#[derive(Debug, Default, Clone)] +/// To prevent this, we ensure that a missing account is never marked as `NotExisting` by always +/// returning `Some` with this type, which will then insert a default [`AccountInfo`] instead +/// of one marked as `AccountState::NotExisting`. +#[derive(Clone, Debug, Default)] pub struct EmptyDBWrapper(EmptyDB); impl DatabaseRef for EmptyDBWrapper { type Error = DatabaseError; - fn basic(&self, address: B160) -> Result, Self::Error> { + fn basic_ref(&self, _address: Address) -> Result, Self::Error> { // Note: this will always return `Some(AccountInfo)`, for the reason explained above - Ok(Some(self.0.basic(address)?.unwrap_or_default())) + Ok(Some(AccountInfo::default())) } - fn code_by_hash(&self, code_hash: B256) -> Result { - Ok(self.0.code_by_hash(code_hash)?) + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + Ok(self.0.code_by_hash_ref(code_hash)?) } - fn storage(&self, address: B160, index: U256) -> Result { - Ok(self.0.storage(address, index)?) + fn storage_ref(&self, address: Address, index: U256) -> Result { + Ok(self.0.storage_ref(address, index)?) } - fn block_hash(&self, number: U256) -> Result { - Ok(self.0.block_hash(number)?) + fn block_hash_ref(&self, number: u64) -> Result { + Ok(self.0.block_hash_ref(number)?) } } #[cfg(test)] mod tests { use super::*; + use alloy_primitives::b256; /// Ensures the `Database(Ref)` implementation for `revm::CacheDB` works as expected /// @@ -118,7 +127,7 @@ mod tests { #[test] fn cache_db_insert_basic_non_existing() { let mut db = CacheDB::new(EmptyDB::default()); - let address = B160::random(); + let address = Address::random(); // call `basic` on a non-existing account let info = Database::basic(&mut db, address).unwrap(); assert!(info.is_none()); @@ -128,7 +137,8 @@ mod tests { // insert the modified account info db.insert_account_info(address, info); - // when fetching again, the `AccountInfo` is still `None` because the state of the account is `AccountState::NotExisting`, See + // when fetching again, the `AccountInfo` is still `None` because the state of the account + // is `AccountState::NotExisting`, see let info = Database::basic(&mut db, address).unwrap(); assert!(info.is_none()); } @@ -137,9 +147,10 @@ mod tests { #[test] fn cache_db_insert_basic_default() { let mut db = CacheDB::new(EmptyDB::default()); - let address = B160::random(); + let address = Address::random(); - let info = DatabaseRef::basic(&db, address).unwrap(); + // We use `basic_ref` here to ensure that the account is not marked as `NotExisting`. + let info = DatabaseRef::basic_ref(&db, address).unwrap(); assert!(info.is_none()); let mut info = info.unwrap_or_default(); info.balance = U256::from(500u64); @@ -156,9 +167,13 @@ mod tests { #[test] fn mem_db_insert_basic_default() { let mut db = MemDb::default(); - let address = B160::random(); + let address = Address::from_word(b256!( + "000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045" + )); let info = Database::basic(&mut db, address).unwrap(); + // We know info exists, as MemDb always returns `Some(AccountInfo)` due to the + // `EmptyDbWrapper`. assert!(info.is_some()); let mut info = info.unwrap(); info.balance = U256::from(500u64); diff --git a/crates/evm/src/executor/backend/mod.rs b/crates/evm/core/src/backend/mod.rs similarity index 62% rename from crates/evm/src/executor/backend/mod.rs rename to crates/evm/core/src/backend/mod.rs index d286cb8a84b76..22e29bb13b47b 100644 --- a/crates/evm/src/executor/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1,57 +1,54 @@ +//! Foundry's main executor backend abstraction and implementation. + use crate::{ - abi::CHEATCODE_ADDRESS, - executor::{ - backend::snapshot::BackendSnapshot, - fork::{CreateFork, ForkId, MultiFork, SharedBackend}, - inspector::{cheatcodes::Cheatcodes, DEFAULT_CREATE2_DEPLOYER}, - snapshot::Snapshots, - }, - utils::{b160_to_h160, h160_to_b160, h256_to_b256, ru256_to_u256, u256_to_ru256}, - CALLER, TEST_CONTRACT_ADDRESS, + constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, + fork::{CreateFork, ForkId, MultiFork}, + state_snapshot::StateSnapshots, + utils::{configure_tx_env, configure_tx_req_env, new_evm_with_inspector}, + InspectorExt, }; -use ethers::{ - prelude::{Block, H160, H256, U256}, - types::{Address, BlockNumber, Transaction, U64}, - utils::keccak256, -}; -pub use in_memory_db::MemDb; +use alloy_genesis::GenesisAccount; +use alloy_network::{AnyRpcBlock, AnyTxEnvelope, TransactionResponse}; +use alloy_primitives::{keccak256, uint, Address, TxKind, B256, U256}; +use alloy_rpc_types::{BlockNumberOrTag, Transaction, TransactionRequest}; +use eyre::Context; +use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; +pub use foundry_fork_db::{cache::BlockchainDbMeta, BlockchainDb, SharedBackend}; use revm::{ db::{CacheDB, DatabaseRef}, - precompile::{Precompiles, SpecId}, + inspectors::NoOpInspector, + precompile::{PrecompileSpecId, Precompiles}, primitives::{ - Account, AccountInfo, Bytecode, CreateScheme, Env, HashMap as Map, Log, ResultAndState, - TransactTo, B160, B256, KECCAK_EMPTY, U256 as rU256, + Account, AccountInfo, BlobExcessGasAndPrice, Bytecode, Env, EnvWithHandlerCfg, EvmState, + EvmStorageSlot, HashMap as Map, Log, ResultAndState, SpecId, KECCAK_EMPTY, }, - Database, DatabaseCommit, Inspector, JournaledState, EVM, + Database, DatabaseCommit, JournaledState, }; use std::{ - collections::{HashMap, HashSet}, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, + collections::{BTreeMap, HashMap, HashSet}, + time::Instant, }; -mod fuzz; -pub mod snapshot; -pub use fuzz::FuzzBackendWrapper; mod diagnostic; - pub use diagnostic::RevertDiagnostic; -pub mod error; -use crate::executor::{ - backend::{error::NoCheatcodeAccessError, in_memory_db::FoundryEvmInMemoryDB}, - inspector::cheatcodes::util::configure_tx_env, -}; -pub use error::{DatabaseError, DatabaseResult}; +mod error; +pub use error::{BackendError, BackendResult, DatabaseError, DatabaseResult}; + +mod cow; +pub use cow::CowBackend; mod in_memory_db; +pub use in_memory_db::{EmptyDBWrapper, FoundryEvmInMemoryDB, MemDb}; + +mod snapshot; +pub use snapshot::{BackendStateSnapshot, RevertStateSnapshotAction, StateSnapshot}; // A `revm::Database` that is used in forking mode type ForkDB = CacheDB; /// Represents a numeric `ForkId` valid only for the existence of the `Backend`. +/// /// The difference between `ForkId` and `LocalForkId` is that `ForkId` tracks pairs of `endpoint + /// block` which can be reused by multiple tests, whereas the `LocalForkId` is unique within a test pub type LocalForkId = U256; @@ -60,23 +57,27 @@ pub type LocalForkId = U256; /// This is used for fast lookup type ForkLookupIndex = usize; -/// All accounts that will have persistent storage across fork swaps. See also [`clone_data()`] -const DEFAULT_PERSISTENT_ACCOUNTS: [H160; 3] = +/// All accounts that will have persistent storage across fork swaps. +const DEFAULT_PERSISTENT_ACCOUNTS: [Address; 3] = [CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, CALLER]; -/// Slot corresponding to "failed" in bytes on the cheatcodes (HEVM) address. -const GLOBAL_FAILURE_SLOT: &str = - "0x6661696c65640000000000000000000000000000000000000000000000000000"; +/// `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); /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities -#[auto_impl::auto_impl(&mut, Box)] -pub trait DatabaseExt: Database { - /// Creates a new snapshot at the current point of execution. +#[auto_impl::auto_impl(&mut)] +pub trait DatabaseExt: Database + DatabaseCommit { + /// Creates a new state snapshot at the current point of execution. /// - /// A snapshot is associated with a new unique id that's created for the snapshot. - /// Snapshots can be reverted: [DatabaseExt::revert], however a snapshot can only be reverted - /// once. After a successful revert, the same snapshot id cannot be used again. - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; + /// A state snapshot is associated with a new unique id that's created for the snapshot. + /// State snapshots can be reverted: [DatabaseExt::revert_state], however, depending on the + /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; + /// Reverts the snapshot if it exists /// /// Returns `true` if the snapshot was successfully reverted, `false` if no snapshot for that id @@ -86,14 +87,26 @@ pub trait DatabaseExt: Database { /// since the snapshots was created. This way we can show logs that were emitted between /// snapshot and its revert. /// This will also revert any changes in the `Env` and replace it with the captured `Env` of - /// `Self::snapshot` - fn revert( + /// `Self::snapshot_state`. + /// + /// Depending on [RevertStateSnapshotAction] it will keep the snapshot alive or delete it. + fn revert_state( &mut self, id: U256, journaled_state: &JournaledState, env: &mut Env, + action: RevertStateSnapshotAction, ) -> Option; + /// Deletes the state snapshot with the given `id` + /// + /// Returns `true` if the snapshot was successfully deleted, `false` if no snapshot for that id + /// exists. + fn delete_state_snapshot(&mut self, id: U256) -> bool; + + /// Deletes all state snapshots. + fn delete_state_snapshots(&mut self); + /// Creates and also selects a new fork /// /// This is basically `create_fork` + `select_fork` @@ -116,7 +129,7 @@ pub trait DatabaseExt: Database { fork: CreateFork, env: &mut Env, journaled_state: &mut JournaledState, - transaction: H256, + transaction: B256, ) -> eyre::Result { let id = self.create_fork_at_transaction(fork, transaction)?; self.select_fork(id, env, journaled_state)?; @@ -130,7 +143,7 @@ pub trait DatabaseExt: Database { fn create_fork_at_transaction( &mut self, fork: CreateFork, - transaction: H256, + transaction: B256, ) -> eyre::Result; /// Selects the fork's state @@ -159,7 +172,7 @@ pub trait DatabaseExt: Database { fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -175,7 +188,7 @@ pub trait DatabaseExt: Database { fn roll_fork_to_transaction( &mut self, id: Option, - transaction: H256, + transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -184,10 +197,19 @@ pub trait DatabaseExt: Database { fn transact( &mut self, id: Option, - transaction: H256, - env: &mut Env, + transaction: B256, + env: Env, + journaled_state: &mut JournaledState, + inspector: &mut dyn InspectorExt, + ) -> eyre::Result<()>; + + /// Executes a given TransactionRequest, commits the new state to the DB + fn transact_from_tx( + &mut self, + transaction: &TransactionRequest, + env: Env, journaled_state: &mut JournaledState, - cheatcodes_inspector: Option<&mut Cheatcodes>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on @@ -196,21 +218,21 @@ pub trait DatabaseExt: Database { /// Returns the Fork url that's currently used in the database, if fork mode is on fn active_fork_url(&self) -> Option; - /// Whether the database is currently in forked + /// Whether the database is currently in forked mode. fn is_forked_mode(&self) -> bool { self.active_fork_id().is_some() } - /// Ensures that an appropriate fork exits + /// Ensures that an appropriate fork exists /// - /// If `id` contains a requested `Fork` this will ensure it exits. + /// If `id` contains a requested `Fork` this will ensure it exists. /// Otherwise, this returns the currently active fork. /// /// # Errors /// /// Returns an error if the given `id` does not match any forks /// - /// Returns an error if no fork exits + /// Returns an error if no fork exists fn ensure_fork(&self, id: Option) -> eyre::Result; /// Ensures that a corresponding `ForkId` exists for the given local `id` @@ -248,6 +270,26 @@ pub trait DatabaseExt: Database { journaled_state: &JournaledState, ) -> Option; + /// Loads the account allocs from the given `allocs` map into the passed [JournaledState]. + /// + /// Returns [Ok] if all accounts were successfully inserted into the journal, [Err] otherwise. + fn load_allocs( + &mut self, + allocs: &BTreeMap, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError>; + + /// Copies bytecode, storage, nonce and balance from the given genesis account to the target + /// address. + /// + /// Returns [Ok] if data was successfully inserted into the journal, [Err] otherwise. + fn clone_account( + &mut self, + source: &GenesisAccount, + target: &Address, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError>; + /// Returns true if the given account is currently marked as persistent. fn is_persistent(&self, acc: &Address) -> bool; @@ -257,15 +299,23 @@ 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 - fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) { + /// 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, + { for acc in accounts { self.remove_persistent_account(&acc); } } /// Extends the persistent accounts with the accounts the iterator yields. - fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) { + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] + fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) + where + Self: Sized, + { for acc in accounts { self.add_persistent_account(acc); } @@ -279,34 +329,49 @@ pub trait DatabaseExt: Database { /// Revokes cheatcode access for the given account /// /// Returns true if the `account` was previously allowed cheatcode access - fn revoke_cheatcode_access(&mut self, account: Address) -> bool; + fn revoke_cheatcode_access(&mut self, account: &Address) -> bool; /// Returns `true` if the given account is allowed to execute cheatcodes - fn has_cheatcode_access(&self, account: Address) -> bool; + fn has_cheatcode_access(&self, account: &Address) -> bool; /// 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<(), NoCheatcodeAccessError> { + fn ensure_cheatcode_access(&self, account: &Address) -> Result<(), BackendError> { if !self.has_cheatcode_access(account) { - return Err(NoCheatcodeAccessError(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<(), NoCheatcodeAccessError> { + fn ensure_cheatcode_access_forking_mode(&self, account: &Address) -> Result<(), BackendError> { if self.is_forked_mode() { - return self.ensure_cheatcode_access(account) + return self.ensure_cheatcode_access(account); } Ok(()) } + + /// 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); + /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -356,10 +421,11 @@ pub trait DatabaseExt: Database { /// afterwards, as well as any snapshots taken after the reverted snapshot, (e.g.: reverting to id /// 0x1 will delete snapshots with ids 0x1, 0x2, etc.) /// -/// **Note:** Snapshots work across fork-swaps, e.g. if fork `A` is currently active, then a +/// **Note:** State snapshots work across fork-swaps, e.g. if fork `A` is currently active, then a /// snapshot is created before fork `B` is selected, then fork `A` will be the active fork again /// after reverting the snapshot. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] +#[must_use] pub struct Backend { /// The access point for managing forks forks: MultiFork, @@ -368,19 +434,19 @@ pub struct Backend { /// The journaled_state to use to initialize new forks with /// /// The way [`revm::JournaledState`] works is, that it holds the "hot" accounts loaded from the - /// underlying `Database` that feeds the Account and State data ([`revm::AccountInfo`])to the - /// journaled_state so it can apply changes to the state while the evm executes. + /// underlying `Database` that feeds the Account and State data to the journaled_state so it + /// can apply changes to the state while the EVM executes. /// /// In a way the `JournaledState` is something like a cache that /// 1. check if account is already loaded (hot) /// 2. if not load from the `Database` (this will then retrieve the account via RPC in forking - /// mode) + /// mode) /// /// To properly initialize we store the `JournaledState` before the first fork is selected /// ([`DatabaseExt::select_fork`]). /// /// This will be an empty `JournaledState`, which will be populated with persistent accounts, - /// See [`Self::update_fork_db()`] and [`clone_data()`]. + /// See [`Self::update_fork_db()`]. fork_init_journaled_state: JournaledState, /// The currently active fork database /// @@ -390,18 +456,21 @@ pub struct Backend { inner: BackendInner, } -// === impl Backend === - impl Backend { /// Creates a new Backend with a spawned multi fork thread. - pub async fn spawn(fork: Option) -> Self { - Self::new(MultiFork::spawn().await, fork) + /// + /// 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` @@ -438,16 +507,11 @@ impl Backend { /// Creates a new instance of `Backend` with fork added to the fork database and sets the fork /// as active - pub(crate) async fn new_with_fork( - id: &ForkId, - fork: Fork, - journaled_state: JournaledState, - ) -> Self { - let mut backend = Self::spawn(None).await; + pub(crate) fn new_with_fork(id: &ForkId, fork: Fork, journaled_state: JournaledState) -> Self { + let mut backend = Self::spawn(None); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); backend.inner.launched_with_fork = Some((id.clone(), fork_ids.0, fork_ids.1)); backend.active_fork_ids = Some(fork_ids); - backend } @@ -462,35 +526,49 @@ impl Backend { } } - pub fn insert_account_info(&mut self, address: H160, account: AccountInfo) { + pub fn insert_account_info(&mut self, address: Address, account: AccountInfo) { if let Some(db) = self.active_fork_db_mut() { - db.insert_account_info(h160_to_b160(address), account) + db.insert_account_info(address, account) } else { - self.mem_db.insert_account_info(h160_to_b160(address), account) + self.mem_db.insert_account_info(address, account) } } /// Inserts a value on an account's storage without overriding account info pub fn insert_account_storage( &mut self, - address: H160, + address: Address, slot: U256, value: U256, ) -> Result<(), DatabaseError> { - let ret = if let Some(db) = self.active_fork_db_mut() { - db.insert_account_storage(h160_to_b160(address), slot.into(), value.into()) + if let Some(db) = self.active_fork_db_mut() { + db.insert_account_storage(address, slot, value) } else { - self.mem_db.insert_account_storage(h160_to_b160(address), slot.into(), value.into()) - }; - - debug_assert!(self.storage(h160_to_b160(address), slot.into()).unwrap() == value.into()); + self.mem_db.insert_account_storage(address, slot, value) + } + } - ret + /// Completely replace an account's storage without overriding account info. + /// + /// When forking, this causes the backend to assume a `0` value for all + /// unset storage slots instead of trying to fetch it. + pub fn replace_account_storage( + &mut self, + address: Address, + storage: Map, + ) -> Result<(), DatabaseError> { + if let Some(db) = self.active_fork_db_mut() { + db.replace_account_storage(address, storage) + } else { + self.mem_db.replace_account_storage(address, storage) + } } /// Returns all snapshots created in this backend - pub fn snapshots(&self) -> &Snapshots> { - &self.inner.snapshots + pub fn state_snapshots( + &self, + ) -> &StateSnapshots> { + &self.inner.state_snapshots } /// Sets the address of the `DSTest` contract that is being executed @@ -501,20 +579,13 @@ 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"); - // toggle the previous sender - if let Some(current) = self.inner.test_contract_address.take() { - self.remove_persistent_account(¤t); - self.revoke_cheatcode_access(acc); - } - self.add_persistent_account(acc); self.allow_cheatcode_access(acc); - self.inner.test_contract_address = Some(acc); self } /// Sets the caller address - pub fn set_caller(&mut self, acc: H160) -> &mut Self { + pub fn set_caller(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting caller account"); self.inner.caller = Some(acc); self.allow_cheatcode_access(acc); @@ -523,97 +594,28 @@ impl Backend { /// Sets the current spec id pub fn set_spec_id(&mut self, spec_id: SpecId) -> &mut Self { - trace!("setting precompile id"); - self.inner.precompile_id = spec_id; + trace!(?spec_id, "setting spec ID"); + self.inner.spec_id = spec_id; self } - /// Returns the address of the set `DSTest` contract - pub fn test_contract_address(&self) -> Option

{ - self.inner.test_contract_address - } - /// Returns the set caller address pub fn caller_address(&self) -> Option
{ self.inner.caller } - /// Failures occurred in snapshots are tracked when the snapshot is reverted + /// Failures occurred in state snapshots are tracked when the state snapshot is reverted. /// - /// If an error occurs in a restored snapshot, the test is considered failed. + /// If an error occurs in a restored state snapshot, the test is considered failed. /// - /// This returns whether there was a reverted snapshot that recorded an error - pub fn has_snapshot_failure(&self) -> bool { - self.inner.has_snapshot_failure.load(Ordering::Relaxed) - } - - pub fn set_snapshot_failure(&self, has_snapshot_failure: bool) { - self.inner.has_snapshot_failure.store(has_snapshot_failure, Ordering::Relaxed); - } - - /// 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() + /// This returns whether there was a reverted state snapshot that recorded an error. + pub fn has_state_snapshot_failure(&self) -> bool { + self.inner.has_state_snapshot_failure } - /// 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(h160_to_b160(address), U256::zero().into()).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 { - let address = h160_to_b160(address); - 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 { - let index: rU256 = - U256::from_str_radix(GLOBAL_FAILURE_SLOT, 16).expect("This is a bug.").into(); - if let Some(account) = current_state.state.get(&h160_to_b160(CHEATCODE_ADDRESS)) { - let value = account.storage.get(&index).cloned().unwrap_or_default().present_value(); - return value == revm::primitives::U256::from(1) - } - - false + /// Sets the state snapshot failure flag. + pub fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { + self.inner.has_state_snapshot_failure = has_state_snapshot_failure } /// When creating or switching forks, we update the AccountInfo of the contract @@ -622,11 +624,6 @@ impl Backend { active_journaled_state: &mut JournaledState, target_fork: &mut Fork, ) { - 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, @@ -641,9 +638,8 @@ impl Backend { active_journaled_state: &mut JournaledState, target_fork: &mut Fork, ) { - if let Some((_, fork_idx)) = self.active_fork_ids.as_ref() { - let active = self.inner.get_fork(*fork_idx); - merge_account_data(accounts, &active.db, active_journaled_state, target_fork) + if let Some(db) = self.active_fork_db() { + merge_account_data(accounts, db, active_journaled_state, target_fork) } else { merge_account_data(accounts, &self.mem_db, active_journaled_state, target_fork) } @@ -684,6 +680,24 @@ impl Backend { self.active_fork_mut().map(|f| &mut f.db) } + /// Returns the current database implementation as a `&dyn` value. + #[inline(always)] + pub fn db(&self) -> &dyn Database { + match self.active_fork_db() { + Some(fork_db) => fork_db, + None => &self.mem_db, + } + } + + /// Returns the current database implementation as a `&mut dyn` value. + #[inline(always)] + pub fn db_mut(&mut self) -> &mut dyn Database { + match self.active_fork_ids.map(|(_, idx)| &mut self.inner.get_fork_mut(idx).db) { + Some(fork_db) => fork_db, + None => &mut self.mem_db, + } + } + /// Creates a snapshot of the currently active database pub(crate) fn create_db_snapshot(&self) -> BackendDatabaseSnapshot { if let Some((id, idx)) = self.active_fork_ids { @@ -712,7 +726,7 @@ impl Backend { all_logs.extend(f.journaled_state.logs.clone()) } }); - return all_logs + return all_logs; } logs @@ -721,47 +735,62 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &Env) { - self.set_caller(b160_to_h160(env.tx.caller)); - self.set_spec_id(SpecId::from_spec_id(env.cfg.spec_id)); + pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { + self.set_caller(env.tx.caller); + self.set_spec_id(env.handler_cfg.spec_id); let test_contract = match env.tx.transact_to { - TransactTo::Call(to) => to, - TransactTo::Create(CreateScheme::Create) => { - revm::primitives::create_address(env.tx.caller, env.tx.nonce.unwrap_or_default()) - } - TransactTo::Create(CreateScheme::Create2 { salt }) => { - let code_hash = H256::from_slice(keccak256(&env.tx.data).as_slice()); - revm::primitives::create2_address(env.tx.caller, h256_to_b256(code_hash), salt) + TxKind::Call(to) => to, + TxKind::Create => { + let nonce = self + .basic_ref(env.tx.caller) + .map(|b| b.unwrap_or_default().nonce) + .unwrap_or_default(); + env.tx.caller.create(nonce) } }; - self.set_test_contract(b160_to_h160(test_contract)); + self.set_test_contract(test_contract); } - /// Executes the configured test call of the `env` without committing state changes - pub fn inspect_ref( + /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. + fn env_with_handler_cfg(&self, env: Env) -> EnvWithHandlerCfg { + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) + } + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// 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( &mut self, - env: &mut Env, - mut inspector: INSP, - ) -> eyre::Result - where - INSP: Inspector, - { + env: &mut EnvWithHandlerCfg, + inspector: &mut I, + ) -> eyre::Result { self.initialize(env); + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - match revm::evm_inner::(env, self, &mut inspector).transact() { - Ok(res) => Ok(res), - Err(e) => eyre::bail!("backend: failed while inspecting: {:?}", e), - } + let res = evm.transact().wrap_err("EVM error")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) } /// Returns true if the address is a precompile - pub fn is_existing_precompile(&self, addr: &B160) -> bool { + pub fn is_existing_precompile(&self, addr: &Address) -> bool { self.inner.precompiles().contains(addr) } - /// Ths will clean up already loaded accounts that would be initialized without the correct data - /// from the fork + /// Sets the initial journaled state to use when initializing forks + #[inline] + fn set_init_journaled_state(&mut self, journaled_state: JournaledState) { + trace!("recording fork init journaled_state"); + self.fork_init_journaled_state = journaled_state; + } + + /// Cleans up already loaded accounts that would be initialized without the correct data from + /// the fork. /// /// It can happen that an account is loaded before the first fork is selected, like /// `getNonce(addr)`, which will load an empty account by default. @@ -769,14 +798,12 @@ 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 .iter() - .filter(|(addr, _)| { - !self.is_existing_precompile(addr) && !self.is_persistent(&b160_to_h160(**addr)) - }) + .filter(|(addr, _)| !self.is_existing_precompile(addr) && !self.is_persistent(addr)) .map(|(addr, _)| addr) .copied() .collect::>(); @@ -785,10 +812,21 @@ impl Backend { let mut journaled_state = self.fork_init_journaled_state.clone(); for loaded_account in loaded_accounts.iter().copied() { trace!(?loaded_account, "replacing account on init"); - let fork_account = Database::basic(&mut fork.db, loaded_account)? - .ok_or(DatabaseError::MissingAccount(b160_to_h160(loaded_account)))?; let init_account = journaled_state.state.get_mut(&loaded_account).expect("exists; qed"); + + // here's an edge case where we need to check if this account has been created, in + // which case we don't need to replace it with the account from the fork because the + // created account takes precedence: for example contract creation in setups + if init_account.is_created() { + trace!(?loaded_account, "skipping created account"); + continue + } + + // 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(BackendError::MissingAccount(loaded_account))?; init_account.info = fork_account; } fork.journaled_state = journaled_state; @@ -800,25 +838,23 @@ impl Backend { fn get_block_number_and_block_for_transaction( &self, id: LocalForkId, - transaction: H256, - ) -> eyre::Result<(U64, Block)> { + transaction: B256, + ) -> eyre::Result<(u64, AnyRpcBlock)> { let fork = self.inner.get_fork_by_id(id)?; let tx = fork.db.db.get_transaction(transaction)?; // get the block number we need to fork if let Some(tx_block) = tx.block_number { - let block = fork.db.db.get_full_block(BlockNumber::Number(tx_block))?; + let block = fork.db.db.get_full_block(tx_block)?; // we need to subtract 1 here because we want the state before the transaction // was mined let fork_block = tx_block - 1; Ok((fork_block, block)) } else { - let block = fork.db.db.get_full_block(BlockNumber::Latest)?; + let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; - let number = block - .number - .ok_or_else(|| DatabaseError::BlockNotFound(BlockNumber::Latest.into()))?; + let number = block.header.number; Ok((number, block)) } @@ -831,39 +867,53 @@ impl Backend { &mut self, id: LocalForkId, env: Env, - tx_hash: H256, + tx_hash: B256, journaled_state: &mut JournaledState, - ) -> eyre::Result> { + ) -> eyre::Result>> { trace!(?id, ?tx_hash, "replay until transaction"); + let persistent_accounts = self.inner.persistent_accounts.clone(); let fork_id = self.ensure_fork_id(id)?.clone(); + let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; - let full_block = fork - .db - .db - .get_full_block(BlockNumber::Number(ru256_to_u256(env.block.number).as_u64().into()))?; + let full_block = fork.db.db.get_full_block(env.block.number.to::())?; + + for tx in full_block.inner.transactions.into_transactions() { + // System transactions such as on L2s don't contain any pricing info so we skip them + // otherwise this would cause reverts + if is_known_system_sender(tx.from) || + tx.transaction_type() == Some(SYSTEM_TRANSACTION_TYPE) + { + trace!(tx=?tx.tx_hash(), "skipping system transaction"); + continue; + } - for tx in full_block.transactions.into_iter() { - if tx.hash().eq(&tx_hash) { + if tx.tx_hash() == tx_hash { // found the target transaction - return Ok(Some(tx)) + return Ok(Some(tx.inner)) } - trace!(tx=?tx.hash, "committing transaction"); - - commit_transaction(tx, env.clone(), journaled_state, fork, &fork_id, None)?; + trace!(tx=?tx.tx_hash(), "committing transaction"); + + commit_transaction( + &tx.inner, + env.clone(), + journaled_state, + fork, + &fork_id, + &persistent_accounts, + &mut NoOpInspector, + )?; } Ok(None) } } -// === impl a bunch of `revm::Database` adjacent implementations === - impl DatabaseExt for Backend { - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { trace!("create snapshot"); - let id = self.inner.snapshots.insert(BackendSnapshot::new( + let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), env.clone(), @@ -872,25 +922,35 @@ impl DatabaseExt for Backend { id } - fn revert( + fn revert_state( &mut self, id: U256, current_state: &JournaledState, current: &mut Env, + action: RevertStateSnapshotAction, ) -> Option { trace!(?id, "revert snapshot"); - if let Some(mut snapshot) = self.inner.snapshots.remove_at(id) { + if let Some(mut snapshot) = self.inner.state_snapshots.remove_at(id) { // Re-insert snapshot to persist it - 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.inner.has_snapshot_failure.store(true, Ordering::Relaxed); + if action.is_keep() { + self.inner.state_snapshots.insert_at(snapshot.clone(), id); + } + + // 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_state_snapshot_failure(true); + } + } } // merge additional logs snapshot.merge(current_state); - let BackendSnapshot { db, mut journaled_state, env } = snapshot; + let BackendStateSnapshot { db, mut journaled_state, env } = snapshot; match db { BackendDatabaseSnapshot::InMemory(mem_db) => { self.mem_db = mem_db; @@ -900,7 +960,7 @@ impl DatabaseExt for Backend { // another caller, so we need to ensure the caller account is present in the // journaled state and database let caller = current.tx.caller; - if !journaled_state.state.contains_key(&caller) { + journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = current_state .state .get(&caller) @@ -911,9 +971,9 @@ impl DatabaseExt for Backend { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } - journaled_state.state.insert(caller, caller_account.into()); - } - self.inner.revert_snapshot(id, fork_id, idx, *fork); + caller_account.into() + }); + self.inner.revert_state_snapshot(id, fork_id, idx, *fork); self.active_fork_ids = Some((id, idx)) } } @@ -928,11 +988,19 @@ impl DatabaseExt for Backend { } } - fn create_fork(&mut self, fork: CreateFork) -> eyre::Result { + fn delete_state_snapshot(&mut self, id: U256) -> bool { + self.inner.state_snapshots.remove_at(id).is_some() + } + + fn delete_state_snapshots(&mut self) { + self.inner.state_snapshots.clear() + } + + fn create_fork(&mut self, create_fork: CreateFork) -> eyre::Result { trace!("create fork"); - let (fork_id, fork, _) = self.forks.create_fork(fork)?; - let fork_db = ForkDB::new(fork); + let (fork_id, fork, _) = self.forks.create_fork(create_fork)?; + let fork_db = ForkDB::new(fork); let (id, _) = self.inner.insert_new_fork(fork_id, fork_db, self.fork_init_journaled_state.clone()); Ok(id) @@ -941,7 +1009,7 @@ impl DatabaseExt for Backend { fn create_fork_at_transaction( &mut self, fork: CreateFork, - transaction: H256, + transaction: B256, ) -> eyre::Result { trace!(?transaction, "create fork at transaction"); let id = self.create_fork(fork)?; @@ -962,6 +1030,7 @@ impl DatabaseExt for Backend { Ok(id) } + /// Select an existing fork by id. /// When switching forks we copy the shared state fn select_fork( &mut self, @@ -972,7 +1041,17 @@ impl DatabaseExt for Backend { trace!(?id, "select fork"); if self.is_active_fork(id) { // nothing to do - return Ok(()) + 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()?; @@ -996,7 +1075,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(b160_to_h160(caller)))?; + .ok_or(BackendError::MissingAccount(caller))?; acc.info = fork_account; target_fork.journaled_state.state.insert(caller, acc); @@ -1008,8 +1087,8 @@ impl DatabaseExt for Backend { // different forks. Since the `JournaledState` is valid for all forks until the // first fork is selected, we need to update it for all forks and use it as init state // for all future forks - trace!("recording fork init journaled_state"); - self.fork_init_journaled_state = active_journaled_state.clone(); + + self.set_init_journaled_state(active_journaled_state.clone()); self.prepare_init_journal_state()?; // Make sure that the next created fork has a depth of 0. @@ -1030,7 +1109,7 @@ impl DatabaseExt for Backend { // necessarily the same caller as for the test, however we must always // ensure that fork's state contains the current sender let caller = env.tx.caller; - if !fork.journaled_state.state.contains_key(&caller) { + fork.journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = active_journaled_state .state .get(&env.tx.caller) @@ -1041,8 +1120,8 @@ impl DatabaseExt for Backend { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } - fork.journaled_state.state.insert(caller, caller_account.into()); - } + caller_account.into() + }); self.update_fork_db(active_journaled_state, &mut fork); @@ -1051,24 +1130,25 @@ 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(()) } - /// This is effectively the same as [`Self::create_select_fork()`] but updating an existing fork + /// This is effectively the same as [`Self::create_select_fork()`] but updating an existing + /// [ForkId] that is mapped to the [LocalForkId] fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?block_number, "roll fork"); let id = self.ensure_fork(id)?; let (fork_id, backend, fork_env) = - self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number.as_u64())?; + self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number)?; // this will update the local mapping self.inner.roll_fork(id, fork_id, backend)?; @@ -1095,16 +1175,22 @@ impl DatabaseExt for Backend { merge_journaled_state_data(addr, journaled_state, &mut active.journaled_state); } - // ensure all previously loaded accounts are present in the journaled state to + // Ensure all previously loaded accounts are present in the journaled state to // prevent issues in the new journalstate, e.g. assumptions that accounts are loaded - // if the account is not touched, we reload it, if it's touched we clone it + // if the account is not touched, we reload it, if it's touched we clone it. + // + // Special case for accounts that are not created: we don't merge their state but + // load it in order to reflect their state at the new block (they should explicitly + // be marked as persistent if it is desired to keep state between fork rolls). for (addr, acc) in journaled_state.state.iter() { - if acc.is_touched { - merge_journaled_state_data( - b160_to_h160(*addr), - journaled_state, - &mut active.journaled_state, - ); + if acc.is_created() { + if acc.is_touched() { + merge_journaled_state_data( + *addr, + journaled_state, + &mut active.journaled_state, + ); + } } else { let _ = active.journaled_state.load_account(*addr, &mut active.db); } @@ -1119,7 +1205,7 @@ impl DatabaseExt for Backend { fn roll_fork_to_transaction( &mut self, id: Option, - transaction: H256, + transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { @@ -1130,16 +1216,9 @@ impl DatabaseExt for Backend { self.get_block_number_and_block_for_transaction(id, transaction)?; // roll the fork to the transaction's block or latest if it's pending - self.roll_fork(Some(id), fork_block.as_u64().into(), env, journaled_state)?; + self.roll_fork(Some(id), fork_block, env, journaled_state)?; - // update the block's env accordingly - env.block.timestamp = block.timestamp.into(); - env.block.coinbase = h160_to_b160(block.author.unwrap_or_default()); - env.block.difficulty = block.difficulty.into(); - env.block.prevrandao = block.mix_hash.map(h256_to_b256); - env.block.basefee = block.base_fee_per_gas.unwrap_or_default().into(); - env.block.gas_limit = block.gas_limit.into(); - env.block.number = u256_to_ru256(block.number.unwrap_or(fork_block).as_u64().into()); + update_env_block(env, &block); // replay all transactions that came before let env = env.clone(); @@ -1152,27 +1231,67 @@ impl DatabaseExt for Backend { fn transact( &mut self, maybe_id: Option, - transaction: H256, - env: &mut Env, + transaction: B256, + mut env: Env, journaled_state: &mut JournaledState, - cheatcodes_inspector: Option<&mut Cheatcodes>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); + let persistent_accounts = self.inner.persistent_accounts.clone(); let id = self.ensure_fork(maybe_id)?; let fork_id = self.ensure_fork_id(id).cloned()?; - let env = if maybe_id.is_none() { - self.forks - .get_env(fork_id.clone())? - .ok_or_else(|| eyre::eyre!("Requested fork `{}` does not exit", id))? - } else { - env.clone() + let tx = { + let fork = self.inner.get_fork_by_id_mut(id)?; + fork.db.db.get_transaction(transaction)? }; + // This is a bit ambiguous because the user wants to transact an arbitrary transaction in + // the current context, but we're assuming the user wants to transact the transaction as it + // was mined. Usually this is used in a combination of a fork at the transaction's parent + // transaction in the block and then the transaction is transacted: + // + // So we modify the env to match the transaction's block. + let (_fork_block, block) = + self.get_block_number_and_block_for_transaction(id, transaction)?; + update_env_block(&mut env, &block); + + let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; - let tx = fork.db.db.get_transaction(transaction)?; + commit_transaction( + &tx, + env, + journaled_state, + fork, + &fork_id, + &persistent_accounts, + inspector, + ) + } + + fn transact_from_tx( + &mut self, + tx: &TransactionRequest, + mut env: Env, + journaled_state: &mut JournaledState, + inspector: &mut dyn InspectorExt, + ) -> eyre::Result<()> { + trace!(?tx, "execute signed transaction"); + + self.commit(journaled_state.state.clone()); - commit_transaction(tx, env, journaled_state, fork, &fork_id, cheatcodes_inspector)?; + let res = { + configure_tx_req_env(&mut env, tx, None)?; + let env = self.env_with_handler_cfg(env); + + let mut db = self.clone(); + let mut evm = new_evm_with_inspector(&mut db, env, inspector); + evm.context.evm.journaled_state.depth = journaled_state.depth + 1; + evm.transact()? + }; + + self.commit(res.state); + update_state(&mut journaled_state.state, self, None)?; Ok(()) } @@ -1189,7 +1308,7 @@ impl DatabaseExt for Backend { fn ensure_fork(&self, id: Option) -> eyre::Result { if let Some(id) = id { if self.inner.issued_local_fork_ids.contains_key(&id) { - return Ok(id) + return Ok(id); } eyre::bail!("Requested fork `{}` does not exit", id) } @@ -1215,7 +1334,7 @@ impl DatabaseExt for Backend { if self.inner.forks.len() == 1 { // we only want to provide additional diagnostics here when in multifork mode with > 1 // forks - return None + return None; } if !active_fork.is_contract(callee) && !is_contract_in_state(journaled_state, callee) { @@ -1242,18 +1361,76 @@ impl DatabaseExt for Backend { active: active_id, available_on, }) - } + }; } None } - fn is_persistent(&self, acc: &Address) -> bool { - self.inner.persistent_accounts.contains(acc) + /// Loads the account allocs from the given `allocs` map into the passed [JournaledState]. + /// + /// Returns [Ok] if all accounts were successfully inserted into the journal, [Err] otherwise. + fn load_allocs( + &mut self, + allocs: &BTreeMap, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError> { + // Loop through all of the allocs defined in the map and commit them to the journal. + for (addr, acc) in allocs.iter() { + self.clone_account(acc, addr, journaled_state)?; + } + + Ok(()) } - fn remove_persistent_account(&mut self, account: &Address) -> bool { - trace!(?account, "remove persistent account"); - self.inner.persistent_accounts.remove(account) + /// Copies bytecode, storage, nonce and balance from the given genesis account to the target + /// address. + /// + /// Returns [Ok] if data was successfully inserted into the journal, [Err] otherwise. + fn clone_account( + &mut self, + source: &GenesisAccount, + target: &Address, + journaled_state: &mut JournaledState, + ) -> Result<(), BackendError> { + // Fetch the account from the journaled state. Will create a new account if it does + // not already exist. + let mut state_acc = journaled_state.load_account(*target, self)?; + + // Set the account's bytecode and code hash, if the `bytecode` field is present. + if let Some(bytecode) = source.code.as_ref() { + state_acc.info.code_hash = keccak256(bytecode); + let bytecode = Bytecode::new_raw(bytecode.0.clone().into()); + state_acc.info.code = Some(bytecode); + } + + // Set the account's storage, if the `storage` field is present. + if let Some(storage) = source.storage.as_ref() { + state_acc.storage = storage + .iter() + .map(|(slot, value)| { + let slot = U256::from_be_bytes(slot.0); + ( + slot, + EvmStorageSlot::new_changed( + state_acc + .storage + .get(&slot) + .map(|s| s.present_value) + .unwrap_or_default(), + U256::from_be_bytes(value.0), + ), + ) + }) + .collect(); + } + // Set the account's nonce and balance. + state_acc.info.nonce = source.nonce.unwrap_or_default(); + state_acc.info.balance = source.balance; + + // Touch the account to ensure the loaded information persists if called in `setUp`. + journaled_state.touch(target); + + Ok(()) } fn add_persistent_account(&mut self, account: Address) -> bool { @@ -1261,94 +1438,76 @@ impl DatabaseExt for Backend { self.inner.persistent_accounts.insert(account) } - fn allow_cheatcode_access(&mut self, account: Address) -> bool { - trace!(?account, "allow cheatcode access"); - self.inner.cheatcode_access_accounts.insert(account) - } - - fn revoke_cheatcode_access(&mut self, account: Address) -> bool { - trace!(?account, "revoke cheatcode access"); - self.inner.cheatcode_access_accounts.remove(&account) + fn remove_persistent_account(&mut self, account: &Address) -> bool { + trace!(?account, "remove persistent account"); + self.inner.persistent_accounts.remove(account) } - fn has_cheatcode_access(&self, account: Address) -> bool { - self.inner.cheatcode_access_accounts.contains(&account) + fn is_persistent(&self, acc: &Address) -> bool { + self.inner.persistent_accounts.contains(acc) } -} -impl DatabaseRef for Backend { - type Error = DatabaseError; - - fn basic(&self, address: B160) -> Result, Self::Error> { - if let Some(db) = self.active_fork_db() { - db.basic(address) - } else { - Ok(self.mem_db.basic(address)?) - } + fn allow_cheatcode_access(&mut self, account: Address) -> bool { + trace!(?account, "allow cheatcode access"); + self.inner.cheatcode_access_accounts.insert(account) } - fn code_by_hash(&self, code_hash: B256) -> Result { - if let Some(db) = self.active_fork_db() { - db.code_by_hash(code_hash) - } else { - Ok(self.mem_db.code_by_hash(code_hash)?) - } + fn revoke_cheatcode_access(&mut self, account: &Address) -> bool { + trace!(?account, "revoke cheatcode access"); + self.inner.cheatcode_access_accounts.remove(account) } - fn storage(&self, address: B160, index: rU256) -> Result { - if let Some(db) = self.active_fork_db() { - DatabaseRef::storage(db, address, index) - } else { - Ok(DatabaseRef::storage(&self.mem_db, address, index)?) - } + fn has_cheatcode_access(&self, account: &Address) -> bool { + self.inner.cheatcode_access_accounts.contains(account) } - fn block_hash(&self, number: rU256) -> Result { - if let Some(db) = self.active_fork_db() { - db.block_hash(number) + 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 { - Ok(self.mem_db.block_hash(number)?) + self.mem_db.block_hashes.insert(block_number, block_hash); } } } -impl<'a> DatabaseRef for &'a mut Backend { +impl DatabaseRef for Backend { type Error = DatabaseError; - fn basic(&self, address: B160) -> Result, Self::Error> { + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { if let Some(db) = self.active_fork_db() { - DatabaseRef::basic(db, address) + db.basic_ref(address) } else { - Ok(DatabaseRef::basic(&self.mem_db, address)?) + Ok(self.mem_db.basic_ref(address)?) } } - fn code_by_hash(&self, code_hash: B256) -> Result { + fn code_by_hash_ref(&self, code_hash: B256) -> Result { if let Some(db) = self.active_fork_db() { - DatabaseRef::code_by_hash(db, code_hash) + db.code_by_hash_ref(code_hash) } else { - Ok(DatabaseRef::code_by_hash(&self.mem_db, code_hash)?) + Ok(self.mem_db.code_by_hash_ref(code_hash)?) } } - fn storage(&self, address: B160, index: rU256) -> Result { + fn storage_ref(&self, address: Address, index: U256) -> Result { if let Some(db) = self.active_fork_db() { - DatabaseRef::storage(db, address, index) + DatabaseRef::storage_ref(db, address, index) } else { - Ok(DatabaseRef::storage(&self.mem_db, address, index)?) + Ok(DatabaseRef::storage_ref(&self.mem_db, address, index)?) } } - fn block_hash(&self, number: rU256) -> Result { + fn block_hash_ref(&self, number: u64) -> Result { if let Some(db) = self.active_fork_db() { - DatabaseRef::block_hash(db, number) + db.block_hash_ref(number) } else { - Ok(DatabaseRef::block_hash(&self.mem_db, number)?) + Ok(self.mem_db.block_hash_ref(number)?) } } } impl DatabaseCommit for Backend { - fn commit(&mut self, changes: Map) { + fn commit(&mut self, changes: Map) { if let Some(db) = self.active_fork_db_mut() { db.commit(changes) } else { @@ -1359,9 +1518,9 @@ impl DatabaseCommit for Backend { impl Database for Backend { type Error = DatabaseError; - fn basic(&mut self, address: B160) -> Result, Self::Error> { + 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)?) } @@ -1369,23 +1528,23 @@ 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)?) } } - fn storage(&mut self, address: B160, index: rU256) -> Result { + 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: rU256) -> 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)?) } @@ -1393,7 +1552,7 @@ impl Database for Backend { } /// Variants of a [revm::Database] -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum BackendDatabaseSnapshot { /// Simple in-memory [revm::Database] InMemory(FoundryEvmInMemoryDB), @@ -1402,20 +1561,18 @@ pub enum BackendDatabaseSnapshot { } /// Represents a fork -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct Fork { db: ForkDB, journaled_state: JournaledState, } -// === impl Fork === - impl Fork { /// Returns true if the account is a contract pub fn is_contract(&self, acc: Address) -> bool { - if let Ok(Some(acc)) = self.db.basic(h160_to_b160(acc)) { + if let Ok(Some(acc)) = self.db.basic_ref(acc) { if acc.code_hash != KECCAK_EMPTY { - return true + return true; } } is_contract_in_state(&self.journaled_state, acc) @@ -1423,7 +1580,7 @@ impl Fork { } /// Container type for various Backend related data -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct BackendInner { /// Stores the `ForkId` of the fork the `Backend` launched with from the start. /// @@ -1448,8 +1605,8 @@ pub struct BackendInner { /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling pub forks: Vec>, - /// Contains snapshots made at a certain point - pub snapshots: Snapshots>, + /// Contains state snapshots made at a certain point + pub state_snapshots: StateSnapshots>, /// Tracks whether there was a failure in a snapshot that was reverted /// /// The Test contract contains a bool variable that is set to true when an `assert` function @@ -1459,12 +1616,7 @@ pub struct BackendInner { /// reverted we get the _current_ `revm::JournaledState` which contains the state that we can /// check if the `_failed` variable is set, /// additionally - pub has_snapshot_failure: Arc, - /// Tracks the address of a Test contract - /// - /// This address can be used to inspect the state of the contract when a test is being - /// executed. E.g. the `_failed` variable of `DSTest` - pub test_contract_address: Option
, + pub has_state_snapshot_failure: bool, /// Tracks the caller of the test function pub caller: Option
, /// Tracks numeric identifiers for forks @@ -1472,17 +1624,13 @@ pub struct BackendInner { /// All accounts that should be kept persistent when switching forks. /// This means all accounts stored here _don't_ use a separate storage section on each fork /// instead the use only one that's persistent across fork swaps. - /// - /// See also [`clone_data()`] pub persistent_accounts: HashSet
, - /// The configured precompile spec id - pub precompile_id: revm::precompile::SpecId, + /// The configured spec id + pub spec_id: SpecId, /// All accounts that are allowed to execute cheatcodes pub cheatcode_access_accounts: HashSet
, } -// === impl BackendInner === - impl BackendInner { pub fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.issued_local_fork_ids @@ -1552,7 +1700,7 @@ impl BackendInner { } /// Reverts the entire fork database - pub fn revert_snapshot( + pub fn revert_state_snapshot( &mut self, id: LocalForkId, fork_id: ForkId, @@ -1624,7 +1772,7 @@ impl BackendInner { fn next_id(&mut self) -> U256 { let id = self.next_fork_id; - self.next_fork_id += U256::one(); + self.next_fork_id += U256::from(1); id } @@ -1639,12 +1787,12 @@ impl BackendInner { } pub fn precompiles(&self) -> &'static Precompiles { - Precompiles::new(self.precompile_id) + Precompiles::new(PrecompileSpecId::from_spec_id(self.spec_id)) } /// Returns a new, empty, `JournaledState` with set precompiles pub fn new_journaled_state(&self) -> JournaledState { - JournaledState::new(self.precompiles().len()) + JournaledState::new(self.spec_id, self.precompiles().addresses().copied().collect()) } } @@ -1655,13 +1803,12 @@ impl Default for BackendInner { issued_local_fork_ids: Default::default(), created_forks: Default::default(), forks: vec![], - snapshots: Default::default(), - has_snapshot_failure: Arc::new(AtomicBool::new(false)), - test_contract_address: None, + state_snapshots: Default::default(), + has_state_snapshot_failure: false, caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), - precompile_id: revm::precompile::SpecId::LATEST, + spec_id: SpecId::LATEST, // grant the cheatcode,default test and caller address access to execute cheatcodes // itself cheatcode_access_accounts: HashSet::from([ @@ -1677,6 +1824,7 @@ impl Default for BackendInner { pub(crate) fn update_current_env_with_fork_env(current: &mut Env, fork: Env) { current.block = fork.block; current.cfg = fork.cfg; + current.tx.chain_id = fork.tx.chain_id; } /// Clones the data of the given `accounts` from the `active` database into the `fork_db` @@ -1707,8 +1855,6 @@ fn merge_journaled_state_data( active_journaled_state: &JournaledState, fork_journaled_state: &mut JournaledState, ) { - let addr = h160_to_b160(addr); - if let Some(mut acc) = active_journaled_state.state.get(&addr).cloned() { trace!(?addr, "updating journaled_state account data"); if let Some(fork_account) = fork_journaled_state.state.get_mut(&addr) { @@ -1729,33 +1875,35 @@ fn merge_db_account_data( ) { trace!(?addr, "merging database data"); - let addr = h160_to_b160(addr); + let Some(acc) = active.accounts.get(&addr) else { return }; - let mut acc = if let Some(acc) = active.accounts.get(&addr).cloned() { - acc - } else { - // Account does not exist - return - }; - - if let Some(code) = active.contracts.get(&acc.info.code_hash).cloned() { - fork_db.contracts.insert(acc.info.code_hash, code); + // port contract cache over + if let Some(code) = active.contracts.get(&acc.info.code_hash) { + trace!("merging contract cache"); + fork_db.contracts.insert(acc.info.code_hash, code.clone()); } - if let Some(fork_account) = fork_db.accounts.get_mut(&addr) { - // This will merge the fork's tracked storage with active storage and update values - fork_account.storage.extend(std::mem::take(&mut acc.storage)); - // swap them so we can insert the account as whole in the next step - std::mem::swap(&mut fork_account.storage, &mut acc.storage); + // port account storage over + use std::collections::hash_map::Entry; + match fork_db.accounts.entry(addr) { + Entry::Vacant(vacant) => { + trace!("target account not present - inserting from active"); + // if the fork_db doesn't have the target account + // insert the entire thing + vacant.insert(acc.clone()); + } + Entry::Occupied(mut occupied) => { + trace!("target account present - merging storage slots"); + // if the fork_db does have the system, + // extend the existing storage (overriding) + let fork_account = occupied.get_mut(); + fork_account.storage.extend(&acc.storage); + } } - - fork_db.accounts.insert(addr, acc); } /// Returns true of the address is a contract fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool { - let acc = h160_to_b160(acc); - journaled_state .state .get(&acc) @@ -1763,42 +1911,68 @@ fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool .unwrap_or_default() } +/// Updates the env's block with the block's data +fn update_env_block(env: &mut Env, block: &AnyRpcBlock) { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.beneficiary; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + env.block.number = U256::from(block.header.number); + if let Some(excess_blob_gas) = block.header.excess_blob_gas { + env.block.blob_excess_gas_and_price = + Some(BlobExcessGasAndPrice::new(excess_blob_gas, false)); + } +} + /// Executes the given transaction and commits state changes to the database _and_ the journaled -/// state, with an optional inspector +/// state, with an inspector. fn commit_transaction( - tx: Transaction, - mut env: Env, + tx: &Transaction, + mut env: EnvWithHandlerCfg, journaled_state: &mut JournaledState, fork: &mut Fork, fork_id: &ForkId, - cheatcodes_inspector: Option<&mut Cheatcodes>, + persistent_accounts: &HashSet
, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { - configure_tx_env(&mut env, &tx); - - let state = { - let mut evm = EVM::new(); - evm.env = env; + configure_tx_env(&mut env.env, tx); + let now = Instant::now(); + let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); - let db = crate::utils::RuntimeOrHandle::new() - .block_on(async move { Backend::new_with_fork(fork_id, fork, journaled_state).await }); - evm.database(db); - - if let Some(inspector) = cheatcodes_inspector { - match evm.inspect(inspector) { - Ok(res) => res.state, - Err(e) => eyre::bail!("backend: failed committing transaction: {:?}", e), - } - } else { - match evm.transact() { - Ok(res) => res.state, - Err(e) => eyre::bail!("backend: failed committing transaction: {:?}", e), + let depth = journaled_state.depth; + let mut db = Backend::new_with_fork(fork_id, fork, journaled_state); + + let mut evm = crate::utils::new_evm_with_inspector(&mut db as _, 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"); + + apply_state_changeset(res.state, journaled_state, fork, persistent_accounts)?; + Ok(()) +} + +/// Helper method which updates data in the state with the data from the database. +/// Does not change state for persistent accounts (for roll fork to transaction and transact). +pub fn update_state( + state: &mut EvmState, + db: &mut DB, + persistent_accounts: Option<&HashSet
>, +) -> Result<(), DB::Error> { + for (addr, acc) in state.iter_mut() { + if !persistent_accounts.is_some_and(|accounts| accounts.contains(addr)) { + acc.info = db.basic(*addr)?.unwrap_or_default(); + for (key, val) in acc.storage.iter_mut() { + val.present_value = db.storage(*addr, *key)?; } } - }; + } - apply_state_changeset(state, journaled_state, fork); Ok(()) } @@ -1808,19 +1982,75 @@ fn apply_state_changeset( state: Map, journaled_state: &mut JournaledState, fork: &mut Fork, -) { - let changed_accounts = state.keys().copied().collect::>(); + persistent_accounts: &HashSet
, +) -> Result<(), BackendError> { // commit the state and update the loaded accounts fork.db.commit(state); - for addr in changed_accounts { - // reload all changed accounts by removing them from the journaled state and reloading them - // from the now updated database - if journaled_state.state.remove(&addr).is_some() { - let _ = journaled_state.load_account(addr, &mut fork.db); - } - if fork.journaled_state.state.remove(&addr).is_some() { - let _ = fork.journaled_state.load_account(addr, &mut fork.db); + update_state(&mut journaled_state.state, &mut fork.db, Some(persistent_accounts))?; + update_state(&mut fork.journaled_state.state, &mut fork.db, Some(persistent_accounts))?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use crate::{backend::Backend, 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/backend/snapshot.rs b/crates/evm/core/src/backend/snapshot.rs new file mode 100644 index 0000000000000..36c4657c2618c --- /dev/null +++ b/crates/evm/core/src/backend/snapshot.rs @@ -0,0 +1,61 @@ +use alloy_primitives::{map::AddressHashMap, B256, U256}; +use revm::{ + primitives::{AccountInfo, Env, HashMap}, + JournaledState, +}; +use serde::{Deserialize, Serialize}; + +/// A minimal abstraction of a state at a certain point in time +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct StateSnapshot { + pub accounts: AddressHashMap, + pub storage: AddressHashMap>, + pub block_hashes: HashMap, +} + +/// Represents a state snapshot taken during evm execution +#[derive(Clone, Debug)] +pub struct BackendStateSnapshot { + pub db: T, + /// The journaled_state state at a specific point + pub journaled_state: JournaledState, + /// Contains the env at the time of the snapshot + pub env: Env, +} + +impl BackendStateSnapshot { + /// Takes a new state snapshot. + pub fn new(db: T, journaled_state: JournaledState, env: Env) -> Self { + Self { db, journaled_state, env } + } + + /// Called when this state snapshot is reverted. + /// + /// Since we want to keep all additional logs that were emitted since the snapshot was taken + /// we'll merge additional logs into the snapshot's `revm::JournaledState`. Additional logs are + /// those logs that are missing in the snapshot's journaled_state, since the current + /// journaled_state includes the same logs, we can simply replace use that See also + /// `DatabaseExt::revert`. + pub fn merge(&mut self, current: &JournaledState) { + self.journaled_state.logs.clone_from(¤t.logs); + } +} + +/// What to do when reverting a state snapshot. +/// +/// Whether to remove the state snapshot or keep it. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum RevertStateSnapshotAction { + /// Remove the state snapshot after reverting. + #[default] + RevertRemove, + /// Keep the state snapshot after reverting. + RevertKeep, +} + +impl RevertStateSnapshotAction { + /// Returns `true` if the action is to keep the state snapshot. + pub fn is_keep(&self) -> bool { + matches!(self, Self::RevertKeep) + } +} diff --git a/crates/evm/core/src/buffer.rs b/crates/evm/core/src/buffer.rs new file mode 100644 index 0000000000000..1db7420d78736 --- /dev/null +++ b/crates/evm/core/src/buffer.rs @@ -0,0 +1,117 @@ +use alloy_primitives::U256; +use revm::interpreter::opcode; + +/// Used to keep track of which buffer is currently active to be drawn by the debugger. +#[derive(Debug, PartialEq)] +pub enum BufferKind { + Memory, + Calldata, + Returndata, +} + +impl BufferKind { + /// Helper to cycle through the active buffers. + pub fn next(&self) -> Self { + match self { + Self::Memory => Self::Calldata, + Self::Calldata => Self::Returndata, + Self::Returndata => Self::Memory, + } + } + + /// Helper to format the title of the active buffer pane + pub fn title(&self, size: usize) -> String { + match self { + Self::Memory => format!("Memory (max expansion: {size} bytes)"), + Self::Calldata => format!("Calldata (size: {size} bytes)"), + Self::Returndata => format!("Returndata (size: {size} bytes)"), + } + } +} + +/// Container for buffer access information. +pub struct BufferAccess { + pub offset: usize, + pub len: usize, +} + +/// Container for read and write buffer access information. +pub struct BufferAccesses { + /// The read buffer kind and access information. + pub read: Option<(BufferKind, BufferAccess)>, + /// The only mutable buffer is the memory buffer, so don't store the buffer kind. + pub write: Option, +} + +/// A utility function to get the buffer access. +/// +/// The memory_access variable stores the index on the stack that indicates the buffer +/// offset/len accessed by the given opcode: +/// (read buffer, buffer read offset, buffer read len, write memory offset, write memory len) +/// \>= 1: the stack index +/// 0: no memory access +/// -1: a fixed len of 32 bytes +/// -2: a fixed len of 1 byte +/// +/// The return value is a tuple about accessed buffer region by the given opcode: +/// (read buffer, buffer read offset, buffer read len, write memory offset, write memory len) +pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { + let buffer_access = match op { + opcode::KECCAK256 | opcode::RETURN | opcode::REVERT => { + (Some((BufferKind::Memory, 1, 2)), None) + } + opcode::CALLDATACOPY => (Some((BufferKind::Calldata, 2, 3)), Some((1, 3))), + opcode::RETURNDATACOPY => (Some((BufferKind::Returndata, 2, 3)), Some((1, 3))), + opcode::CALLDATALOAD => (Some((BufferKind::Calldata, 1, -1)), None), + opcode::CODECOPY => (None, Some((1, 3))), + opcode::EXTCODECOPY => (None, Some((2, 4))), + opcode::MLOAD => (Some((BufferKind::Memory, 1, -1)), None), + opcode::MSTORE => (None, Some((1, -1))), + opcode::MSTORE8 => (None, Some((1, -2))), + opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => { + (Some((BufferKind::Memory, 1, 2)), None) + } + opcode::CREATE | opcode::CREATE2 => (Some((BufferKind::Memory, 2, 3)), None), + 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(), + }; + + let stack_len = stack.len(); + let get_size = |stack_index| match stack_index { + -2 => Some(1), + -1 => Some(32), + 0 => None, + 1.. => { + if (stack_index as usize) <= stack_len { + Some(stack[stack_len - stack_index as usize].saturating_to()) + } else { + None + } + } + _ => panic!("invalid stack index"), + }; + + if buffer_access.0.is_some() || buffer_access.1.is_some() { + let (read, write) = buffer_access; + let read_access = read.and_then(|b| { + let (buffer, offset, len) = b; + Some((buffer, BufferAccess { offset: get_size(offset)?, len: get_size(len)? })) + }); + let write_access = write.and_then(|b| { + let (offset, len) = b; + Some(BufferAccess { offset: get_size(offset)?, len: get_size(len)? }) + }); + Some(BufferAccesses { read: read_access, write: write_access }) + } else { + None + } +} diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs new file mode 100644 index 0000000000000..2e4fdb52617b8 --- /dev/null +++ b/crates/evm/core/src/constants.rs @@ -0,0 +1,66 @@ +use alloy_primitives::{address, b256, hex, Address, B256}; + +/// The cheatcode handler address. +/// +/// This is the same address as the one used in DappTools's HEVM. +/// +/// This is calculated as: +/// `address(bytes20(uint160(uint256(keccak256('hevm cheat code')))))` +pub const CHEATCODE_ADDRESS: Address = address!("7109709ECfa91a80626fF3989D68f67F5b1DD12D"); + +/// The contract hash at [`CHEATCODE_ADDRESS`]. +/// +/// This is calculated as: +/// `keccak256(abi.encodePacked(CHEATCODE_ADDRESS))`. +pub const CHEATCODE_CONTRACT_HASH: B256 = + b256!("b0450508e5a2349057c3b4c9c84524d62be4bb17e565dbe2df34725a26872291"); + +/// The Hardhat console address. +/// +/// See: +pub const HARDHAT_CONSOLE_ADDRESS: Address = address!("000000000000000000636F6e736F6c652e6c6f67"); + +/// Stores the caller address to be used as *sender* account for: +/// - deploying Test contracts +/// - deploying Script contracts +/// +/// Derived from `address(uint160(uint256(keccak256("foundry default caller"))))`, +/// which is equal to `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38`. +pub const CALLER: Address = address!("1804c8AB1F12E6bbf3894d4083f33e07309d1f38"); + +/// The default test contract address. +pub const TEST_CONTRACT_ADDRESS: Address = address!("b4c79daB8f259C7Aee6E5b2Aa729821864227e84"); + +/// Magic return value returned by the `assume` cheatcode. +pub const MAGIC_ASSUME: &[u8] = b"FOUNDRY::ASSUME"; + +/// Magic return value returned by the `skip` cheatcode. Optionally appended with a reason. +pub const MAGIC_SKIP: &[u8] = b"FOUNDRY::SKIP"; + +/// Test timeout return value. +pub const TEST_TIMEOUT: &str = "FOUNDRY::TEST_TIMEOUT"; + +/// The address that deploys the default CREATE2 deployer contract. +pub const DEFAULT_CREATE2_DEPLOYER_DEPLOYER: Address = + address!("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"); +/// The hash of the default CREATE2 deployer code. +/// +/// This is calculated as `keccak256([`DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE`])`. +pub const DEFAULT_CREATE2_DEPLOYER_CODEHASH: B256 = + b256!("2fa86add0aed31f33a762c9d88e807c475bd51d0f52bd0955754b2608f7e4989"); + +#[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/decode.rs b/crates/evm/core/src/decode.rs new file mode 100644 index 0000000000000..ef1e54daba891 --- /dev/null +++ b/crates/evm/core/src/decode.rs @@ -0,0 +1,227 @@ +//! Various utilities to decode test results. + +use crate::abi::{console, Vm}; +use alloy_dyn_abi::JsonAbiExt; +use alloy_json_abi::{Error, JsonAbi}; +use alloy_primitives::{hex, map::HashMap, Log, Selector}; +use alloy_sol_types::{SolEventInterface, SolInterface, SolValue}; +use foundry_common::SELECTOR_LEN; +use itertools::Itertools; +use revm::interpreter::InstructionResult; +use std::{fmt, sync::OnceLock}; + +/// A skip reason. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SkipReason(pub Option); + +impl SkipReason { + /// Decodes a skip reason, if any. + pub fn decode(raw_result: &[u8]) -> Option { + raw_result.strip_prefix(crate::constants::MAGIC_SKIP).map(|reason| { + let reason = String::from_utf8_lossy(reason).into_owned(); + Self((!reason.is_empty()).then_some(reason)) + }) + } + + /// Decodes a skip reason from a string that was obtained by formatting `Self`. + /// + /// This is a hack to support re-decoding a skip reason in proptest. + pub fn decode_self(s: &str) -> Option { + s.strip_prefix("skipped").map(|rest| Self(rest.strip_prefix(": ").map(ToString::to_string))) + } +} + +impl fmt::Display for SkipReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("skipped")?; + if let Some(reason) = &self.0 { + f.write_str(": ")?; + f.write_str(reason)?; + } + Ok(()) + } +} + +/// Decode a set of logs, only returning logs from DSTest logging events and Hardhat's `console.log` +pub fn decode_console_logs(logs: &[Log]) -> Vec { + logs.iter().filter_map(decode_console_log).collect() +} + +/// Decode a single log. +/// +/// This function returns [None] if it is not a DSTest log or the result of a Hardhat +/// `console.log`. +pub fn decode_console_log(log: &Log) -> Option { + console::ds::ConsoleEvents::decode_log(log, false).ok().map(|decoded| decoded.to_string()) +} + +/// Decodes revert data. +#[derive(Clone, Debug, Default)] +pub struct RevertDecoder { + /// The custom errors to use for decoding. + pub errors: HashMap>, +} + +impl Default for &RevertDecoder { + fn default() -> Self { + static EMPTY: OnceLock = OnceLock::new(); + EMPTY.get_or_init(RevertDecoder::new) + } +} + +impl RevertDecoder { + /// Creates a new, empty revert decoder. + pub fn new() -> Self { + Self::default() + } + + /// Sets the ABIs to use for error decoding. + /// + /// Note that this is decently expensive as it will hash all errors for faster indexing. + pub fn with_abis<'a>(mut self, abi: impl IntoIterator) -> Self { + self.extend_from_abis(abi); + self + } + + /// Sets the ABI to use for error decoding. + /// + /// Note that this is decently expensive as it will hash all errors for faster indexing. + pub fn with_abi(mut self, abi: &JsonAbi) -> Self { + self.extend_from_abi(abi); + self + } + + /// Sets the ABI to use for error decoding, if it is present. + /// + /// Note that this is decently expensive as it will hash all errors for faster indexing. + pub fn with_abi_opt(mut self, abi: Option<&JsonAbi>) -> Self { + if let Some(abi) = abi { + self.extend_from_abi(abi); + } + self + } + + /// Extends the decoder with the given ABI's custom errors. + pub fn extend_from_abis<'a>(&mut self, abi: impl IntoIterator) { + for abi in abi { + self.extend_from_abi(abi); + } + } + + /// Extends the decoder with the given ABI's custom errors. + pub fn extend_from_abi(&mut self, abi: &JsonAbi) { + for error in abi.errors() { + self.push_error(error.clone()); + } + } + + /// Adds a custom error to use for decoding. + pub fn push_error(&mut self, error: Error) { + self.errors.entry(error.selector()).or_default().push(error); + } + + /// Tries to decode an error message from the given revert bytes. + /// + /// Note that this is just a best-effort guess, and should not be relied upon for anything other + /// than user output. + pub fn decode(&self, err: &[u8], status: Option) -> String { + self.maybe_decode(err, status).unwrap_or_else(|| { + if err.is_empty() { + "".to_string() + } else { + trimmed_hex(err) + } + }) + } + + /// Tries to decode an error message from the given revert bytes. + /// + /// See [`decode`](Self::decode) for more information. + pub fn maybe_decode(&self, err: &[u8], status: Option) -> Option { + let Some((selector, data)) = err.split_first_chunk::() else { + if let Some(status) = status { + if !status.is_ok() { + return Some(format!("EvmError: {status:?}")); + } + } + return if err.is_empty() { + None + } else { + Some(format!("custom error bytes {}", hex::encode_prefixed(err))) + }; + }; + + if let Some(reason) = SkipReason::decode(err) { + return Some(reason.to_string()); + } + + // Solidity's `Error(string)` or `Panic(uint256)`, or `Vm`'s custom errors. + if let Ok(e) = alloy_sol_types::ContractError::::abi_decode(err, false) { + return Some(e.to_string()); + } + + // Custom errors. + if let Some(errors) = self.errors.get(selector) { + for error in errors { + // If we don't decode, don't return an error, try to decode as a string later. + if let Ok(decoded) = error.abi_decode_input(data, false) { + return Some(format!( + "{}({})", + error.name, + decoded.iter().map(foundry_common::fmt::format_token).format(", ") + )); + } + } + } + + // ABI-encoded `string`. + if let Ok(s) = String::abi_decode(err, true) { + return Some(s); + } + + // ASCII string. + if err.is_ascii() { + return Some(std::str::from_utf8(err).unwrap().to_string()); + } + + // Generic custom error. + Some({ + let mut s = format!("custom error {}", hex::encode_prefixed(selector)); + if !data.is_empty() { + s.push_str(": "); + match std::str::from_utf8(data) { + Ok(data) => s.push_str(data), + Err(_) => s.push_str(&hex::encode(data)), + } + } + s + }) + } +} + +fn trimmed_hex(s: &[u8]) -> String { + let n = 32; + if s.len() <= n { + hex::encode(s) + } else { + format!( + "{}…{} ({} bytes)", + &hex::encode(&s[..n / 2]), + &hex::encode(&s[s.len() - n / 2..]), + s.len(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_trimmed_hex() { + assert_eq!(trimmed_hex(&hex::decode("1234567890").unwrap()), "1234567890"); + assert_eq!( + trimmed_hex(&hex::decode("492077697368207275737420737570706F72746564206869676865722D6B696E646564207479706573").unwrap()), + "49207769736820727573742073757070…6865722d6b696e646564207479706573 (41 bytes)" + ); + } +} diff --git a/crates/evm/src/executor/fork/database.rs b/crates/evm/core/src/fork/database.rs similarity index 62% rename from crates/evm/src/executor/fork/database.rs rename to crates/evm/core/src/fork/database.rs index c9b58c839de89..53595e451fd2f 100644 --- a/crates/evm/src/executor/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -1,18 +1,16 @@ //! A revm database that forks off a remote client use crate::{ - executor::{ - backend::{error::DatabaseError, snapshot::StateSnapshot}, - fork::{BlockchainDb, SharedBackend}, - snapshot::Snapshots, - }, - revm::db::CacheDB, + backend::{RevertStateSnapshotAction, StateSnapshot}, + state_snapshot::StateSnapshots, }; -use ethers::{prelude::U256, types::BlockId}; +use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_rpc_types::BlockId; +use foundry_fork_db::{BlockchainDb, DatabaseError, SharedBackend}; use parking_lot::Mutex; use revm::{ - db::DatabaseRef, - primitives::{Account, AccountInfo, Bytecode, HashMap as Map, B160, B256, U256 as rU256}, + db::{CacheDB, DatabaseRef}, + primitives::{Account, AccountInfo, Bytecode}, Database, DatabaseCommit, }; use std::sync::Arc; @@ -23,24 +21,24 @@ use std::sync::Arc; /// endpoint. The inner in-memory database holds this storage and will be used for write operations. /// This database uses the `backend` for read and the `db` for write operations. But note the /// `backend` will also write (missing) data to the `db` in the background -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ForkedDatabase { - /// responsible for fetching missing data + /// Responsible for fetching missing data. /// - /// This is responsible for getting data + /// This is responsible for getting data. backend: SharedBackend, /// Cached Database layer, ensures that changes are not written to the database that /// exclusively stores the state of the remote client. /// /// This separates Read/Write operations - /// - reads from the `SharedBackend as DatabaseRef` writes to the internal cache storage + /// - reads from the `SharedBackend as DatabaseRef` writes to the internal cache storage. cache_db: CacheDB, - /// Contains all the data already fetched + /// Contains all the data already fetched. /// - /// This exclusively stores the _unchanged_ remote client state + /// This exclusively stores the _unchanged_ remote client state. db: BlockchainDb, - /// holds the snapshot state of a blockchain - snapshots: Arc>>, + /// Holds the state snapshots of a blockchain. + state_snapshots: Arc>>, } impl ForkedDatabase { @@ -50,7 +48,7 @@ impl ForkedDatabase { cache_db: CacheDB::new(backend.clone()), backend, db, - snapshots: Arc::new(Mutex::new(Default::default())), + state_snapshots: Arc::new(Mutex::new(Default::default())), } } @@ -62,8 +60,8 @@ impl ForkedDatabase { &mut self.cache_db } - pub fn snapshots(&self) -> &Arc>> { - &self.snapshots + pub fn state_snapshots(&self) -> &Arc>> { + &self.state_snapshots } /// Reset the fork to a fresh forked state, and optionally update the fork config @@ -94,32 +92,35 @@ impl ForkedDatabase { &self.db } - pub fn create_snapshot(&self) -> ForkDbSnapshot { + pub fn create_state_snapshot(&self) -> ForkDbStateSnapshot { let db = self.db.db(); - let snapshot = StateSnapshot { + let state_snapshot = StateSnapshot { accounts: db.accounts.read().clone(), storage: db.storage.read().clone(), block_hashes: db.block_hashes.read().clone(), }; - ForkDbSnapshot { local: self.cache_db.clone(), snapshot } + ForkDbStateSnapshot { local: self.cache_db.clone(), state_snapshot } } - pub fn insert_snapshot(&self) -> U256 { - let snapshot = self.create_snapshot(); - let mut snapshots = self.snapshots().lock(); - let id = snapshots.insert(snapshot); + pub fn insert_state_snapshot(&self) -> U256 { + let state_snapshot = self.create_state_snapshot(); + let mut state_snapshots = self.state_snapshots().lock(); + let id = state_snapshots.insert(state_snapshot); trace!(target: "backend::forkdb", "Created new snapshot {}", id); id } - pub fn revert_snapshot(&mut self, id: U256) -> bool { - let snapshot = { self.snapshots().lock().remove_at(id) }; - if let Some(snapshot) = snapshot { - self.snapshots().lock().insert_at(snapshot.clone(), id); - let ForkDbSnapshot { + /// Removes the snapshot from the tracked snapshot and sets it as the current state + pub fn revert_state_snapshot(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { + let state_snapshot = { self.state_snapshots().lock().remove_at(id) }; + if let Some(state_snapshot) = state_snapshot { + if action.is_keep() { + self.state_snapshots().lock().insert_at(state_snapshot.clone(), id); + } + let ForkDbStateSnapshot { local, - snapshot: StateSnapshot { accounts, storage, block_hashes }, - } = snapshot; + state_snapshot: StateSnapshot { accounts, storage, block_hashes }, + } = state_snapshot; let db = self.inner().db(); { let mut accounts_lock = db.accounts.write(); @@ -151,10 +152,10 @@ impl ForkedDatabase { impl Database for ForkedDatabase { type Error = DatabaseError; - fn basic(&mut self, address: B160) -> Result, Self::Error> { + fn basic(&mut self, address: Address) -> Result, Self::Error> { // Note: this will always return Some, since the `SharedBackend` will always load the // account, this differs from `::basic`, See also - // [MemDb::ensure_loaded](crate::executor::backend::MemDb::ensure_loaded) + // [MemDb::ensure_loaded](crate::backend::MemDb::ensure_loaded) Database::basic(&mut self.cache_db, address) } @@ -162,11 +163,11 @@ impl Database for ForkedDatabase { Database::code_by_hash(&mut self.cache_db, code_hash) } - fn storage(&mut self, address: B160, index: rU256) -> Result { + fn storage(&mut self, address: Address, index: U256) -> Result { Database::storage(&mut self.cache_db, address, index) } - fn block_hash(&mut self, number: rU256) -> Result { + fn block_hash(&mut self, number: u64) -> Result { Database::block_hash(&mut self.cache_db, number) } } @@ -174,25 +175,25 @@ impl Database for ForkedDatabase { impl DatabaseRef for ForkedDatabase { type Error = DatabaseError; - fn basic(&self, address: B160) -> Result, Self::Error> { - self.cache_db.basic(address) + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + self.cache_db.basic_ref(address) } - fn code_by_hash(&self, code_hash: B256) -> Result { - self.cache_db.code_by_hash(code_hash) + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.cache_db.code_by_hash_ref(code_hash) } - fn storage(&self, address: B160, index: rU256) -> Result { - DatabaseRef::storage(&self.cache_db, address, index) + fn storage_ref(&self, address: Address, index: U256) -> Result { + DatabaseRef::storage_ref(&self.cache_db, address, index) } - fn block_hash(&self, number: rU256) -> Result { - self.cache_db.block_hash(number) + fn block_hash_ref(&self, number: u64) -> Result { + self.cache_db.block_hash_ref(number) } } impl DatabaseCommit for ForkedDatabase { - fn commit(&mut self, changes: Map) { + fn commit(&mut self, changes: HashMap) { self.database_mut().commit(changes) } } @@ -201,15 +202,13 @@ impl DatabaseCommit for ForkedDatabase { /// /// This mimics `revm::CacheDB` #[derive(Clone, Debug)] -pub struct ForkDbSnapshot { +pub struct ForkDbStateSnapshot { pub local: CacheDB, - pub snapshot: StateSnapshot, + pub state_snapshot: StateSnapshot, } -// === impl DbSnapshot === - -impl ForkDbSnapshot { - fn get_storage(&self, address: B160, index: rU256) -> Option { +impl ForkDbStateSnapshot { + fn get_storage(&self, address: Address, index: U256) -> Option { self.local.accounts.get(&address).and_then(|account| account.storage.get(&index)).copied() } } @@ -217,46 +216,46 @@ impl ForkDbSnapshot { // This `DatabaseRef` implementation works similar to `CacheDB` which prioritizes modified elements, // and uses another db as fallback // We prioritize stored changed accounts/storage -impl DatabaseRef for ForkDbSnapshot { +impl DatabaseRef for ForkDbStateSnapshot { type Error = DatabaseError; - fn basic(&self, address: B160) -> Result, Self::Error> { + fn basic_ref(&self, address: Address) -> Result, Self::Error> { match self.local.accounts.get(&address) { Some(account) => Ok(Some(account.info.clone())), None => { - let mut acc = self.snapshot.accounts.get(&address).cloned(); + let mut acc = self.state_snapshot.accounts.get(&address).cloned(); if acc.is_none() { - acc = self.local.basic(address)?; + acc = self.local.basic_ref(address)?; } Ok(acc) } } } - fn code_by_hash(&self, code_hash: B256) -> Result { - self.local.code_by_hash(code_hash) + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.local.code_by_hash_ref(code_hash) } - fn storage(&self, address: B160, index: rU256) -> Result { + fn storage_ref(&self, address: Address, index: U256) -> Result { match self.local.accounts.get(&address) { Some(account) => match account.storage.get(&index) { Some(entry) => Ok(*entry), None => match self.get_storage(address, index) { - None => DatabaseRef::storage(&self.local, address, index), + None => DatabaseRef::storage_ref(&self.local, address, index), Some(storage) => Ok(storage), }, }, None => match self.get_storage(address, index) { - None => DatabaseRef::storage(&self.local, address, index), + None => DatabaseRef::storage_ref(&self.local, address, index), Some(storage) => Ok(storage), }, } } - fn block_hash(&self, number: rU256) -> Result { - match self.snapshot.block_hashes.get(&number).copied() { - None => self.local.block_hash(number), + fn block_hash_ref(&self, number: u64) -> Result { + match self.state_snapshot.block_hashes.get(&U256::from(number)).copied() { + None => self.local.block_hash_ref(number), Some(block_hash) => Ok(block_hash), } } @@ -265,15 +264,15 @@ impl DatabaseRef for ForkDbSnapshot { #[cfg(test)] mod tests { use super::*; - use crate::executor::fork::BlockchainDbMeta; - use foundry_common::get_http_provider; + use crate::backend::BlockchainDbMeta; + use foundry_common::provider::get_http_provider; use std::collections::BTreeSet; /// Demonstrates that `Database::basic` for `ForkedDatabase` will always return the /// `AccountInfo` #[tokio::test(flavor = "multi_thread")] async fn fork_db_insert_basic_default() { - let rpc = foundry_utils::rpc::next_http_rpc_endpoint(); + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); let provider = get_http_provider(rpc.clone()); let meta = BlockchainDbMeta { cfg_env: Default::default(), @@ -285,12 +284,12 @@ mod tests { let backend = SharedBackend::spawn_backend(Arc::new(provider), db.clone(), None).await; let mut db = ForkedDatabase::new(backend, db); - let address = B160::random(); + let address = Address::random(); let info = Database::basic(&mut db, address).unwrap(); assert!(info.is_some()); let mut info = info.unwrap(); - info.balance = rU256::from(500u64); + info.balance = U256::from(500u64); // insert the modified account info db.database_mut().insert_account_info(address, info.clone()); diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs new file mode 100644 index 0000000000000..e900192248354 --- /dev/null +++ b/crates/evm/core/src/fork/init.rs @@ -0,0 +1,86 @@ +use crate::utils::apply_chain_and_block_specific_env_changes; +use alloy_consensus::BlockHeader; +use alloy_primitives::{Address, U256}; +use alloy_provider::{network::BlockResponse, Network, Provider}; +use alloy_rpc_types::{BlockNumberOrTag, BlockTransactionsKind}; +use eyre::WrapErr; +use foundry_common::NON_ARCHIVE_NODE_WARNING; +use revm::primitives::{BlockEnv, CfgEnv, Env, TxEnv}; + +/// Initializes a REVM block environment based on a forked +/// ethereum provider. +pub async fn environment>( + provider: &P, + memory_limit: u64, + gas_price: Option, + override_chain_id: Option, + pin_block: Option, + origin: Address, + disable_block_gas_limit: bool, +) -> eyre::Result<(Env, N::BlockResponse)> { + let block_number = if let Some(pin_block) = pin_block { + pin_block + } else { + provider.get_block_number().await.wrap_err("failed to get latest block number")? + }; + let (fork_gas_price, rpc_chain_id, block) = tokio::try_join!( + provider.get_gas_price(), + provider.get_chain_id(), + provider.get_block_by_number( + BlockNumberOrTag::Number(block_number), + BlockTransactionsKind::Hashes + ) + )?; + let block = if let Some(block) = block { + block + } else { + if let Ok(latest_block) = provider.get_block_number().await { + // If the `eth_getBlockByNumber` call succeeds, but returns null instead of + // the block, and the block number is less than equal the latest block, then + // the user is forking from a non-archive node with an older block number. + if block_number <= latest_block { + error!("{NON_ARCHIVE_NODE_WARNING}"); + } + eyre::bail!( + "failed to get block for block number: {block_number}; \ + latest block number: {latest_block}" + ); + } + eyre::bail!("failed to get block for block number: {block_number}") + }; + + let mut cfg = CfgEnv::default(); + cfg.chain_id = override_chain_id.unwrap_or(rpc_chain_id); + cfg.memory_limit = memory_limit; + cfg.limit_contract_code_size = Some(usize::MAX); + // EIP-3607 rejects transactions from senders with deployed code. + // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller + // is a contract. So we disable the check by default. + cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = disable_block_gas_limit; + + let mut env = Env { + cfg, + block: BlockEnv { + number: U256::from(block.header().number()), + timestamp: U256::from(block.header().timestamp()), + coinbase: block.header().beneficiary(), + difficulty: block.header().difficulty(), + 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() + }, + tx: TxEnv { + caller: origin, + gas_price: U256::from(gas_price.unwrap_or(fork_gas_price)), + chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)), + gas_limit: block.header().gas_limit() as u64, + ..Default::default() + }, + }; + + apply_chain_and_block_specific_env_changes::(&mut env, &block); + + Ok((env, block)) +} diff --git a/crates/evm/src/executor/fork/mod.rs b/crates/evm/core/src/fork/mod.rs similarity index 78% rename from crates/evm/src/executor/fork/mod.rs rename to crates/evm/core/src/fork/mod.rs index 8805c62b9356c..9401c2d32ed58 100644 --- a/crates/evm/src/executor/fork/mod.rs +++ b/crates/evm/core/src/fork/mod.rs @@ -1,23 +1,16 @@ -mod backend; - use super::opts::EvmOpts; -pub use backend::{BackendHandler, SharedBackend}; - use revm::primitives::Env; mod init; pub use init::environment; -mod cache; -pub use cache::{BlockchainDb, BlockchainDbMeta, JsonBlockCacheDB, MemDb}; - pub mod database; mod multi; pub use multi::{ForkId, MultiFork, MultiForkHandler}; /// Represents a _fork_ of a remote chain whose data is available only via the `url` endpoint. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct CreateFork { /// Whether to enable rpc storage caching for this fork pub enable_caching: bool, diff --git a/crates/evm/src/executor/fork/multi.rs b/crates/evm/core/src/fork/multi.rs similarity index 59% rename from crates/evm/src/executor/fork/multi.rs rename to crates/evm/core/src/fork/multi.rs index 66631b6dc3b9a..56bd7f3a3ce34 100644 --- a/crates/evm/src/executor/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -1,19 +1,15 @@ -//! Support for running multiple fork backend +//! 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::{ - executor::fork::{BackendHandler, BlockchainDb, BlockchainDbMeta, CreateFork, SharedBackend}, - utils::ru256_to_u256, -}; -use ethers::{ - abi::{AbiDecode, AbiEncode, AbiError}, - providers::{Http, Provider, RetryClient}, - types::{BlockId, BlockNumber}, -}; -use foundry_common::ProviderBuilder; +use super::CreateFork; +use alloy_consensus::BlockHeader; +use alloy_primitives::{map::HashMap, U256}; +use alloy_provider::network::BlockResponse; +use foundry_common::provider::{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}, @@ -22,21 +18,39 @@ use futures::{ }; use revm::primitives::Env; use std::{ - collections::HashMap, - fmt, + fmt::{self, Write}, pin::Pin, sync::{ + atomic::AtomicUsize, mpsc::{channel as oneshot_channel, Sender as OneshotSender}, Arc, }, time::Duration, }; -/// The identifier for a specific fork, this could be the name of the network a custom descriptive -/// name. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +/// The _unique_ identifier for a specific fork, this could be the name of the network a custom +/// descriptive name. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ForkId(pub String); +impl ForkId { + /// Returns the identifier for a Fork from a URL and block number. + pub fn new(url: &str, num: Option) -> Self { + let mut id = url.to_string(); + id.push('@'); + match num { + Some(n) => write!(id, "{n:#x}").unwrap(), + None => id.push_str("latest"), + } + Self(id) + } + + /// Returns the identifier of the fork. + pub fn as_str(&self) -> &str { + &self.0 + } +} + impl fmt::Display for ForkId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) @@ -49,70 +63,60 @@ impl> From for ForkId { } } -impl AbiEncode for ForkId { - fn encode(self) -> Vec { - AbiEncode::encode(self.0) - } -} - -impl AbiDecode for ForkId { - fn decode(bytes: impl AsRef<[u8]>) -> Result { - Ok(Self(String::decode(bytes)?)) - } -} - /// The Sender half of multi fork pair. -/// Can send requests to the `MultiForkHandler` to create forks -#[derive(Debug, Clone)] +/// 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 MultiForkBackend === - 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 async fn spawn() -> Self { + 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-thread".to_string()) + .name("multi-fork-backend".into()) .spawn(move || { let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .expect("failed to create multi-fork-backend-thread tokio runtime"); + .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 }); }) - .expect("failed to spawn multi fork handler thread"); + .expect("failed to spawn thread"); trace!(target: "fork::multi", "spawned MultiForkHandler thread"); 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(); @@ -121,9 +125,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, @@ -136,7 +140,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(); @@ -145,7 +149,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> { @@ -157,7 +170,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> { @@ -168,63 +181,61 @@ impl MultiFork { } } -type Handler = BackendHandler>>>; +type Handler = BackendHandler>; -type CreateFuture = Pin> + Send>>; +type CreateFuture = + Pin> + Send>>; 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 + /// 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 -#[must_use = "MultiForkHandler does nothing unless polled."] +/// 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 pending_tasks: Vec, - /// All created Forks in order to reuse them + /// All _unique_ forkids mapped to their corresponding backend. + /// + /// Note: The backend can be shared by multiple ForkIds if the target the same provider and + /// block number. forks: HashMap, - /// The retries to allow for new providers - retries: u32, - - /// Initial backoff delay for requests - backoff: u64, - - /// Optional periodic interval to flush rpc cache + /// Optional periodic interval to flush rpc cache. flush_cache_interval: Option, } -// === impl MultiForkHandler === - impl MultiForkHandler { fn new(incoming: Receiver) -> Self { Self { @@ -232,14 +243,11 @@ impl MultiForkHandler { handlers: Default::default(), pending_tasks: Default::default(), forks: Default::default(), - retries: 8, - // 800ms - backoff: 800, flush_cache_interval: None, } } - /// 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)); @@ -252,7 +260,7 @@ impl MultiForkHandler { #[allow(irrefutable_let_patterns)] if let ForkTask::Create(_, in_progress, _, additional) = task { if in_progress == id { - return Some(additional) + return Some(additional); } } } @@ -260,24 +268,44 @@ impl MultiForkHandler { } fn create_fork(&mut self, fork: CreateFork, sender: CreateSender) { - let fork_id = create_fork_id(&fork.url, fork.evm_opts.fork_block_number); + let fork_id = ForkId::new(&fork.url, fork.evm_opts.fork_block_number); trace!(?fork_id, "created new forkId"); - if let Some(fork) = self.forks.get_mut(&fork_id) { - fork.num_senders += 1; - let _ = sender.send(Ok((fork_id, fork.backend.clone(), fork.opts.env.clone()))); - } else { - // 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 - } + // 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. + let task = Box::pin(create_fork(fork)); + self.pending_tasks.push(ForkTask::Create(task, fork_id, sender, Vec::new())); + } + + fn insert_new_fork( + &mut self, + fork_id: ForkId, + fork: CreatedFork, + sender: CreateSender, + additional_senders: Vec, + ) { + self.forks.insert(fork_id.clone(), fork.clone()); + let _ = sender.send(Ok((fork_id.clone(), fork.backend.clone(), fork.opts.env.clone()))); + + // Notify all additional senders and track unique forkIds. + for sender in additional_senders { + let next_fork_id = fork.inc_senders(fork_id.clone()); + self.forks.insert(next_fork_id.clone(), fork.clone()); + let _ = sender.send(Ok((next_fork_id, fork.backend.clone(), fork.opts.env.clone()))); + } + } - let retries = self.retries; - let backoff = self.backoff; - // need to create a new fork - let task = Box::pin(create_fork(fork, retries, backoff)); - self.pending_tasks.push(ForkTask::Create(task, fork_id, sender, Vec::new())); + /// 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; } } @@ -301,9 +329,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(()); @@ -316,48 +347,47 @@ 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 + break; } Poll::Pending => break, } } - // 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 { ForkTask::Create(mut fut, id, sender, additional_senders) => { if let Poll::Ready(resp) = fut.poll_unpin(cx) { match resp { - Ok((fork, handler)) => { - pin.handlers.push((id.clone(), handler)); - let backend = fork.backend.clone(); - let env = fork.opts.env.clone(); - pin.forks.insert(id.clone(), fork); - - let _ = sender.send(Ok((id.clone(), backend.clone(), env.clone()))); - - // also notify all additional senders - for sender in additional_senders { - let _ = - sender.send(Ok((id.clone(), backend.clone(), env.clone()))); + Ok((fork_id, fork, handler)) => { + if let Some(fork) = pin.forks.get(&fork_id).cloned() { + pin.insert_new_fork( + fork.inc_senders(fork_id), + fork, + sender, + additional_senders, + ); + } else { + pin.handlers.push((fork_id.clone(), handler)); + pin.insert_new_fork(fork_id, fork, sender, additional_senders); } } Err(err) => { @@ -379,7 +409,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) { @@ -394,10 +424,10 @@ impl Future for MultiForkHandler { if pin.handlers.is_empty() && pin.incoming.is_done() { trace!(target: "fork::multi", "completed"); - return Poll::Ready(()) + return Poll::Ready(()); } - // periodically flush cached RPC state + // Periodically flush cached RPC state. if pin .flush_cache_interval .as_mut() @@ -407,10 +437,13 @@ 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 - std::thread::spawn(move || { - forks.into_iter().for_each(|fork| fork.flush_cache()); - }); + // Flush this on new thread to not block here. + std::thread::Builder::new() + .name("flusher".into()) + .spawn(move || { + forks.into_iter().for_each(|fork| fork.flush_cache()); + }) + .expect("failed to spawn thread"); } Poll::Pending @@ -418,28 +451,36 @@ impl Future for MultiForkHandler { } /// Tracks the created Fork -#[derive(Debug)] +#[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 - num_senders: usize, + /// consumers. + num_senders: Arc, } -// === impl CreatedFork === - impl CreatedFork { pub fn new(opts: CreateFork, backend: SharedBackend) -> Self { - Self { opts, backend, num_senders: 1 } + Self { opts, backend, num_senders: Arc::new(AtomicUsize::new(1)) } + } + + /// Increment senders and return unique identifier of the fork. + fn inc_senders(&self, fork_id: ForkId) -> ForkId { + format!( + "{}-{}", + fork_id.as_str(), + self.num_senders.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + ) + .into() } } /// 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. @@ -462,50 +503,39 @@ impl Drop for ShutDownMultiFork { } } -/// Returns the identifier for a Fork which consists of the url and the block number -fn create_fork_id(url: &str, num: Option) -> ForkId { - let num = num.map(|num| BlockNumber::Number(num.into())).unwrap_or(BlockNumber::Latest); - ForkId(format!("{url}@{num}")) -} - -/// Creates a new fork +/// Creates a new fork. /// -/// This will establish a new `Provider` to the endpoint and return the Fork Backend -async fn create_fork( - mut fork: CreateFork, - retries: u32, - backoff: u64, -) -> eyre::Result<(CreatedFork, Handler)> { +/// This will establish a new `Provider` to the endpoint and return the Fork Backend. +async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Handler)> { let provider = Arc::new( ProviderBuilder::new(fork.url.as_str()) - .max_retry(retries) - .initial_backoff(backoff) + .maybe_max_retry(fork.evm_opts.fork_retries) + .maybe_initial_backoff(fork.evm_opts.fork_retry_backoff) + .maybe_headers(fork.evm_opts.fork_headers.clone()) .compute_units_per_second(fork.evm_opts.get_compute_units_per_second()) .build()?, ); - // 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 - .number - .map(|num| num.as_u64()) - .unwrap_or_else(|| ru256_to_u256(meta.block_env.number).as_u64()); + let number = block.header().number(); - // 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(ru256_to_u256(meta.cfg_env.chain_id).as_u64(), number) + Config::foundry_block_cache_dir(meta.cfg_env.chain_id, number) } else { None }; let db = BlockchainDb::new(meta, cache_path); - let (backend, handler) = - SharedBackend::new(provider, db, Some(BlockId::Number(BlockNumber::Number(number.into())))); + let (backend, handler) = SharedBackend::new(provider, db, Some(number.into())); let fork = CreatedFork::new(fork, backend); - Ok((fork, handler)) + let fork_id = ForkId::new(&fork.opts.url, number.into()); + + Ok((fork_id, fork, handler)) } diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs new file mode 100644 index 0000000000000..80fef528c9ac5 --- /dev/null +++ b/crates/evm/core/src/ic.rs @@ -0,0 +1,132 @@ +use alloy_primitives::map::rustc_hash::FxHashMap; +use eyre::Result; +use revm::interpreter::{ + opcode::{PUSH0, PUSH1, PUSH32}, + OpCode, +}; +use revm_inspectors::opcode::immediate_size; +use serde::Serialize; + +/// Maps from program counter to instruction counter. +/// +/// Inverse of [`IcPcMap`]. +#[derive(Debug, Clone, Serialize)] +#[serde(transparent)] +pub struct PcIcMap { + pub inner: FxHashMap, +} + +impl PcIcMap { + /// Creates a new `PcIcMap` for the given code. + pub fn new(code: &[u8]) -> Self { + Self { inner: make_map::(code) } + } + + /// Returns the length of the map. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns `true` if the map is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Returns the instruction counter for the given program counter. + pub fn get(&self, pc: u32) -> Option { + self.inner.get(&pc).copied() + } +} + +/// Map from instruction counter to program counter. +/// +/// Inverse of [`PcIcMap`]. +pub struct IcPcMap { + pub inner: FxHashMap, +} + +impl IcPcMap { + /// Creates a new `IcPcMap` for the given code. + pub fn new(code: &[u8]) -> Self { + Self { inner: make_map::(code) } + } + + /// Returns the length of the map. + pub fn len(&self) -> usize { + self.inner.len() + } + + /// Returns `true` if the map is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + /// Returns the program counter for the given instruction counter. + pub fn get(&self, ic: u32) -> Option { + self.inner.get(&ic).copied() + } +} + +fn make_map(code: &[u8]) -> FxHashMap { + assert!(code.len() <= u32::MAX as usize, "bytecode is too big"); + + let mut map = FxHashMap::with_capacity_and_hasher(code.len(), Default::default()); + + let mut pc = 0usize; + let mut cumulative_push_size = 0usize; + while pc < code.len() { + let ic = pc - cumulative_push_size; + if PC_FIRST { + map.insert(pc as u32, ic as u32); + } else { + map.insert(ic as u32, pc as u32); + } + + if (PUSH1..=PUSH32).contains(&code[pc]) { + // Skip the push bytes. + let push_size = (code[pc] - PUSH0) as usize; + pc += push_size; + cumulative_push_size += push_size; + } + + pc += 1; + } + + map.shrink_to_fit(); + + map +} + +/// Represents a single instruction consisting of the opcode and its immediate data. +pub struct Instruction<'a> { + /// OpCode, if it could be decoded. + pub op: Option, + /// Immediate data following the opcode. + pub immediate: &'a [u8], + /// Program counter of the opcode. + pub pc: u32, +} + +/// Decodes raw opcode bytes into [`Instruction`]s. +pub fn decode_instructions(code: &[u8]) -> Result>> { + assert!(code.len() <= u32::MAX as usize, "bytecode is too big"); + + let mut pc = 0usize; + let mut steps = Vec::new(); + + while pc < code.len() { + let op = OpCode::new(code[pc]); + pc += 1; + let immediate_size = op.map(|op| immediate_size(op, &code[pc..])).unwrap_or(0) as usize; + + if pc + immediate_size > code.len() { + eyre::bail!("incomplete sequence of bytecode"); + } + + steps.push(Instruction { op, pc: pc as u32, immediate: &code[pc..pc + immediate_size] }); + + pc += immediate_size; + } + + Ok(steps) +} diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs new file mode 100644 index 0000000000000..cf678551e21d6 --- /dev/null +++ b/crates/evm/core/src/lib.rs @@ -0,0 +1,70 @@ +//! # foundry-evm-core +//! +//! Core EVM abstractions. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use crate::constants::DEFAULT_CREATE2_DEPLOYER; +use alloy_primitives::Address; +use auto_impl::auto_impl; +use backend::DatabaseExt; +use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, EvmContext, Inspector}; +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 backend; +pub mod buffer; +pub mod constants; +pub mod decode; +pub mod fork; +pub mod opcodes; +pub mod opts; +pub mod precompiles; +pub mod state_snapshot; +pub mod utils; + +/// An extension trait that allows us to add additional hooks to Inspector for later use in +/// handlers. +#[auto_impl(&mut, Box)] +pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> { + /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. + /// + /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 + /// factory. + fn should_use_create2_factory( + &mut self, + _context: &mut EvmContext<&mut dyn DatabaseExt>, + _inputs: &mut CreateInputs, + ) -> bool { + false + } + + /// Simulates `console.log` invocation. + fn console_log(&mut self, msg: &str) { + let _ = msg; + } + + /// Returns `true` if the current network is Odyssey. + fn is_odyssey(&self) -> bool { + false + } + + /// Returns the CREATE2 deployer address. + fn create2_deployer(&self) -> Address { + DEFAULT_CREATE2_DEPLOYER + } +} + +impl InspectorExt for NoOpInspector {} + +impl InspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/opcodes.rs b/crates/evm/core/src/opcodes.rs new file mode 100644 index 0000000000000..3251036c78f7a --- /dev/null +++ b/crates/evm/core/src/opcodes.rs @@ -0,0 +1,25 @@ +//! Opcode utils + +use revm::interpreter::OpCode; + +/// Returns true if the opcode modifies memory. +/// +/// +#[inline] +pub const fn modifies_memory(opcode: OpCode) -> bool { + matches!( + opcode, + OpCode::EXTCODECOPY | + OpCode::MLOAD | + OpCode::MSTORE | + OpCode::MSTORE8 | + OpCode::MCOPY | + OpCode::CODECOPY | + OpCode::CALLDATACOPY | + OpCode::RETURNDATACOPY | + OpCode::CALL | + OpCode::CALLCODE | + OpCode::DELEGATECALL | + OpCode::STATICCALL + ) +} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs new file mode 100644 index 0000000000000..d135cceb904f9 --- /dev/null +++ b/crates/evm/core/src/opts.rs @@ -0,0 +1,300 @@ +use super::fork::environment; +use crate::{constants::DEFAULT_CREATE2_DEPLOYER, fork::CreateFork}; +use alloy_primitives::{Address, B256, U256}; +use alloy_provider::{network::AnyRpcBlock, Provider}; +use eyre::WrapErr; +use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; +use foundry_config::{Chain, Config, GasLimit}; +use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; +use serde::{Deserialize, Serialize}; +use std::fmt::Write; +use url::Url; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EvmOpts { + /// The EVM environment configuration. + #[serde(flatten)] + pub env: Env, + + /// Fetch state over a remote instead of starting from empty state. + #[serde(rename = "eth_rpc_url")] + pub fork_url: Option, + + /// Pins the block number for the state fork. + pub fork_block_number: Option, + + /// The number of retries. + pub fork_retries: Option, + + /// Initial retry backoff. + pub fork_retry_backoff: Option, + + /// Headers to use with `fork_url` + pub fork_headers: Option>, + + /// The available compute units per second. + /// + /// See also + pub compute_units_per_second: Option, + + /// Disables RPC rate limiting entirely. + pub no_rpc_rate_limit: bool, + + /// Disables storage caching entirely. + pub no_storage_caching: bool, + + /// The initial balance of each deployed test contract. + pub initial_balance: U256, + + /// The address which will be executing all tests. + pub sender: Address, + + /// Enables the FFI cheatcode. + pub ffi: bool, + + /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. + pub always_use_create_2_factory: bool, + + /// Verbosity mode of EVM output as number of occurrences. + pub verbosity: u8, + + /// The memory limit per EVM execution in bytes. + /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. + pub memory_limit: u64, + + /// Whether to enable isolation of calls. + pub isolate: bool, + + /// Whether to disable block gas limit checks. + pub disable_block_gas_limit: bool, + + /// whether to enable Odyssey features. + pub odyssey: bool, + + /// The CREATE2 deployer's address. + pub create2_deployer: Address, +} + +impl Default for EvmOpts { + fn default() -> Self { + Self { + env: Env::default(), + fork_url: None, + fork_block_number: None, + fork_retries: None, + fork_retry_backoff: None, + fork_headers: None, + compute_units_per_second: None, + no_rpc_rate_limit: false, + no_storage_caching: false, + initial_balance: U256::default(), + sender: Address::default(), + ffi: false, + always_use_create_2_factory: false, + verbosity: 0, + memory_limit: 0, + isolate: false, + disable_block_gas_limit: false, + odyssey: false, + create2_deployer: DEFAULT_CREATE2_DEPLOYER, + } + } +} + +impl EvmOpts { + /// Configures a new `revm::Env` + /// + /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain + /// id, ) + pub async fn evm_env(&self) -> eyre::Result { + if let Some(ref fork_url) = self.fork_url { + Ok(self.fork_evm_env(fork_url).await?.0) + } else { + Ok(self.local_evm_env()) + } + } + + /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint. + /// And the block that was used to configure the environment. + pub async fn fork_evm_env( + &self, + fork_url: &str, + ) -> eyre::Result<(revm::primitives::Env, AnyRpcBlock)> { + let provider = ProviderBuilder::new(fork_url) + .compute_units_per_second(self.get_compute_units_per_second()) + .build()?; + environment( + &provider, + self.memory_limit, + self.env.gas_price.map(|v| v as u128), + self.env.chain_id, + self.fork_block_number, + self.sender, + self.disable_block_gas_limit, + ) + .await + .wrap_err_with(|| { + let mut msg = "could not instantiate forked environment".to_string(); + if let Ok(url) = Url::parse(fork_url) { + if let Some(provider) = url.host() { + write!(msg, " with provider {provider}").unwrap(); + } + } + msg + }) + } + + /// Returns the `revm::Env` configured with only local settings + pub fn local_evm_env(&self) -> revm::primitives::Env { + let mut cfg = CfgEnv::default(); + cfg.chain_id = self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID); + cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX)); + cfg.memory_limit = self.memory_limit; + // EIP-3607 rejects transactions from senders with deployed code. + // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the + // caller is a contract. So we disable the check by default. + cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = self.disable_block_gas_limit; + + revm::primitives::Env { + block: BlockEnv { + number: U256::from(self.env.block_number), + coinbase: self.env.block_coinbase, + timestamp: U256::from(self.env.block_timestamp), + 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: 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(), + caller: self.sender, + ..Default::default() + }, + } + } + + /// Helper function that returns the [CreateFork] to use, if any. + /// + /// storage caching for the [CreateFork] will be enabled if + /// - `fork_url` is present + /// - `fork_block_number` is present + /// - `StorageCachingConfig` allows the `fork_url` + chain ID pair + /// - storage is allowed (`no_storage_caching = false`) + /// + /// If all these criteria are met, then storage caching is enabled and storage info will be + /// written to `///storage.json`. + /// + /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will + /// be at `~/.foundry/cache/mainnet/14435000/storage.json`. + pub fn get_fork(&self, config: &Config, env: revm::primitives::Env) -> Option { + let url = self.fork_url.clone()?; + let enable_caching = config.enable_caching(&url, env.cfg.chain_id); + Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() }) + } + + /// Returns the gas limit to use + pub fn gas_limit(&self) -> u64 { + self.env.block_gas_limit.unwrap_or(self.env.gas_limit).0 + } + + /// Returns the configured chain id, which will be + /// - the value of `chain_id` if set + /// - mainnet if `fork_url` contains "mainnet" + /// - the chain if `fork_url` is set and the endpoints returned its chain id successfully + /// - mainnet otherwise + pub async fn get_chain_id(&self) -> u64 { + if let Some(id) = self.env.chain_id { + return id; + } + self.get_remote_chain_id().await.unwrap_or(Chain::mainnet()).id() + } + + /// Returns the available compute units per second, which will be + /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled) + /// - the assigned compute units, if `compute_units_per_second` is set + /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise + pub fn get_compute_units_per_second(&self) -> u64 { + if self.no_rpc_rate_limit { + u64::MAX + } else if let Some(cups) = self.compute_units_per_second { + return cups; + } else { + ALCHEMY_FREE_TIER_CUPS + } + } + + /// Returns the chain ID from the RPC, if any. + pub async fn get_remote_chain_id(&self) -> Option { + if let Some(ref url) = self.fork_url { + trace!(?url, "retrieving chain via eth_chainId"); + let provider = ProviderBuilder::new(url.as_str()) + .compute_units_per_second(self.get_compute_units_per_second()) + .build() + .ok() + .unwrap_or_else(|| panic!("Failed to establish provider to {url}")); + + if let Ok(id) = provider.get_chain_id().await { + return Some(Chain::from(id)); + } + + // Provider URLs could be of the format `{CHAIN_IDENTIFIER}-mainnet` + // (e.g. Alchemy `opt-mainnet`, `arb-mainnet`), fallback to this method only + // if we're not able to retrieve chain id from `RetryProvider`. + if url.contains("mainnet") { + trace!(?url, "auto detected mainnet chain"); + return Some(Chain::mainnet()); + } + } + + None + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct Env { + /// The block gas limit. + pub gas_limit: GasLimit, + + /// The `CHAINID` opcode value. + pub chain_id: Option, + + /// the tx.gasprice value during EVM execution + /// + /// This is an Option, so we can determine in fork mode whether to use the config's gas price + /// (if set by user) or the remote client's gas price. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub gas_price: Option, + + /// the base fee in a block + pub block_base_fee_per_gas: u64, + + /// the tx.origin value during EVM execution + pub tx_origin: Address, + + /// the block.coinbase value during EVM execution + pub block_coinbase: Address, + + /// the block.timestamp value during EVM execution + pub block_timestamp: u64, + + /// the block.number value during EVM execution" + pub block_number: u64, + + /// the block.difficulty value during EVM execution + pub block_difficulty: u64, + + /// Previous block beacon chain random value. Before merge this field is used for mix_hash + pub block_prevrandao: B256, + + /// the block.gaslimit value during EVM execution + #[serde(default, skip_serializing_if = "Option::is_none")] + pub block_gas_limit: Option, + + /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_size_limit: Option, +} diff --git a/crates/evm/core/src/precompiles.rs b/crates/evm/core/src/precompiles.rs new file mode 100644 index 0000000000000..c70dc06d21442 --- /dev/null +++ b/crates/evm/core/src/precompiles.rs @@ -0,0 +1,73 @@ +use alloy_primitives::{address, Address, Bytes, B256}; +use revm::{ + precompile::{secp256r1::p256_verify as revm_p256_verify, PrecompileWithAddress}, + primitives::{Precompile, PrecompileResult}, +}; + +/// 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, + ODYSSEY_P256_ADDRESS, +]; + +/// [EIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) secp256r1 precompile address on Odyssey. +/// +/// +pub const ODYSSEY_P256_ADDRESS: Address = address!("0000000000000000000000000000000000000014"); + +/// Wrapper around revm P256 precompile, matching EIP-7212 spec. +/// +/// Per Optimism implementation, P256 precompile returns empty bytes on failure, but per EIP-7212 it +/// should be 32 bytes of zeros instead. +pub fn p256_verify(input: &Bytes, gas_limit: u64) -> PrecompileResult { + revm_p256_verify(input, gas_limit).map(|mut result| { + if result.bytes.is_empty() { + result.bytes = B256::default().into(); + } + + result + }) +} + +/// [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212#specification) secp256r1 precompile. +pub const ODYSSEY_P256: PrecompileWithAddress = + PrecompileWithAddress(ODYSSEY_P256_ADDRESS, Precompile::Standard(p256_verify)); diff --git a/crates/evm/core/src/state_snapshot.rs b/crates/evm/core/src/state_snapshot.rs new file mode 100644 index 0000000000000..3be1172aded5d --- /dev/null +++ b/crates/evm/core/src/state_snapshot.rs @@ -0,0 +1,74 @@ +//! Support for snapshotting different states + +use alloy_primitives::{map::HashMap, U256}; +use std::ops::Add; + +/// Represents all state snapshots +#[derive(Clone, Debug)] +pub struct StateSnapshots { + id: U256, + state_snapshots: HashMap, +} + +impl StateSnapshots { + fn next_id(&mut self) -> U256 { + let id = self.id; + self.id = id.saturating_add(U256::from(1)); + id + } + + /// Returns the state snapshot with the given id `id` + pub fn get(&self, id: U256) -> Option<&T> { + self.state_snapshots.get(&id) + } + + /// Removes the state snapshot with the given `id`. + /// + /// This will also remove any state snapshots taken after the state snapshot with the `id`. + /// e.g.: reverting to id 1 will delete snapshots with ids 1, 2, 3, etc.) + pub fn remove(&mut self, id: U256) -> Option { + let snapshot_state = self.state_snapshots.remove(&id); + + // Revert all state snapshots taken after the state snapshot with the `id` + let mut to_revert = id.add(U256::from(1)); + while to_revert < self.id { + self.state_snapshots.remove(&to_revert); + to_revert += U256::from(1); + } + + snapshot_state + } + + /// Removes all state snapshots. + pub fn clear(&mut self) { + self.state_snapshots.clear(); + } + + /// Removes the state snapshot with the given `id`. + /// + /// Does not remove state snapshots after it. + pub fn remove_at(&mut self, id: U256) -> Option { + self.state_snapshots.remove(&id) + } + + /// Inserts the new state snapshot and returns the id. + pub fn insert(&mut self, state_snapshot: T) -> U256 { + let id = self.next_id(); + self.state_snapshots.insert(id, state_snapshot); + id + } + + /// Inserts the new state snapshot at the given `id`. + /// + /// Does not auto-increment the next `id`. + pub fn insert_at(&mut self, state_snapshot: T, id: U256) -> U256 { + self.state_snapshots.insert(id, state_snapshot); + id + } +} + +impl Default for StateSnapshots { + fn default() -> Self { + Self { id: U256::ZERO, state_snapshots: HashMap::default() } + } +} diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs new file mode 100644 index 0000000000000..ac19e91555743 --- /dev/null +++ b/crates/evm/core/src/utils.rs @@ -0,0 +1,360 @@ +pub use crate::ic::*; +use crate::{ + backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, precompiles::ODYSSEY_P256, + InspectorExt, +}; +use alloy_consensus::BlockHeader; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_network::AnyTxEnvelope; +use alloy_primitives::{Address, Selector, TxKind, B256, U256}; +use alloy_provider::{network::BlockResponse, Network}; +use alloy_rpc_types::{Transaction, TransactionRequest}; +use foundry_common::is_impersonated_tx; +use foundry_config::NamedChain; +use foundry_fork_db::DatabaseError; +use revm::{ + handler::register::EvmHandler, + interpreter::{ + return_ok, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, + Gas, InstructionResult, InterpreterResult, + }, + precompile::secp256r1::P256VERIFY, + primitives::{CreateScheme, EVMError, HandlerCfg, SpecId, KECCAK_EMPTY}, + FrameOrResult, FrameResult, +}; +use std::{cell::RefCell, rc::Rc, sync::Arc}; + +pub use revm::primitives::EvmState as StateChangeset; + +/// Depending on the configured chain id and block number this should apply any specific changes +/// +/// - checks for prevrandao mixhash after merge +/// - applies chain specifics: on Arbitrum `block.number` is the L1 block +/// +/// Should be called with proper chain id (retrieved from provider if not provided). +pub fn apply_chain_and_block_specific_env_changes( + env: &mut revm::primitives::Env, + block: &N::BlockResponse, +) { + use NamedChain::*; + if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { + let block_number = block.header().number(); + + match chain { + Mainnet => { + // after merge difficulty is supplanted with prevrandao EIP-4399 + if block_number >= 15_537_351u64 { + env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); + } + + return; + } + Moonbeam | Moonbase | Moonriver | MoonbeamDev => { + if env.block.prevrandao.is_none() { + // + env.block.prevrandao = Some(B256::random()); + } + } + c if c.is_arbitrum() => { + // on arbitrum `block.number` is the L1 block which is included in the + // `l1BlockNumber` field + if let Some(l1_block_number) = block + .other_fields() + .and_then(|other| other.get("l1BlockNumber").cloned()) + .and_then(|l1_block_number| { + serde_json::from_value::(l1_block_number).ok() + }) + { + env.block.number = l1_block_number; + } + } + _ => {} + } + } + + // if difficulty is `0` we assume it's past merge + if block.header().difficulty().is_zero() { + env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); + } +} + +/// Given an ABI and selector, it tries to find the respective function. +pub fn get_function<'a>( + contract_name: &str, + selector: Selector, + abi: &'a JsonAbi, +) -> eyre::Result<&'a Function> { + abi.functions() + .find(|func| func.selector() == selector) + .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}")) +} + +/// Configures the env for the given RPC transaction. +/// Accounts for an impersonated transaction by resetting the `env.tx.caller` field to `tx.from`. +pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { + let impersonated_from = is_impersonated_tx(&tx.inner).then_some(tx.from); + if let AnyTxEnvelope::Ethereum(tx) = &tx.inner { + configure_tx_req_env(env, &tx.clone().into(), impersonated_from).expect("cannot fail"); + } +} + +/// Configures the env for the given RPC transaction request. +/// `impersonated_from` is the address of the impersonated account. This helps account for an +/// impersonated transaction by resetting the `env.tx.caller` field to `impersonated_from`. +pub fn configure_tx_req_env( + env: &mut revm::primitives::Env, + tx: &TransactionRequest, + impersonated_from: Option
, +) -> eyre::Result<()> { + let TransactionRequest { + nonce, + from, + to, + value, + gas_price, + gas, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + ref input, + chain_id, + ref blob_versioned_hashes, + ref access_list, + transaction_type: _, + ref authorization_list, + sidecar: _, + } = *tx; + + // If no `to` field then set create kind: https://eips.ethereum.org/EIPS/eip-2470#deployment-transaction + env.tx.transact_to = to.unwrap_or(TxKind::Create); + // If the transaction is impersonated, we need to set the caller to the from + // address Ref: https://github.com/foundry-rs/foundry/issues/9541 + env.tx.caller = + impersonated_from.unwrap_or(from.ok_or_else(|| eyre::eyre!("missing `from` field"))?); + env.tx.gas_limit = gas.ok_or_else(|| eyre::eyre!("missing `gas` field"))?; + env.tx.nonce = nonce; + env.tx.value = value.unwrap_or_default(); + env.tx.data = input.input().cloned().unwrap_or_default(); + env.tx.chain_id = chain_id; + + // Type 1, EIP-2930 + env.tx.access_list = access_list.clone().unwrap_or_default().0.into_iter().collect(); + + // Type 2, EIP-1559 + env.tx.gas_price = U256::from(gas_price.or(max_fee_per_gas).unwrap_or_default()); + env.tx.gas_priority_fee = max_priority_fee_per_gas.map(U256::from); + + // Type 3, EIP-4844 + env.tx.blob_hashes = blob_versioned_hashes.clone().unwrap_or_default(); + env.tx.max_fee_per_blob_gas = max_fee_per_blob_gas.map(U256::from); + + // Type 4, EIP-7702 + if let Some(authorization_list) = authorization_list { + env.tx.authorization_list = + Some(revm::primitives::AuthorizationList::Signed(authorization_list.clone())); + } + + Ok(()) +} + +/// Get the gas used, accounting for refunds +pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { + let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 }; + spent - (refunded).min(spent / refund_quotient) +} + +fn get_create2_factory_call_inputs( + salt: U256, + inputs: CreateInputs, + deployer: Address, +) -> CallInputs { + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); + CallInputs { + caller: inputs.caller, + bytecode_address: deployer, + target_address: deployer, + scheme: CallScheme::Call, + value: CallValue::Transfer(inputs.value), + input: calldata.into(), + gas_limit: inputs.gas_limit, + is_static: false, + return_memory_offset: 0..0, + is_eof: false, + } +} + +/// Used for routing certain CREATE2 invocations through CREATE2_DEPLOYER. +/// +/// Overrides create hook with CALL frame if [InspectorExt::should_use_create2_factory] returns +/// true. Keeps track of overridden frames and handles outcome in the overridden insert_call_outcome +/// hook by inserting decoded address directly into interpreter. +/// +/// Should be installed after [revm::inspector_handle_register] and before any other registers. +pub fn create2_handler_register( + handler: &mut EvmHandler<'_, I, &mut dyn DatabaseExt>, +) { + let create2_overrides = Rc::>>::new(RefCell::new(Vec::new())); + + let create2_overrides_inner = create2_overrides.clone(); + let old_handle = handler.execution.create.clone(); + handler.execution.create = + Arc::new(move |ctx, mut inputs| -> Result> { + let CreateScheme::Create2 { salt } = inputs.scheme else { + return old_handle(ctx, inputs); + }; + if !ctx.external.should_use_create2_factory(&mut ctx.evm, &mut inputs) { + return old_handle(ctx, inputs); + } + + let gas_limit = inputs.gas_limit; + + // Get CREATE2 deployer. + let create2_deployer = ctx.external.create2_deployer(); + // Generate call inputs for CREATE2 factory. + let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs, create2_deployer); + + // Call inspector to change input or return outcome. + let outcome = ctx.external.call(&mut ctx.evm, &mut call_inputs); + + // Push data about current override to the stack. + create2_overrides_inner + .borrow_mut() + .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); + + // Sanity check that CREATE2 deployer exists. + let code_hash = ctx.evm.load_account(create2_deployer)?.info.code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: format!("missing CREATE2 deployer: {create2_deployer}").into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + }))) + } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH { + return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 deployer bytecode".into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + }))) + } + + // 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); + + if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { + ctx.external + .initialize_interp(&mut frame.frame_data_mut().interpreter, &mut ctx.evm) + } + frame_or_result + }); + + 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. + if create2_overrides_inner + .borrow() + .last() + .is_some_and(|(depth, _)| *depth == ctx.evm.journaled_state.depth()) + { + let (_, call_inputs) = create2_overrides_inner.borrow_mut().pop().unwrap(); + outcome = ctx.external.call_end(&mut ctx.evm, &call_inputs, outcome); + + // Decode address from output. + let address = match outcome.instruction_result() { + return_ok!() => Address::try_from(outcome.output().as_ref()) + .map_err(|_| { + outcome.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + frame + .frame_data_mut() + .interpreter + .insert_create_outcome(CreateOutcome { address, result: outcome.result }); + + Ok(()) + } else { + old_handle(ctx, frame, shared_memory, outcome) + } + }); +} + +/// Adds Odyssey P256 precompile to the list of loaded precompiles. +pub fn odyssey_handler_register(handler: &mut EvmHandler<'_, EXT, DB>) { + let prev = handler.pre_execution.load_precompiles.clone(); + handler.pre_execution.load_precompiles = Arc::new(move || { + let mut loaded_precompiles = prev(); + + loaded_precompiles.extend([ODYSSEY_P256, P256VERIFY]); + + loaded_precompiles + }); +} + +/// Creates a new EVM with the given inspector. +pub fn new_evm_with_inspector<'evm, 'i, 'db, I: InspectorExt + ?Sized>( + db: &'db mut dyn DatabaseExt, + env: revm::primitives::EnvWithHandlerCfg, + inspector: &'i mut I, +) -> revm::Evm<'evm, &'i mut I, &'db mut dyn DatabaseExt> { + let revm::primitives::EnvWithHandlerCfg { env, handler_cfg } = env; + + // NOTE: We could use `revm::Evm::builder()` here, but on the current patch it has some + // performance issues. + /* + revm::Evm::builder() + .with_db(db) + .with_env(env) + .with_external_context(inspector) + .with_handler_cfg(handler_cfg) + .append_handler_register(revm::inspector_handle_register) + .append_handler_register(create2_handler_register) + .build() + */ + + let mut handler = revm::Handler::new(handler_cfg); + handler.append_handler_register_plain(revm::inspector_handle_register); + if inspector.is_odyssey() { + handler.append_handler_register_plain(odyssey_handler_register); + } + handler.append_handler_register_plain(create2_handler_register); + + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + + revm::Evm::new(context, handler) +} + +pub fn new_evm_with_existing_context<'a>( + inner: revm::InnerEvmContext<&'a mut dyn DatabaseExt>, + inspector: &'a mut dyn InspectorExt, +) -> revm::Evm<'a, &'a mut dyn InspectorExt, &'a mut dyn DatabaseExt> { + let handler_cfg = HandlerCfg::new(inner.spec_id()); + + let mut handler = revm::Handler::new(handler_cfg); + handler.append_handler_register_plain(revm::inspector_handle_register); + if inspector.is_odyssey() { + handler.append_handler_register_plain(odyssey_handler_register); + } + handler.append_handler_register_plain(create2_handler_register); + + let context = + revm::Context::new(revm::EvmContext { inner, precompiles: Default::default() }, inspector); + revm::Evm::new(context, handler) +} diff --git a/crates/evm/core/test-data/storage.json b/crates/evm/core/test-data/storage.json new file mode 100644 index 0000000000000..6a602f7a642ab --- /dev/null +++ b/crates/evm/core/test-data/storage.json @@ -0,0 +1 @@ +{"meta":{"cfg_env":{"chain_id":1,"spec_id":"LATEST","perf_all_precompiles_have_balance":false,"memory_limit":4294967295,"perf_analyse_created_bytecodes":"Analyse","limit_contract_code_size":24576,"disable_coinbase_tip":false},"block_env":{"number":"0xdc42b8","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x1","difficulty":"0x0","basefee":"0x0","gas_limit":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},"hosts":["mainnet.infura.io"]},"accounts":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"balance":"0x0","code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null,"nonce":0}},"storage":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"0x0":"0x0","0x1":"0x0","0x2":"0x0","0x3":"0x0","0x4":"0x0","0x5":"0x0","0x6":"0x0","0x7":"0x0","0x8":"0x0","0x9":"0x0"}},"block_hashes":{}} \ No newline at end of file diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml new file mode 100644 index 0000000000000..e38d33e2a5907 --- /dev/null +++ b/crates/evm/coverage/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "foundry-evm-coverage" +description = "EVM bytecode coverage analysis" + +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.workspace = true +foundry-compilers.workspace = true +foundry-evm-core.workspace = true + +alloy-primitives.workspace = true +eyre.workspace = true +revm.workspace = true +semver.workspace = true +tracing.workspace = true +rayon.workspace = true diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs new file mode 100644 index 0000000000000..89374668031a8 --- /dev/null +++ b/crates/evm/coverage/src/analysis.rs @@ -0,0 +1,651 @@ +use super::{CoverageItem, CoverageItemKind, SourceLocation}; +use alloy_primitives::map::HashMap; +use foundry_common::TestFunctionExt; +use foundry_compilers::artifacts::{ + ast::{self, Ast, Node, NodeType}, + Source, +}; +use rayon::prelude::*; +use std::sync::Arc; + +/// A visitor that walks the AST of a single contract and finds coverage items. +#[derive(Clone, Debug)] +pub struct ContractVisitor<'a> { + /// The source ID of the contract. + source_id: u32, + /// The source code that contains the AST being walked. + source: &'a str, + + /// The name of the contract being walked. + contract_name: &'a Arc, + + /// The current branch ID + branch_id: u32, + /// Stores the last line we put in the items collection to ensure we don't push duplicate lines + last_line: u32, + + /// Coverage items + pub items: Vec, +} + +impl<'a> ContractVisitor<'a> { + pub fn new(source_id: usize, source: &'a str, contract_name: &'a Arc) -> Self { + Self { + source_id: source_id.try_into().expect("too many sources"), + source, + contract_name, + branch_id: 0, + last_line: 0, + items: Vec::new(), + } + } + + pub fn visit_contract(&mut self, node: &Node) -> eyre::Result<()> { + // Find all functions and walk their AST + for node in &node.nodes { + match node.node_type { + NodeType::FunctionDefinition => { + self.visit_function_definition(node)?; + } + NodeType::ModifierDefinition => { + self.visit_modifier_or_yul_fn_definition(node)?; + } + _ => {} + } + } + Ok(()) + } + + fn visit_function_definition(&mut self, node: &Node) -> eyre::Result<()> { + let Some(body) = &node.body else { return Ok(()) }; + + let name: String = + node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; + let kind: String = + node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; + + // TODO: We currently can only detect empty bodies in normal functions, not any of the other + // kinds: https://github.com/foundry-rs/foundry/issues/9458 + if kind != "function" && !has_statements(body) { + return Ok(()); + } + + // `fallback`, `receive`, and `constructor` functions have an empty `name`. + // Use the `kind` itself as the name. + let name = if name.is_empty() { kind } else { name }; + + self.push_item_kind(CoverageItemKind::Function { name }, &node.src); + self.visit_block(body) + } + + 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"))?; + + match &node.body { + Some(body) => { + self.push_item_kind(CoverageItemKind::Function { name }, &node.src); + self.visit_block(body) + } + _ => Ok(()), + } + } + + fn visit_block(&mut self, node: &Node) -> eyre::Result<()> { + let statements: Vec = node.attribute("statements").unwrap_or_default(); + + for statement in &statements { + self.visit_statement(statement)?; + } + + Ok(()) + } + + fn visit_statement(&mut self, node: &Node) -> eyre::Result<()> { + match node.node_type { + // Blocks + NodeType::Block | NodeType::UncheckedBlock | NodeType::YulBlock => { + self.visit_block(node) + } + // Inline assembly block + NodeType::InlineAssembly => self.visit_block( + &node + .attribute("AST") + .ok_or_else(|| eyre::eyre!("inline assembly block with no AST attribute"))?, + ), + // Simple statements + NodeType::Break | + NodeType::Continue | + NodeType::EmitStatement | + NodeType::RevertStatement | + NodeType::YulAssignment | + NodeType::YulBreak | + NodeType::YulContinue | + NodeType::YulLeave | + NodeType::YulVariableDeclaration => { + self.push_item_kind(CoverageItemKind::Statement, &node.src); + Ok(()) + } + // Skip placeholder statements as they are never referenced in source maps. + NodeType::PlaceholderStatement => Ok(()), + // Return with eventual subcall + NodeType::Return => { + self.push_item_kind(CoverageItemKind::Statement, &node.src); + if let Some(expr) = node.attribute("expression") { + self.visit_expression(&expr)?; + } + Ok(()) + } + // Variable declaration + NodeType::VariableDeclarationStatement => { + self.push_item_kind(CoverageItemKind::Statement, &node.src); + if let Some(expr) = node.attribute("initialValue") { + self.visit_expression(&expr)?; + } + Ok(()) + } + // While loops + NodeType::DoWhileStatement | NodeType::WhileStatement => { + self.visit_expression( + &node + .attribute("condition") + .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, + )?; + + let body = node + .body + .as_deref() + .ok_or_else(|| eyre::eyre!("while statement had no body node"))?; + self.visit_block_or_statement(body) + } + // For loops + NodeType::ForStatement => { + if let Some(stmt) = node.attribute("initializationExpression") { + self.visit_statement(&stmt)?; + } + if let Some(expr) = node.attribute("condition") { + self.visit_expression(&expr)?; + } + if let Some(stmt) = node.attribute("loopExpression") { + self.visit_statement(&stmt)?; + } + + let body = node + .body + .as_deref() + .ok_or_else(|| eyre::eyre!("for statement had no body node"))?; + self.visit_block_or_statement(body) + } + // Expression statement + NodeType::ExpressionStatement | NodeType::YulExpressionStatement => self + .visit_expression( + &node + .attribute("expression") + .ok_or_else(|| eyre::eyre!("expression statement had no expression"))?, + ), + // If statement + NodeType::IfStatement => { + self.visit_expression( + &node + .attribute("condition") + .ok_or_else(|| eyre::eyre!("if statement had no condition"))?, + )?; + + let true_body: Node = node + .attribute("trueBody") + .ok_or_else(|| eyre::eyre!("if statement had no true body"))?; + + // We need to store the current branch ID here since visiting the body of either of + // the if blocks may increase `self.branch_id` in the case of nested if statements. + let branch_id = self.branch_id; + + // We increase the branch ID here such that nested branches do not use the same + // branch ID as we do. + self.branch_id += 1; + + match node.attribute::("falseBody") { + // Both if/else statements. + Some(false_body) => { + // Add branch coverage items only if one of true/branch bodies contains + // statements. + if has_statements(&true_body) || has_statements(&false_body) { + // The branch instruction is mapped to the first opcode within the true + // body source range. + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 0, + is_first_opcode: true, + }, + &true_body.src, + ); + // Add the coverage item for branch 1 (false body). + // The relevant source range for the false branch is the `else` + // statement itself and the false body of the else statement. + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 1, + is_first_opcode: false, + }, + &ast::LowFidelitySourceLocation { + start: node.src.start, + length: false_body.src.length.map(|length| { + false_body.src.start - true_body.src.start + length + }), + index: node.src.index, + }, + ); + + // Process the true body. + self.visit_block_or_statement(&true_body)?; + // Process the false body. + self.visit_block_or_statement(&false_body)?; + } + } + None => { + // Add single branch coverage only if it contains statements. + if has_statements(&true_body) { + // Add the coverage item for branch 0 (true body). + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 0, + is_first_opcode: true, + }, + &true_body.src, + ); + // Process the true body. + self.visit_block_or_statement(&true_body)?; + } + } + } + + Ok(()) + } + NodeType::YulIf => { + self.visit_expression( + &node + .attribute("condition") + .ok_or_else(|| eyre::eyre!("yul if statement had no condition"))?, + )?; + let body = node + .body + .as_deref() + .ok_or_else(|| eyre::eyre!("yul if statement had no body"))?; + + // We need to store the current branch ID here since visiting the body of either of + // the if blocks may increase `self.branch_id` in the case of nested if statements. + let branch_id = self.branch_id; + + // We increase the branch ID here such that nested branches do not use the same + // branch ID as we do + self.branch_id += 1; + + self.push_item_kind( + CoverageItemKind::Branch { branch_id, path_id: 0, is_first_opcode: false }, + &node.src, + ); + self.visit_block(body)?; + + Ok(()) + } + // Try-catch statement. Coverage is reported for expression, for each clause and their + // bodies (if any). + NodeType::TryStatement => { + 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_kind(CoverageItemKind::Statement, &clause.src); + self.visit_statement(&clause)?; + + // Add coverage for clause body only if it is not empty. + if let Some(block) = clause.attribute::("block") { + if has_statements(&block) { + self.push_item_kind(CoverageItemKind::Statement, &block.src); + 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_kind(CoverageItemKind::Statement, &case.src); + self.visit_statement(&case)?; + + if let Some(body) = case.body { + self.push_item_kind(CoverageItemKind::Statement, &body.src); + self.visit_block(&body)? + } + } + Ok(()) + } + NodeType::YulForLoop => { + if let Some(condition) = node.attribute("condition") { + self.visit_expression(&condition)?; + } + if let Some(pre) = node.attribute::("pre") { + self.visit_block(&pre)? + } + if let Some(post) = node.attribute::("post") { + self.visit_block(&post)? + } + + if let Some(body) = &node.body { + self.push_item_kind(CoverageItemKind::Statement, &body.src); + self.visit_block(body)? + } + Ok(()) + } + NodeType::YulFunctionDefinition => self.visit_modifier_or_yul_fn_definition(node), + _ => { + warn!("unexpected node type, expected a statement: {:?}", node.node_type); + Ok(()) + } + } + } + + fn visit_expression(&mut self, node: &Node) -> eyre::Result<()> { + match node.node_type { + NodeType::Assignment | + NodeType::UnaryOperation | + NodeType::Conditional | + NodeType::YulFunctionCall => { + self.push_item_kind(CoverageItemKind::Statement, &node.src); + Ok(()) + } + NodeType::FunctionCall => { + // Do not count other kinds of calls towards coverage (like `typeConversion` + // and `structConstructorCall`). + let kind: Option = node.attribute("kind"); + if let Some("functionCall") = kind.as_deref() { + self.push_item_kind(CoverageItemKind::Statement, &node.src); + + let expr: Option = node.attribute("expression"); + if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { + // Might be a require call, add branch coverage. + // Asserts should not be considered branches: . + let name: Option = expr.and_then(|expr| expr.attribute("name")); + if let Some("require") = name.as_deref() { + let branch_id = self.branch_id; + self.branch_id += 1; + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 0, + is_first_opcode: false, + }, + &node.src, + ); + self.push_item_kind( + CoverageItemKind::Branch { + branch_id, + path_id: 1, + is_first_opcode: false, + }, + &node.src, + ); + } + } + } + + Ok(()) + } + NodeType::BinaryOperation => { + self.push_item_kind(CoverageItemKind::Statement, &node.src); + + // visit left and right expressions + // There could possibly a function call in the left or right expression + // e.g: callFunc(a) + callFunc(b) + if let Some(expr) = node.attribute("leftExpression") { + self.visit_expression(&expr)?; + } + + if let Some(expr) = node.attribute("rightExpression") { + self.visit_expression(&expr)?; + } + + Ok(()) + } + // Does not count towards coverage + NodeType::FunctionCallOptions | + NodeType::Identifier | + NodeType::IndexAccess | + NodeType::IndexRangeAccess | + NodeType::Literal | + NodeType::YulLiteralValue | + NodeType::YulIdentifier => Ok(()), + _ => { + warn!("unexpected node type, expected an expression: {:?}", node.node_type); + Ok(()) + } + } + } + + fn visit_block_or_statement(&mut self, node: &Node) -> eyre::Result<()> { + match node.node_type { + NodeType::Block => self.visit_block(node), + NodeType::Break | + NodeType::Continue | + NodeType::DoWhileStatement | + NodeType::EmitStatement | + NodeType::ExpressionStatement | + NodeType::ForStatement | + NodeType::IfStatement | + NodeType::InlineAssembly | + NodeType::Return | + NodeType::RevertStatement | + NodeType::TryStatement | + NodeType::VariableDeclarationStatement | + NodeType::YulVariableDeclaration | + NodeType::WhileStatement => self.visit_statement(node), + // Skip placeholder statements as they are never referenced in source maps. + NodeType::PlaceholderStatement => Ok(()), + _ => { + warn!("unexpected node type, expected block or statement: {:?}", node.node_type); + Ok(()) + } + } + } + + /// Creates a coverage item for a given kind and source location. Pushes item to the internal + /// collection (plus additional coverage line if item is a statement). + fn push_item_kind(&mut self, kind: CoverageItemKind, src: &ast::LowFidelitySourceLocation) { + let item = CoverageItem { kind, loc: self.source_location_for(src), hits: 0 }; + + // Push a line item if we haven't already. + debug_assert!(!matches!(item.kind, CoverageItemKind::Line)); + if self.last_line < item.loc.lines.start { + self.items.push(CoverageItem { + kind: CoverageItemKind::Line, + loc: item.loc.clone(), + hits: 0, + }); + self.last_line = item.loc.lines.start; + } + + self.items.push(item); + } + + fn source_location_for(&self, loc: &ast::LowFidelitySourceLocation) -> SourceLocation { + let bytes_start = loc.start as u32; + let bytes_end = (loc.start + loc.length.unwrap_or(0)) as u32; + let bytes = bytes_start..bytes_end; + + let start_line = self.source[..bytes.start as usize].lines().count() as u32; + let n_lines = self.source[bytes.start as usize..bytes.end as usize].lines().count() as u32; + let lines = start_line..start_line + n_lines; + SourceLocation { + source_id: self.source_id as usize, + contract_name: self.contract_name.clone(), + bytes, + lines, + } + } +} + +/// Helper function to check if a given node is or contains any statement. +fn has_statements(node: &Node) -> bool { + match node.node_type { + NodeType::DoWhileStatement | + NodeType::EmitStatement | + NodeType::ExpressionStatement | + NodeType::ForStatement | + NodeType::IfStatement | + NodeType::RevertStatement | + NodeType::TryStatement | + NodeType::VariableDeclarationStatement | + NodeType::WhileStatement => true, + _ => node.attribute::>("statements").is_some_and(|s| !s.is_empty()), + } +} + +/// Coverage source analysis. +#[derive(Clone, Debug, Default)] +pub struct SourceAnalysis { + /// All the coverage items. + all_items: Vec, + /// Source ID to `(offset, len)` into `all_items`. + map: Vec<(u32, u32)>, +} + +impl SourceAnalysis { + /// Analyzes contracts in the sources held by the source analyzer. + /// + /// Coverage items are found by: + /// - Walking the AST of each contract (except interfaces) + /// - Recording the items of each contract + /// + /// Each coverage item contains relevant information to find opcodes corresponding to them: the + /// source ID the item is in, the source code range of the item, and the contract name the item + /// is in. + /// + /// Note: Source IDs are only unique per compilation job; that is, a code base compiled with + /// two different solc versions will produce overlapping source IDs if the compiler version is + /// not taken into account. + pub fn new(data: &SourceFiles<'_>) -> eyre::Result { + let mut sourced_items = data + .sources + .par_iter() + .flat_map_iter(|(&source_id, SourceFile { source, ast })| { + let items = ast.nodes.iter().map(move |node| { + if !matches!(node.node_type, NodeType::ContractDefinition) { + return Ok(vec![]); + } + + // Skip interfaces which have no function implementations. + let contract_kind: String = node + .attribute("contractKind") + .ok_or_else(|| eyre::eyre!("Contract has no kind"))?; + if contract_kind == "interface" { + return Ok(vec![]); + } + + let name = node + .attribute("name") + .ok_or_else(|| eyre::eyre!("Contract has no name"))?; + + let mut visitor = ContractVisitor::new(source_id, &source.content, &name); + visitor.visit_contract(node)?; + let mut items = visitor.items; + + let is_test = items.iter().any(|item| { + if let CoverageItemKind::Function { name } = &item.kind { + name.is_any_test() + } else { + false + } + }); + if is_test { + items.clear(); + } + + Ok(items) + }); + items.map(move |items| items.map(|items| (source_id, items))) + }) + .collect::)>>>()?; + + // Create mapping and merge items. + sourced_items.sort_by_key(|(id, items)| (*id, items.first().map(|i| i.loc.bytes.start))); + let Some(&(max_idx, _)) = sourced_items.last() else { return Ok(Self::default()) }; + let len = max_idx + 1; + let mut all_items = Vec::new(); + let mut map = vec![(u32::MAX, 0); len]; + for (idx, items) in sourced_items { + // Assumes that all `idx` items are consecutive, guaranteed by the sort above. + if map[idx].0 == u32::MAX { + map[idx].0 = all_items.len() as u32; + } + map[idx].1 += items.len() as u32; + all_items.extend(items); + } + + Ok(Self { all_items, map }) + } + + /// Returns all the coverage items. + pub fn all_items(&self) -> &[CoverageItem] { + &self.all_items + } + + /// Returns all the mutable coverage items. + pub fn all_items_mut(&mut self) -> &mut Vec { + &mut self.all_items + } + + /// Returns an iterator over the coverage items and their IDs for the given source. + pub fn items_for_source_enumerated( + &self, + source_id: u32, + ) -> impl Iterator { + let (base_id, items) = self.items_for_source(source_id); + items.iter().enumerate().map(move |(idx, item)| (base_id + idx as u32, item)) + } + + /// Returns the base item ID and all the coverage items for the given source. + pub fn items_for_source(&self, source_id: u32) -> (u32, &[CoverageItem]) { + let (mut offset, len) = self.map.get(source_id as usize).copied().unwrap_or_default(); + if offset == u32::MAX { + offset = 0; + } + (offset, &self.all_items[offset as usize..][..len as usize]) + } + + /// Returns the coverage item for the given item ID. + #[inline] + pub fn get(&self, item_id: u32) -> Option<&CoverageItem> { + self.all_items.get(item_id as usize) + } +} + +/// A list of versioned sources and their ASTs. +#[derive(Debug, Default)] +pub struct SourceFiles<'a> { + /// The versioned sources. + pub sources: HashMap>, +} + +/// The source code and AST of a file. +#[derive(Debug)] +pub struct SourceFile<'a> { + /// The source code. + pub source: Source, + /// The AST of the source code. + pub ast: &'a Ast, +} diff --git a/crates/evm/src/coverage/anchors.rs b/crates/evm/coverage/src/anchors.rs similarity index 50% rename from crates/evm/src/coverage/anchors.rs rename to crates/evm/coverage/src/anchors.rs index 9aa1bd9efcb9a..3d46065518c1b 100644 --- a/crates/evm/src/coverage/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -1,49 +1,39 @@ -use super::{CoverageItem, CoverageItemKind, ItemAnchor, SourceLocation}; -use crate::utils::ICPCMap; -use ethers::prelude::{ - sourcemap::{SourceElement, SourceMap}, - Bytes, -}; -use revm::{ - interpreter::opcode::{self, spec_opcode_gas}, - primitives::SpecId, -}; +use super::{CoverageItemKind, ItemAnchor, SourceLocation}; +use crate::analysis::SourceAnalysis; +use alloy_primitives::map::rustc_hash::FxHashSet; +use eyre::ensure; +use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap}; +use foundry_evm_core::utils::IcPcMap; +use revm::interpreter::opcode; /// Attempts to find anchors for the given items using the given source map and bytecode. pub fn find_anchors( - bytecode: &Bytes, + bytecode: &[u8], source_map: &SourceMap, - ic_pc_map: &ICPCMap, - item_ids: &[usize], - items: &[CoverageItem], + ic_pc_map: &IcPcMap, + analysis: &SourceAnalysis, ) -> Vec { - item_ids + let mut seen_sources = FxHashSet::default(); + source_map .iter() - .filter_map(|item_id| { - let item = items.get(*item_id)?; - + .filter_map(|element| element.index()) + .filter(|&source| seen_sources.insert(source)) + .flat_map(|source| analysis.items_for_source_enumerated(source)) + .filter_map(|(item_id, item)| { match item.kind { - CoverageItemKind::Branch { path_id, .. } => { - match find_anchor_branch(bytecode, source_map, *item_id, &item.loc) { - Ok(anchors) => match path_id { - 0 => Some(anchors.0), - 1 => Some(anchors.1), - _ => panic!("Too many paths for branch"), - }, - Err(e) => { - warn!("Could not find anchor for item: {}, error: {e}", item); - None + CoverageItemKind::Branch { path_id, is_first_opcode: false, .. } => { + find_anchor_branch(bytecode, source_map, item_id, &item.loc).map(|anchors| { + match path_id { + 0 => anchors.0, + 1 => anchors.1, + _ => panic!("too many path IDs for branch"), } - } + }) } - _ => match find_anchor_simple(source_map, ic_pc_map, *item_id, &item.loc) { - Ok(anchor) => Some(anchor), - Err(e) => { - warn!("Could not find anchor for item: {}, error: {e}", item); - None - } - }, + _ => find_anchor_simple(source_map, ic_pc_map, item_id, &item.loc), } + .inspect_err(|err| warn!(%item, %err, "could not find anchor")) + .ok() }) .collect() } @@ -51,21 +41,18 @@ pub fn find_anchors( /// Find an anchor representing the first opcode within the given source range. pub fn find_anchor_simple( source_map: &SourceMap, - ic_pc_map: &ICPCMap, - item_id: usize, + ic_pc_map: &IcPcMap, + item_id: u32, loc: &SourceLocation, ) -> eyre::Result { - let instruction = source_map - .iter() - .enumerate() - .find_map(|(ic, element)| is_in_source_range(element, loc).then_some(ic)) - .ok_or_else(|| { - eyre::eyre!("Could not find anchor: No matching instruction in range {}", loc) - })?; + let instruction = + source_map.iter().position(|element| is_in_source_range(element, loc)).ok_or_else( + || eyre::eyre!("Could not find anchor: No matching instruction in range {loc}"), + )?; Ok(ItemAnchor { - instruction: *ic_pc_map.get(&instruction).ok_or_else(|| { - eyre::eyre!("We found an anchor, but we cant translate it to a program counter") + instruction: ic_pc_map.get(instruction as u32).ok_or_else(|| { + eyre::eyre!("We found an anchor, but we can't translate it to a program counter") })?, item_id, }) @@ -100,24 +87,22 @@ pub fn find_anchor_simple( /// counter of the first branch, and return an item for that program counter, and the /// program counter immediately after the JUMPI instruction. pub fn find_anchor_branch( - bytecode: &Bytes, + bytecode: &[u8], source_map: &SourceMap, - item_id: usize, + item_id: u32, loc: &SourceLocation, ) -> eyre::Result<(ItemAnchor, ItemAnchor)> { - // NOTE(onbjerg): We use `SpecId::LATEST` here since it does not matter; the only difference - // is the gas cost. - let opcode_infos = spec_opcode_gas(SpecId::LATEST); - let mut anchors: Option<(ItemAnchor, ItemAnchor)> = None; let mut pc = 0; let mut cumulative_push_size = 0; - while pc < bytecode.0.len() { - let op = bytecode.0[pc]; + while pc < bytecode.len() { + let op = bytecode[pc]; // We found a push, so we do some PC -> IC translation accounting, but we also check if // this push is coupled with the JUMPI we are interested in. - if opcode_infos[op as usize].is_push() { + + // Check if Opcode is PUSH + if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { let element = if let Some(element) = source_map.get(pc - cumulative_push_size) { element } else { @@ -133,29 +118,25 @@ pub fn find_anchor_branch( // Check if we are in the source range we are interested in, and if the next opcode // is a JUMPI - if is_in_source_range(element, loc) && bytecode.0[pc + 1] == opcode::JUMPI { + if is_in_source_range(element, loc) && bytecode[pc + 1] == opcode::JUMPI { // We do not support program counters bigger than usize. This is also an // assumption in REVM, so this is just a sanity check. - if push_size > 8 { - panic!("We found the anchor for the branch, but it refers to a program counter bigger than 64 bits."); - } + ensure!(push_size <= 8, "jump destination overflow"); // Convert the push bytes for the second branch's PC to a usize let push_bytes_start = pc - push_size + 1; - let mut pc_bytes: [u8; 8] = [0; 8]; - for (i, push_byte) in - bytecode.0[push_bytes_start..push_bytes_start + push_size].iter().enumerate() - { - pc_bytes[8 - push_size + i] = *push_byte; - } - + let push_bytes = &bytecode[push_bytes_start..push_bytes_start + push_size]; + let mut pc_bytes = [0u8; 8]; + pc_bytes[8 - push_size..].copy_from_slice(push_bytes); + let pc_jump = u64::from_be_bytes(pc_bytes); + let pc_jump = u32::try_from(pc_jump).expect("PC is too big"); anchors = Some(( ItemAnchor { item_id, // The first branch is the opcode directly after JUMPI - instruction: pc + 2, + instruction: (pc + 2) as u32, }, - ItemAnchor { item_id, instruction: usize::from_be_bytes(pc_bytes) }, + ItemAnchor { item_id, instruction: pc_jump }, )); } } @@ -167,14 +148,25 @@ pub fn find_anchor_branch( /// Calculates whether `element` is within the range of the target `location`. fn is_in_source_range(element: &SourceElement, location: &SourceLocation) -> bool { - let source_ids_match = element.index.map_or(false, |a| a as usize == location.source_id); + // Source IDs must match. + let source_ids_match = element.index_i32() == location.source_id as i32; + if !source_ids_match { + return false; + } // Needed because some source ranges in the source map mark the entire contract... - let is_within_start = element.offset >= location.start; + let is_within_start = element.offset() >= location.bytes.start; + if !is_within_start { + return false; + } - let start_of_ranges = location.start.max(element.offset); + let start_of_ranges = location.bytes.start.max(element.offset()); let end_of_ranges = - (location.start + location.length.unwrap_or_default()).min(element.offset + element.length); + (location.bytes.start + location.len()).min(element.offset() + element.length()); + let within_ranges = start_of_ranges <= end_of_ranges; + if !within_ranges { + return false; + } - source_ids_match && is_within_start && start_of_ranges <= end_of_ranges + true } diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs new file mode 100644 index 0000000000000..6a6c50b093c8f --- /dev/null +++ b/crates/evm/coverage/src/inspector.rs @@ -0,0 +1,102 @@ +use crate::{HitMap, HitMaps}; +use alloy_primitives::B256; +use revm::{interpreter::Interpreter, Database, EvmContext, Inspector}; +use std::ptr::NonNull; + +/// Inspector implementation for collecting coverage information. +#[derive(Clone, Debug)] +pub struct CoverageCollector { + // NOTE: `current_map` is always a valid reference into `maps`. + // It is accessed only through `get_or_insert_map` which guarantees that it's valid. + // Both of these fields are unsafe to access directly outside of `*insert_map`. + current_map: NonNull, + current_hash: B256, + + maps: HitMaps, +} + +// SAFETY: See comments on `current_map`. +unsafe impl Send for CoverageCollector {} +unsafe impl Sync for CoverageCollector {} + +impl Default for CoverageCollector { + fn default() -> Self { + Self { + current_map: NonNull::dangling(), + current_hash: B256::ZERO, + maps: Default::default(), + } + } +} + +impl Inspector for CoverageCollector { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { + get_or_insert_contract_hash(interpreter); + self.insert_map(interpreter); + } + + #[inline] + fn step(&mut self, interpreter: &mut Interpreter, _context: &mut EvmContext) { + let map = self.get_or_insert_map(interpreter); + map.hit(interpreter.program_counter() as u32); + } +} + +impl CoverageCollector { + /// Finish collecting coverage information and return the [`HitMaps`]. + pub fn finish(self) -> HitMaps { + self.maps + } + + /// Gets the hit map for the current contract, or inserts a new one if it doesn't exist. + /// + /// The map is stored in `current_map` and returned as a mutable reference. + /// See comments on `current_map` for more details. + #[inline] + fn get_or_insert_map(&mut self, interpreter: &mut Interpreter) -> &mut HitMap { + let hash = get_or_insert_contract_hash(interpreter); + if self.current_hash != *hash { + self.insert_map(interpreter); + } + // SAFETY: See comments on `current_map`. + unsafe { self.current_map.as_mut() } + } + + #[cold] + #[inline(never)] + fn insert_map(&mut self, interpreter: &Interpreter) { + let Some(hash) = interpreter.contract.hash else { eof_panic() }; + self.current_hash = hash; + // Converts the mutable reference to a `NonNull` pointer. + self.current_map = self + .maps + .entry(hash) + .or_insert_with(|| HitMap::new(interpreter.contract.bytecode.original_bytes())) + .into(); + } +} + +/// Helper function for extracting contract hash used to record coverage hit map. +/// +/// If the contract hash is zero (contract not yet created but it's going to be created in current +/// tx) then the hash is calculated from the bytecode. +#[inline] +fn get_or_insert_contract_hash(interpreter: &mut Interpreter) -> &B256 { + let Some(hash) = interpreter.contract.hash.as_mut() else { eof_panic() }; + if hash.is_zero() { + set_contract_hash(hash, &interpreter.contract.bytecode); + } + hash +} + +#[cold] +#[inline(never)] +fn set_contract_hash(hash: &mut B256, bytecode: &revm::primitives::Bytecode) { + *hash = bytecode.hash_slow(); +} + +#[cold] +#[inline(never)] +fn eof_panic() -> ! { + panic!("coverage does not support EOF"); +} diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs new file mode 100644 index 0000000000000..793e0ee56670c --- /dev/null +++ b/crates/evm/coverage/src/lib.rs @@ -0,0 +1,486 @@ +//! # foundry-evm-coverage +//! +//! EVM bytecode coverage analysis. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + +use alloy_primitives::{ + map::{B256HashMap, HashMap}, + Bytes, +}; +use analysis::SourceAnalysis; +use eyre::Result; +use foundry_compilers::artifacts::sourcemap::SourceMap; +use semver::Version; +use std::{ + collections::BTreeMap, + fmt::Display, + num::NonZeroU32, + ops::{Deref, DerefMut, Range}, + path::{Path, PathBuf}, + sync::Arc, +}; + +pub mod analysis; +pub mod anchors; + +mod inspector; +pub use inspector::CoverageCollector; + +/// A coverage report. +/// +/// A coverage report contains coverage items and opcodes corresponding to those items (called +/// "anchors"). A single coverage item may be referred to by multiple anchors. +#[derive(Clone, Debug, Default)] +pub struct CoverageReport { + /// A map of source IDs to the source path. + pub source_paths: HashMap<(Version, usize), PathBuf>, + /// A map of source paths to source IDs. + pub source_paths_to_ids: HashMap<(Version, PathBuf), usize>, + /// All coverage items for the codebase, keyed by the compiler version. + pub analyses: HashMap, + /// All item anchors for the codebase, keyed by their contract ID. + pub anchors: HashMap, Vec)>, + /// All the bytecode hits for the codebase. + pub bytecode_hits: HashMap, + /// The bytecode -> source mappings. + pub source_maps: HashMap, +} + +impl CoverageReport { + /// Add a source file path. + pub fn add_source(&mut self, version: Version, source_id: usize, path: PathBuf) { + self.source_paths.insert((version.clone(), source_id), path.clone()); + self.source_paths_to_ids.insert((version, path), source_id); + } + + /// Get the source ID for a specific source file path. + pub fn get_source_id(&self, version: Version, path: PathBuf) -> Option { + self.source_paths_to_ids.get(&(version, path)).copied() + } + + /// Add the source maps. + pub fn add_source_maps( + &mut self, + source_maps: impl IntoIterator, + ) { + self.source_maps.extend(source_maps); + } + + /// Add a [`SourceAnalysis`] to this report. + pub fn add_analysis(&mut self, version: Version, analysis: SourceAnalysis) { + self.analyses.insert(version, analysis); + } + + /// Add anchors to this report. + pub fn add_anchors( + &mut self, + anchors: impl IntoIterator, Vec))>, + ) { + self.anchors.extend(anchors); + } + + /// Returns an iterator over coverage summaries by source file path. + pub fn summary_by_file(&self) -> impl Iterator { + self.by_file(|summary: &mut CoverageSummary, item| summary.add_item(item)) + } + + /// Returns an iterator over coverage items by source file path. + pub fn items_by_file(&self) -> impl Iterator)> { + self.by_file(|list: &mut Vec<_>, item| list.push(item)) + } + + fn by_file<'a, T: Default>( + &'a self, + mut f: impl FnMut(&mut T, &'a CoverageItem), + ) -> impl Iterator { + let mut by_file: BTreeMap<&Path, T> = BTreeMap::new(); + for (version, items) in &self.analyses { + for item in items.all_items() { + let key = (version.clone(), item.loc.source_id); + let Some(path) = self.source_paths.get(&key) else { continue }; + f(by_file.entry(path).or_default(), item); + } + } + by_file.into_iter() + } + + /// Processes data from a [`HitMap`] and sets hit counts for coverage items in this coverage + /// map. + /// + /// This function should only be called *after* all the relevant sources have been processed and + /// added to the map (see [`add_source`](Self::add_source)). + pub fn add_hit_map( + &mut self, + contract_id: &ContractId, + hit_map: &HitMap, + is_deployed_code: bool, + ) -> Result<()> { + // Add bytecode level hits. + self.bytecode_hits + .entry(contract_id.clone()) + .and_modify(|m| m.merge(hit_map)) + .or_insert_with(|| hit_map.clone()); + + // Add source level hits. + if let Some(anchors) = self.anchors.get(contract_id) { + let anchors = if is_deployed_code { &anchors.1 } else { &anchors.0 }; + for anchor in anchors { + if let Some(hits) = hit_map.get(anchor.instruction) { + self.analyses + .get_mut(&contract_id.version) + .and_then(|items| items.all_items_mut().get_mut(anchor.item_id as usize)) + .expect("Anchor refers to non-existent coverage item") + .hits += hits.get(); + } + } + } + + Ok(()) + } + + /// Retains all the coverage items specified by `predicate`. + /// + /// This function should only be called after all the sources were used, otherwise, the output + /// will be missing the ones that are dependent on them. + pub fn retain_sources(&mut self, mut predicate: impl FnMut(&Path) -> bool) { + self.analyses.retain(|version, analysis| { + analysis.all_items_mut().retain(|item| { + self.source_paths + .get(&(version.clone(), item.loc.source_id)) + .map(|path| predicate(path)) + .unwrap_or(false) + }); + !analysis.all_items().is_empty() + }); + } +} + +/// A collection of [`HitMap`]s. +#[derive(Clone, Debug, Default)] +pub struct HitMaps(pub B256HashMap); + +impl HitMaps { + /// Merges two `Option`. + pub fn merge_opt(a: &mut Option, b: Option) { + match (a, b) { + (_, None) => {} + (a @ None, Some(b)) => *a = Some(b), + (Some(a), Some(b)) => a.merge(b), + } + } + + /// Merges two `HitMaps`. + pub fn merge(&mut self, other: Self) { + self.reserve(other.len()); + for (code_hash, other) in other.0 { + self.entry(code_hash).and_modify(|e| e.merge(&other)).or_insert(other); + } + } + + /// Merges two `HitMaps`. + pub fn merged(mut self, other: Self) -> Self { + self.merge(other); + self + } +} + +impl Deref for HitMaps { + type Target = B256HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HitMaps { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Hit data for an address. +/// +/// Contains low-level data about hit counters for the instructions in the bytecode of a contract. +#[derive(Clone, Debug)] +pub struct HitMap { + bytecode: Bytes, + hits: HashMap, +} + +impl HitMap { + /// Create a new hitmap with the given bytecode. + #[inline] + pub fn new(bytecode: Bytes) -> Self { + Self { bytecode, hits: HashMap::with_capacity_and_hasher(1024, Default::default()) } + } + + /// Returns the bytecode. + #[inline] + pub fn bytecode(&self) -> &Bytes { + &self.bytecode + } + + /// Returns the number of hits for the given program counter. + #[inline] + pub fn get(&self, pc: u32) -> Option { + NonZeroU32::new(self.hits.get(&pc).copied().unwrap_or(0)) + } + + /// Increase the hit counter by 1 for the given program counter. + #[inline] + pub fn hit(&mut self, pc: u32) { + self.hits(pc, 1) + } + + /// Increase the hit counter by `hits` for the given program counter. + #[inline] + pub fn hits(&mut self, pc: u32, hits: u32) { + *self.hits.entry(pc).or_default() += hits; + } + + /// Merge another hitmap into this, assuming the bytecode is consistent + pub fn merge(&mut self, other: &Self) { + self.hits.reserve(other.len()); + for (pc, hits) in other.iter() { + self.hits(pc, hits); + } + } + + /// Returns an iterator over all the program counters and their hit counts. + #[inline] + pub fn iter(&self) -> impl Iterator + '_ { + self.hits.iter().map(|(&pc, &hits)| (pc, hits)) + } + + /// Returns the number of program counters hit in the hitmap. + #[inline] + pub fn len(&self) -> usize { + self.hits.len() + } + + /// Returns `true` if the hitmap is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.hits.is_empty() + } +} + +/// A unique identifier for a contract +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ContractId { + pub version: Version, + pub source_id: usize, + pub contract_name: Arc, +} + +impl Display for ContractId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Contract \"{}\" (solc {}, source ID {})", + self.contract_name, self.version, self.source_id + ) + } +} + +/// An item anchor describes what instruction marks a [CoverageItem] as covered. +#[derive(Clone, Debug)] +pub struct ItemAnchor { + /// The program counter for the opcode of this anchor. + pub instruction: u32, + /// The item ID this anchor points to. + pub item_id: u32, +} + +impl Display for ItemAnchor { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "IC {} -> Item {}", self.instruction, self.item_id) + } +} + +#[derive(Clone, Debug)] +pub enum CoverageItemKind { + /// An executable line in the code. + Line, + /// A statement in the code. + Statement, + /// A branch in the code. + Branch { + /// The ID that identifies the branch. + /// + /// There may be multiple items with the same branch ID - they belong to the same branch, + /// but represent different paths. + branch_id: u32, + /// The path ID for this branch. + /// + /// The first path has ID 0, the next ID 1, and so on. + path_id: u32, + /// If true, then the branch anchor is the first opcode within the branch source range. + is_first_opcode: bool, + }, + /// A function in the code. + Function { + /// The name of the function. + name: String, + }, +} + +#[derive(Clone, Debug)] +pub struct CoverageItem { + /// The coverage item kind. + pub kind: CoverageItemKind, + /// The location of the item in the source code. + pub loc: SourceLocation, + /// The number of times this item was hit. + pub hits: u32, +} + +impl Display for CoverageItem { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.kind { + CoverageItemKind::Line => { + write!(f, "Line")?; + } + CoverageItemKind::Statement => { + write!(f, "Statement")?; + } + CoverageItemKind::Branch { branch_id, path_id, .. } => { + write!(f, "Branch (branch: {branch_id}, path: {path_id})")?; + } + CoverageItemKind::Function { name } => { + write!(f, r#"Function "{name}""#)?; + } + } + write!(f, " (location: {}, hits: {})", self.loc, self.hits) + } +} + +/// A source location. +#[derive(Clone, Debug)] +pub struct SourceLocation { + /// The source ID. + pub source_id: usize, + /// The contract this source range is in. + pub contract_name: Arc, + /// Byte range. + pub bytes: Range, + /// Line range. Indices are 1-based. + pub lines: Range, +} + +impl Display for SourceLocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "source ID {}, lines {:?}, bytes {:?}", self.source_id, self.lines, self.bytes) + } +} + +impl SourceLocation { + /// Returns the length of the byte range. + pub fn len(&self) -> u32 { + self.bytes.len() as u32 + } + + /// Returns true if the byte range is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// Coverage summary for a source file. +#[derive(Clone, Debug, Default)] +pub struct CoverageSummary { + /// The number of executable lines in the source file. + pub line_count: usize, + /// The number of lines that were hit. + pub line_hits: usize, + /// The number of statements in the source file. + pub statement_count: usize, + /// The number of statements that were hit. + pub statement_hits: usize, + /// The number of branches in the source file. + pub branch_count: usize, + /// The number of branches that were hit. + pub branch_hits: usize, + /// The number of functions in the source file. + pub function_count: usize, + /// The number of functions hit. + pub function_hits: usize, +} + +impl CoverageSummary { + /// Creates a new, empty coverage summary. + pub fn new() -> Self { + Self::default() + } + + /// Creates a coverage summary from a collection of coverage items. + pub fn from_items<'a>(items: impl IntoIterator) -> Self { + let mut summary = Self::default(); + summary.add_items(items); + summary + } + + /// Adds another coverage summary to this one. + pub fn merge(&mut self, other: &Self) { + let Self { + line_count, + line_hits, + statement_count, + statement_hits, + branch_count, + branch_hits, + function_count, + function_hits, + } = self; + *line_count += other.line_count; + *line_hits += other.line_hits; + *statement_count += other.statement_count; + *statement_hits += other.statement_hits; + *branch_count += other.branch_count; + *branch_hits += other.branch_hits; + *function_count += other.function_count; + *function_hits += other.function_hits; + } + + /// Adds a coverage item to this summary. + pub fn add_item(&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; + } + } + } + } + + /// Adds multiple coverage items to this summary. + pub fn add_items<'a>(&mut self, items: impl IntoIterator) { + for item in items { + self.add_item(item); + } + } +} diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml new file mode 100644 index 0000000000000..214b241e31ab8 --- /dev/null +++ b/crates/evm/evm/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "foundry-evm" +description = "Main Foundry EVM backend abstractions" + +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-cheatcodes.workspace = true +foundry-common.workspace = true +foundry-compilers.workspace = true +foundry-config.workspace = true +foundry-evm-core.workspace = true +foundry-evm-coverage.workspace = true +foundry-evm-fuzz.workspace = true +foundry-evm-traces.workspace = true + +alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-json-abi.workspace = true +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-sol-types.workspace = true +revm = { workspace = true, default-features = false, features = [ + "std", + "serde", + "memory_limit", + "optional_eip3607", + "optional_block_gas_limit", + "optional_no_base_fee", + "arbitrary", + "c-kzg", +] } +revm-inspectors.workspace = true + +eyre.workspace = true +parking_lot.workspace = true +proptest.workspace = true +thiserror.workspace = true +tracing.workspace = true +indicatif = "0.17" +serde.workspace = true diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs new file mode 100644 index 0000000000000..c371a6550b879 --- /dev/null +++ b/crates/evm/evm/src/executors/builder.rs @@ -0,0 +1,88 @@ +use crate::{executors::Executor, inspectors::InspectorStackBuilder}; +use foundry_evm_core::backend::Backend; +use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; + +/// The builder that allows to configure an evm [`Executor`] which a stack of optional +/// [`revm::Inspector`]s, such as [`Cheatcodes`]. +/// +/// By default, the [`Executor`] will be configured with an empty [`InspectorStack`]. +/// +/// [`Cheatcodes`]: super::Cheatcodes +/// [`InspectorStack`]: super::InspectorStack +#[derive(Clone, Debug)] +#[must_use = "builders do nothing unless you call `build` on them"] +pub struct ExecutorBuilder { + /// The configuration used to build an `InspectorStack`. + stack: InspectorStackBuilder, + /// The gas limit. + gas_limit: Option, + /// The spec ID. + spec_id: SpecId, + legacy_assertions: bool, +} + +impl Default for ExecutorBuilder { + #[inline] + fn default() -> Self { + Self { + stack: InspectorStackBuilder::new(), + gas_limit: None, + spec_id: SpecId::LATEST, + legacy_assertions: false, + } + } +} + +impl ExecutorBuilder { + /// Create a new executor builder. + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Modify the inspector stack. + #[inline] + pub fn inspectors( + mut self, + f: impl FnOnce(InspectorStackBuilder) -> InspectorStackBuilder, + ) -> Self { + self.stack = f(self.stack); + self + } + + /// Sets the EVM spec to use. + #[inline] + pub fn spec_id(mut self, spec: SpecId) -> Self { + self.spec_id = spec; + self + } + + /// Sets the executor gas limit. + #[inline] + pub fn gas_limit(mut self, gas_limit: u64) -> Self { + self.gas_limit = Some(gas_limit); + self + } + + /// Sets the `legacy_assertions` flag. + #[inline] + pub fn legacy_assertions(mut self, legacy_assertions: bool) -> Self { + self.legacy_assertions = legacy_assertions; + self + } + + /// Builds the executor as configured. + #[inline] + pub fn build(self, env: Env, db: Backend) -> Executor { + let Self { mut stack, gas_limit, spec_id, legacy_assertions } = self; + 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); + Executor::new(db, env, stack.build(), gas_limit, legacy_assertions) + } +} diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs new file mode 100644 index 0000000000000..889013f2d1027 --- /dev/null +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -0,0 +1,288 @@ +use crate::executors::{Executor, FuzzTestTimer, RawCallResult}; +use alloy_dyn_abi::JsonAbiExt; +use alloy_json_abi::Function; +use alloy_primitives::{map::HashMap, Address, Bytes, Log, U256}; +use eyre::Result; +use foundry_common::evm::Breakpoints; +use foundry_config::FuzzConfig; +use foundry_evm_core::{ + constants::{MAGIC_ASSUME, TEST_TIMEOUT}, + decode::{RevertDecoder, SkipReason}, +}; +use foundry_evm_coverage::HitMaps; +use foundry_evm_fuzz::{ + strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, + BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzFixtures, FuzzTestResult, +}; +use foundry_evm_traces::SparsedTraceArena; +use indicatif::ProgressBar; +use proptest::test_runner::{TestCaseError, TestError, TestRunner}; +use std::{cell::RefCell, collections::BTreeMap}; + +mod types; +pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; + +/// Contains data collected during fuzz test runs. +#[derive(Default)] +pub struct FuzzTestData { + // Stores the first fuzz case. + pub first_case: Option, + // Stored gas usage per fuzz case. + pub gas_by_case: Vec<(u64, u64)>, + // Stores the result and calldata of the last failed call, if any. + pub counterexample: (Bytes, RawCallResult), + // Stores up to `max_traces_to_collect` traces. + pub traces: Vec, + // Stores breakpoints for the last fuzz case. + pub breakpoints: Option, + // Stores coverage information for all fuzz cases. + pub coverage: Option, + // Stores logs for all fuzz cases + pub logs: Vec, + // Stores gas snapshots for all fuzz cases + pub gas_snapshots: BTreeMap>, + // Deprecated cheatcodes mapped to their replacements. + pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, +} + +/// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`]. +/// +/// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contract with +/// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the +/// configuration which can be overridden via [environment variables](proptest::test_runner::Config) +pub struct FuzzedExecutor { + /// The EVM executor + executor: Executor, + /// The fuzzer + runner: TestRunner, + /// The account that calls tests + sender: Address, + /// The fuzz configuration + config: FuzzConfig, +} + +impl FuzzedExecutor { + /// Instantiates a fuzzed executor given a testrunner + pub fn new( + executor: Executor, + runner: TestRunner, + sender: Address, + config: FuzzConfig, + ) -> Self { + Self { executor, runner, sender, config } + } + + /// Fuzzes the provided function, assuming it is available at the contract at `address` + /// If `should_fail` is set to `true`, then it will stop only when there's a success + /// test case. + /// + /// Returns a list of all the consumed gas and calldata of every fuzz case + #[allow(clippy::too_many_arguments)] + pub fn fuzz( + &self, + func: &Function, + fuzz_fixtures: &FuzzFixtures, + deployed_libs: &[Address], + address: Address, + rd: &RevertDecoder, + progress: Option<&ProgressBar>, + ) -> FuzzTestResult { + // Stores the fuzz test execution data. + let execution_data = RefCell::new(FuzzTestData::default()); + let state = self.build_fuzz_state(deployed_libs); + let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); + let strategy = proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures), + dictionary_weight => fuzz_calldata_from_state(func.clone(), &state), + ]; + // We want to collect at least one trace which will be displayed to user. + let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; + let show_logs = self.config.show_logs; + + // Start timer for this fuzz test. + let timer = FuzzTestTimer::new(self.config.timeout); + + let run_result = self.runner.clone().run(&strategy, |calldata| { + // Check if the timeout has been reached. + if timer.is_timed_out() { + return Err(TestCaseError::fail(TEST_TIMEOUT)); + } + + let fuzz_res = self.single_fuzz(address, calldata)?; + + // If running with progress then increment current run. + if let Some(progress) = progress { + progress.inc(1); + }; + + match fuzz_res { + FuzzOutcome::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 data.traces.len() == max_traces_to_collect { + data.traces.pop(); + } + data.traces.push(call_traces); + data.breakpoints.replace(case.breakpoints); + } + + if show_logs { + data.logs.extend(case.logs); + } + + HitMaps::merge_opt(&mut data.coverage, case.coverage); + + data.deprecated_cheatcodes = case.deprecated_cheatcodes; + + Ok(()) + } + FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: status, + counterexample: outcome, + .. + }) => { + // We cannot use the calldata returned by the test runner in `TestError::Fail`, + // since that input represents the last run case, which may not correspond with + // our failure - when a fuzz case fails, proptest will try to run at least one + // more case to find a minimal failure case. + let reason = rd.maybe_decode(&outcome.1.result, Some(status)); + execution_data.borrow_mut().logs.extend(outcome.1.logs.clone()); + execution_data.borrow_mut().counterexample = outcome; + // HACK: we have to use an empty string here to denote `None`. + Err(TestCaseError::fail(reason.unwrap_or_default())) + } + } + }); + + let fuzz_result = execution_data.into_inner(); + let (calldata, call) = fuzz_result.counterexample; + + 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: fuzz_result.first_case.unwrap_or_default(), + gas_by_case: fuzz_result.gas_by_case, + success: run_result.is_ok(), + skipped: false, + reason: None, + counterexample: None, + logs: fuzz_result.logs, + labeled_addresses: call.labels, + traces: last_run_traces, + breakpoints: last_run_breakpoints, + gas_report_traces: traces.into_iter().map(|a| a.arena).collect(), + coverage: fuzz_result.coverage, + deprecated_cheatcodes: fuzz_result.deprecated_cheatcodes, + }; + + match run_result { + Ok(()) => {} + Err(TestError::Abort(reason)) => { + let msg = reason.message(); + // Currently the only operation that can trigger proptest global rejects is the + // `vm.assume` cheatcode, thus we surface this info to the user when the fuzz test + // aborts due to too many global rejects, making the error message more actionable. + result.reason = if msg == "Too many global rejects" { + let error = FuzzError::TooManyRejects(self.runner.config().max_global_rejects); + Some(error.to_string()) + } else { + Some(msg.to_string()) + }; + } + Err(TestError::Fail(reason, _)) => { + let reason = reason.to_string(); + if reason == TEST_TIMEOUT { + // If the reason is a timeout, we consider the fuzz test successful. + result.success = true; + } else { + result.reason = (!reason.is_empty()).then_some(reason); + let args = if let Some(data) = calldata.get(4..) { + func.abi_decode_input(data, false).unwrap_or_default() + } else { + vec![] + }; + + result.counterexample = Some(CounterExample::Single( + BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + )); + } + } + } + + if let Some(reason) = &result.reason { + if let Some(reason) = SkipReason::decode_self(reason) { + result.skipped = true; + result.reason = reason.0; + } + } + + state.log_stats(); + + result + } + + /// Granular and single-step function that runs only one fuzz and returns either a `CaseOutcome` + /// or a `CounterExampleOutcome` + pub fn single_fuzz( + &self, + address: Address, + calldata: alloy_primitives::Bytes, + ) -> Result { + let mut call = self + .executor + .call_raw(self.sender, address, calldata.clone(), U256::ZERO) + .map_err(|e| TestCaseError::fail(e.to_string()))?; + + // Handle `vm.assume`. + if call.result.as_ref() == MAGIC_ASSUME { + return Err(TestCaseError::reject(FuzzError::AssumeReject)) + } + + let (breakpoints, deprecated_cheatcodes) = + call.cheatcodes.as_ref().map_or_else(Default::default, |cheats| { + (cheats.breakpoints.clone(), cheats.deprecated.clone()) + }); + + let success = self.executor.is_raw_call_mut_success(address, &mut call, false); + if success { + Ok(FuzzOutcome::Case(CaseOutcome { + case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, + traces: call.traces, + coverage: call.coverage, + breakpoints, + logs: call.logs, + deprecated_cheatcodes, + })) + } else { + Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { + exit_reason: call.exit_reason, + counterexample: (calldata, call), + breakpoints, + })) + } + } + + /// Stores fuzz state for use with [fuzz_calldata_from_state] + pub fn build_fuzz_state(&self, deployed_libs: &[Address]) -> EvmFuzzState { + if let Some(fork_db) = self.executor.backend().active_fork_db() { + EvmFuzzState::new(fork_db, self.config.dictionary, deployed_libs) + } else { + EvmFuzzState::new( + self.executor.backend().mem_db(), + self.config.dictionary, + deployed_libs, + ) + } + } +} diff --git a/crates/evm/evm/src/executors/fuzz/types.rs b/crates/evm/evm/src/executors/fuzz/types.rs new file mode 100644 index 0000000000000..f5399c5c3ac1a --- /dev/null +++ b/crates/evm/evm/src/executors/fuzz/types.rs @@ -0,0 +1,42 @@ +use crate::executors::RawCallResult; +use alloy_primitives::{map::HashMap, Bytes, Log}; +use foundry_common::evm::Breakpoints; +use foundry_evm_coverage::HitMaps; +use foundry_evm_fuzz::FuzzCase; +use foundry_evm_traces::SparsedTraceArena; +use revm::interpreter::InstructionResult; + +/// Returned by a single fuzz in the case of a successful run +#[derive(Debug)] +pub struct CaseOutcome { + /// Data of a single fuzz test case. + pub case: FuzzCase, + /// The traces of the call. + pub traces: Option, + /// The coverage info collected during the call. + pub coverage: Option, + /// Breakpoints char pc map. + pub breakpoints: Breakpoints, + /// logs of a single fuzz test case. + pub logs: Vec, + // Deprecated cheatcodes mapped to their replacements. + pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, +} + +/// Returned by a single fuzz when a counterexample has been discovered +#[derive(Debug)] +pub struct CounterExampleOutcome { + /// Minimal reproduction test case for failing test. + pub counterexample: (Bytes, RawCallResult), + /// The status of the call. + pub exit_reason: InstructionResult, + /// Breakpoints char pc map. + pub breakpoints: Breakpoints, +} + +/// Outcome of a single fuzz +#[derive(Debug)] +pub enum FuzzOutcome { + Case(CaseOutcome), + CounterExample(CounterExampleOutcome), +} diff --git a/crates/evm/evm/src/executors/invariant/error.rs b/crates/evm/evm/src/executors/invariant/error.rs new file mode 100644 index 0000000000000..f5eef18ed7529 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/error.rs @@ -0,0 +1,102 @@ +use super::{BasicTxDetails, InvariantContract}; +use crate::executors::RawCallResult; +use alloy_primitives::{Address, Bytes}; +use foundry_config::InvariantConfig; +use foundry_evm_core::decode::RevertDecoder; +use foundry_evm_fuzz::{invariant::FuzzRunIdentifiedContracts, Reason}; +use proptest::test_runner::TestError; + +/// Stores information about failures and reverts of the invariant tests. +#[derive(Clone, Default)] +pub struct InvariantFailures { + /// Total number of reverts. + pub reverts: usize, + /// The latest revert reason of a run. + pub revert_reason: Option, + /// Maps a broken invariant to its specific error. + pub error: Option, +} + +impl InvariantFailures { + pub fn new() -> Self { + Self::default() + } + + pub fn into_inner(self) -> (usize, Option) { + (self.reverts, self.error) + } +} + +#[derive(Clone, Debug)] +pub enum InvariantFuzzError { + Revert(FailedInvariantCaseData), + BrokenInvariant(FailedInvariantCaseData), + MaxAssumeRejects(u32), +} + +impl InvariantFuzzError { + pub fn revert_reason(&self) -> Option { + match self { + Self::BrokenInvariant(case_data) | Self::Revert(case_data) => { + (!case_data.revert_reason.is_empty()).then(|| case_data.revert_reason.clone()) + } + Self::MaxAssumeRejects(allowed) => { + Some(format!("`vm.assume` rejected too many inputs ({allowed} allowed)")) + } + } + } +} + +#[derive(Clone, Debug)] +pub struct FailedInvariantCaseData { + /// The proptest error occurred as a result of a test case. + pub test_error: TestError>, + /// The return reason of the offending call. + pub return_reason: Reason, + /// The revert string of the offending call. + pub revert_reason: String, + /// Address of the invariant asserter. + pub addr: Address, + /// Function calldata for invariant check. + pub calldata: Bytes, + /// Inner fuzzing Sequence coming from overriding calls. + pub inner_sequence: Vec>, + /// Shrink run limit + pub shrink_run_limit: u32, + /// Fail on revert, used to check sequence when shrinking. + pub fail_on_revert: bool, +} + +impl FailedInvariantCaseData { + pub fn new( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, + calldata: &[BasicTxDetails], + call_result: RawCallResult, + inner_sequence: &[Option], + ) -> Self { + // Collect abis of fuzzed and invariant contracts to decode custom error. + let revert_reason = RevertDecoder::new() + .with_abis(targeted_contracts.targets.lock().iter().map(|(_, c)| &c.abi)) + .with_abi(invariant_contract.abi) + .decode(call_result.result.as_ref(), Some(call_result.exit_reason)); + + let func = invariant_contract.invariant_function; + debug_assert!(func.inputs.is_empty()); + let origin = func.name.as_str(); + Self { + test_error: TestError::Fail( + format!("{origin}, reason: {revert_reason}").into(), + calldata.to_vec(), + ), + return_reason: "".into(), + revert_reason, + addr: invariant_contract.address, + calldata: func.selector().to_vec().into(), + inner_sequence: inner_sequence.to_vec(), + shrink_run_limit: invariant_config.shrink_run_limit, + fail_on_revert: invariant_config.fail_on_revert, + } + } +} diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs new file mode 100644 index 0000000000000..3837e2b841139 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -0,0 +1,920 @@ +use crate::{ + executors::{Executor, RawCallResult}, + inspectors::Fuzzer, +}; +use alloy_primitives::{Address, Bytes, FixedBytes, Selector, U256}; +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, + TEST_TIMEOUT, + }, + precompiles::PRECOMPILES, +}; +use foundry_evm_fuzz::{ + invariant::{ + ArtifactFilters, BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract, + RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts, + }, + strategies::{invariant_strat, override_call_strat, EvmFuzzState}, + FuzzCase, FuzzFixtures, FuzzedCases, +}; +use foundry_evm_traces::{CallTraceArena, SparsedTraceArena}; +use indicatif::ProgressBar; +use parking_lot::RwLock; +use proptest::{ + strategy::{Strategy, ValueTree}, + test_runner::{TestCaseError, TestRunner}, +}; +use result::{assert_after_invariant, assert_invariants, can_continue}; +use revm::primitives::HashMap; +use shrink::shrink_sequence; +use std::{ + cell::RefCell, + collections::{btree_map::Entry, HashMap as Map}, + sync::Arc, +}; + +mod error; +pub use error::{InvariantFailures, InvariantFuzzError}; +use foundry_evm_coverage::HitMaps; + +mod replay; +pub use replay::{replay_error, replay_run}; + +mod result; +pub use result::InvariantFuzzTestResult; +use serde::{Deserialize, Serialize}; + +mod shrink; +use crate::executors::{EvmError, FuzzTestTimer}; +pub use shrink::check_sequence; + +sol! { + interface IInvariantTest { + #[derive(Default)] + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + #[derive(Default)] + struct FuzzArtifactSelector { + string artifact; + bytes4[] selectors; + } + + #[derive(Default)] + struct FuzzInterface { + address addr; + string[] artifacts; + } + + function afterInvariant() external; + + #[derive(Default)] + function excludeArtifacts() public view returns (string[] memory excludedArtifacts); + + #[derive(Default)] + function excludeContracts() public view returns (address[] memory excludedContracts); + + #[derive(Default)] + function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors); + + #[derive(Default)] + function excludeSenders() public view returns (address[] memory excludedSenders); + + #[derive(Default)] + function targetArtifacts() public view returns (string[] memory targetedArtifacts); + + #[derive(Default)] + function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors); + + #[derive(Default)] + function targetContracts() public view returns (address[] memory targetedContracts); + + #[derive(Default)] + function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors); + + #[derive(Default)] + function targetSenders() public view returns (address[] memory targetedSenders); + + #[derive(Default)] + function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces); + } +} + +/// Contains invariant metrics for a single fuzzed selector. +#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct InvariantMetrics { + // Count of fuzzed selector calls. + pub calls: usize, + // Count of fuzzed selector reverts. + pub reverts: usize, + // Count of fuzzed selector discards (through assume cheatcodes). + pub discards: usize, +} + +/// 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, + // Metrics for each fuzzed selector. + pub metrics: Map, + + // Proptest runner to query for random values. + // The strategy only comes with the first `input`. We fill the rest of the `inputs` + // until the desired `depth` so we can use the evolving fuzz dictionary + // during the run. + pub branch_runner: TestRunner, +} + +/// 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, + metrics: Map::default(), + 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) { + HitMaps::merge_opt(&mut self.execution_data.borrow_mut().coverage, new_coverage); + } + + /// Update metrics for a fuzzed selector, extracted from tx details. + /// Always increments number of calls; discarded runs (through assume cheatcodes) are tracked + /// separated from reverts. + pub fn record_metrics(&self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) { + if let Some(metric_key) = + self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details) + { + let test_metrics = &mut self.execution_data.borrow_mut().metrics; + let invariant_metrics = test_metrics.entry(metric_key).or_default(); + invariant_metrics.calls += 1; + if discarded { + invariant_metrics.discards += 1; + } else if reverted { + invariant_metrics.reverts += 1; + } + } + } + + /// 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.into_iter().map(|arena| arena.arena).collect()); + } + 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`] implementer which provides fuzzing support using [`proptest`]. +/// +/// 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. + runner: TestRunner, + /// The invariant configuration + config: InvariantConfig, + /// Contracts deployed with `setUp()` + setup_contracts: &'a ContractsByAddress, + /// Contracts that are part of the project but have not been deployed yet. We need the bytecode + /// to identify them from the stateset changes. + project_contracts: &'a ContractsByArtifact, + /// Filters contracts to be fuzzed through their artifact identifiers. + artifact_filters: ArtifactFilters, +} + +impl<'a> InvariantExecutor<'a> { + /// Instantiates a fuzzed executor EVM given a testrunner + pub fn new( + executor: Executor, + runner: TestRunner, + config: InvariantConfig, + setup_contracts: &'a ContractsByAddress, + project_contracts: &'a ContractsByArtifact, + ) -> Self { + Self { + executor, + runner, + config, + setup_contracts, + project_contracts, + artifact_filters: ArtifactFilters::default(), + } + } + + /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address`. + pub fn invariant_fuzz( + &mut self, + invariant_contract: InvariantContract<'_>, + fuzz_fixtures: &FuzzFixtures, + deployed_libs: &[Address], + progress: Option<&ProgressBar>, + ) -> Result { + // Throw an error to abort test run if the invariant function accepts input params + if !invariant_contract.invariant_function.inputs.is_empty() { + return Err(eyre!("Invariant test function should have no inputs")) + } + + let (invariant_test, invariant_strategy) = + self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?; + + // Start timer for this invariant test. + let timer = FuzzTestTimer::new(self.config.timeout); + + 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 && invariant_test.reverts() > 0 { + return Err(TestCaseError::fail("call reverted")) + } + + while current_run.depth < self.config.depth { + // Check if the timeout has been reached. + if timer.is_timed_out() { + // Since we never record a revert here the test is still considered + // successful even though it timed out. We *want* + // this behavior for now, so that's ok, but + // future developers should be aware of this. + return Err(TestCaseError::fail(TEST_TIMEOUT)); + } + + let tx = current_run.inputs.last().ok_or_else(|| { + TestCaseError::fail("no input generated to called fuzz target") + })?; + + // Execute call from the randomly generated sequence without committing state. + // State is committed only if call is not a magic assume. + let mut call_result = current_run + .executor + .call_raw( + tx.sender, + tx.call_details.target, + tx.call_details.calldata.clone(), + U256::ZERO, + ) + .map_err(|e| TestCaseError::fail(e.to_string()))?; + + let discarded = call_result.result.as_ref() == MAGIC_ASSUME; + if self.config.show_metrics { + invariant_test.record_metrics(tx, call_result.reverted, discarded); + } + + // Collect coverage from last fuzzed call. + invariant_test.merge_coverage(call_result.coverage.clone()); + + if discarded { + 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( + "reached maximum number of `vm.assume` rejects", + )); + } + } else { + // Commit executed call result. + current_run.executor.commit(&mut call_result); + + // Collect data for fuzzing from the state changeset. + // This step updates the state dictionary and therefore invalidates the + // ValueTree in use by the current run. This manifestsitself in proptest + // observing a different input case than what it was called with, and creates + // inconsistencies whenever proptest tries to use the input case after test + // execution. + // See . + let mut state_changeset = call_result.state_changeset.clone(); + if !call_result.reverted { + collect_data( + &invariant_test, + &mut state_changeset, + tx, + &call_result, + self.config.depth, + ); + } + + // Collect created contracts and add to fuzz targets only if targeted contracts + // are updatable. + 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}"); + } + current_run.fuzz_runs.push(FuzzCase { + calldata: tx.call_details.calldata.clone(), + gas: call_result.gas_used, + stipend: call_result.stipend, + }); + + // Determine if test can continue or should exit. + let result = can_continue( + &invariant_contract, + &invariant_test, + &mut current_run, + &self.config, + call_result, + &state_changeset, + ) + .map_err(|e| TestCaseError::fail(e.to_string()))?; + if !result.can_continue || current_run.depth == self.config.depth - 1 { + invariant_test.set_last_run_inputs(¤t_run.inputs); + } + // If test cannot continue then stop current run and exit test suite. + if !result.can_continue { + return Err(TestCaseError::fail("test cannot continue")) + } + + invariant_test.set_last_call_results(result.call_result); + current_run.depth += 1; + } + + // Generates the next call from the run using the recently updated + // dictionary. + current_run.inputs.push( + invariant_strategy + .new_tree(&mut invariant_test.execution_data.borrow_mut().branch_runner) + .map_err(|_| TestCaseError::Fail("Could not generate case".into()))? + .current(), + ); + } + + // Call `afterInvariant` only if it is declared and test didn't fail already. + if invariant_contract.call_after_invariant && !invariant_test.has_errors() { + assert_after_invariant( + &invariant_contract, + &invariant_test, + ¤t_run, + &self.config, + ) + .map_err(|_| TestCaseError::Fail("Failed to call afterInvariant".into()))?; + } + + // 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 { + progress.inc(1); + } + + Ok(()) + }); + + trace!(?fuzz_fixtures); + invariant_test.fuzz_state.log_stats(); + + let result = invariant_test.execution_data.into_inner(); + Ok(InvariantFuzzTestResult { + error: result.failures.error, + cases: result.fuzz_cases, + reverts: result.failures.reverts, + last_run_inputs: result.last_run_inputs, + gas_report_traces: result.gas_report_traces, + coverage: result.coverage, + metrics: result.metrics, + }) + } + + /// Prepares certain structures to execute the invariant tests: + /// * Invariant Fuzz Test. + /// * Invariant Strategy + fn prepare_test( + &mut self, + invariant_contract: &InvariantContract<'_>, + fuzz_fixtures: &FuzzFixtures, + deployed_libs: &[Address], + ) -> 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) = + self.select_contracts_and_senders(invariant_contract.address)?; + + // Stores fuzz state for use with [fuzz_calldata_from_state]. + let fuzz_state = EvmFuzzState::new( + self.executor.backend().mem_db(), + self.config.dictionary, + deployed_libs, + ); + + // Creates the invariant strategy. + let strategy = invariant_strat( + fuzz_state.clone(), + targeted_senders, + targeted_contracts.clone(), + self.config.dictionary.dictionary_weight, + fuzz_fixtures.clone(), + ) + .no_shrink(); + + // Allows `override_call_strat` to use the address given by the Fuzzer inspector during + // EVM execution. + let mut call_generator = None; + if self.config.call_override { + let target_contract_ref = Arc::new(RwLock::new(Address::ZERO)); + + call_generator = Some(RandomCallGenerator::new( + invariant_contract.address, + self.runner.clone(), + override_call_strat( + fuzz_state.clone(), + targeted_contracts.clone(), + target_contract_ref.clone(), + fuzz_fixtures.clone(), + ), + target_contract_ref, + )); + } + + self.executor.inspector_mut().fuzzer = + Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true }); + + // 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(), + ), + strategy, + )) + } + + /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string + /// format). They will be used to filter contracts after the `setUp`, and more importantly, + /// during the runs. + /// + /// Also excludes any contract without any mutable functions. + /// + /// Priority: + /// + /// targetArtifactSelectors > excludeArtifacts > targetArtifacts + pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> { + let result = self + .executor + .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {}); + + // Insert them into the executor `targeted_abi`. + for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in + result.targetedArtifactSelectors + { + let identifier = self.validate_selected_contract(artifact, &selectors)?; + self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors); + } + + let selected = self + .executor + .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {}); + let excluded = self + .executor + .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {}); + + // Insert `excludeArtifacts` into the executor `excluded_abi`. + for contract in excluded.excludedArtifacts { + let identifier = self.validate_selected_contract(contract, &[])?; + + if !self.artifact_filters.excluded.contains(&identifier) { + self.artifact_filters.excluded.push(identifier); + } + } + + // Exclude any artifact without mutable functions. + for (artifact, contract) in self.project_contracts.iter() { + if contract + .abi + .functions() + .filter(|func| { + !matches!( + func.state_mutability, + alloy_json_abi::StateMutability::Pure | + alloy_json_abi::StateMutability::View + ) + }) + .count() == + 0 && + !self.artifact_filters.excluded.contains(&artifact.identifier()) + { + self.artifact_filters.excluded.push(artifact.identifier()); + } + } + + // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen + // before. + for contract in selected.targetedArtifacts { + let identifier = self.validate_selected_contract(contract, &[])?; + + if !self.artifact_filters.targeted.contains_key(&identifier) && + !self.artifact_filters.excluded.contains(&identifier) + { + self.artifact_filters.targeted.insert(identifier, vec![]); + } + } + Ok(()) + } + + /// Makes sure that the contract exists in the project. If so, it returns its artifact + /// identifier. + fn validate_selected_contract( + &mut self, + contract: String, + selectors: &[FixedBytes<4>], + ) -> Result { + if let Some((artifact, contract_data)) = + self.project_contracts.find_by_name_or_identifier(&contract)? + { + // Check that the selectors really exist for this contract. + for selector in selectors { + contract_data + .abi + .functions() + .find(|func| func.selector().as_slice() == selector.as_slice()) + .wrap_err(format!("{contract} does not have the selector {selector:?}"))?; + } + + return Ok(artifact.identifier()) + } + eyre::bail!("{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."); + } + + /// Selects senders and contracts based on the contract methods `targetSenders() -> address[]`, + /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`. + pub fn select_contracts_and_senders( + &self, + to: Address, + ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> { + let targeted_senders = self + .executor + .call_sol_default(to, &IInvariantTest::targetSendersCall {}) + .targetedSenders; + let mut excluded_senders = self + .executor + .call_sol_default(to, &IInvariantTest::excludeSendersCall {}) + .excludedSenders; + // Extend with default excluded addresses - https://github.com/foundry-rs/foundry/issues/4163 + excluded_senders.extend([ + CHEATCODE_ADDRESS, + 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 = self + .executor + .call_sol_default(to, &IInvariantTest::targetContractsCall {}) + .targetedContracts; + let excluded = self + .executor + .call_sol_default(to, &IInvariantTest::excludeContractsCall {}) + .excludedContracts; + + let contracts = self + .setup_contracts + .iter() + .filter(|&(addr, (identifier, _))| { + *addr != to && + *addr != CHEATCODE_ADDRESS && + *addr != HARDHAT_CONSOLE_ADDRESS && + (selected.is_empty() || selected.contains(addr)) && + (excluded.is_empty() || !excluded.contains(addr)) && + self.artifact_filters.matches(identifier) + }) + .map(|(addr, (identifier, abi))| { + (*addr, TargetedContract::new(identifier.clone(), abi.clone())) + }) + .collect(); + let mut contracts = TargetedContracts { inner: contracts }; + + self.target_interfaces(to, &mut contracts)?; + + self.select_selectors(to, &mut contracts)?; + + // There should be at least one contract identified as target for fuzz runs. + if contracts.is_empty() { + eyre::bail!("No contracts to fuzz."); + } + + Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty()))) + } + + /// Extends the contracts and selectors to fuzz with the addresses and ABIs specified in + /// `targetInterfaces() -> (address, string[])[]`. Enables targeting of addresses that are + /// not deployed during `setUp` such as when fuzzing in a forked environment. Also enables + /// targeting of delegate proxies and contracts deployed with `create` or `create2`. + pub fn target_interfaces( + &self, + invariant_address: Address, + targeted_contracts: &mut TargetedContracts, + ) -> Result<()> { + let interfaces = self + .executor + .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {}) + .targetedInterfaces; + + // Since `targetInterfaces` returns a tuple array there is no guarantee + // that the addresses are unique this map is used to merge functions of + // the specified interfaces for the same address. For example: + // `[(addr1, ["IERC20", "IOwnable"])]` and `[(addr1, ["IERC20"]), (addr1, ("IOwnable"))]` + // should be equivalent. + let mut combined = TargetedContracts::new(); + + // Loop through each address and its associated artifact identifiers. + // We're borrowing here to avoid taking full ownership. + for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces { + // Identifiers are specified as an array, so we loop through them. + for identifier in artifacts { + // Try to find the contract by name or identifier in the project's contracts. + if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier) + { + combined + // Check if there's an entry for the given key in the 'combined' map. + .entry(*addr) + // If the entry exists, extends its ABI with the function list. + .and_modify(|entry| { + // Extend the ABI's function list with the new functions. + entry.abi.functions.extend(abi.functions.clone()); + }) + // Otherwise insert it into the map. + .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi)); + } + } + } + + targeted_contracts.extend(combined.inner); + + Ok(()) + } + + /// Selects the functions to fuzz based on the contract method `targetSelectors()` and + /// `targetArtifactSelectors()`. + pub fn select_selectors( + &self, + address: Address, + targeted_contracts: &mut TargetedContracts, + ) -> Result<()> { + for (address, (identifier, _)) in self.setup_contracts.iter() { + if let Some(selectors) = self.artifact_filters.targeted.get(identifier) { + self.add_address_with_functions(*address, selectors, false, targeted_contracts)?; + } + } + + // Collect contract functions marked as target for fuzzing campaign. + let selectors = + self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {}); + for IInvariantTest::FuzzSelector { addr, selectors } in selectors.targetedSelectors { + self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?; + } + + // Collect contract functions excluded from fuzzing campaign. + let selectors = + self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {}); + for IInvariantTest::FuzzSelector { addr, selectors } in selectors.excludedSelectors { + self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?; + } + + Ok(()) + } + + /// Adds the address and fuzzed or excluded functions to `TargetedContracts`. + fn add_address_with_functions( + &self, + address: Address, + selectors: &[Selector], + should_exclude: bool, + targeted_contracts: &mut TargetedContracts, + ) -> eyre::Result<()> { + // Do not add address in target contracts if no function selected. + if selectors.is_empty() { + return Ok(()) + } + + let contract = match targeted_contracts.entry(address) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| { + eyre::eyre!( + "[{}] address does not have an associated contract: {}", + if should_exclude { "excludeSelectors" } else { "targetSelectors" }, + address + ) + })?; + entry.insert(TargetedContract::new(identifier.clone(), abi.clone())) + } + }; + contract.add_selectors(selectors.iter().copied(), should_exclude)?; + Ok(()) + } +} + +/// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA +/// before inserting it into the dictionary. Otherwise, we flood the dictionary with +/// randomly generated addresses. +fn collect_data( + invariant_test: &InvariantTest, + state_changeset: &mut HashMap, + tx: &BasicTxDetails, + call_result: &RawCallResult, + run_depth: u32, +) { + // Verify it has no code. + let mut has_code = false; + if let Some(Some(code)) = + state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref()) + { + has_code = !code.is_empty(); + } + + // We keep the nonce changes to apply later. + let mut sender_changeset = None; + if !has_code { + sender_changeset = state_changeset.remove(&tx.sender); + } + + // Collect values from fuzzed call result and add them to fuzz dictionary. + invariant_test.fuzz_state.collect_values_from_call( + &invariant_test.targeted_contracts, + tx, + &call_result.result, + &call_result.logs, + &*state_changeset, + run_depth, + ); + + // Re-add changes + if let Some(changed) = sender_changeset { + state_changeset.insert(tx.sender, changed); + } +} + +/// Calls the `afterInvariant()` function on a contract. +/// Returns call result and if call succeeded. +/// The state after the call is not persisted. +pub(crate) fn call_after_invariant_function( + executor: &Executor, + to: Address, +) -> std::result::Result<(RawCallResult, bool), EvmError> { + let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR); + let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?; + let success = executor.is_raw_call_mut_success(to, &mut call_result, false); + Ok((call_result, success)) +} + +/// Calls the invariant function and returns call result and if succeeded. +pub(crate) fn call_invariant_function( + executor: &Executor, + address: Address, + calldata: Bytes, +) -> Result<(RawCallResult, bool)> { + let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?; + let success = executor.is_raw_call_mut_success(address, &mut call_result, false); + Ok((call_result, success)) +} diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs new file mode 100644 index 0000000000000..24897f8e5fbaa --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -0,0 +1,157 @@ +use super::{ + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + shrink_sequence, +}; +use crate::executors::Executor; +use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::{map::HashMap, Log}; +use eyre::Result; +use foundry_common::{ContractsByAddress, ContractsByArtifact}; +use foundry_evm_coverage::HitMaps; +use foundry_evm_fuzz::{ + invariant::{BasicTxDetails, InvariantContract}, + BaseCounterExample, +}; +use foundry_evm_traces::{load_contracts, TraceKind, TraceMode, Traces}; +use indicatif::ProgressBar; +use parking_lot::RwLock; +use proptest::test_runner::TestError; +use revm::primitives::U256; +use std::sync::Arc; + +/// Replays a call sequence for collecting logs and traces. +/// Returns counterexample to be used when the call sequence is a failed scenario. +#[allow(clippy::too_many_arguments)] +pub fn replay_run( + invariant_contract: &InvariantContract<'_>, + mut executor: Executor, + known_contracts: &ContractsByArtifact, + mut ided_contracts: ContractsByAddress, + logs: &mut Vec, + traces: &mut Traces, + coverage: &mut Option, + deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>, + inputs: &[BasicTxDetails], + show_solidity: bool, +) -> Result> { + // We want traces for a failed case. + if executor.inspector().tracer.is_none() { + executor.set_tracing(TraceMode::Call); + } + + let mut counterexample_sequence = vec![]; + + // Replay each call from the sequence, collect logs, traces and coverage. + for tx in inputs { + let call_result = executor.transact_raw( + tx.sender, + tx.call_details.target, + tx.call_details.calldata.clone(), + U256::ZERO, + )?; + + logs.extend(call_result.logs); + traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); + HitMaps::merge_opt(coverage, call_result.coverage); + + // Identify newly generated contracts, if they exist. + ided_contracts + .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts)); + + // Create counter example to be used in failed case. + counterexample_sequence.push(BaseCounterExample::from_invariant_call( + tx.sender, + tx.call_details.target, + &tx.call_details.calldata, + &ided_contracts, + call_result.traces, + show_solidity, + )); + } + + // Replay invariant to collect logs and traces. + // We do this only once at the end of the replayed sequence. + // Checking after each call doesn't add valuable info for passing scenario + // (invariant call result is always success) nor for failed scenarios + // (invariant call result is always success until the last call that breaks it). + let (invariant_result, invariant_success) = call_invariant_function( + &executor, + invariant_contract.address, + invariant_contract.invariant_function.abi_encode_input(&[])?.into(), + )?; + traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap())); + logs.extend(invariant_result.logs); + deprecated_cheatcodes.extend( + invariant_result + .cheatcodes + .as_ref() + .map_or_else(Default::default, |cheats| cheats.deprecated.clone()), + ); + + // Collect after invariant logs and traces. + if invariant_contract.call_after_invariant && invariant_success { + let (after_invariant_result, _) = + call_after_invariant_function(&executor, invariant_contract.address)?; + traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap())); + logs.extend(after_invariant_result.logs); + } + + Ok(counterexample_sequence) +} + +/// Replays the error case, shrinks the failing sequence and collects all necessary traces. +#[allow(clippy::too_many_arguments)] +pub fn replay_error( + failed_case: &FailedInvariantCaseData, + invariant_contract: &InvariantContract<'_>, + mut executor: Executor, + known_contracts: &ContractsByArtifact, + ided_contracts: ContractsByAddress, + logs: &mut Vec, + traces: &mut Traces, + coverage: &mut Option, + deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>, + progress: Option<&ProgressBar>, + show_solidity: bool, +) -> Result> { + match failed_case.test_error { + // Don't use at the moment. + TestError::Abort(_) => Ok(vec![]), + TestError::Fail(_, ref calls) => { + // Shrink sequence of failed calls. + let calls = shrink_sequence( + failed_case, + calls, + &executor, + invariant_contract.call_after_invariant, + progress, + )?; + + set_up_inner_replay(&mut executor, &failed_case.inner_sequence); + + // Replay calls to get the counterexample and to collect logs, traces and coverage. + replay_run( + invariant_contract, + executor, + known_contracts, + ided_contracts, + logs, + traces, + coverage, + deprecated_cheatcodes, + &calls, + show_solidity, + ) + } + } +} + +/// Sets up the calls generated by the internal fuzzer, if they exist. +fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { + if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer { + if let Some(call_generator) = &mut fuzzer.call_generator { + call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); + call_generator.set_replay(true); + } + } +} diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs new file mode 100644 index 0000000000000..8920a1209342a --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -0,0 +1,184 @@ +use super::{ + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + InvariantFailures, InvariantFuzzError, InvariantMetrics, 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, +}; +use revm_inspectors::tracing::CallTraceArena; +use std::{borrow::Cow, collections::HashMap}; + +/// The outcome of an invariant fuzz test +#[derive(Debug)] +pub struct InvariantFuzzTestResult { + pub error: Option, + /// Every successful fuzz test case + pub cases: Vec, + /// Number of reverted fuzz calls + pub reverts: usize, + /// The entire inputs of the last run of the invariant campaign, used for + /// replaying the run for collecting traces. + 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, + /// Fuzzed selectors metrics collected during the invariant test runs. + pub metrics: HashMap, +} + +/// Enriched results of an invariant run check. +/// +/// Contains the success condition and call results of the last run +pub(crate) struct RichInvariantResults { + pub(crate) can_continue: bool, + pub(crate) call_result: Option, +} + +impl RichInvariantResults { + fn new(can_continue: bool, call_result: Option) -> Self { + Self { can_continue, call_result } + } +} + +/// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the +/// external `invariant_failures.failed_invariant` map and returns a generic error. +/// Either returns the call result if successful, or nothing if there was an error. +pub(crate) fn assert_invariants( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, + executor: &Executor, + calldata: &[BasicTxDetails], + invariant_failures: &mut InvariantFailures, +) -> Result> { + let mut inner_sequence = vec![]; + + if let Some(fuzzer) = &executor.inspector().fuzzer { + if let Some(call_generator) = &fuzzer.call_generator { + inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); + } + } + + let (call_result, success) = call_invariant_function( + executor, + invariant_contract.address, + invariant_contract.invariant_function.abi_encode_input(&[])?.into(), + )?; + if !success { + // We only care about invariants which we haven't broken yet. + if invariant_failures.error.is_none() { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + targeted_contracts, + calldata, + call_result, + &inner_sequence, + ); + invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data)); + return Ok(None); + } + } + + Ok(Some(call_result)) +} + +/// 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, + state_changeset: &StateChangeset, +) -> Result { + let mut call_results = None; + + let handlers_succeeded = || { + 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 { + invariant_run.run_traces.push(traces); + } + + call_results = assert_invariants( + invariant_contract, + invariant_config, + &invariant_test.targeted_contracts, + &invariant_run.executor, + &invariant_run.inputs, + &mut invariant_test.execution_data.borrow_mut().failures, + )?; + if call_results.is_none() { + return Ok(RichInvariantResults::new(false, None)); + } + } else { + // Increase the amount of reverts. + let mut invariant_data = invariant_test.execution_data.borrow_mut(); + invariant_data.failures.reverts += 1; + // If fail on revert is set, we must return immediately. + if invariant_config.fail_on_revert { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + &invariant_test.targeted_contracts, + &invariant_run.inputs, + call_result, + &[], + ); + 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)) +} + +/// Given the executor state, asserts conditions within `afterInvariant` function. +/// If call fails then the invariant test is considered failed. +pub(crate) fn assert_after_invariant( + invariant_contract: &InvariantContract<'_>, + invariant_test: &InvariantTest, + invariant_run: &InvariantTestRun, + invariant_config: &InvariantConfig, +) -> Result { + let (call_result, success) = + 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, + &invariant_test.targeted_contracts, + &invariant_run.inputs, + call_result, + &[], + ); + invariant_test.set_error(InvariantFuzzError::BrokenInvariant(case_data)); + } + Ok(success) +} diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs new file mode 100644 index 0000000000000..c468c58eefa63 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -0,0 +1,183 @@ +use crate::executors::{ + invariant::{ + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + }, + Executor, +}; +use alloy_primitives::{Address, Bytes, U256}; +use foundry_evm_core::constants::MAGIC_ASSUME; +use foundry_evm_fuzz::invariant::BasicTxDetails; +use indicatif::ProgressBar; +use proptest::bits::{BitSetLike, VarBitSet}; +use std::cmp::min; + +#[derive(Clone, Copy, Debug)] +struct Shrink { + call_index: usize, +} + +/// Shrinker for a call sequence failure. +/// Iterates sequence call sequence top down and removes calls one by one. +/// If the failure is still reproducible with removed call then moves to the next one. +/// If the failure is not reproducible then restore removed call and moves to next one. +#[derive(Debug)] +struct CallSequenceShrinker { + /// Length of call sequence to be shrunk. + call_sequence_len: usize, + /// Call ids contained in current shrunk sequence. + included_calls: VarBitSet, + /// Current shrunk call id. + shrink: Shrink, + /// Previous shrunk call id. + prev_shrink: Option, +} + +impl CallSequenceShrinker { + fn new(call_sequence_len: usize) -> Self { + Self { + call_sequence_len, + included_calls: VarBitSet::saturated(call_sequence_len), + shrink: Shrink { call_index: 0 }, + prev_shrink: None, + } + } + + /// Return candidate shrink sequence to be tested, by removing ids from original sequence. + fn current(&self) -> impl Iterator + '_ { + (0..self.call_sequence_len).filter(|&call_id| self.included_calls.test(call_id)) + } + + /// Removes next call from sequence. + fn simplify(&mut self) -> bool { + if self.shrink.call_index >= self.call_sequence_len { + // We reached the end of call sequence, nothing left to simplify. + false + } else { + // Remove current call. + self.included_calls.clear(self.shrink.call_index); + // Record current call as previous call. + self.prev_shrink = Some(self.shrink); + // Remove next call index + self.shrink = Shrink { call_index: self.shrink.call_index + 1 }; + true + } + } + + /// Reverts removed call from sequence and tries to simplify next call. + fn complicate(&mut self) -> bool { + match self.prev_shrink { + Some(shrink) => { + // Undo the last call removed. + self.included_calls.set(shrink.call_index); + self.prev_shrink = None; + // Try to simplify next call. + self.simplify() + } + None => false, + } + } +} + +/// Shrinks the failure case to its smallest sequence of calls. +/// +/// Maximal shrinkage is guaranteed if the shrink_run_limit is not set to a value lower than the +/// length of failed call sequence. +/// +/// The shrunk call sequence always respect the order failure is reproduced as it is tested +/// top-down. +pub(crate) fn shrink_sequence( + failed_case: &FailedInvariantCaseData, + calls: &[BasicTxDetails], + executor: &Executor, + call_after_invariant: bool, + progress: Option<&ProgressBar>, +) -> eyre::Result> { + trace!(target: "forge::test", "Shrinking sequence of {} calls.", calls.len()); + + // Reset run count and display shrinking message. + if let Some(progress) = progress { + progress.set_length(min(calls.len(), failed_case.shrink_run_limit as usize) as u64); + progress.reset(); + progress.set_message(" Shrink"); + } + + // Special case test: the invariant is *unsatisfiable* - it took 0 calls to + // break the invariant -- consider emitting a warning. + let (_, success) = + call_invariant_function(executor, failed_case.addr, failed_case.calldata.clone())?; + if !success { + return Ok(vec![]); + } + + let mut shrinker = CallSequenceShrinker::new(calls.len()); + for _ in 0..failed_case.shrink_run_limit { + // Check candidate sequence result. + match check_sequence( + executor.clone(), + calls, + shrinker.current().collect(), + failed_case.addr, + failed_case.calldata.clone(), + failed_case.fail_on_revert, + call_after_invariant, + ) { + // If candidate sequence still fails then shrink more if possible. + Ok((false, _)) if !shrinker.simplify() => break, + // If candidate sequence pass then restore last removed call and shrink other + // calls if possible. + Ok((true, _)) if !shrinker.complicate() => break, + _ => {} + } + + if let Some(progress) = progress { + progress.inc(1); + } + } + + Ok(shrinker.current().map(|idx| &calls[idx]).cloned().collect()) +} + +/// Checks if the given call sequence breaks the invariant. +/// +/// Used in shrinking phase for checking candidate sequences and in replay failures phase to test +/// persisted failures. +/// Returns the result of invariant check (and afterInvariant call if needed) and if sequence was +/// entirely applied. +pub fn check_sequence( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, + fail_on_revert: bool, + call_after_invariant: bool, +) -> eyre::Result<(bool, bool)> { + // Apply the call sequence. + for call_index in sequence { + let tx = &calls[call_index]; + let call_result = executor.transact_raw( + tx.sender, + tx.call_details.target, + tx.call_details.calldata.clone(), + U256::ZERO, + )?; + // Ignore calls reverted with `MAGIC_ASSUME`. This is needed to handle failed scenarios that + // are replayed with a modified version of test driver (that use new `vm.assume` + // cheatcodes). + if call_result.reverted && fail_on_revert && call_result.result.as_ref() != MAGIC_ASSUME { + // Candidate sequence fails test. + // We don't have to apply remaining calls to check sequence. + return Ok((false, false)); + } + } + + // Check the invariant for call sequence. + let (_, mut success) = call_invariant_function(&executor, test_address, calldata)?; + // Check after invariant result if invariant is success and `afterInvariant` function is + // declared. + if success && call_after_invariant { + (_, success) = call_after_invariant_function(&executor, test_address)?; + } + + Ok((success, true)) +} diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs new file mode 100644 index 0000000000000..5c26a6d6cdc5d --- /dev/null +++ b/crates/evm/evm/src/executors/mod.rs @@ -0,0 +1,996 @@ +//! EVM executor abstractions, which can execute calls. +//! +//! Used for running tests, scripts, and interacting with the inner backend which holds the state. + +// TODO: The individual executors in this module should be moved into the respective crates, and the +// `Executor` struct should be accessed using a trait defined in `foundry-evm-core` instead of +// the concrete `Executor` type. + +use crate::inspectors::{ + cheatcodes::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStack, +}; +use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; +use alloy_json_abi::Function; +use alloy_primitives::{ + map::{AddressHashMap, HashMap}, + Address, Bytes, Log, U256, +}; +use alloy_sol_types::{sol, SolCall}; +use foundry_evm_core::{ + backend::{Backend, BackendError, BackendResult, CowBackend, DatabaseExt, GLOBAL_FAIL_SLOT}, + constants::{ + CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, + DEFAULT_CREATE2_DEPLOYER_CODE, DEFAULT_CREATE2_DEPLOYER_DEPLOYER, + }, + decode::{RevertDecoder, SkipReason}, + utils::StateChangeset, + InspectorExt, +}; +use foundry_evm_coverage::HitMaps; +use foundry_evm_traces::{SparsedTraceArena, TraceMode}; +use revm::{ + db::{DatabaseCommit, DatabaseRef}, + interpreter::{return_ok, InstructionResult}, + primitives::{ + AuthorizationList, BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, + ResultAndState, SignedAuthorization, SpecId, TxEnv, TxKind, + }, +}; +use std::{ + borrow::Cow, + time::{Duration, Instant}, +}; + +mod builder; +pub use builder::ExecutorBuilder; + +pub mod fuzz; +pub use fuzz::FuzzedExecutor; + +pub mod invariant; +pub use invariant::InvariantExecutor; + +mod trace; +pub use trace::TracingExecutor; + +sol! { + interface ITest { + function setUp() external; + function failed() external view returns (bool failed); + + #[derive(Default)] + function beforeTestSetup(bytes4 testSelector) public view returns (bytes[] memory beforeTestCalldata); + } +} + +/// EVM executor. +/// +/// The executor can be configured with various `revm::Inspector`s, like `Cheatcodes`. +/// +/// There are multiple ways of interacting the EVM: +/// - `call`: executes a transaction, but does not persist any state changes; similar to `eth_call`, +/// where the EVM state is unchanged after the call. +/// - `transact`: executes a transaction and persists the state changes +/// - `deploy`: a special case of `transact`, specialized for persisting the state of a contract +/// deployment +/// - `setup`: a special case of `transact`, used to set up the environment for a test +#[derive(Clone, Debug)] +pub struct Executor { + /// The underlying `revm::Database` that contains the EVM storage. + // Note: We do not store an EVM here, since we are really + // only interested in the database. REVM's `EVM` is a thin + // wrapper around spawning a new EVM on every call anyway, + // so the performance difference should be negligible. + backend: Backend, + /// The EVM environment. + env: EnvWithHandlerCfg, + /// The Revm inspector stack. + inspector: InspectorStack, + /// The gas limit for calls and deployments. + gas_limit: u64, + /// Whether `failed()` should be called on the test contract to determine if the test failed. + legacy_assertions: bool, +} + +impl Executor { + /// Creates a new `ExecutorBuilder`. + #[inline] + pub fn builder() -> ExecutorBuilder { + ExecutorBuilder::new() + } + + /// Creates a new `Executor` with the given arguments. + #[inline] + pub fn new( + mut backend: Backend, + env: EnvWithHandlerCfg, + inspector: InspectorStack, + gas_limit: u64, + legacy_assertions: bool, + ) -> Self { + // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks + // do not fail. + backend.insert_account_info( + CHEATCODE_ADDRESS, + revm::primitives::AccountInfo { + code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))), + // Also set the code hash manually so that it's not computed later. + // The code hash value does not matter, as long as it's not zero or `KECCAK_EMPTY`. + code_hash: CHEATCODE_CONTRACT_HASH, + ..Default::default() + }, + ); + + Self { backend, env, inspector, gas_limit, legacy_assertions } + } + + 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.spec_id() + } + + /// Sets the EVM spec ID. + pub fn set_spec_id(&mut self, spec_id: SpecId) { + self.env.handler_cfg.spec_id = spec_id; + } + + /// Returns the gas limit for calls and deployments. + /// + /// This is different from the gas limit imposed by the passed in environment, as those limits + /// are used by the EVM for certain opcodes like `gaslimit`. + pub fn gas_limit(&self) -> u64 { + self.gas_limit + } + + /// Sets the gas limit for calls and deployments. + pub fn set_gas_limit(&mut self, gas_limit: u64) { + self.gas_limit = gas_limit; + } + + /// Returns whether `failed()` should be called on the test contract to determine if the test + /// failed. + pub fn legacy_assertions(&self) -> bool { + self.legacy_assertions + } + + /// Sets whether `failed()` should be called on the test contract to determine if the test + /// failed. + pub fn set_legacy_assertions(&mut self, legacy_assertions: bool) { + self.legacy_assertions = legacy_assertions; + } + + /// 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() + .basic_ref(DEFAULT_CREATE2_DEPLOYER)? + .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; + + // If the deployer is not currently deployed, deploy the default one. + if create2_deployer_account.code.is_none_or(|code| code.is_empty()) { + 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"); + + self.set_balance(creator, initial_balance)?; + } + Ok(()) + } + + /// Set the balance of an account. + pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> { + trace!(?address, ?amount, "setting account balance"); + let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); + account.balance = amount; + self.backend_mut().insert_account_info(address, account); + Ok(()) + } + + /// Gets the balance of an account + 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) -> BackendResult<()> { + let mut account = self.backend().basic_ref(address)?.unwrap_or_default(); + account.nonce = nonce; + self.backend_mut().insert_account_info(address, account); + Ok(()) + } + + /// Returns the nonce of an account. + 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) -> BackendResult { + Ok(self.backend().basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true)) + } + + #[inline] + 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_mut().print(trace_printer); + self + } + + #[inline] + pub fn create2_deployer(&self) -> Address { + self.inspector().create2_deployer() + } + + /// Deploys a contract and commits the new state to the underlying database. + /// + /// Executes a CREATE transaction with the contract `code` and persistent database state + /// modifications. + pub fn deploy( + &mut self, + from: Address, + code: Bytes, + value: U256, + rd: Option<&RevertDecoder>, + ) -> Result { + let env = self.build_test_env(from, TxKind::Create, code, value); + self.deploy_with_env(env, rd) + } + + /// Deploys a contract using the given `env` and commits the new state to the underlying + /// database. + /// + /// # Panics + /// + /// 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, TxKind::Create), + "Expected create transaction, got {:?}", + env.tx.transact_to + ); + trace!(sender=%env.tx.caller, "deploying contract"); + + let mut result = self.transact_with_env(env)?; + result = result.into_result(rd)?; + let Some(Output::Create(_, Some(address))) = result.out else { + panic!("Deployment succeeded, but no address was returned: {result:#?}"); + }; + + // 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_mut().add_persistent_account(address); + + debug!(%address, "deployed contract"); + + Ok(DeployResult { raw: result, address }) + } + + /// Calls the `setUp()` function on a contract. + /// + /// This will commit any state changes to the underlying database. + /// + /// 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
, + to: Address, + rd: Option<&RevertDecoder>, + ) -> Result { + trace!(?from, ?to, "setting up contract"); + + let from = from.unwrap_or(CALLER); + 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_mut().block = res.env.block.clone(); + // and also the chainid, which can be set manually + self.env_mut().cfg.chain_id = res.env.cfg.chain_id; + + 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) + } + + /// Performs a call to an account on the current state of the VM. + pub fn call( + &self, + from: Address, + to: Address, + func: &Function, + args: &[DynSolValue], + value: U256, + rd: Option<&RevertDecoder>, + ) -> Result { + let calldata = Bytes::from(func.abi_encode_input(args)?); + let result = self.call_raw(from, to, calldata, value)?; + result.into_decoded_result(func, rd) + } + + /// Performs a call to an account on the current state of the VM. + pub fn call_sol( + &self, + from: Address, + to: Address, + args: &C, + value: U256, + rd: Option<&RevertDecoder>, + ) -> Result, EvmError> { + let calldata = Bytes::from(args.abi_encode()); + let mut raw = self.call_raw(from, to, calldata, value)?; + raw = raw.into_result(rd)?; + Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result, false)?, raw }) + } + + /// Performs a call to an account on the current state of the VM. + pub fn transact( + &mut self, + from: Address, + to: Address, + func: &Function, + args: &[DynSolValue], + value: U256, + rd: Option<&RevertDecoder>, + ) -> Result { + let calldata = Bytes::from(func.abi_encode_input(args)?); + let result = self.transact_raw(from, to, calldata, value)?; + result.into_decoded_result(func, rd) + } + + /// Performs a raw call to an account on the current state of the VM. + pub fn call_raw( + &self, + from: Address, + to: Address, + calldata: Bytes, + value: U256, + ) -> eyre::Result { + let env = self.build_test_env(from, TxKind::Call(to), calldata, value); + self.call_with_env(env) + } + + /// Performs a raw call to an account on the current state of the VM with an EIP-7702 + /// authorization list. + pub fn call_raw_with_authorization( + &mut self, + from: Address, + to: Address, + calldata: Bytes, + value: U256, + authorization_list: Vec, + ) -> eyre::Result { + let mut env = self.build_test_env(from, to.into(), calldata, value); + env.tx.authorization_list = Some(AuthorizationList::Signed(authorization_list)); + self.call_with_env(env) + } + + /// Performs a raw call to an account on the current state of the VM. + pub fn transact_raw( + &mut self, + from: Address, + to: Address, + calldata: Bytes, + value: U256, + ) -> eyre::Result { + let env = self.build_test_env(from, TxKind::Call(to), calldata, value); + self.transact_with_env(env) + } + + /// 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_borrowed(self.backend()); + let result = backend.inspect(&mut env, &mut inspector)?; + convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure()) + } + + /// 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 = self.backend_mut(); + let result = backend.inspect(&mut env, &mut inspector)?; + let mut result = + convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure())?; + self.commit(&mut result); + Ok(result) + } + + /// Commit the changeset to the database and adjust `self.inspector_config` values according to + /// 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. + self.backend_mut().commit(result.state_changeset.clone()); + + // Persist cheatcode state. + self.inspector_mut().cheatcodes = result.cheatcodes.take(); + if let Some(cheats) = self.inspector_mut().cheatcodes.as_mut() { + // Clear broadcastable transactions + cheats.broadcastable_transactions.clear(); + cheats.ignored_traces.ignored.clear(); + + // if tracing was paused but never unpaused, we should begin next frame with tracing + // still paused + if let Some(last_pause_call) = cheats.ignored_traces.last_pause_call.as_mut() { + *last_pause_call = (0, 0); + } + } + + // Persist the changed environment. + self.inspector_mut().set_env(&result.env); + } + + /// 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()`. + pub fn is_raw_call_mut_success( + &self, + address: Address, + call_result: &mut RawCallResult, + should_fail: bool, + ) -> bool { + self.is_raw_call_success( + address, + Cow::Owned(std::mem::take(&mut call_result.state_changeset)), + call_result, + should_fail, + ) + } + + /// Returns `true` if a test can be considered successful. + /// + /// 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, + state_changeset: Cow<'_, StateChangeset>, + call_result: &RawCallResult, + should_fail: bool, + ) -> bool { + if call_result.has_state_snapshot_failure { + // a failure occurred in a reverted snapshot, which is considered a failed test + return should_fail; + } + self.is_success(address, call_result.reverted, state_changeset, should_fail) + } + + /// Returns `true` if a test can be considered successful. + /// + /// If the call succeeded, we also have to check the global and local failure flags. + /// + /// 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. + /// + /// 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). + /// + /// 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, + reverted: bool, + state_changeset: Cow<'_, StateChangeset>, + should_fail: bool, + ) -> bool { + let success = self.is_success_raw(address, reverted, state_changeset); + 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 { + // The call reverted. + if reverted { + return false; + } + + // A failure occurred in a reverted snapshot, which is considered a failed test. + if self.backend().has_state_snapshot_failure() { + return false; + } + + // 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(); + + // 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 }; + backend.insert_account_info(address, acc.unwrap_or_default()); + } + + // If this test failed any asserts, then this changeset will contain changes + // `false -> true` for the contract's `failed` variable and the `globalFailure` flag + // in the state of the cheatcode address, + // which are both read when we call `"failed()(bool)"` in the next step. + backend.commit(state_changeset.into_owned()); + + // Check if a DSTest assertion failed + 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 } }) => { + trace!(failed, "DSTest::failed()"); + !failed + } + Err(err) => { + trace!(%err, "failed to call DSTest::failed()"); + true + } + } + } + } + + /// Creates the environment to use when executing a transaction in a test context + /// + /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by + /// the cheatcode state in between calls. + fn build_test_env( + &self, + caller: Address, + transact_to: TxKind, + data: Bytes, + value: U256, + ) -> EnvWithHandlerCfg { + let env = Env { + 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: U256::from(self.gas_limit), + ..self.env().block.clone() + }, + tx: TxEnv { + caller, + transact_to, + data, + value, + // As above, we set the gas price to 0. + gas_price: U256::ZERO, + gas_priority_fee: None, + gas_limit: self.gas_limit, + ..self.env().tx.clone() + }, + }; + + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.spec_id()) + } + + pub fn call_sol_default(&self, to: Address, args: &C) -> C::Return + where + C::Return: Default, + { + self.call_sol(CALLER, to, args, U256::ZERO, None) + .map(|c| c.decoded_result) + .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE)) + .unwrap_or_default() + } +} + +/// Represents the context after an execution error occurred. +#[derive(Debug, thiserror::Error)] +#[error("execution reverted: {reason} (gas: {})", raw.gas_used)] +pub struct ExecutionErr { + /// The raw result of the call. + pub raw: RawCallResult, + /// The revert reason. + pub reason: String, +} + +impl std::ops::Deref for ExecutionErr { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for ExecutionErr { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } +} + +#[derive(Debug, thiserror::Error)] +pub enum EvmError { + /// Error which occurred during execution of a transaction. + #[error(transparent)] + Execution(#[from] Box), + /// Error which occurred during ABI encoding/decoding. + #[error(transparent)] + Abi(#[from] alloy_dyn_abi::Error), + /// Error caused which occurred due to calling the `skip` cheatcode. + #[error("{0}")] + Skip(SkipReason), + /// Any other error. + #[error("{0}")] + Eyre( + #[from] + #[source] + eyre::Report, + ), +} + +impl From for EvmError { + fn from(err: ExecutionErr) -> Self { + Self::Execution(Box::new(err)) + } +} + +impl From for EvmError { + fn from(err: alloy_sol_types::Error) -> Self { + Self::Abi(err.into()) + } +} + +/// The result of a deployment. +#[derive(Debug)] +pub struct DeployResult { + /// The raw result of the deployment. + pub raw: RawCallResult, + /// The address of the deployed contract + pub address: Address, +} + +impl std::ops::Deref for DeployResult { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for DeployResult { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } +} + +impl From for RawCallResult { + fn from(d: DeployResult) -> Self { + d.raw + } +} + +/// The result of a raw call. +#[derive(Debug)] +pub struct RawCallResult { + /// The status of the call + pub exit_reason: InstructionResult, + /// Whether the call reverted or not + pub reverted: bool, + /// Whether the call includes a snapshot failure + /// + /// This is tracked separately from revert because a snapshot failure can occur without a + /// revert, since assert failures are stored in a global variable (ds-test legacy) + pub has_state_snapshot_failure: bool, + /// The raw result of the call. + pub result: Bytes, + /// The gas used for the call + pub gas_used: u64, + /// Refunded gas + pub gas_refunded: u64, + /// The initial gas stipend for the transaction + pub stipend: u64, + /// The logs emitted during the call + pub logs: Vec, + /// The labels assigned to addresses during the call + pub labels: AddressHashMap, + /// The traces of the call + pub traces: Option, + /// The coverage info collected during the call + pub coverage: Option, + /// Scripted transactions generated from this call + pub transactions: Option, + /// The changeset of the state. + pub state_changeset: StateChangeset, + /// The `revm::Env` after the call + pub env: EnvWithHandlerCfg, + /// The cheatcode states after execution + pub cheatcodes: Option, + /// The raw output of the execution + pub out: Option, + /// The chisel state + pub chisel_state: Option<(Vec, Vec, InstructionResult)>, +} + +impl Default for RawCallResult { + fn default() -> Self { + Self { + exit_reason: InstructionResult::Continue, + reverted: false, + has_state_snapshot_failure: false, + result: Bytes::new(), + gas_used: 0, + gas_refunded: 0, + stipend: 0, + logs: Vec::new(), + labels: HashMap::default(), + traces: None, + coverage: None, + transactions: None, + state_changeset: HashMap::default(), + env: EnvWithHandlerCfg::new_with_spec_id(Box::default(), SpecId::LATEST), + cheatcodes: Default::default(), + out: None, + chisel_state: None, + } + } +} + +impl RawCallResult { + /// Unpacks an EVM result. + pub fn from_evm_result(r: Result) -> eyre::Result<(Self, Option)> { + match r { + Ok(r) => Ok((r, None)), + Err(EvmError::Execution(e)) => Ok((e.raw, Some(e.reason))), + Err(e) => Err(e.into()), + } + } + + /// Unpacks an execution result. + pub fn from_execution_result(r: Result) -> (Self, Option) { + match r { + Ok(r) => (r, None), + Err(e) => (e.raw, Some(e.reason)), + } + } + + /// Converts the result of the call into an `EvmError`. + pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError { + if let Some(reason) = SkipReason::decode(&self.result) { + return EvmError::Skip(reason); + } + let reason = rd.unwrap_or_default().decode(&self.result, Some(self.exit_reason)); + EvmError::Execution(Box::new(self.into_execution_error(reason))) + } + + /// Converts the result of the call into an `ExecutionErr`. + pub fn into_execution_error(self, reason: String) -> ExecutionErr { + ExecutionErr { raw: self, reason } + } + + /// Returns an `EvmError` if the call failed, otherwise returns `self`. + pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result { + if self.exit_reason.is_ok() { + Ok(self) + } else { + Err(self.into_evm_error(rd)) + } + } + + /// Decodes the result of the call with the given function. + pub fn into_decoded_result( + mut self, + func: &Function, + rd: Option<&RevertDecoder>, + ) -> Result { + self = self.into_result(rd)?; + let mut result = func.abi_decode_output(&self.result, false)?; + let decoded_result = if result.len() == 1 { + result.pop().unwrap() + } else { + // combine results into a tuple + DynSolValue::Tuple(result) + }; + Ok(CallResult { raw: self, decoded_result }) + } + + /// Returns the transactions generated from this call. + pub fn transactions(&self) -> Option<&BroadcastableTransactions> { + self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions) + } +} + +/// The result of a call. +pub struct CallResult { + /// The raw result of the call. + pub raw: RawCallResult, + /// The decoded result of the call. + pub decoded_result: T, +} + +impl std::ops::Deref for CallResult { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for CallResult { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } +} + +/// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` +fn convert_executed_result( + env: EnvWithHandlerCfg, + inspector: InspectorStack, + ResultAndState { result, state: state_changeset }: ResultAndState, + has_state_snapshot_failure: bool, +) -> eyre::Result { + let (exit_reason, gas_refunded, gas_used, out, exec_logs) = match result { + ExecutionResult::Success { reason, gas_used, gas_refunded, output, logs, .. } => { + (reason.into(), gas_refunded, gas_used, Some(output), logs) + } + ExecutionResult::Revert { gas_used, output } => { + // Need to fetch the unused gas + (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output)), vec![]) + } + ExecutionResult::Halt { reason, gas_used } => { + (reason.into(), 0_u64, gas_used, None, vec![]) + } + }; + let gas = revm::interpreter::gas::calculate_initial_tx_gas( + env.spec_id(), + &env.tx.data, + env.tx.transact_to.is_create(), + &env.tx.access_list, + 0, + ); + + let result = match &out { + Some(Output::Call(data)) => data.clone(), + _ => Bytes::new(), + }; + + let InspectorData { mut logs, labels, traces, coverage, cheatcodes, chisel_state } = + inspector.collect(); + + if logs.is_empty() { + logs = exec_logs; + } + + let transactions = cheatcodes + .as_ref() + .map(|c| c.broadcastable_transactions.clone()) + .filter(|txs| !txs.is_empty()); + + Ok(RawCallResult { + exit_reason, + reverted: !matches!(exit_reason, return_ok!()), + has_state_snapshot_failure, + result, + gas_used, + gas_refunded, + stipend: gas.initial_gas, + logs, + labels, + traces, + coverage, + transactions, + state_changeset, + env, + cheatcodes, + out, + chisel_state, + }) +} + +/// Timer for a fuzz test. +pub struct FuzzTestTimer { + /// Inner fuzz test timer - (test start time, test duration). + inner: Option<(Instant, Duration)>, +} + +impl FuzzTestTimer { + pub fn new(timeout: Option) -> Self { + Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) } + } + + /// Whether the current fuzz test timed out and should be stopped. + pub fn is_timed_out(&self) -> bool { + self.inner.is_some_and(|(start, duration)| start.elapsed() > duration) + } +} diff --git a/crates/evm/evm/src/executors/trace.rs b/crates/evm/evm/src/executors/trace.rs new file mode 100644 index 0000000000000..b55517a672d57 --- /dev/null +++ b/crates/evm/evm/src/executors/trace.rs @@ -0,0 +1,70 @@ +use crate::executors::{Executor, ExecutorBuilder}; +use alloy_primitives::Address; +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::TraceMode; +use revm::primitives::{Env, SpecId}; +use std::ops::{Deref, DerefMut}; + +/// A default executor with tracing enabled +pub struct TracingExecutor { + executor: Executor, +} + +impl TracingExecutor { + pub fn new( + env: revm::primitives::Env, + fork: Option, + version: Option, + trace_mode: TraceMode, + odyssey: bool, + create2_deployer: Address, + ) -> Self { + let db = Backend::spawn(fork); + Self { + // configures a bare version of the evm executor: no cheatcode inspector is enabled, + // tracing will be enabled only for the targeted transaction + executor: ExecutorBuilder::new() + .inspectors(|stack| { + stack.trace_mode(trace_mode).odyssey(odyssey).create2_deployer(create2_deployer) + }) + .spec_id(evm_spec_id(version.unwrap_or_default(), odyssey)) + .build(env, db), + } + } + + /// Returns the spec id of the executor + pub fn spec_id(&self) -> SpecId { + self.executor.spec_id() + } + + /// uses the fork block number from the config + pub async fn get_fork_material( + config: &Config, + mut evm_opts: EvmOpts, + ) -> eyre::Result<(Env, Option, Option, bool)> { + evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); + evm_opts.fork_block_number = config.fork_block_number; + + let env = evm_opts.evm_env().await?; + + let fork = evm_opts.get_fork(config, env.clone()); + + Ok((env, fork, evm_opts.get_remote_chain_id().await, evm_opts.odyssey)) + } +} + +impl Deref for TracingExecutor { + type Target = Executor; + + fn deref(&self) -> &Self::Target { + &self.executor + } +} + +impl DerefMut for TracingExecutor { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.executor + } +} diff --git a/crates/evm/evm/src/inspectors/chisel_state.rs b/crates/evm/evm/src/inspectors/chisel_state.rs new file mode 100644 index 0000000000000..023389ed4dfff --- /dev/null +++ b/crates/evm/evm/src/inspectors/chisel_state.rs @@ -0,0 +1,37 @@ +use alloy_primitives::U256; +use revm::{ + interpreter::{InstructionResult, Interpreter}, + Database, EvmContext, Inspector, +}; + +/// An inspector for Chisel +#[derive(Clone, Debug, Default)] +pub struct ChiselState { + /// The PC of the final instruction + pub final_pc: usize, + /// The final state of the REPL contract call + pub state: Option<(Vec, Vec, InstructionResult)>, +} + +impl ChiselState { + /// Create a new Chisel state inspector. + #[inline] + pub fn new(final_pc: usize) -> Self { + Self { final_pc, state: None } + } +} + +impl Inspector for ChiselState { + #[cold] + fn step_end(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + // If we are at the final pc of the REPL contract execution, set the state. + // Subtraction can't overflow because `pc` is always at least 1 in `step_end`. + if self.final_pc == interp.program_counter() - 1 { + self.state = Some(( + interp.stack.data().clone(), + interp.shared_memory.context_memory().to_vec(), + interp.instruction_result, + )) + } + } +} diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs new file mode 100644 index 0000000000000..570fb47dbe238 --- /dev/null +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -0,0 +1,79 @@ +use alloy_primitives::Log; +use alloy_sol_types::{SolEvent, SolInterface, SolValue}; +use foundry_common::{fmt::ConsoleFmt, ErrorExt}; +use foundry_evm_core::{abi::console, constants::HARDHAT_CONSOLE_ADDRESS, InspectorExt}; +use revm::{ + interpreter::{ + CallInputs, CallOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, + }, + Database, EvmContext, Inspector, +}; + +/// An inspector that collects logs during execution. +/// +/// The inspector collects logs from the `LOG` opcodes as well as Hardhat-style `console.sol` logs. +#[derive(Clone, Debug, Default)] +pub struct LogCollector { + /// The collected logs. Includes both `LOG` opcodes and Hardhat-style `console.sol` logs. + pub logs: Vec, +} + +impl LogCollector { + #[cold] + fn do_hardhat_log(&mut self, inputs: &CallInputs) -> Option { + if let Err(err) = self.hardhat_log(&inputs.input) { + let result = InstructionResult::Revert; + let output = err.abi_encode_revert(); + return Some(CallOutcome { + result: InterpreterResult { result, output, gas: Gas::new(inputs.gas_limit) }, + memory_offset: inputs.return_memory_offset.clone(), + }) + } + None + } + + fn hardhat_log(&mut self, data: &[u8]) -> alloy_sol_types::Result<()> { + let decoded = console::hh::ConsoleCalls::abi_decode(data, false)?; + self.logs.push(hh_to_ds(&decoded)); + Ok(()) + } +} + +impl Inspector for LogCollector { + fn log(&mut self, _interp: &mut Interpreter, _context: &mut EvmContext, log: &Log) { + self.logs.push(log.clone()); + } + + fn call( + &mut self, + _context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + if inputs.target_address == HARDHAT_CONSOLE_ADDRESS { + return self.do_hardhat_log(inputs); + } + None + } +} + +impl InspectorExt for LogCollector { + fn console_log(&mut self, msg: &str) { + self.logs.push(new_console_log(msg)); + } +} + +/// Converts a Hardhat `console.log` call to a DSTest `log(string)` event. +fn hh_to_ds(call: &console::hh::ConsoleCalls) -> Log { + // Convert the parameters of the call to their string representation using `ConsoleFmt`. + let msg = call.fmt(Default::default()); + new_console_log(&msg) +} + +/// Creates a `console.log(string)` event. +fn new_console_log(msg: &str) -> Log { + Log::new_unchecked( + HARDHAT_CONSOLE_ADDRESS, + vec![console::ds::log::SIGNATURE_HASH], + msg.abi_encode().into(), + ) +} diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs new file mode 100644 index 0000000000000..41008397a1cbc --- /dev/null +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -0,0 +1,17 @@ +//! EVM inspectors. + +pub use foundry_cheatcodes::{self as cheatcodes, Cheatcodes, CheatsConfig}; +pub use foundry_evm_coverage::CoverageCollector; +pub use foundry_evm_fuzz::Fuzzer; +pub use foundry_evm_traces::{StackSnapshotType, TracingInspector, TracingInspectorConfig}; + +pub use revm_inspectors::access_list::AccessListInspector; + +mod chisel_state; +pub use chisel_state::ChiselState; + +mod logs; +pub use logs::LogCollector; + +mod stack; +pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder}; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs new file mode 100644 index 0000000000000..90879736c55b6 --- /dev/null +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -0,0 +1,1180 @@ +use super::{ + Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Fuzzer, LogCollector, + TracingInspector, +}; +use alloy_primitives::{map::AddressHashMap, Address, Bytes, Log, TxKind, U256}; +use foundry_cheatcodes::{CheatcodesExecutor, Wallets}; +use foundry_evm_core::{backend::DatabaseExt, InspectorExt}; +use foundry_evm_coverage::HitMaps; +use foundry_evm_traces::{SparsedTraceArena, TraceMode}; +use revm::{ + inspectors::CustomPrintTracer, + interpreter::{ + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs, + EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, + }, + primitives::{ + Account, AccountStatus, BlockEnv, CreateScheme, Env, EnvWithHandlerCfg, ExecutionResult, + HashMap, Output, TransactTo, + }, + EvmContext, Inspector, +}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; + +#[derive(Clone, Debug, Default)] +#[must_use = "builders do nothing unless you call `build` on them"] +pub struct InspectorStackBuilder { + /// The block environment. + /// + /// Used in the cheatcode handler to overwrite the block environment separately from the + /// execution block environment. + pub block: Option, + /// The gas price. + /// + /// Used in the cheatcode handler to overwrite the gas price separately from the gas price + /// in the execution environment. + pub gas_price: Option, + /// The cheatcodes config. + pub cheatcodes: Option>, + /// The fuzzer inspector and its state, if it exists. + pub fuzzer: Option, + /// Whether to enable tracing. + pub trace_mode: TraceMode, + /// Whether logs should be collected. + pub logs: Option, + /// Whether coverage info should be collected. + pub coverage: Option, + /// Whether to print all opcode traces into the console. Useful for debugging the EVM. + pub print: Option, + /// The chisel state inspector. + pub chisel_state: Option, + /// Whether to enable call isolation. + /// In isolation mode all top-level calls are executed as a separate transaction in a separate + /// EVM context, enabling more precise gas accounting and transaction state changes. + pub enable_isolation: bool, + /// Whether to enable Odyssey features. + pub odyssey: bool, + /// The wallets to set in the cheatcodes context. + pub wallets: Option, + /// The CREATE2 deployer address. + pub create2_deployer: Address, +} + +impl InspectorStackBuilder { + /// Create a new inspector stack builder. + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Set the block environment. + #[inline] + pub fn block(mut self, block: BlockEnv) -> Self { + self.block = Some(block); + self + } + + /// Set the gas price. + #[inline] + pub fn gas_price(mut self, gas_price: U256) -> Self { + self.gas_price = Some(gas_price); + self + } + + /// Enable cheatcodes with the given config. + #[inline] + pub fn cheatcodes(mut self, config: Arc) -> Self { + self.cheatcodes = Some(config); + self + } + + /// Set the wallets. + #[inline] + pub fn wallets(mut self, wallets: Wallets) -> Self { + self.wallets = Some(wallets); + self + } + + /// Set the fuzzer inspector. + #[inline] + pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self { + self.fuzzer = Some(fuzzer); + self + } + + /// Set the Chisel inspector. + #[inline] + pub fn chisel_state(mut self, final_pc: usize) -> Self { + self.chisel_state = Some(final_pc); + self + } + + /// Set whether to collect logs. + #[inline] + pub fn logs(mut self, yes: bool) -> Self { + self.logs = Some(yes); + self + } + + /// Set whether to collect coverage information. + #[inline] + pub fn coverage(mut self, yes: bool) -> Self { + self.coverage = Some(yes); + self + } + + /// Set whether to enable the trace printer. + #[inline] + pub fn print(mut self, yes: bool) -> Self { + self.print = Some(yes); + self + } + + /// Set whether to enable the tracer. + #[inline] + pub fn trace_mode(mut self, mode: TraceMode) -> Self { + if self.trace_mode < mode { + self.trace_mode = mode + } + self + } + + /// Set whether to enable the call isolation. + /// For description of call isolation, see [`InspectorStack::enable_isolation`]. + #[inline] + pub fn enable_isolation(mut self, yes: bool) -> Self { + self.enable_isolation = yes; + self + } + + /// Set whether to enable Odyssey features. + /// For description of call isolation, see [`InspectorStack::enable_isolation`]. + #[inline] + pub fn odyssey(mut self, yes: bool) -> Self { + self.odyssey = yes; + self + } + + #[inline] + pub fn create2_deployer(mut self, create2_deployer: Address) -> Self { + self.create2_deployer = create2_deployer; + self + } + + /// Builds the stack of inspectors to use when transacting/committing on the EVM. + pub fn build(self) -> InspectorStack { + let Self { + block, + gas_price, + cheatcodes, + fuzzer, + trace_mode, + logs, + coverage, + print, + chisel_state, + enable_isolation, + odyssey, + wallets, + create2_deployer, + } = self; + let mut stack = InspectorStack::new(); + + // inspectors + if let Some(config) = cheatcodes { + let mut cheatcodes = Cheatcodes::new(config); + // Set wallets if they are provided + if let Some(wallets) = wallets { + cheatcodes.set_wallets(wallets); + } + stack.set_cheatcodes(cheatcodes); + } + + if let Some(fuzzer) = fuzzer { + stack.set_fuzzer(fuzzer); + } + if let Some(chisel_state) = chisel_state { + stack.set_chisel(chisel_state); + } + stack.collect_coverage(coverage.unwrap_or(false)); + stack.collect_logs(logs.unwrap_or(true)); + stack.print(print.unwrap_or(false)); + stack.tracing(trace_mode); + + stack.enable_isolation(enable_isolation); + stack.odyssey(odyssey); + stack.set_create2_deployer(create2_deployer); + + // environment, must come after all of the inspectors + if let Some(block) = block { + stack.set_block(&block); + } + if let Some(gas_price) = gas_price { + stack.set_gas_price(gas_price); + } + + stack + } +} + +/// Helper macro to call the same method on multiple inspectors without resorting to dynamic +/// dispatch. +#[macro_export] +macro_rules! call_inspectors { + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => { + $( + if let Some($id) = $inspector { + ({ #[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; + } + } + )+ + }; +} + +/// The collected results of [`InspectorStack`]. +pub struct InspectorData { + pub logs: Vec, + pub labels: AddressHashMap, + pub traces: Option, + pub coverage: Option, + pub cheatcodes: Option, + pub chisel_state: Option<(Vec, Vec, InstructionResult)>, +} + +/// Contains data about the state of outer/main EVM which created and invoked the inner EVM context. +/// Used to adjust EVM state while in inner context. +/// +/// We need this to avoid breaking changes due to EVM behavior differences in isolated vs +/// non-isolated mode. For descriptions and workarounds for those changes see: +#[derive(Debug, Clone)] +pub struct InnerContextData { + /// Origin of the transaction in the outer EVM context. + original_origin: Address, +} + +/// An inspector that calls multiple inspectors in sequence. +/// +/// If a call to an inspector returns a value other than [InstructionResult::Continue] (or +/// equivalent) the remaining inspectors are not called. +/// +/// 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 fuzzer: Option, + pub log_collector: Option, + pub printer: Option, + pub tracer: Option, + pub enable_isolation: bool, + pub odyssey: bool, + pub create2_deployer: Address, + + /// Flag marking if we are in the inner EVM context. + pub in_inner_context: bool, + pub inner_context_data: Option, + pub top_frame_journal: HashMap, +} + +/// 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>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box { + Box::new(InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }) + } + + fn tracing_inspector(&mut self) -> Option<&mut Option> { + Some(&mut self.tracer) + } +} + +impl InspectorStack { + /// Creates a new inspector stack. + /// + /// Note that the stack is empty by default, and you must add inspectors to it. + /// This is done by calling the `set_*` methods on the stack directly, or by building the stack + /// with [`InspectorStack`]. + #[inline] + pub fn new() -> Self { + Self::default() + } + + /// Logs the status of the inspectors. + pub fn log_status(&self) { + trace!(enabled=%{ + let mut enabled = Vec::with_capacity(16); + macro_rules! push { + ($($id:ident),* $(,)?) => { + $( + if self.$id.is_some() { + enabled.push(stringify!($id)); + } + )* + }; + } + push!(cheatcodes, chisel_state, coverage, fuzzer, log_collector, printer, tracer); + if self.enable_isolation { + enabled.push("isolation"); + } + format!("[{}]", enabled.join(", ")) + }); + } + + /// Set variables from an environment for the relevant inspectors. + #[inline] + pub fn set_env(&mut self, env: &Env) { + self.set_block(&env.block); + self.set_gas_price(env.tx.gas_price); + } + + /// Sets the block for the relevant inspectors. + #[inline] + pub fn set_block(&mut self, block: &BlockEnv) { + if let Some(cheatcodes) = &mut self.cheatcodes { + cheatcodes.block = Some(block.clone()); + } + } + + /// Sets the gas price for the relevant inspectors. + #[inline] + pub fn set_gas_price(&mut self, gas_price: U256) { + if let Some(cheatcodes) = &mut self.cheatcodes { + cheatcodes.gas_price = Some(gas_price); + } + } + + /// Set the cheatcodes inspector. + #[inline] + pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes) { + self.cheatcodes = Some(cheatcodes); + } + + /// Set the fuzzer inspector. + #[inline] + pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) { + self.fuzzer = Some(fuzzer); + } + + /// Set the Chisel inspector. + #[inline] + pub fn set_chisel(&mut self, final_pc: usize) { + self.chisel_state = Some(ChiselState::new(final_pc)); + } + + /// Set whether to enable the coverage collector. + #[inline] + pub fn collect_coverage(&mut self, yes: bool) { + self.coverage = yes.then(Default::default); + } + + /// Set whether to enable call isolation. + #[inline] + pub fn enable_isolation(&mut self, yes: bool) { + self.enable_isolation = yes; + } + + /// Set whether to enable call isolation. + #[inline] + pub fn odyssey(&mut self, yes: bool) { + self.odyssey = yes; + } + + /// Set the CREATE2 deployer address. + #[inline] + pub fn set_create2_deployer(&mut self, deployer: Address) { + self.create2_deployer = deployer; + } + + /// Set whether to enable the log collector. + #[inline] + pub fn collect_logs(&mut self, yes: bool) { + self.log_collector = yes.then(Default::default); + } + + /// Set whether to enable the trace printer. + #[inline] + pub fn print(&mut self, yes: bool) { + self.printer = yes.then(Default::default); + } + + /// Set whether to enable the tracer. + #[inline] + 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 { + mut cheatcodes, + inner: InspectorStackInner { chisel_state, coverage, log_collector, tracer, .. }, + } = self; + + let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| { + let ignored = cheatcodes + .as_mut() + .map(|cheatcodes| { + let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored); + + // If the last pause call was not resumed, ignore the rest of the trace + if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call { + ignored.insert(last_pause_call, (arena.nodes().len(), 0)); + } + + ignored + }) + .unwrap_or_default(); + + SparsedTraceArena { arena, ignored } + }); + + InspectorData { + logs: log_collector.map(|logs| logs.logs).unwrap_or_default(), + labels: cheatcodes + .as_ref() + .map(|cheatcodes| cheatcodes.labels.clone()) + .unwrap_or_default(), + traces, + coverage: coverage.map(|coverage| coverage.finish()), + cheatcodes, + chisel_state: chisel_state.and_then(|state| state.state), + } + } + + #[inline(always)] + fn as_mut(&mut self) -> InspectorStackRefMut<'_> { + InspectorStackRefMut { cheatcodes: self.cheatcodes.as_mut(), inner: &mut self.inner } + } +} + +impl InspectorStackRefMut<'_> { + /// 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 dyn DatabaseExt>) { + let inner_context_data = + self.inner_context_data.as_ref().expect("should be called in inner context"); + ecx.env.tx.caller = inner_context_data.original_origin; + } + + fn do_call_end( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + let result = outcome.result.result; + call_inspectors!( + #[ret] + [&mut self.fuzzer, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| { + let new_outcome = inspector.call_end(ecx, inputs, 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) + }, + ); + + outcome + } + + fn do_create_end( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + let result = outcome.result.result; + call_inspectors!( + #[ret] + [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| { + let new_outcome = inspector.create_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) + }, + ); + + outcome + } + + fn do_eofcreate_end( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + let result = outcome.result.result; + call_inspectors!( + #[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) + }, + ); + + outcome + } + + fn transact_inner( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + transact_to: TransactTo, + caller: Address, + input: Bytes, + gas_limit: u64, + value: U256, + ) -> (InterpreterResult, Option
) { + let ecx = &mut ecx.inner; + + let cached_env = ecx.env.clone(); + + ecx.env.block.basefee = U256::ZERO; + ecx.env.tx.caller = caller; + ecx.env.tx.transact_to = transact_to; + ecx.env.tx.data = input; + ecx.env.tx.value = value; + // Add 21000 to the gas limit to account for the base cost of transaction. + ecx.env.tx.gas_limit = gas_limit + 21000; + // If we haven't disabled gas limit checks, ensure that transaction gas limit will not + // exceed block gas limit. + if !ecx.env.cfg.disable_block_gas_limit { + ecx.env.tx.gas_limit = + std::cmp::min(ecx.env.tx.gas_limit, ecx.env.block.gas_limit.to()); + } + ecx.env.tx.gas_price = U256::ZERO; + + self.inner_context_data = Some(InnerContextData { original_origin: cached_env.tx.caller }); + self.in_inner_context = true; + + let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); + let res = self.with_stack(|inspector| { + let mut evm = crate::utils::new_evm_with_inspector(&mut ecx.db, env, inspector); + + evm.context.evm.inner.journaled_state.state = { + let mut state = ecx.journaled_state.state.clone(); + + for (addr, acc_mut) in &mut state { + // mark all accounts cold, besides preloaded addresses + if !ecx.journaled_state.warm_preloaded_addresses.contains(addr) { + acc_mut.mark_cold(); + } + + // mark all slots cold + for slot_mut in acc_mut.storage.values_mut() { + slot_mut.is_cold = true; + slot_mut.original_value = slot_mut.present_value; + } + } + + state + }; + + // set depth to 1 to make sure traces are collected correctly + evm.context.evm.inner.journaled_state.depth = 1; + + let res = evm.transact(); + + // need to reset the env in case it was modified via cheatcodes during execution + ecx.env = evm.context.evm.inner.env; + res + }); + + self.in_inner_context = false; + self.inner_context_data = None; + + ecx.env.tx = cached_env.tx; + ecx.env.block.basefee = cached_env.block.basefee; + + let mut gas = Gas::new(gas_limit); + + let Ok(res) = res else { + // Should we match, encode and propagate error as a revert reason? + let result = + InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas }; + return (result, None); + }; + + for (addr, mut acc) in res.state { + let Some(acc_mut) = ecx.journaled_state.state.get_mut(&addr) else { + ecx.journaled_state.state.insert(addr, acc); + continue + }; + + // make sure accounts that were warmed earlier do not become cold + if acc.status.contains(AccountStatus::Cold) && + !acc_mut.status.contains(AccountStatus::Cold) + { + acc.status -= AccountStatus::Cold; + } + acc_mut.info = acc.info; + acc_mut.status |= acc.status; + + for (key, val) in acc.storage { + let Some(slot_mut) = acc_mut.storage.get_mut(&key) else { + acc_mut.storage.insert(key, val); + continue + }; + slot_mut.present_value = val.present_value; + slot_mut.is_cold &= val.is_cold; + } + } + + let (result, address, output) = match res.result { + ExecutionResult::Success { reason, gas_used, gas_refunded, logs: _, output } => { + gas.set_refund(gas_refunded as i64); + let _ = gas.record_cost(gas_used); + let address = match output { + Output::Create(_, address) => address, + Output::Call(_) => None, + }; + (reason.into(), address, output.into_data()) + } + ExecutionResult::Halt { reason, gas_used } => { + let _ = gas.record_cost(gas_used); + (reason.into(), None, Bytes::new()) + } + ExecutionResult::Revert { gas_used, output } => { + let _ = gas.record_cost(gas_used); + (InstructionResult::Revert, None, output) + } + }; + (InterpreterResult { result, output, gas }, address) + } + + /// Moves out of references, constructs an [`InspectorStack`] and runs the given closure with + /// it. + fn with_stack(&mut self, f: impl FnOnce(&mut InspectorStack) -> O) -> O { + let mut stack = InspectorStack { + cheatcodes: self.cheatcodes.as_deref_mut().map(std::mem::take), + inner: std::mem::take(self.inner), + }; + + let out = f(&mut stack); + + if let Some(cheats) = self.cheatcodes.as_deref_mut() { + *cheats = stack.cheatcodes.take().unwrap(); + } + + *self.inner = stack.inner; + + out + } + + /// Invoked at the beginning of a new top-level (0 depth) frame. + fn top_level_frame_start(&mut self, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + if self.enable_isolation { + // If we're in isolation mode, we need to keep track of the state at the beginning of + // the frame to be able to roll back on revert + self.top_frame_journal = ecx.journaled_state.state.clone(); + } + } + + /// Invoked at the end of root frame. + fn top_level_frame_end( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + result: InstructionResult, + ) { + if !result.is_revert() { + return; + } + // Encountered a revert, since cheatcodes may have altered the evm state in such a way + // that violates some constraints, e.g. `deal`, we need to manually roll back on revert + // before revm reverts the state itself + if let Some(cheats) = self.cheatcodes.as_mut() { + cheats.on_revert(ecx); + } + + // If we're in isolation mode, we need to rollback to state before the root frame was + // created We can't rely on revm's journal because it doesn't account for changes + // made by isolated calls + if self.enable_isolation { + ecx.journaled_state.state = std::mem::take(&mut self.top_frame_journal); + } + } +} + +impl Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'_> { + fn initialize_interp( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { + call_inspectors!( + [&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.initialize_interp(interpreter, ecx), + ); + } + + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + call_inspectors!( + [ + &mut self.fuzzer, + &mut self.tracer, + &mut self.coverage, + &mut self.cheatcodes, + &mut self.printer, + ], + |inspector| inspector.step(interpreter, ecx), + ); + } + + fn step_end( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { + call_inspectors!( + [&mut self.tracer, &mut self.cheatcodes, &mut self.chisel_state, &mut self.printer], + |inspector| inspector.step_end(interpreter, ecx), + ); + } + + fn log( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + log: &Log, + ) { + call_inspectors!( + [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.log(interpreter, ecx, log), + ); + } + + fn call( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + call: &mut CallInputs, + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 1 { + self.adjust_evm_data_for_inner_context(ecx); + return None; + } + + if ecx.journaled_state.depth == 0 { + self.top_level_frame_start(ecx); + } + + call_inspectors!( + #[ret] + [&mut self.fuzzer, &mut self.tracer, &mut self.log_collector, &mut self.printer], + |inspector| { + let mut out = None; + if let Some(output) = inspector.call(ecx, call) { + if output.result.result != InstructionResult::Continue { + out = Some(Some(output)); + } + } + out + }, + ); + + if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() { + // Handle mocked functions, replace bytecode address with mock if matched. + if let Some(mocks) = cheatcodes.mocked_functions.get(&call.target_address) { + // Check if any mock function set for call data or if catch-all mock function set + // for selector. + if let Some(target) = mocks + .get(&call.input) + .or_else(|| call.input.get(..4).and_then(|selector| mocks.get(selector))) + { + call.bytecode_address = *target; + } + } + + if let Some(output) = cheatcodes.call_with_executor(ecx, call, self.inner) { + if output.result.result != InstructionResult::Continue { + return Some(output); + } + } + } + + if self.enable_isolation && + call.scheme == CallScheme::Call && + !self.in_inner_context && + ecx.journaled_state.depth == 1 + { + let (result, _) = self.transact_inner( + ecx, + 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() }); + } + + None + } + + fn call_end( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + // We are processing inner context outputs in the outer context, so need to avoid processing + // twice. + if self.in_inner_context && ecx.journaled_state.depth == 1 { + return outcome; + } + + let outcome = self.do_call_end(ecx, inputs, outcome); + + if ecx.journaled_state.depth == 0 { + self.top_level_frame_end(ecx, outcome.result.result); + } + + outcome + } + + fn create( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + create: &mut CreateInputs, + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 1 { + self.adjust_evm_data_for_inner_context(ecx); + return None; + } + + if ecx.journaled_state.depth == 0 { + self.top_level_frame_start(ecx); + } + + call_inspectors!( + #[ret] + [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + |inspector| inspector.create(ecx, create).map(Some), + ); + + if !matches!(create.scheme, CreateScheme::Create2 { .. }) && + self.enable_isolation && + !self.in_inner_context && + ecx.journaled_state.depth == 1 + { + let (result, address) = self.transact_inner( + ecx, + TxKind::Create, + create.caller, + create.init_code.clone(), + create.gas_limit, + create.value, + ); + return Some(CreateOutcome { result, address }); + } + + None + } + + fn create_end( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + // We are processing inner context outputs in the outer context, so need to avoid processing + // twice. + if self.in_inner_context && ecx.journaled_state.depth == 1 { + return outcome; + } + + let outcome = self.do_create_end(ecx, call, outcome); + + if ecx.journaled_state.depth == 0 { + self.top_level_frame_end(ecx, outcome.result.result); + } + + outcome + } + + fn eofcreate( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + create: &mut EOFCreateInputs, + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 1 { + self.adjust_evm_data_for_inner_context(ecx); + return None; + } + + if ecx.journaled_state.depth == 0 { + self.top_level_frame_start(ecx); + } + + call_inspectors!( + #[ret] + [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + |inspector| inspector.eofcreate(ecx, create).map(Some), + ); + + if matches!(create.kind, EOFCreateKind::Tx { .. }) && + self.enable_isolation && + !self.in_inner_context && + ecx.journaled_state.depth == 1 + { + 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<&mut dyn DatabaseExt>, + call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + // We are processing inner context outputs in the outer context, so need to avoid processing + // twice. + if self.in_inner_context && ecx.journaled_state.depth == 1 { + return outcome; + } + + let outcome = self.do_eofcreate_end(ecx, call, outcome); + + if ecx.journaled_state.depth == 0 { + self.top_level_frame_end(ecx, outcome.result.result); + } + + outcome + } + + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { + Inspector::<&mut dyn DatabaseExt>::selfdestruct(inspector, contract, target, value) + }); + } +} + +impl InspectorExt for InspectorStackRefMut<'_> { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + inputs: &mut CreateInputs, + ) -> bool { + call_inspectors!( + #[ret] + [&mut self.cheatcodes], + |inspector| { inspector.should_use_create2_factory(ecx, inputs).then_some(true) }, + ); + + false + } + + fn console_log(&mut self, msg: &str) { + call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log( + inspector, msg + )); + } + + fn is_odyssey(&self) -> bool { + self.inner.odyssey + } + + fn create2_deployer(&self) -> Address { + self.inner.create2_deployer + } +} + +impl Inspector<&mut dyn DatabaseExt> for InspectorStack { + #[inline] + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { + self.as_mut().step(interpreter, ecx) + } + + #[inline] + fn step_end( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { + self.as_mut().step_end(interpreter, ecx) + } + + fn call( + &mut self, + context: &mut EvmContext<&mut dyn DatabaseExt>, + inputs: &mut CallInputs, + ) -> Option { + self.as_mut().call(context, inputs) + } + + fn call_end( + &mut self, + context: &mut EvmContext<&mut dyn DatabaseExt>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + self.as_mut().call_end(context, inputs, outcome) + } + + fn create( + &mut self, + context: &mut EvmContext<&mut dyn DatabaseExt>, + create: &mut CreateInputs, + ) -> Option { + self.as_mut().create(context, create) + } + + fn create_end( + &mut self, + context: &mut EvmContext<&mut dyn DatabaseExt>, + call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.as_mut().create_end(context, call, outcome) + } + + fn eofcreate( + &mut self, + context: &mut EvmContext<&mut dyn DatabaseExt>, + create: &mut EOFCreateInputs, + ) -> Option { + self.as_mut().eofcreate(context, create) + } + + fn eofcreate_end( + &mut self, + context: &mut EvmContext<&mut dyn DatabaseExt>, + call: &EOFCreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.as_mut().eofcreate_end(context, call, outcome) + } + + fn initialize_interp( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { + self.as_mut().initialize_interp(interpreter, ecx) + } + + fn log( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + log: &Log, + ) { + self.as_mut().log(interpreter, ecx, log) + } + + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + Inspector::<&mut dyn DatabaseExt>::selfdestruct(&mut self.as_mut(), contract, target, value) + } +} + +impl InspectorExt for InspectorStack { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + inputs: &mut CreateInputs, + ) -> bool { + self.as_mut().should_use_create2_factory(ecx, inputs) + } + + fn is_odyssey(&self) -> bool { + self.odyssey + } + + fn create2_deployer(&self) -> Address { + self.create2_deployer + } +} + +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 new file mode 100644 index 0000000000000..15858c0f393aa --- /dev/null +++ b/crates/evm/evm/src/lib.rs @@ -0,0 +1,21 @@ +//! # foundry-evm +//! +//! Main Foundry EVM backend abstractions. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + +pub mod executors; +pub mod inspectors; + +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; + +// TODO: We should probably remove these, but it's a pretty big breaking change. +#[doc(hidden)] +pub use revm; diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml new file mode 100644 index 0000000000000..452eb49790f9e --- /dev/null +++ b/crates/evm/fuzz/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "foundry-evm-fuzz" +description = "EVM fuzzing implementation using `proptest`" + +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.workspace = true +foundry-compilers.workspace = true +foundry-config.workspace = true +foundry-evm-core.workspace = true +foundry-evm-coverage.workspace = true +foundry-evm-traces.workspace = true + +alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-json-abi.workspace = true +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", + "map-indexmap", +] } +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "optional_eip3607", + "optional_block_gas_limit", + "optional_no_base_fee", + "arbitrary", +] } + +eyre.workspace = true +itertools.workspace = true +parking_lot.workspace = true +proptest.workspace = true +rand.workspace = true +serde.workspace = true +thiserror.workspace = true +tracing.workspace = true diff --git a/crates/evm/fuzz/src/error.rs b/crates/evm/fuzz/src/error.rs new file mode 100644 index 0000000000000..8fc3c1897af65 --- /dev/null +++ b/crates/evm/fuzz/src/error.rs @@ -0,0 +1,18 @@ +//! Errors related to fuzz tests. + +use proptest::test_runner::Reason; + +/// Possible errors when running fuzz tests +#[derive(Debug, thiserror::Error)] +pub enum FuzzError { + #[error("`vm.assume` reject")] + AssumeReject, + #[error("`vm.assume` rejected too many inputs ({0} allowed)")] + TooManyRejects(u32), +} + +impl From for Reason { + fn from(error: FuzzError) -> Self { + error.to_string().into() + } +} diff --git a/crates/evm/fuzz/src/inspector.rs b/crates/evm/fuzz/src/inspector.rs new file mode 100644 index 0000000000000..052d87dac2dd1 --- /dev/null +++ b/crates/evm/fuzz/src/inspector.rs @@ -0,0 +1,96 @@ +use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState}; +use revm::{ + interpreter::{CallInputs, CallOutcome, CallScheme, Interpreter}, + Database, EvmContext, Inspector, +}; + +/// An inspector that can fuzz and collect data for that effect. +#[derive(Clone, Debug)] +pub struct Fuzzer { + /// Given a strategy, it generates a random call. + pub call_generator: Option, + /// If set, it collects `stack` and `memory` values for fuzzing purposes. + pub collect: bool, + /// If `collect` is set, we store the collected values in this fuzz dictionary. + pub fuzz_state: EvmFuzzState, +} + +impl Inspector for Fuzzer { + #[inline] + fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + // We only collect `stack` and `memory` data before and after calls. + if self.collect { + self.collect_data(interp); + self.collect = false; + } + } + + #[inline] + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { + // We don't want to override the very first call made to the test contract. + if self.call_generator.is_some() && ecx.env.tx.caller != inputs.caller { + self.override_call(inputs); + } + + // We only collect `stack` and `memory` data before and after calls. + // this will be turned off on the next `step` + self.collect = true; + + None + } + + #[inline] + fn call_end( + &mut self, + _context: &mut EvmContext, + _inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + if let Some(ref mut call_generator) = self.call_generator { + call_generator.used = false; + } + + // We only collect `stack` and `memory` data before and after calls. + // this will be turned off on the next `step` + self.collect = true; + + outcome + } +} + +impl Fuzzer { + /// Collects `stack` and `memory` values into the fuzz dictionary. + fn collect_data(&mut self, interpreter: &Interpreter) { + self.fuzz_state.collect_values(interpreter.stack().data().iter().copied().map(Into::into)); + + // TODO: disabled for now since it's flooding the dictionary + // for index in 0..interpreter.shared_memory.len() / 32 { + // let mut slot = [0u8; 32]; + // slot.clone_from_slice(interpreter.shared_memory.get_slice(index * 32, 32)); + + // state.insert(slot); + // } + } + + /// Overrides an external call and tries to call any method of msg.sender. + fn override_call(&mut self, call: &mut CallInputs) { + if let Some(ref mut call_generator) = self.call_generator { + // We only override external calls which are not coming from the test contract. + if call.caller != call_generator.test_address && + call.scheme == CallScheme::Call && + !call_generator.used + { + // There's only a 30% chance that an override happens. + if let Some(tx) = call_generator.next(call.caller, call.target_address) { + *call.input = tx.call_details.calldata.0; + call.caller = tx.sender; + call.target_address = tx.call_details.target; + + // TODO: in what scenarios can the following be problematic + call.bytecode_address = tx.call_details.target; + call_generator.used = true; + } + } + } + } +} diff --git a/crates/evm/src/fuzz/invariant/call_override.rs b/crates/evm/fuzz/src/invariant/call_override.rs similarity index 73% rename from crates/evm/src/fuzz/invariant/call_override.rs rename to crates/evm/fuzz/src/invariant/call_override.rs index 9a935e2e751a1..dddf591526f08 100644 --- a/crates/evm/src/fuzz/invariant/call_override.rs +++ b/crates/evm/fuzz/src/invariant/call_override.rs @@ -1,6 +1,5 @@ -use super::BasicTxDetails; -use crate::executor::Executor; -use ethers::types::{Address, Bytes}; +use super::{BasicTxDetails, CallDetails}; +use alloy_primitives::Address; use parking_lot::{Mutex, RwLock}; use proptest::{ option::weighted, @@ -11,14 +10,14 @@ use std::sync::Arc; /// Given a TestRunner and a strategy, it generates calls. Used inside the Fuzzer inspector to /// override external calls to test for potential reentrancy vulnerabilities.. -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct RandomCallGenerator { /// Address of the test contract. pub test_address: Address, /// Runner that will generate the call from the strategy. pub runner: Arc>, /// Strategy to be used to generate calls from `target_reference`. - pub strategy: SBoxedStrategy>, + pub strategy: SBoxedStrategy>, /// Reference to which contract we want a fuzzed calldata from. pub target_reference: Arc>, /// Flag to know if a call has been overridden. Don't allow nesting for now. @@ -34,17 +33,15 @@ impl RandomCallGenerator { pub fn new( test_address: Address, runner: TestRunner, - strategy: SBoxedStrategy<(Address, Bytes)>, + strategy: impl Strategy + Send + Sync + 'static, target_reference: Arc>, ) -> Self { - let strategy = weighted(0.9, strategy).sboxed(); - - RandomCallGenerator { + Self { test_address, runner: Arc::new(Mutex::new(runner)), - strategy, + strategy: weighted(0.9, strategy).sboxed(), target_reference, - last_sequence: Arc::new(RwLock::new(vec![])), + last_sequence: Arc::default(), replay: false, used: false, } @@ -72,7 +69,7 @@ impl RandomCallGenerator { ) } else { // TODO: Do we want it to be 80% chance only too ? - let new_caller = original_target; + let sender = original_target; // Set which contract we mostly (80% chance) want to generate calldata from. *self.target_reference.write() = original_caller; @@ -83,20 +80,10 @@ impl RandomCallGenerator { .new_tree(&mut self.runner.lock()) .unwrap() .current() - .map(|(new_target, calldata)| (new_caller, (new_target, calldata))); + .map(|call_details| BasicTxDetails { sender, call_details }); self.last_sequence.write().push(choice.clone()); choice } } } - -/// Sets up the calls generated by the internal fuzzer, if they exist. -pub fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { - if let Some(ref mut fuzzer) = executor.inspector_config_mut().fuzzer { - if let Some(ref mut call_generator) = 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/src/fuzz/invariant/filters.rs b/crates/evm/fuzz/src/invariant/filters.rs similarity index 69% rename from crates/evm/src/fuzz/invariant/filters.rs rename to crates/evm/fuzz/src/invariant/filters.rs index 2e79ce207cf3a..520e5b5afcd6e 100644 --- a/crates/evm/src/fuzz/invariant/filters.rs +++ b/crates/evm/fuzz/src/invariant/filters.rs @@ -1,52 +1,52 @@ -use crate::utils::get_function; -use ethers::{ - abi::{Abi, Address, FixedBytes, Function}, - solc::ArtifactId, -}; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{Address, Selector}; +use foundry_compilers::ArtifactId; +use foundry_evm_core::utils::get_function; use std::collections::BTreeMap; /// Contains which contracts are to be targeted or excluded on an invariant test through their /// artifact identifiers. #[derive(Default)] pub struct ArtifactFilters { - /// List of `contract_path:contract_name` which are to be targeted. If list of functions is not - /// empty, target only those. - pub targeted: BTreeMap>, + /// List of `contract_path:contract_name` along with selectors, which are to be targeted. If + /// list of functions is not empty, target only those. + pub targeted: BTreeMap>, /// List of `contract_path:contract_name` which are to be excluded. pub excluded: Vec, } impl ArtifactFilters { + /// Returns `true` if the given identifier matches this filter. + pub fn matches(&self, identifier: &str) -> bool { + (self.targeted.is_empty() || self.targeted.contains_key(identifier)) && + (self.excluded.is_empty() || !self.excluded.iter().any(|id| id == identifier)) + } + /// Gets all the targeted functions from `artifact`. Returns error, if selectors do not match /// the `artifact`. /// - /// An empty vector means that it targets any mutable function. See `select_random_function` for - /// more. + /// An empty vector means that it targets any mutable function. pub fn get_targeted_functions( &self, artifact: &ArtifactId, - abi: &Abi, + abi: &JsonAbi, ) -> eyre::Result>> { if let Some(selectors) = self.targeted.get(&artifact.identifier()) { let functions = selectors .iter() - .map(|selector| get_function(&artifact.name, selector, abi)) + .map(|selector| get_function(&artifact.name, *selector, abi).cloned()) .collect::>>()?; - // targetArtifactSelectors > excludeArtifacts > targetArtifacts if functions.is_empty() && self.excluded.contains(&artifact.identifier()) { return Ok(None) } - return Ok(Some(functions)) } - // If no contract is specifically targeted, and this contract is not excluded, then accept // all functions. if self.targeted.is_empty() && !self.excluded.contains(&artifact.identifier()) { return Ok(Some(vec![])) } - Ok(None) } } @@ -63,13 +63,11 @@ pub struct SenderFilters { impl SenderFilters { pub fn new(mut targeted: Vec
, mut excluded: Vec
) -> Self { - let addr_0 = Address::zero(); + let addr_0 = Address::ZERO; if !excluded.contains(&addr_0) { excluded.push(addr_0); } - targeted.retain(|addr| !excluded.contains(addr)); - - SenderFilters { targeted, excluded } + Self { targeted, excluded } } } diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs new file mode 100644 index 0000000000000..49f27e9f3950a --- /dev/null +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -0,0 +1,242 @@ +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{Address, Bytes, Selector}; +use itertools::Either; +use parking_lot::Mutex; +use std::{collections::BTreeMap, sync::Arc}; + +mod call_override; +pub use call_override::RandomCallGenerator; + +mod filters; +pub use filters::{ArtifactFilters, SenderFilters}; +use foundry_common::{ContractsByAddress, ContractsByArtifact}; +use foundry_evm_core::utils::{get_function, StateChangeset}; + +/// Contracts identified as targets during a fuzz run. +/// +/// During execution, any newly created contract is added as target and used through the rest of +/// the fuzz run if the collection is updatable (no `targetContract` specified in `setUp`). +#[derive(Clone, Debug)] +pub struct FuzzRunIdentifiedContracts { + /// Contracts identified as targets during a fuzz run. + pub targets: Arc>, + /// Whether target contracts are updatable or not. + pub is_updatable: bool, +} + +impl FuzzRunIdentifiedContracts { + /// Creates a new `FuzzRunIdentifiedContracts` instance. + pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self { + Self { targets: Arc::new(Mutex::new(targets)), is_updatable } + } + + /// If targets are updatable, collect all contracts created during an invariant run (which + /// haven't been discovered yet). + pub fn collect_created_contracts( + &self, + state_changeset: &StateChangeset, + project_contracts: &ContractsByArtifact, + setup_contracts: &ContractsByAddress, + artifact_filters: &ArtifactFilters, + created_contracts: &mut Vec
, + ) -> eyre::Result<()> { + if !self.is_updatable { + return Ok(()); + } + + let mut targets = self.targets.lock(); + for (address, account) in state_changeset { + if setup_contracts.contains_key(address) { + continue; + } + if !account.is_touched() { + continue; + } + let Some(code) = &account.info.code else { + continue; + }; + if code.is_empty() { + continue; + } + let Some((artifact, contract)) = + project_contracts.find_by_deployed_code(code.original_byte_slice()) + else { + continue; + }; + let Some(functions) = + artifact_filters.get_targeted_functions(artifact, &contract.abi)? + else { + continue; + }; + created_contracts.push(*address); + let contract = TargetedContract { + identifier: artifact.name.clone(), + abi: contract.abi.clone(), + targeted_functions: functions, + excluded_functions: Vec::new(), + }; + targets.insert(*address, contract); + } + Ok(()) + } + + /// Clears targeted contracts created during an invariant run. + pub fn clear_created_contracts(&self, created_contracts: Vec
) { + if !created_contracts.is_empty() { + let mut targets = self.targets.lock(); + for addr in created_contracts.iter() { + targets.remove(addr); + } + } + } +} + +/// A collection of contracts identified as targets for invariant testing. +#[derive(Clone, Debug, Default)] +pub struct TargetedContracts { + /// The inner map of targeted contracts. + pub inner: BTreeMap, +} + +impl TargetedContracts { + /// Returns a new `TargetedContracts` instance. + pub fn new() -> Self { + Self::default() + } + + /// Returns fuzzed contract abi and fuzzed function from address and provided calldata. + /// + /// Used to decode return values and logs in order to add values into fuzz dictionary. + pub fn fuzzed_artifacts(&self, tx: &BasicTxDetails) -> (Option<&JsonAbi>, Option<&Function>) { + match self.inner.get(&tx.call_details.target) { + Some(c) => ( + Some(&c.abi), + c.abi.functions().find(|f| f.selector() == tx.call_details.calldata[..4]), + ), + None => (None, None), + } + } + + /// Returns flatten target contract address and functions to be fuzzed. + /// Includes contract targeted functions if specified, else all mutable contract functions. + pub fn fuzzed_functions(&self) -> impl Iterator { + self.inner + .iter() + .filter(|(_, c)| !c.abi.functions.is_empty()) + .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f))) + } + + /// Identifies fuzzed contract and function based on given tx details and returns unique metric + /// key composed from contract identifier and function name. + pub fn fuzzed_metric_key(&self, tx: &BasicTxDetails) -> Option { + self.inner.get(&tx.call_details.target).and_then(|contract| { + contract + .abi + .functions() + .find(|f| f.selector() == tx.call_details.calldata[..4]) + .map(|function| format!("{}.{}", contract.identifier.clone(), function.name)) + }) + } +} + +impl std::ops::Deref for TargetedContracts { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl std::ops::DerefMut for TargetedContracts { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// A contract identified as target for invariant testing. +#[derive(Clone, Debug)] +pub struct TargetedContract { + /// The contract identifier. This is only used in error messages. + pub identifier: String, + /// The contract's ABI. + pub abi: JsonAbi, + /// The targeted functions of the contract. + pub targeted_functions: Vec, + /// The excluded functions of the contract. + pub excluded_functions: Vec, +} + +impl TargetedContract { + /// Returns a new `TargetedContract` instance. + pub fn new(identifier: String, abi: JsonAbi) -> Self { + Self { identifier, abi, targeted_functions: Vec::new(), excluded_functions: Vec::new() } + } + + /// Helper to retrieve functions to fuzz for specified abi. + /// Returns specified targeted functions if any, else mutable abi functions that are not + /// marked as excluded. + pub fn abi_fuzzed_functions(&self) -> impl Iterator { + if !self.targeted_functions.is_empty() { + Either::Left(self.targeted_functions.iter()) + } else { + Either::Right(self.abi.functions().filter(|&func| { + !matches!( + func.state_mutability, + alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View + ) && !self.excluded_functions.contains(func) + })) + } + } + + /// Returns the function for the given selector. + pub fn get_function(&self, selector: Selector) -> eyre::Result<&Function> { + get_function(&self.identifier, selector, &self.abi) + } + + /// Adds the specified selectors to the targeted functions. + pub fn add_selectors( + &mut self, + selectors: impl IntoIterator, + should_exclude: bool, + ) -> eyre::Result<()> { + for selector in selectors { + if should_exclude { + self.excluded_functions.push(self.get_function(selector)?.clone()); + } else { + self.targeted_functions.push(self.get_function(selector)?.clone()); + } + } + Ok(()) + } +} + +/// Details of a transaction generated by invariant strategy for fuzzing a target. +#[derive(Clone, Debug)] +pub struct BasicTxDetails { + // Transaction sender address. + pub sender: Address, + // Transaction call details. + pub call_details: CallDetails, +} + +/// Call details of a transaction generated to fuzz invariant target. +#[derive(Clone, Debug)] +pub struct CallDetails { + // Address of target contract. + pub target: Address, + // The data of the transaction. + pub calldata: Bytes, +} + +/// Test contract which is testing its invariants. +#[derive(Clone, Debug)] +pub struct InvariantContract<'a> { + /// Address of the test contract. + pub address: Address, + /// Invariant function present in the test contract. + pub invariant_function: &'a Function, + /// If true, `afterInvariant` function is called after each invariant run. + pub call_after_invariant: bool, + /// ABI of the test contract. + pub abi: &'a JsonAbi, +} diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs new file mode 100644 index 0000000000000..65ef76f16c989 --- /dev/null +++ b/crates/evm/fuzz/src/lib.rs @@ -0,0 +1,383 @@ +//! # foundry-evm-fuzz +//! +//! EVM fuzzing implementation using [`proptest`]. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + +use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; +use alloy_primitives::{ + map::{AddressHashMap, HashMap}, + Address, Bytes, Log, +}; +use foundry_common::{calc, contracts::ContractsByAddress, evm::Breakpoints}; +use foundry_evm_coverage::HitMaps; +use foundry_evm_traces::{CallTraceArena, SparsedTraceArena}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; +use std::{fmt, sync::Arc}; + +pub use proptest::test_runner::{Config as FuzzConfig, Reason}; + +mod error; +pub use error::FuzzError; + +pub mod invariant; +pub mod strategies; + +mod inspector; +pub use inspector::Fuzzer; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum CounterExample { + /// Call used as a counter example for fuzz tests. + Single(BaseCounterExample), + /// Original sequence size and sequence of calls used as a counter example for invariant tests. + Sequence(usize, Vec), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BaseCounterExample { + /// Address which makes the call. + pub sender: Option
, + /// Address to which to call to. + pub addr: Option
, + /// The data to provide. + pub calldata: Bytes, + /// Contract name if it exists. + pub contract_name: Option, + /// Function name if it exists. + pub func_name: Option, + /// Function signature if it exists. + pub signature: Option, + /// Pretty formatted args used to call the function. + pub args: Option, + /// Unformatted args used to call the function. + pub raw_args: Option, + /// Counter example traces. + #[serde(skip)] + pub traces: Option, + /// Whether to display sequence as solidity. + #[serde(skip)] + pub show_solidity: bool, +} + +impl BaseCounterExample { + /// Creates counter example representing a step from invariant call sequence. + pub fn from_invariant_call( + sender: Address, + addr: Address, + bytes: &Bytes, + contracts: &ContractsByAddress, + traces: Option, + show_solidity: bool, + ) -> Self { + if let Some((name, abi)) = &contracts.get(&addr) { + if let Some(func) = abi.functions().find(|f| f.selector() == bytes[..4]) { + // skip the function selector when decoding + if let Ok(args) = func.abi_decode_input(&bytes[4..], false) { + return Self { + sender: Some(sender), + addr: Some(addr), + calldata: bytes.clone(), + contract_name: Some(name.clone()), + func_name: Some(func.name.clone()), + signature: Some(func.signature()), + args: Some( + foundry_common::fmt::format_tokens(&args).format(", ").to_string(), + ), + raw_args: Some( + foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string(), + ), + traces, + show_solidity, + }; + } + } + } + + Self { + sender: Some(sender), + addr: Some(addr), + calldata: bytes.clone(), + contract_name: None, + func_name: None, + signature: None, + args: None, + raw_args: None, + traces, + show_solidity: false, + } + } + + /// Creates counter example for a fuzz test failure. + pub fn from_fuzz_call( + bytes: Bytes, + args: Vec, + traces: Option, + ) -> Self { + Self { + sender: None, + addr: None, + calldata: bytes, + contract_name: None, + func_name: None, + signature: None, + args: Some(foundry_common::fmt::format_tokens(&args).format(", ").to_string()), + raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()), + traces, + show_solidity: false, + } + } +} + +impl fmt::Display for BaseCounterExample { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Display counterexample as solidity. + if self.show_solidity { + if let (Some(sender), Some(contract), Some(address), Some(func_name), Some(args)) = + (&self.sender, &self.contract_name, &self.addr, &self.func_name, &self.raw_args) + { + writeln!(f, "\t\tvm.prank({sender});")?; + write!( + f, + "\t\t{}({}).{}({});", + contract.split_once(':').map_or(contract.as_str(), |(_, contract)| contract), + address, + func_name, + args + )?; + + return Ok(()) + } + } + + // Regular counterexample display. + if let Some(sender) = self.sender { + write!(f, "\t\tsender={sender} addr=")? + } + + if let Some(name) = &self.contract_name { + write!(f, "[{name}]")? + } + + if let Some(addr) = &self.addr { + write!(f, "{addr} ")? + } + + if let Some(sig) = &self.signature { + write!(f, "calldata={sig}")? + } else { + write!(f, "calldata={}", &self.calldata)? + } + + if let Some(args) = &self.args { + write!(f, " args=[{args}]") + } else { + write!(f, " args=[]") + } + } +} + +/// The outcome of a fuzz test +#[derive(Debug)] +pub struct FuzzTestResult { + /// we keep this for the debugger + pub first_case: FuzzCase, + /// Gas usage (gas_used, call_stipend) per cases + pub gas_by_case: Vec<(u64, u64)>, + /// Whether the test case was successful. This means that the transaction executed + /// properly, or that there was a revert and that the test was expected to fail + /// (prefixed with `testFail`) + pub success: bool, + /// Whether the test case was skipped. `reason` will contain the skip reason, if any. + pub skipped: bool, + + /// If there was a revert, this field will be populated. Note that the test can + /// still be successful (i.e self.success == true) when it's expected to fail. + pub reason: Option, + + /// Minimal reproduction test case for failing fuzz tests + pub counterexample: Option, + + /// Any captured & parsed as strings logs along the test's execution which should + /// be printed to the user. + pub logs: Vec, + + /// Labeled addresses + pub labeled_addresses: AddressHashMap, + + /// Exemplary traces for a fuzz run of the test function + /// + /// **Note** We only store a single trace of a successful fuzz call, otherwise we would get + /// `num(fuzz_cases)` traces, one for each run, which is neither helpful nor performant. + pub traces: Option, + + /// Additional traces used for gas report construction. + /// Those traces should not be displayed. + pub gas_report_traces: Vec, + + /// Raw coverage info + pub coverage: Option, + + /// Breakpoints for debugger. Correspond to the same fuzz case as `traces`. + pub breakpoints: Option, + + // Deprecated cheatcodes mapped to their replacements. + pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, +} + +impl FuzzTestResult { + /// Returns the median gas of all test cases + pub fn median_gas(&self, with_stipend: bool) -> u64 { + let mut values = self.gas_values(with_stipend); + values.sort_unstable(); + calc::median_sorted(&values) + } + + /// Returns the average gas use of all test cases + pub fn mean_gas(&self, with_stipend: bool) -> u64 { + let mut values = self.gas_values(with_stipend); + values.sort_unstable(); + calc::mean(&values) + } + + fn gas_values(&self, with_stipend: bool) -> Vec { + self.gas_by_case + .iter() + .map(|gas| if with_stipend { gas.0 } else { gas.0.saturating_sub(gas.1) }) + .collect() + } +} + +/// Data of a single fuzz test case +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct FuzzCase { + /// The calldata used for this fuzz test + pub calldata: Bytes, + /// Consumed gas + pub gas: u64, + /// The initial gas stipend for the transaction + pub stipend: u64, +} + +/// Container type for all successful test cases +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +pub struct FuzzedCases { + cases: Vec, +} + +impl FuzzedCases { + #[inline] + pub fn new(mut cases: Vec) -> Self { + cases.sort_by_key(|c| c.gas); + Self { cases } + } + + #[inline] + pub fn cases(&self) -> &[FuzzCase] { + &self.cases + } + + #[inline] + pub fn into_cases(self) -> Vec { + self.cases + } + + /// Get the last [FuzzCase] + #[inline] + pub fn last(&self) -> Option<&FuzzCase> { + self.cases.last() + } + + /// Returns the median gas of all test cases + #[inline] + pub fn median_gas(&self, with_stipend: bool) -> u64 { + let mut values = self.gas_values(with_stipend); + values.sort_unstable(); + calc::median_sorted(&values) + } + + /// Returns the average gas use of all test cases + #[inline] + pub fn mean_gas(&self, with_stipend: bool) -> u64 { + let mut values = self.gas_values(with_stipend); + values.sort_unstable(); + calc::mean(&values) + } + + #[inline] + fn gas_values(&self, with_stipend: bool) -> Vec { + self.cases + .iter() + .map(|c| if with_stipend { c.gas } else { c.gas.saturating_sub(c.stipend) }) + .collect() + } + + /// Returns the case with the highest gas usage + #[inline] + pub fn highest(&self) -> Option<&FuzzCase> { + self.cases.last() + } + + /// Returns the case with the lowest gas usage + #[inline] + pub fn lowest(&self) -> Option<&FuzzCase> { + self.cases.first() + } + + /// Returns the highest amount of gas spent on a fuzz case + #[inline] + pub fn highest_gas(&self, with_stipend: bool) -> u64 { + self.highest() + .map(|c| if with_stipend { c.gas } else { c.gas - c.stipend }) + .unwrap_or_default() + } + + /// Returns the lowest amount of gas spent on a fuzz case + #[inline] + pub fn lowest_gas(&self) -> u64 { + self.lowest().map(|c| c.gas).unwrap_or_default() + } +} + +/// Fixtures to be used for fuzz tests. +/// +/// The key represents name of the fuzzed parameter, value holds possible fuzzed values. +/// For example, for a fixture function declared as +/// `function fixture_sender() external returns (address[] memory senders)` +/// the fuzz fixtures will contain `sender` key with `senders` array as value +#[derive(Clone, Default, Debug)] +pub struct FuzzFixtures { + inner: Arc>, +} + +impl FuzzFixtures { + pub fn new(fixtures: HashMap) -> Self { + Self { inner: Arc::new(fixtures) } + } + + /// Returns configured fixtures for `param_name` fuzzed parameter. + pub fn param_fixtures(&self, param_name: &str) -> Option<&[DynSolValue]> { + if let Some(param_fixtures) = self.inner.get(&normalize_fixture(param_name)) { + param_fixtures.as_fixed_array().or_else(|| param_fixtures.as_array()) + } else { + None + } + } +} + +/// Extracts fixture name from a function name. +/// For example: fixtures defined in `fixture_Owner` function will be applied for `owner` parameter. +pub fn fixture_name(function_name: String) -> String { + normalize_fixture(function_name.strip_prefix("fixture").unwrap()) +} + +/// Normalize fixture parameter name, for example `_Owner` to `owner`. +fn normalize_fixture(param_name: &str) -> String { + param_name.trim_matches('_').to_ascii_lowercase() +} diff --git a/crates/evm/fuzz/src/strategies/calldata.rs b/crates/evm/fuzz/src/strategies/calldata.rs new file mode 100644 index 0000000000000..6d9c0340bdca9 --- /dev/null +++ b/crates/evm/fuzz/src/strategies/calldata.rs @@ -0,0 +1,88 @@ +use crate::{ + strategies::{fuzz_param_from_state, fuzz_param_with_fixtures, EvmFuzzState}, + FuzzFixtures, +}; +use alloy_dyn_abi::JsonAbiExt; +use alloy_json_abi::Function; +use alloy_primitives::Bytes; +use proptest::prelude::Strategy; + +/// Given a function, it returns a strategy which generates valid calldata +/// for that function's input types, following declared test fixtures. +pub fn fuzz_calldata(func: Function, fuzz_fixtures: &FuzzFixtures) -> impl Strategy { + // We need to compose all the strategies generated for each parameter in all + // possible combinations, accounting any parameter declared fixture + let strats = func + .inputs + .iter() + .map(|input| { + fuzz_param_with_fixtures( + &input.selector_type().parse().unwrap(), + fuzz_fixtures.param_fixtures(&input.name), + &input.name, + ) + }) + .collect::>(); + strats.prop_map(move |values| { + func.abi_encode_input(&values) + .unwrap_or_else(|_| { + panic!( + "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, values + ) + }) + .into() + }) +} + +/// Given a function and some state, it returns a strategy which generated valid calldata for the +/// given function's input types, based on state taken from the EVM. +pub fn fuzz_calldata_from_state( + func: Function, + state: &EvmFuzzState, +) -> impl Strategy { + let strats = func + .inputs + .iter() + .map(|input| fuzz_param_from_state(&input.selector_type().parse().unwrap(), state)) + .collect::>(); + strats + .prop_map(move |values| { + func.abi_encode_input(&values) + .unwrap_or_else(|_| { + panic!( + "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, values + ) + }) + .into() + }) + .no_shrink() +} + +#[cfg(test)] +mod tests { + use crate::{strategies::fuzz_calldata, FuzzFixtures}; + use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; + use alloy_json_abi::Function; + use alloy_primitives::{map::HashMap, Address}; + use proptest::prelude::Strategy; + + #[test] + fn can_fuzz_with_fixtures() { + let function = Function::parse("test_fuzzed_address(address addressFixture)").unwrap(); + + let address_fixture = DynSolValue::Address(Address::random()); + let mut fixtures = HashMap::default(); + fixtures.insert( + "addressFixture".to_string(), + DynSolValue::Array(vec![address_fixture.clone()]), + ); + + let expected = function.abi_encode_input(&[address_fixture]).unwrap(); + let strategy = fuzz_calldata(function, &FuzzFixtures::new(fixtures)); + let _ = strategy.prop_map(move |fuzzed| { + assert_eq!(expected, fuzzed); + }); + } +} diff --git a/crates/evm/src/fuzz/strategies/int.rs b/crates/evm/fuzz/src/strategies/int.rs similarity index 63% rename from crates/evm/src/fuzz/strategies/int.rs rename to crates/evm/fuzz/src/strategies/int.rs index 0d6122199ee3e..3732de0617325 100644 --- a/crates/evm/src/fuzz/strategies/int.rs +++ b/crates/evm/fuzz/src/strategies/int.rs @@ -1,13 +1,12 @@ -use ethers::{core::rand::Rng, prelude::Sign}; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::{Sign, I256, U256}; use proptest::{ strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; - -use ethers::types::{I256, U256}; +use rand::Rng; /// Value tree for signed ints (up to int256). -/// This is very similar to [proptest::BinarySearch] pub struct IntValueTree { /// Lower base (by absolute value) lo: I256, @@ -25,12 +24,12 @@ impl IntValueTree { /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. fn new(start: I256, fixed: bool) -> Self { - Self { lo: I256::zero(), curr: start, hi: start, fixed } + Self { lo: I256::ZERO, curr: start, hi: start, fixed } } fn reposition(&mut self) -> bool { let interval = self.hi - self.lo; - let new_mid = self.lo + interval / I256::from(2); + let new_mid = self.lo + interval / I256::from_raw(U256::from(2)); if new_mid == self.curr { false @@ -56,20 +55,23 @@ impl ValueTree for IntValueTree { } fn simplify(&mut self) -> bool { - if self.fixed || !IntValueTree::magnitude_greater(self.hi, self.lo) { + if self.fixed || !Self::magnitude_greater(self.hi, self.lo) { return false } - self.hi = self.curr; self.reposition() } fn complicate(&mut self) -> bool { - if self.fixed || !IntValueTree::magnitude_greater(self.hi, self.lo) { + if self.fixed || !Self::magnitude_greater(self.hi, self.lo) { return false } - self.lo = self.curr + if self.hi.is_negative() { I256::minus_one() } else { I256::one() }; + self.lo = if self.curr != I256::MIN && self.curr != I256::MAX { + self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE } + } else { + self.curr + }; self.reposition() } @@ -78,15 +80,23 @@ impl ValueTree for IntValueTree { /// Value tree for signed ints (up to int256). /// The strategy combines 3 different strategies, each assigned a specific weight: /// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` -/// param). Then generate a value for this bit size. +/// param). Then generate a value for this bit size. /// 2. Generate a random value around the edges (+/- 3 around min, 0 and max possible value) /// 3. Generate a value from a predefined fixtures set +/// +/// To define int fixtures: +/// - return an array of possible values for a parameter named `amount` declare a function `function +/// fixture_amount() public returns (int32[] memory)`. +/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values +/// `function testFuzz_int32(int32 amount)`. +/// +/// If fixture is not a valid int type then error is raised and random value generated. #[derive(Debug)] pub struct IntStrategy { /// Bit size of int (e.g. 256) bits: usize, /// A set of fixtures to be generated - fixtures: Vec, + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, /// The weight for fixtures @@ -100,10 +110,10 @@ impl IntStrategy { /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Vec) -> Self { + pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { Self { bits, - fixtures, + fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, @@ -113,13 +123,15 @@ impl IntStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); - let offset = I256::from(rng.gen_range(0..4)); - let umax: U256 = (U256::from(1u8) << U256::from(self.bits - 1)) - 1; + let offset = I256::from_raw(U256::from(rng.gen_range(0..4))); + let umax: U256 = (U256::from(1) << (self.bits - 1)) - U256::from(1); // Choose if we want values around min, -0, +0, or max let kind = rng.gen_range(0..4); let start = match kind { - 0 => I256::overflowing_from_sign_and_abs(Sign::Negative, umax + 1).0 + offset, - 1 => -offset - I256::one(), + 0 => { + I256::overflowing_from_sign_and_abs(Sign::Negative, umax + U256::from(1)).0 + offset + } + 1 => -offset - I256::ONE, 2 => offset, 3 => I256::overflowing_from_sign_and_abs(Sign::Positive, umax).0 - offset, _ => unreachable!(), @@ -128,22 +140,32 @@ impl IntStrategy { } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { - // generate edge cases if there's no fixtures + // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_edge_tree(runner) + return self.generate_random_tree(runner) + } + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if let Some(int_fixture) = fixture.as_int() { + if int_fixture.1 == self.bits { + return Ok(IntValueTree::new(int_fixture.0, false)); + } } - let idx = runner.rng().gen_range(0..self.fixtures.len()); - Ok(IntValueTree::new(self.fixtures[idx], false)) + // If fixture is not a valid type, raise error and generate random value. + error!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits)); + self.generate_random_tree(runner) } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); + // generate random number of bits uniformly let bits = rng.gen_range(0..=self.bits); if bits == 0 { - return Ok(IntValueTree::new(I256::zero(), false)) + return Ok(IntValueTree::new(I256::ZERO, false)) } // init 2 128-bit randoms @@ -167,10 +189,11 @@ impl IntStrategy { inner[1] = (lower >> 64) as u64; inner[2] = (higher & mask64) as u64; inner[3] = (higher >> 64) as u64; - let sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative }; + // we have a small bias here, i.e. intN::min will never be generated // but it's ok since it's generated in `fn generate_edge_tree(...)` - let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256(inner)); + let sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative }; + let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256::from_limbs(inner)); Ok(IntValueTree::new(start, false)) } @@ -191,3 +214,25 @@ impl Strategy for IntStrategy { } } } + +#[cfg(test)] +mod tests { + use crate::strategies::int::IntValueTree; + use alloy_primitives::I256; + use proptest::strategy::ValueTree; + + #[test] + fn test_int_tree_complicate_should_not_overflow() { + let mut int_tree = IntValueTree::new(I256::MAX, false); + assert_eq!(int_tree.hi, I256::MAX); + assert_eq!(int_tree.curr, I256::MAX); + int_tree.complicate(); + assert_eq!(int_tree.lo, I256::MAX); + + let mut int_tree = IntValueTree::new(I256::MIN, false); + assert_eq!(int_tree.hi, I256::MIN); + assert_eq!(int_tree.curr, I256::MIN); + int_tree.complicate(); + assert_eq!(int_tree.lo, I256::MIN); + } +} diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs new file mode 100644 index 0000000000000..c7d04dd1ab02a --- /dev/null +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -0,0 +1,127 @@ +use super::{fuzz_calldata, fuzz_param_from_state}; +use crate::{ + invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters}, + strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState}, + FuzzFixtures, +}; +use alloy_json_abi::Function; +use alloy_primitives::Address; +use parking_lot::RwLock; +use proptest::prelude::*; +use rand::seq::IteratorRandom; +use std::{rc::Rc, sync::Arc}; + +/// Given a target address, we generate random calldata. +pub fn override_call_strat( + fuzz_state: EvmFuzzState, + contracts: FuzzRunIdentifiedContracts, + target: Arc>, + fuzz_fixtures: FuzzFixtures, +) -> impl Strategy + Send + Sync + 'static { + let contracts_ref = contracts.targets.clone(); + proptest::prop_oneof![ + 80 => proptest::strategy::LazyJust::new(move || *target.read()), + 20 => any::() + .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())), + ] + .prop_flat_map(move |target_address| { + let fuzz_state = fuzz_state.clone(); + let fuzz_fixtures = fuzz_fixtures.clone(); + + let func = { + let contracts = contracts.targets.lock(); + let contract = contracts.get(&target_address).unwrap_or_else(|| { + // Choose a random contract if target selected by lazy strategy is not in fuzz run + // identified contracts. This can happen when contract is created in `setUp` call + // but is not included in targetContracts. + contracts.values().choose(&mut rand::thread_rng()).unwrap() + }); + let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect(); + any::().prop_map(move |index| index.get(&fuzzed_functions).clone()) + }; + + func.prop_flat_map(move |func| { + fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func) + }) + }) +} + +/// Creates the invariant strategy. +/// +/// Given the known and future contracts, it generates the next call by fuzzing the `caller`, +/// `calldata` and `target`. The generated data is evaluated lazily for every single call to fully +/// leverage the evolving fuzz dictionary. +/// +/// The fuzzed parameters can be filtered through different methods implemented in the test +/// contract: +/// +/// `targetContracts()`, `targetSenders()`, `excludeContracts()`, `targetSelectors()` +pub fn invariant_strat( + fuzz_state: EvmFuzzState, + senders: SenderFilters, + contracts: FuzzRunIdentifiedContracts, + dictionary_weight: u32, + fuzz_fixtures: FuzzFixtures, +) -> impl Strategy { + let senders = Rc::new(senders); + any::() + .prop_flat_map(move |selector| { + let contracts = contracts.targets.lock(); + let functions = contracts.fuzzed_functions(); + let (target_address, target_function) = selector.select(functions); + let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight); + let call_details = fuzz_contract_with_calldata( + &fuzz_state, + &fuzz_fixtures, + *target_address, + target_function.clone(), + ); + (sender, call_details) + }) + .prop_map(|(sender, call_details)| BasicTxDetails { sender, call_details }) +} + +/// Strategy to select a sender address: +/// * If `senders` is empty, then it's either a random address (10%) or from the dictionary (90%). +/// * If `senders` is not empty, a random address is chosen from the list of senders. +fn select_random_sender( + fuzz_state: &EvmFuzzState, + senders: Rc, + dictionary_weight: u32, +) -> impl Strategy { + if !senders.targeted.is_empty() { + any::().prop_map(move |index| *index.get(&senders.targeted)).boxed() + } else { + assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100"); + proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address), + dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state), + ] + .prop_map(move |addr| addr.as_address().unwrap()) + // Too many exclusions can slow down testing. + .prop_filter("excluded sender", move |addr| !senders.excluded.contains(addr)) + .boxed() + } +} + +/// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata +/// for that function's input types. +pub fn fuzz_contract_with_calldata( + fuzz_state: &EvmFuzzState, + fuzz_fixtures: &FuzzFixtures, + target: Address, + func: Function, +) -> impl Strategy { + // We need to compose all the strategies generated for each parameter in all possible + // combinations. + // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning. + #[allow(clippy::arc_with_non_send_sync)] + prop_oneof![ + 60 => fuzz_calldata(func.clone(), fuzz_fixtures), + 40 => fuzz_calldata_from_state(func, fuzz_state), + ] + .prop_map(move |calldata| { + trace!(input=?calldata); + CallDetails { target, calldata } + }) +} diff --git a/crates/evm/fuzz/src/strategies/mod.rs b/crates/evm/fuzz/src/strategies/mod.rs new file mode 100644 index 0000000000000..1d8e647a52f3f --- /dev/null +++ b/crates/evm/fuzz/src/strategies/mod.rs @@ -0,0 +1,17 @@ +mod int; +pub use int::IntStrategy; + +mod uint; +pub use uint::UintStrategy; + +mod param; +pub use param::{fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures}; + +mod calldata; +pub use calldata::{fuzz_calldata, fuzz_calldata_from_state}; + +mod state; +pub use state::EvmFuzzState; + +mod invariants; +pub use invariants::{fuzz_contract_with_calldata, invariant_strat, override_call_strat}; diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs new file mode 100644 index 0000000000000..43dcdae7b00f3 --- /dev/null +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -0,0 +1,254 @@ +use super::state::EvmFuzzState; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::{Address, B256, I256, U256}; +use proptest::prelude::*; +use rand::{rngs::StdRng, SeedableRng}; + +/// The max length of arrays we fuzz for is 256. +const MAX_ARRAY_LEN: usize = 256; + +/// Given a parameter type, returns a strategy for generating values for that type. +/// +/// See [`fuzz_param_with_fixtures`] for more information. +pub fn fuzz_param(param: &DynSolType) -> BoxedStrategy { + fuzz_param_inner(param, None) +} + +/// Given a parameter type and configured fixtures for param name, returns a strategy for generating +/// values for that type. +/// +/// Fixtures can be currently generated for uint, int, address, bytes and +/// string types and are defined for parameter name. +/// For example, fixtures for parameter `owner` of type `address` can be defined in a function with +/// a `function fixture_owner() public returns (address[] memory)` signature. +/// +/// Fixtures are matched on parameter name, hence fixtures defined in +/// `fixture_owner` function can be used in a fuzzed test function with a signature like +/// `function testFuzz_ownerAddress(address owner, uint amount)`. +/// +/// Raises an error if all the fixture types are not of the same type as the input parameter. +/// +/// Works with ABI Encoder v2 tuples. +pub fn fuzz_param_with_fixtures( + param: &DynSolType, + fixtures: Option<&[DynSolValue]>, + name: &str, +) -> BoxedStrategy { + fuzz_param_inner(param, fixtures.map(|f| (f, name))) +} + +fn fuzz_param_inner( + param: &DynSolType, + mut fuzz_fixtures: Option<(&[DynSolValue], &str)>, +) -> BoxedStrategy { + if let Some((fixtures, name)) = fuzz_fixtures { + if !fixtures.iter().all(|f| f.matches(param)) { + error!("fixtures for {name:?} do not match type {param}"); + fuzz_fixtures = None; + } + } + let fuzz_fixtures = fuzz_fixtures.map(|(f, _)| f); + + let value = || { + let default_strategy = DynSolValue::type_strategy(param); + if let Some(fixtures) = fuzz_fixtures { + proptest::prop_oneof![ + 50 => { + let fixtures = fixtures.to_vec(); + any::() + .prop_map(move |index| index.get(&fixtures).clone()) + }, + 50 => default_strategy, + ] + .boxed() + } else { + default_strategy.boxed() + } + }; + + match *param { + DynSolType::Address => value(), + DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures) + .prop_map(move |x| DynSolValue::Int(x, n)) + .boxed(), + DynSolType::Uint(n @ 8..=256) => super::UintStrategy::new(n, fuzz_fixtures) + .prop_map(move |x| DynSolValue::Uint(x, n)) + .boxed(), + DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), + DynSolType::Bytes => value(), + DynSolType::FixedBytes(_size @ 1..=32) => value(), + DynSolType::String => value() + .prop_map(move |value| { + DynSolValue::String( + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), + ) + }) + .boxed(), + DynSolType::Tuple(ref params) => params + .iter() + .map(|param| fuzz_param_inner(param, None)) + .collect::>() + .prop_map(DynSolValue::Tuple) + .boxed(), + DynSolType::FixedArray(ref param, size) => { + proptest::collection::vec(fuzz_param_inner(param, None), size) + .prop_map(DynSolValue::FixedArray) + .boxed() + } + DynSolType::Array(ref param) => { + proptest::collection::vec(fuzz_param_inner(param, None), 0..MAX_ARRAY_LEN) + .prop_map(DynSolValue::Array) + .boxed() + } + _ => panic!("unsupported fuzz param type: {param}"), + } +} + +/// Given a parameter type, returns a strategy for generating values for that type, given some EVM +/// fuzz state. +/// +/// Works with ABI Encoder v2 tuples. +pub fn fuzz_param_from_state( + param: &DynSolType, + state: &EvmFuzzState, +) -> BoxedStrategy { + // Value strategy that uses the state. + let value = || { + let state = state.clone(); + let param = param.clone(); + // Generate a bias and use it to pick samples or non-persistent values (50 / 50). + // Use `Index` instead of `Selector` when selecting a value to avoid iterating over the + // entire dictionary. + any::<(bool, prop::sample::Index)>().prop_map(move |(bias, index)| { + let state = state.dictionary_read(); + let values = if bias { state.samples(¶m) } else { None } + .unwrap_or_else(|| state.values()) + .as_slice(); + values[index.index(values.len())] + }) + }; + + // Convert the value based on the parameter type + match *param { + DynSolType::Address => { + let deployed_libs = state.deployed_libs.clone(); + value() + .prop_map(move |value| { + let mut fuzzed_addr = Address::from_word(value); + if !deployed_libs.contains(&fuzzed_addr) { + DynSolValue::Address(fuzzed_addr) + } else { + let mut rng = StdRng::seed_from_u64(0x1337); // use deterministic rng + + // Do not use addresses of deployed libraries as fuzz input, instead return + // a deterministically random address. We cannot filter out this value (via + // `prop_filter_map`) as proptest can invoke this closure after test + // execution, and returning a `None` will cause it to panic. + // See and . + loop { + fuzzed_addr.randomize_with(&mut rng); + if !deployed_libs.contains(&fuzzed_addr) { + break; + } + } + + DynSolValue::Address(fuzzed_addr) + } + }) + .boxed() + } + DynSolType::Function => value() + .prop_map(move |value| { + DynSolValue::Function(alloy_primitives::Function::from_word(value)) + }) + .boxed(), + DynSolType::FixedBytes(size @ 1..=32) => value() + .prop_map(move |mut v| { + v[size..].fill(0); + DynSolValue::FixedBytes(B256::from(v), size) + }) + .boxed(), + DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), + DynSolType::String => DynSolValue::type_strategy(param) + .prop_map(move |value| { + DynSolValue::String( + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), + ) + }) + .boxed(), + DynSolType::Bytes => { + value().prop_map(move |value| DynSolValue::Bytes(value.0.into())).boxed() + } + DynSolType::Int(n @ 8..=256) => match n / 8 { + 32 => value() + .prop_map(move |value| DynSolValue::Int(I256::from_raw(value.into()), 256)) + .boxed(), + 1..=31 => value() + .prop_map(move |value| { + // Generate a uintN in the correct range, then shift it to the range of intN + // by subtracting 2^(N-1) + let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n); + let max_int_plus1 = U256::from(1).wrapping_shl(n - 1); + let num = I256::from_raw(uint.wrapping_sub(max_int_plus1)); + DynSolValue::Int(num, n) + }) + .boxed(), + _ => unreachable!(), + }, + DynSolType::Uint(n @ 8..=256) => match n / 8 { + 32 => value() + .prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value.0), 256)) + .boxed(), + 1..=31 => value() + .prop_map(move |value| { + let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n); + DynSolValue::Uint(uint, n) + }) + .boxed(), + _ => unreachable!(), + }, + DynSolType::Tuple(ref params) => params + .iter() + .map(|p| fuzz_param_from_state(p, state)) + .collect::>() + .prop_map(DynSolValue::Tuple) + .boxed(), + DynSolType::FixedArray(ref param, size) => { + proptest::collection::vec(fuzz_param_from_state(param, state), size) + .prop_map(DynSolValue::FixedArray) + .boxed() + } + DynSolType::Array(ref param) => { + proptest::collection::vec(fuzz_param_from_state(param, state), 0..MAX_ARRAY_LEN) + .prop_map(DynSolValue::Array) + .boxed() + } + _ => panic!("unsupported fuzz param type: {param}"), + } +} + +#[cfg(test)] +mod tests { + use crate::{ + strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, + FuzzFixtures, + }; + use foundry_common::abi::get_func; + use foundry_config::FuzzDictionaryConfig; + use revm::db::{CacheDB, EmptyDB}; + + #[test] + fn can_fuzz_array() { + let f = "testArray(uint64[2] calldata values)"; + let func = get_func(f).unwrap(); + let db = CacheDB::new(EmptyDB::default()); + let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), &[]); + let strategy = proptest::prop_oneof![ + 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default()), + 40 => fuzz_calldata_from_state(func, &state), + ]; + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + let mut runner = proptest::test_runner::TestRunner::new(cfg); + let _ = runner.run(&strategy, |_| Ok(())); + } +} diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs new file mode 100644 index 0000000000000..9bcca6aa16d2e --- /dev/null +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -0,0 +1,386 @@ +use crate::invariant::{BasicTxDetails, FuzzRunIdentifiedContracts}; +use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt}; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{ + map::{AddressIndexSet, B256IndexSet, HashMap}, + Address, Bytes, Log, B256, U256, +}; +use foundry_config::FuzzDictionaryConfig; +use foundry_evm_core::utils::StateChangeset; +use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock}; +use revm::{ + db::{CacheDB, DatabaseRef, DbAccount}, + interpreter::opcode, + primitives::AccountInfo, +}; +use std::{collections::BTreeMap, fmt, sync::Arc}; + +/// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB). +/// +/// This is to limit the performance impact of fuzz tests that might deploy arbitrarily sized +/// bytecode (as is the case with Solmate). +const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024; + +/// A set of arbitrary 32 byte data from the VM used to generate values for the strategy. +/// +/// Wrapped in a shareable container. +#[derive(Clone, Debug)] +pub struct EvmFuzzState { + inner: Arc>, + /// Addresses of external libraries deployed in test setup, excluded from fuzz test inputs. + pub deployed_libs: Vec
, +} + +impl EvmFuzzState { + pub fn new( + db: &CacheDB, + config: FuzzDictionaryConfig, + deployed_libs: &[Address], + ) -> Self { + // Sort accounts to ensure deterministic dictionary generation from the same setUp state. + let mut accs = db.accounts.iter().collect::>(); + accs.sort_by_key(|(address, _)| *address); + + // Create fuzz dictionary and insert values from db state. + let mut dictionary = FuzzDictionary::new(config); + dictionary.insert_db_values(accs); + Self { inner: Arc::new(RwLock::new(dictionary)), deployed_libs: deployed_libs.to_vec() } + } + + pub fn collect_values(&self, values: impl IntoIterator) { + let mut dict = self.inner.write(); + for value in values { + dict.insert_value(value); + } + } + + /// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to + /// the given [FuzzDictionaryConfig]. + pub fn collect_values_from_call( + &self, + fuzzed_contracts: &FuzzRunIdentifiedContracts, + tx: &BasicTxDetails, + result: &Bytes, + logs: &[Log], + state_changeset: &StateChangeset, + run_depth: u32, + ) { + let mut dict = self.inner.write(); + { + let targets = fuzzed_contracts.targets.lock(); + let (target_abi, target_function) = targets.fuzzed_artifacts(tx); + dict.insert_logs_values(target_abi, logs, run_depth); + dict.insert_result_values(target_function, result, run_depth); + } + dict.insert_new_state_values(state_changeset); + } + + /// Removes all newly added entries from the dictionary. + /// + /// Should be called between fuzz/invariant runs to avoid accumulating data derived from fuzz + /// inputs. + pub fn revert(&self) { + self.inner.write().revert(); + } + + pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> { + self.inner.read() + } + + /// Logs stats about the current state. + pub fn log_stats(&self) { + self.inner.read().log_stats(); + } +} + +// We're using `IndexSet` to have a stable element order when restoring persisted state, as well as +// for performance when iterating over the sets. +#[derive(Default)] +pub struct FuzzDictionary { + /// Collected state values. + state_values: B256IndexSet, + /// Addresses that already had their PUSH bytes collected. + addresses: AddressIndexSet, + /// Configuration for the dictionary. + config: FuzzDictionaryConfig, + /// Number of state values initially collected from db. + /// Used to revert new collected values at the end of each run. + db_state_values: usize, + /// Number of address values initially collected from db. + /// Used to revert new collected addresses at the end of each run. + db_addresses: usize, + /// Sample typed values that are collected from call result and used across invariant runs. + sample_values: HashMap, + + misses: usize, + hits: usize, +} + +impl fmt::Debug for FuzzDictionary { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FuzzDictionary") + .field("state_values", &self.state_values.len()) + .field("addresses", &self.addresses) + .finish() + } +} + +impl FuzzDictionary { + pub fn new(config: FuzzDictionaryConfig) -> Self { + let mut dictionary = Self { config, ..Default::default() }; + dictionary.prefill(); + dictionary + } + + /// Insert common values into the dictionary at initialization. + fn prefill(&mut self) { + self.insert_value(B256::ZERO); + } + + /// Insert values from initial db state into fuzz dictionary. + /// These values are persisted across invariant runs. + fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) { + for (address, account) in db_state { + // Insert basic account information + self.insert_value(address.into_word()); + // Insert push bytes + self.insert_push_bytes_values(address, &account.info); + // Insert storage values. + if self.config.include_storage { + // Sort storage values before inserting to ensure deterministic dictionary. + let values = account.storage.iter().collect::>(); + for (slot, value) in values { + self.insert_storage_value(slot, value); + } + } + } + + // We need at least some state data if DB is empty, + // otherwise we can't select random data for state fuzzing. + if self.values().is_empty() { + // Prefill with a random address. + self.insert_value(Address::random().into_word()); + } + + // Record number of values and addresses inserted from db to be used for reverting at the + // end of each run. + self.db_state_values = self.state_values.len(); + self.db_addresses = self.addresses.len(); + } + + /// Insert values collected from call result into fuzz dictionary. + fn insert_result_values( + &mut self, + function: Option<&Function>, + result: &Bytes, + run_depth: u32, + ) { + if let Some(function) = function { + if !function.outputs.is_empty() { + // Decode result and collect samples to be used in subsequent fuzz runs. + if let Ok(decoded_result) = function.abi_decode_output(result, false) { + self.insert_sample_values(decoded_result, run_depth); + } + } + } + } + + /// Insert values from call log topics and data into fuzz dictionary. + fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) { + let mut samples = Vec::new(); + // Decode logs with known events and collect samples from indexed fields and event body. + for log in logs { + let mut log_decoded = false; + // Try to decode log with events from contract abi. + if let Some(abi) = abi { + for event in abi.events() { + if let Ok(decoded_event) = event.decode_log(log, false) { + samples.extend(decoded_event.indexed); + samples.extend(decoded_event.body); + log_decoded = true; + break; + } + } + } + + // If we weren't able to decode event then we insert raw data in fuzz dictionary. + if !log_decoded { + for &topic in log.topics() { + self.insert_value(topic); + } + let chunks = log.data.data.chunks_exact(32); + let rem = chunks.remainder(); + for chunk in chunks { + self.insert_value(chunk.try_into().unwrap()); + } + if !rem.is_empty() { + self.insert_value(B256::right_padding_from(rem)); + } + } + } + + // Insert samples collected from current call in fuzz dictionary. + self.insert_sample_values(samples, run_depth); + } + + /// Insert values from call state changeset into fuzz dictionary. + /// These values are removed at the end of current run. + fn insert_new_state_values(&mut self, state_changeset: &StateChangeset) { + for (address, account) in state_changeset { + // Insert basic account information. + self.insert_value(address.into_word()); + // Insert push bytes. + self.insert_push_bytes_values(address, &account.info); + // Insert storage values. + if self.config.include_storage { + for (slot, value) in &account.storage { + self.insert_storage_value(slot, &value.present_value); + } + } + } + } + + /// Insert values from push bytes into fuzz dictionary. + /// Values are collected only once for a given address. + /// If values are newly collected then they are removed at the end of current run. + fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) { + if self.config.include_push_bytes && !self.addresses.contains(address) { + // Insert push bytes + if let Some(code) = &account_info.code { + self.insert_address(*address); + self.collect_push_bytes(code.bytes_slice()); + } + } + } + + fn collect_push_bytes(&mut self, code: &[u8]) { + let mut i = 0; + let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT); + while i < len { + let op = code[i]; + if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { + let push_size = (op - opcode::PUSH1 + 1) as usize; + let push_start = i + 1; + let push_end = push_start + push_size; + // As a precaution, if a fuzz test deploys malformed bytecode (such as using + // `CREATE2`) this will terminate the loop early. + if push_start > code.len() || push_end > code.len() { + break; + } + + let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap(); + if push_value != U256::ZERO { + // Never add 0 to the dictionary as it's always present. + self.insert_value(push_value.into()); + + // Also add the value below and above the push value to the dictionary. + self.insert_value((push_value - U256::from(1)).into()); + + if push_value != U256::MAX { + self.insert_value((push_value + U256::from(1)).into()); + } + } + + i += push_size; + } + i += 1; + } + } + + /// Insert values from single storage slot and storage value into fuzz dictionary. + /// If storage values are newly collected then they are removed at the end of current run. + fn insert_storage_value(&mut self, storage_slot: &U256, storage_value: &U256) { + self.insert_value(B256::from(*storage_slot)); + self.insert_value(B256::from(*storage_value)); + // also add the value below and above the storage value to the dictionary. + if *storage_value != U256::ZERO { + let below_value = storage_value - U256::from(1); + self.insert_value(B256::from(below_value)); + } + if *storage_value != U256::MAX { + let above_value = storage_value + U256::from(1); + self.insert_value(B256::from(above_value)); + } + } + + /// Insert address into fuzz dictionary. + /// If address is newly collected then it is removed by index at the end of current run. + fn insert_address(&mut self, address: Address) { + if self.addresses.len() < self.config.max_fuzz_dictionary_addresses { + self.addresses.insert(address); + } + } + + /// Insert raw value into fuzz dictionary. + /// If value is newly collected then it is removed by index at the end of current run. + fn insert_value(&mut self, value: B256) { + if self.state_values.len() < self.config.max_fuzz_dictionary_values { + let new_value = self.state_values.insert(value); + let counter = if new_value { &mut self.misses } else { &mut self.hits }; + *counter += 1; + } + } + + /// Insert sample values that are reused across multiple runs. + /// The number of samples is limited to invariant run depth. + /// If collected samples limit is reached then values are inserted as regular values. + pub fn insert_sample_values( + &mut self, + sample_values: impl IntoIterator, + limit: u32, + ) { + for sample in sample_values { + if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) { + if let Some(values) = self.sample_values.get_mut(&sample_type) { + if values.len() < limit as usize { + values.insert(sample_value); + } else { + // Insert as state value (will be removed at the end of the run). + self.insert_value(sample_value); + } + } else { + self.sample_values.entry(sample_type).or_default().insert(sample_value); + } + } + } + } + + pub fn values(&self) -> &B256IndexSet { + &self.state_values + } + + pub fn len(&self) -> usize { + self.state_values.len() + } + + pub fn is_empty(&self) -> bool { + self.state_values.is_empty() + } + + #[inline] + pub fn samples(&self, param_type: &DynSolType) -> Option<&B256IndexSet> { + self.sample_values.get(param_type) + } + + #[inline] + pub fn addresses(&self) -> &AddressIndexSet { + &self.addresses + } + + /// Revert values and addresses collected during the run by truncating to initial db len. + pub fn revert(&mut self) { + self.state_values.truncate(self.db_state_values); + self.addresses.truncate(self.db_addresses); + } + + pub fn log_stats(&self) { + trace!( + addresses.len = self.addresses.len(), + sample.len = self.sample_values.len(), + state.len = self.state_values.len(), + state.misses = self.misses, + state.hits = self.hits, + "FuzzDictionary stats", + ); + } +} diff --git a/crates/evm/src/fuzz/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs similarity index 66% rename from crates/evm/src/fuzz/strategies/uint.rs rename to crates/evm/fuzz/src/strategies/uint.rs index 6438aa8c51794..af133efa00826 100644 --- a/crates/evm/src/fuzz/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -1,13 +1,12 @@ -use ethers::core::rand::Rng; +use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::U256; use proptest::{ strategy::{NewTree, Strategy, ValueTree}, test_runner::TestRunner, }; - -use ethers::types::U256; +use rand::Rng; /// Value tree for unsigned ints (up to uint256). -/// This is very similar to [proptest::BinarySearch] pub struct UintValueTree { /// Lower base lo: U256, @@ -25,12 +24,12 @@ impl UintValueTree { /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. fn new(start: U256, fixed: bool) -> Self { - Self { lo: 0.into(), curr: start, hi: start, fixed } + Self { lo: U256::ZERO, curr: start, hi: start, fixed } } fn reposition(&mut self) -> bool { let interval = self.hi - self.lo; - let new_mid = self.lo + interval / 2; + let new_mid = self.lo + interval / U256::from(2); if new_mid == self.curr { false @@ -52,7 +51,6 @@ impl ValueTree for UintValueTree { if self.fixed || (self.hi <= self.lo) { return false } - self.hi = self.curr; self.reposition() } @@ -62,7 +60,7 @@ impl ValueTree for UintValueTree { return false } - self.lo = self.curr + 1; + self.lo = self.curr + U256::from(1); self.reposition() } } @@ -70,15 +68,23 @@ impl ValueTree for UintValueTree { /// Value tree for unsigned ints (up to uint256). /// The strategy combines 3 different strategies, each assigned a specific weight: /// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` -/// param). Then generate a value for this bit size. +/// param). Then generate a value for this bit size. /// 2. Generate a random value around the edges (+/- 3 around 0 and max possible value) /// 3. Generate a value from a predefined fixtures set +/// +/// To define uint fixtures: +/// - return an array of possible values for a parameter named `amount` declare a function `function +/// fixture_amount() public returns (uint32[] memory)`. +/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values +/// `function testFuzz_uint32(uint32 amount)`. +/// +/// If fixture is not a valid uint type then error is raised and random value generated. #[derive(Debug)] pub struct UintStrategy { /// Bit size of uint (e.g. 256) bits: usize, /// A set of fixtures to be generated - fixtures: Vec, + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, /// The weight for fixtures @@ -92,10 +98,10 @@ impl UintStrategy { /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Vec) -> Self { + pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { Self { bits, - fixtures, + fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, @@ -104,32 +110,35 @@ impl UintStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); - // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); - let max = if self.bits < 256 { - (U256::from(1u8) << U256::from(self.bits)) - 1 - } else { - U256::MAX - }; - let start = if is_min { offset } else { max - offset }; - + let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; Ok(UintValueTree::new(start, false)) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { - // generate edge cases if there's no fixtures + // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_edge_tree(runner) + return self.generate_random_tree(runner) + } + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if let Some(uint_fixture) = fixture.as_uint() { + if uint_fixture.1 == self.bits { + return Ok(UintValueTree::new(uint_fixture.0, false)); + } } - let idx = runner.rng().gen_range(0..self.fixtures.len()); - Ok(UintValueTree::new(self.fixtures[idx], false)) + // If fixture is not a valid type, raise error and generate random value. + error!("{:?} is not a valid {} fixture", fixture, DynSolType::Uint(self.bits)); + self.generate_random_tree(runner) } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); + // generate random number of bits uniformly let bits = rng.gen_range(0..=self.bits); @@ -154,16 +163,23 @@ impl UintStrategy { inner[1] = (lower >> 64) as u64; inner[2] = (higher & mask64) as u64; inner[3] = (higher >> 64) as u64; - let start: U256 = U256(inner); + let start: U256 = U256::from_limbs(inner); Ok(UintValueTree::new(start, false)) } + + fn type_max(&self) -> U256 { + if self.bits < 256 { + (U256::from(1) << self.bits) - U256::from(1) + } else { + U256::MAX + } + } } impl Strategy for UintStrategy { type Tree = UintValueTree; type Value = U256; - fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; let bias = runner.rng().gen_range(0..total_weight); @@ -175,3 +191,19 @@ impl Strategy for UintStrategy { } } } + +#[cfg(test)] +mod tests { + use crate::strategies::uint::UintValueTree; + use alloy_primitives::U256; + use proptest::strategy::ValueTree; + + #[test] + fn test_uint_tree_complicate_max() { + let mut uint_tree = UintValueTree::new(U256::MAX, false); + assert_eq!(uint_tree.hi, U256::MAX); + assert_eq!(uint_tree.curr, U256::MAX); + uint_tree.complicate(); + assert_eq!(uint_tree.lo, U256::MIN); + } +} diff --git a/crates/evm/src/coverage/analysis.rs b/crates/evm/src/coverage/analysis.rs deleted file mode 100644 index b9f54a7499b90..0000000000000 --- a/crates/evm/src/coverage/analysis.rs +++ /dev/null @@ -1,636 +0,0 @@ -use super::{ContractId, CoverageItem, CoverageItemKind, SourceLocation}; -use ethers::solc::artifacts::ast::{self, Ast, Node, NodeType}; -use foundry_common::TestFunctionExt; -use semver::Version; -use std::collections::{HashMap, HashSet}; - -/// A visitor that walks the AST of a single contract and finds coverage items. -#[derive(Debug, Clone)] -pub struct ContractVisitor<'a> { - /// The source ID of the contract. - source_id: usize, - /// The source code that contains the AST being walked. - source: &'a str, - - /// The name of the contract being walked. - contract_name: String, - - /// The current branch ID - branch_id: usize, - /// Stores the last line we put in the items collection to ensure we don't push duplicate lines - last_line: usize, - - /// Coverage items - pub items: Vec, - - /// Node IDs of this contract's base contracts, as well as IDs for referenced contracts such as - /// libraries - pub base_contract_node_ids: HashSet, -} - -impl<'a> ContractVisitor<'a> { - pub fn new(source_id: usize, source: &'a str, contract_name: String) -> Self { - Self { - source_id, - source, - contract_name, - branch_id: 0, - last_line: 0, - items: Vec::new(), - base_contract_node_ids: HashSet::new(), - } - } - - pub fn visit(mut self, contract_ast: Node) -> eyre::Result { - let linearized_base_contracts: Vec = - contract_ast.attribute("linearizedBaseContracts").ok_or_else(|| { - eyre::eyre!( - "The contract's AST node is missing a list of linearized base contracts" - ) - })?; - - // We skip the first ID because that's the ID of the contract itself - self.base_contract_node_ids.extend(&linearized_base_contracts[1..]); - - // Find all functions and walk their AST - for node in contract_ast.nodes { - if node.node_type == NodeType::FunctionDefinition { - self.visit_function_definition(node.clone())?; - } - } - - Ok(self) - } - - fn visit_function_definition(&mut self, mut node: Node) -> eyre::Result<()> { - let name: String = - node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; - - // TODO(onbjerg): Re-enable constructor parsing when we walk both the deployment and runtime - // sourcemaps. Currently this fails because we are trying to look for anchors in the runtime - // sourcemap. - // TODO(onbjerg): Figure out why we cannot find anchors for the receive function - let kind: String = - node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; - if kind == "constructor" || kind == "receive" { - return Ok(()) - } - - match node.body.take() { - Some(body) => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Function { name }, - loc: self.source_location_for(&node.src), - hits: 0, - }); - self.visit_block(*body) - } - _ => Ok(()), - } - } - - fn visit_block(&mut self, node: Node) -> eyre::Result<()> { - let statements: Vec = node.attribute("statements").unwrap_or_default(); - - for statement in statements { - self.visit_statement(statement)?; - } - - Ok(()) - } - 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 => { - self.visit_block(node) - } - // Inline assembly block - NodeType::InlineAssembly => self.visit_block( - node.attribute("AST") - .ok_or_else(|| eyre::eyre!("inline assembly block with no AST attribute"))?, - ), - // Simple statements - NodeType::Break | - NodeType::Continue | - NodeType::EmitStatement | - NodeType::PlaceholderStatement | - NodeType::RevertStatement | - NodeType::YulAssignment | - NodeType::YulBreak | - NodeType::YulContinue | - NodeType::YulLeave => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - Ok(()) - } - - // Return with eventual subcall - NodeType::Return => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - if let Some(expr) = node.attribute("expression") { - self.visit_expression(expr)?; - } - Ok(()) - } - - // Variable declaration - NodeType::VariableDeclarationStatement => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - if let Some(expr) = node.attribute("initialValue") { - self.visit_expression(expr)?; - } - Ok(()) - } - // While loops - NodeType::DoWhileStatement | NodeType::WhileStatement => { - self.visit_expression( - node.attribute("condition") - .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, - )?; - - let body = - node.body.ok_or_else(|| eyre::eyre!("while statement had no body node"))?; - self.visit_block_or_statement(*body) - } - // For loops - NodeType::ForStatement => { - if let Some(stmt) = node.attribute("initializationExpression") { - self.visit_statement(stmt)?; - } - if let Some(expr) = node.attribute("condition") { - self.visit_expression(expr)?; - } - if let Some(stmt) = node.attribute("loopExpression") { - self.visit_statement(stmt)?; - } - - let body = - node.body.ok_or_else(|| eyre::eyre!("for statement had no body node"))?; - self.visit_block_or_statement(*body) - } - // Expression statement - NodeType::ExpressionStatement | NodeType::YulExpressionStatement => self - .visit_expression( - node.attribute("expression") - .ok_or_else(|| eyre::eyre!("expression statement had no expression"))?, - ), - // If statement - NodeType::IfStatement => { - self.visit_expression( - node.attribute("condition") - .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, - )?; - - let true_body: Node = node - .attribute("trueBody") - .ok_or_else(|| eyre::eyre!("if statement had no true body"))?; - - // We need to store the current branch ID here since visiting the body of either of - // the if blocks may increase `self.branch_id` in the case of nested if statements. - let branch_id = self.branch_id; - - // We increase the branch ID here such that nested branches do not use the same - // branch ID as we do - self.branch_id += 1; - - // 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( - ðers::solc::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 - let false_body: Option = node.attribute("falseBody"); - if let Some(false_body) = false_body { - self.visit_block_or_statement(false_body)?; - } - - Ok(()) - } - NodeType::YulIf => { - self.visit_expression( - node.attribute("condition") - .ok_or_else(|| eyre::eyre!("yul if statement had no condition"))?, - )?; - let body = node.body.ok_or_else(|| eyre::eyre!("yul if statement had no body"))?; - - // We need to store the current branch ID here since visiting the body of either of - // the if blocks may increase `self.branch_id` in the case of nested if statements. - let branch_id = self.branch_id; - - // We increase the branch ID here such that nested branches do not use the same - // branch ID as we do - self.branch_id += 1; - - self.push_item(CoverageItem { - kind: CoverageItemKind::Branch { branch_id, path_id: 0 }, - loc: self.source_location_for(&node.src), - hits: 0, - }); - self.visit_block(*body)?; - - Ok(()) - } - // Try-catch statement - 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"))?, - ) - } - _ => { - warn!("unexpected node type, expected a statement: {:?}", node.node_type); - Ok(()) - } - } - } - - fn visit_expression(&mut self, node: Node) -> eyre::Result<()> { - // TODO - // elementarytypenameexpression - // memberaccess - // newexpression - // tupleexpression - // yulfunctioncall - match node.node_type { - NodeType::Assignment | NodeType::UnaryOperation => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - Ok(()) - } - NodeType::BinaryOperation => { - self.push_item(CoverageItem { - kind: CoverageItemKind::Statement, - loc: self.source_location_for(&node.src), - hits: 0, - }); - - // visit left and right expressions - // There could possibly a function call in the left or right expression - // e.g: callFunc(a) + callFunc(b) - if let Some(expr) = node.attribute("leftExpression") { - self.visit_expression(expr)?; - } - - if let Some(expr) = node.attribute("rightExpression") { - self.visit_expression(expr)?; - } - - 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"); - match expr.as_ref().map(|expr| &expr.node_type) { - // Might be a require/assert call - Some(NodeType::Identifier) => { - 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; - } - } - // Might be a call to a library - Some(NodeType::MemberAccess) => { - let referenced_declaration_id = expr - .and_then(|expr| expr.attribute("expression")) - .and_then(|subexpr: Node| subexpr.attribute("referencedDeclaration")); - if let Some(id) = referenced_declaration_id { - self.base_contract_node_ids.insert(id); - } - } - _ => (), - } - - 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 | - NodeType::IndexAccess | - NodeType::IndexRangeAccess | - NodeType::Literal | - NodeType::YulLiteralValue | - NodeType::YulIdentifier => Ok(()), - _ => { - warn!("unexpected node type, expected an expression: {:?}", node.node_type); - Ok(()) - } - } - } - - fn visit_block_or_statement(&mut self, node: Node) -> eyre::Result<()> { - match node.node_type { - NodeType::Block => self.visit_block(node), - NodeType::Break | - NodeType::Continue | - NodeType::DoWhileStatement | - NodeType::EmitStatement | - NodeType::ExpressionStatement | - NodeType::ForStatement | - NodeType::IfStatement | - NodeType::InlineAssembly | - NodeType::PlaceholderStatement | - NodeType::Return | - NodeType::RevertStatement | - NodeType::TryStatement | - NodeType::VariableDeclarationStatement | - NodeType::WhileStatement => self.visit_statement(node), - _ => { - warn!("unexpected node type, expected block or statement: {:?}", node.node_type); - Ok(()) - } - } - } - - /// Pushes a coverage item to the internal collection, and might push a line item as well. - fn push_item(&mut self, item: CoverageItem) { - let source_location = &item.loc; - - // Push a line item if we haven't already - if matches!(item.kind, CoverageItemKind::Statement | CoverageItemKind::Branch { .. }) && - self.last_line < source_location.line - { - self.items.push(CoverageItem { - kind: CoverageItemKind::Line, - loc: source_location.clone(), - hits: 0, - }); - self.last_line = source_location.line; - } - - self.items.push(item); - } - - fn source_location_for(&self, loc: &ast::LowFidelitySourceLocation) -> SourceLocation { - SourceLocation { - source_id: self.source_id, - contract_name: self.contract_name.clone(), - start: loc.start, - length: loc.length, - 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, - }); - } -} - -#[derive(Debug)] -pub struct SourceAnalysis { - /// A collection of coverage items. - pub items: Vec, - /// A mapping of contract IDs to item IDs relevant to the contract (including items in base - /// contracts). - pub contract_items: HashMap>, -} - -/// Analyzes a set of sources to find coverage items. -#[derive(Default, Clone, Debug)] -pub struct SourceAnalyzer { - /// A map of source IDs to their source code - sources: HashMap, - /// A map of AST node IDs of contracts to their contract IDs. - contract_ids: HashMap, - /// A map of contract IDs to their AST nodes. - contracts: HashMap, - /// A collection of coverage items. - items: Vec, - /// A map of contract IDs to item IDs. - contract_items: HashMap>, - /// A map of contracts to their base contracts - contract_bases: HashMap>, -} - -impl SourceAnalyzer { - /// Creates a new source analyzer. - /// - /// The source analyzer expects all given sources to belong to the same compilation job - /// (defined by `version`). - pub fn new( - version: Version, - asts: HashMap, - sources: HashMap, - ) -> eyre::Result { - let mut analyzer = SourceAnalyzer { sources, ..Default::default() }; - - // TODO: Skip interfaces - for (source_id, ast) in asts.into_iter() { - for child in ast.nodes { - if !matches!(child.node_type, NodeType::ContractDefinition) { - continue - } - - let node_id = - child.id.ok_or_else(|| eyre::eyre!("The contract's AST node has no ID"))?; - let contract_id = ContractId { - version: version.clone(), - source_id, - contract_name: child - .attribute("name") - .ok_or_else(|| eyre::eyre!("Contract has no name"))?, - }; - analyzer.contract_ids.insert(node_id, contract_id.clone()); - analyzer.contracts.insert(contract_id, child); - } - } - - Ok(analyzer) - } - - /// Analyzes contracts in the sources held by the source analyzer. - /// - /// Coverage items are found by: - /// - Walking the AST of each contract (except interfaces) - /// - Recording the items and base contracts of each contract - /// - /// Finally, the item IDs of each contract and its base contracts are flattened, and the return - /// value represents all coverage items in the project, along with a mapping of contract IDs to - /// item IDs. - /// - /// Each coverage item contains relevant information to find opcodes corresponding to them: the - /// source ID the item is in, the source code range of the item, and the contract name the item - /// is in. - /// - /// Note: Source IDs are only unique per compilation job; that is, a code base compiled with - /// two different solc versions will produce overlapping source IDs if the compiler version is - /// not taken into account. - pub fn analyze(mut self) -> eyre::Result { - // Analyze the contracts - self.analyze_contracts()?; - - // Flatten the data - let mut flattened: HashMap> = HashMap::new(); - for contract_id in self.contract_items.keys() { - let mut item_ids: Vec = Vec::new(); - - // - // for a specific contract (id == contract_id): - // - // self.contract_bases.get(contract_id) includes the following contracts: - // 1. all the ancestors of this contract (including parent, grandparent, ... - // contracts) - // 2. the libraries **directly** used by this contract - // - // The missing contracts are: - // 1. libraries used in ancestors of this contracts - // 2. libraries used in libaries (i.e libs indirectly used by this contract) - // - // We want to find out all the above contracts and libraries related to this contract. - - for contract_or_lib in { - // A set of contracts and libraries related to this contract (will include "this" - // contract itself) - let mut contracts_libraries: HashSet<&ContractId> = HashSet::new(); - - // we use a stack for depth-first search. - let mut stack: Vec<&ContractId> = Vec::new(); - - // push "this" contract onto the stack - stack.push(contract_id); - - while let Some(contract_or_lib) = stack.pop() { - // whenever a contract_or_lib is removed from the stack, it is added to the set - contracts_libraries.insert(contract_or_lib); - - // push all ancestors of contract_or_lib and libraries used by contract_or_lib - // onto the stack - if let Some(bases) = self.contract_bases.get(contract_or_lib) { - stack.extend( - bases.iter().filter(|base| !contracts_libraries.contains(base)), - ); - } - } - - contracts_libraries - } { - // get items of each contract or library - if let Some(items) = self.contract_items.get(contract_or_lib) { - item_ids.extend(items.iter()); - } - } - - // If there are no items for this contract, then it was most likely filtered - if !item_ids.is_empty() { - flattened.insert(contract_id.clone(), item_ids); - } - } - - Ok(SourceAnalysis { items: self.items.clone(), contract_items: flattened }) - } - - fn analyze_contracts(&mut self) -> eyre::Result<()> { - for contract_id in self.contracts.keys() { - // Find this contract's coverage items if we haven't already - if self.contract_items.get(contract_id).is_none() { - let ContractVisitor { items, base_contract_node_ids, .. } = ContractVisitor::new( - contract_id.source_id, - self.sources.get(&contract_id.source_id).unwrap_or_else(|| { - panic!( - "We should have the source code for source ID {}", - contract_id.source_id - ) - }), - contract_id.contract_name.clone(), - ) - .visit( - self.contracts - .get(contract_id) - .unwrap_or_else(|| { - panic!("We should have the AST of contract: {contract_id:?}") - }) - .clone(), - )?; - - let is_test = items.iter().any(|item| { - if let CoverageItemKind::Function { name } = &item.kind { - name.is_test() - } else { - false - } - }); - - // Record this contract's base contracts - // We don't do this for test contracts because we don't care about them - if !is_test { - self.contract_bases.insert( - contract_id.clone(), - base_contract_node_ids - .iter() - .filter_map(|base_contract_node_id| { - self.contract_ids.get(base_contract_node_id).cloned() - }) - .collect(), - ); - } - - // For tests and contracts with no items we still record an empty Vec so we don't - // end up here again - if items.is_empty() || is_test { - self.contract_items.insert(contract_id.clone(), Vec::new()); - } else { - let item_ids: Vec = - (self.items.len()..self.items.len() + items.len()).collect(); - self.items.extend(items); - self.contract_items.insert(contract_id.clone(), item_ids.clone()); - } - } - } - - Ok(()) - } -} diff --git a/crates/evm/src/coverage/mod.rs b/crates/evm/src/coverage/mod.rs deleted file mode 100644 index 59ee950c2c8a4..0000000000000 --- a/crates/evm/src/coverage/mod.rs +++ /dev/null @@ -1,377 +0,0 @@ -pub mod analysis; -pub mod anchors; - -use bytes::Bytes; -use ethers::types::H256; -use semver::Version; -use std::{ - collections::{BTreeMap, HashMap}, - fmt::Display, - ops::{AddAssign, Deref, DerefMut}, -}; - -/// A coverage report. -/// -/// A coverage report contains coverage items and opcodes corresponding to those items (called -/// "anchors"). A single coverage item may be referred to by multiple anchors. -#[derive(Default, Debug, Clone)] -pub struct CoverageReport { - /// A map of source IDs to the source path - pub source_paths: HashMap<(Version, usize), String>, - /// A map of source paths to source IDs - pub source_paths_to_ids: HashMap<(Version, String), usize>, - /// All coverage items for the codebase, keyed by the compiler version. - pub items: HashMap>, - /// All item anchors for the codebase, keyed by their contract ID. - pub anchors: HashMap>, -} - -impl CoverageReport { - /// Add a source file path. - pub fn add_source(&mut self, version: Version, source_id: usize, path: String) { - self.source_paths.insert((version.clone(), source_id), path.clone()); - self.source_paths_to_ids.insert((version, path), source_id); - } - - /// Get the source ID for a specific source file path. - pub fn get_source_id(&self, version: Version, path: String) -> Option<&usize> { - self.source_paths_to_ids.get(&(version, path)) - } - - /// Add coverage items to this report - pub fn add_items(&mut self, version: Version, items: Vec) { - self.items.entry(version).or_default().extend(items); - } - - /// Add anchors to this report - pub fn add_anchors(&mut self, anchors: HashMap>) { - self.anchors.extend(anchors); - } - - /// Get coverage summaries by source file path - pub fn summary_by_file(&self) -> impl Iterator { - let mut summaries: BTreeMap = BTreeMap::new(); - - for (version, items) in self.items.iter() { - for item in items { - let mut summary = summaries - .entry( - self.source_paths - .get(&(version.clone(), item.loc.source_id)) - .cloned() - .unwrap_or_else(|| { - format!("Unknown (ID: {}, solc: {version})", item.loc.source_id) - }), - ) - .or_default(); - summary += item; - } - } - - summaries.into_iter() - } - - /// Get coverage items by source file path - pub fn items_by_source(&self) -> impl Iterator)> { - let mut items_by_source: BTreeMap> = BTreeMap::new(); - - for (version, items) in self.items.iter() { - for item in items { - items_by_source - .entry( - self.source_paths - .get(&(version.clone(), item.loc.source_id)) - .cloned() - .unwrap_or_else(|| { - format!("Unknown (ID: {}, solc: {version})", item.loc.source_id) - }), - ) - .or_default() - .push(item.clone()); - } - } - - items_by_source.into_iter() - } - - /// Processes data from a [HitMap] and sets hit counts for coverage items in this coverage map. - /// - /// This function should only be called *after* all the relevant sources have been processed and - /// added to the map (see [add_source]). - pub fn add_hit_map(&mut self, contract_id: &ContractId, hit_map: &HitMap) { - if let Some(anchors) = self.anchors.get(contract_id) { - for anchor in anchors { - if let Some(hits) = hit_map.hits.get(&anchor.instruction) { - self.items - .get_mut(&contract_id.version) - .and_then(|items| items.get_mut(anchor.item_id)) - .expect("Anchor refers to non-existent coverage item") - .hits += hits; - } - } - } - } -} - -/// A collection of [HitMap]s -#[derive(Default, Clone, Debug)] -pub struct HitMaps(pub HashMap); - -impl HitMaps { - pub fn merge(mut self, other: HitMaps) -> Self { - for (code_hash, hit_map) in other.0.into_iter() { - if let Some(HitMap { hits: extra_hits, .. }) = self.insert(code_hash, hit_map) { - for (pc, hits) in extra_hits.into_iter() { - self.entry(code_hash) - .and_modify(|map| *map.hits.entry(pc).or_default() += hits); - } - } - } - self - } -} - -impl Deref for HitMaps { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for HitMaps { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Hit data for an address. -/// -/// Contains low-level data about hit counters for the instructions in the bytecode of a contract. -#[derive(Debug, Clone)] -pub struct HitMap { - pub bytecode: Bytes, - pub hits: BTreeMap, -} - -impl HitMap { - pub fn new(bytecode: Bytes) -> Self { - Self { bytecode, hits: BTreeMap::new() } - } - - /// Increase the hit counter for the given program counter. - pub fn hit(&mut self, pc: usize) { - *self.hits.entry(pc).or_default() += 1; - } -} - -/// A unique identifier for a contract -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct ContractId { - pub version: Version, - pub source_id: usize, - pub contract_name: String, -} - -impl Display for ContractId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Contract \"{}\" (solc {}, source ID {})", - self.contract_name, self.version, self.source_id - ) - } -} - -/// An item anchor describes what instruction marks a [CoverageItem] as covered. -#[derive(Clone, Debug)] -pub struct ItemAnchor { - /// The program counter for the opcode of this anchor - pub instruction: usize, - /// The item ID this anchor points to - pub item_id: usize, -} - -impl Display for ItemAnchor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "IC {} -> Item {}", self.instruction, self.item_id) - } -} - -#[derive(Clone, Debug)] -pub enum CoverageItemKind { - /// An executable line in the code. - Line, - /// A statement in the code. - Statement, - /// A branch in the code. - Branch { - /// The ID that identifies the branch. - /// - /// There may be multiple items with the same branch ID - they belong to the same branch, - /// but represent different paths. - branch_id: usize, - /// The path ID for this branch. - /// - /// The first path has ID 0, the next ID 1, and so on. - path_id: usize, - }, - /// A function in the code. - Function { - /// The name of the function. - name: String, - }, -} - -#[derive(Clone, Debug)] -pub struct CoverageItem { - /// The coverage item kind. - pub kind: CoverageItemKind, - /// The location of the item in the source code. - pub loc: SourceLocation, - /// The number of times this item was hit. - pub hits: u64, -} - -impl Display for CoverageItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.kind { - CoverageItemKind::Line => { - write!(f, "Line")?; - } - CoverageItemKind::Statement => { - write!(f, "Statement")?; - } - CoverageItemKind::Branch { branch_id, path_id } => { - write!(f, "Branch (branch: {branch_id}, path: {path_id})")?; - } - CoverageItemKind::Function { name } => { - write!(f, r#"Function "{name}""#)?; - } - } - write!(f, " (location: {}, hits: {})", self.loc, self.hits) - } -} - -#[derive(Debug, Clone)] -pub struct SourceLocation { - /// The source ID. - pub source_id: usize, - /// The contract this source range is in. - pub contract_name: String, - /// Start byte in the source code. - pub start: usize, - /// Number of bytes in the source code. - pub length: Option, - /// The line in the source code. - pub line: usize, -} - -impl Display for SourceLocation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "source ID {}, line {}, chars {}-{}", - self.source_id, - self.line, - self.start, - self.length.map_or(self.start, |length| self.start + length) - ) - } -} - -/// Coverage summary for a source file. -#[derive(Default, Debug, Clone)] -pub struct CoverageSummary { - /// The number of executable lines in the source file. - pub line_count: usize, - /// The number of lines that were hit. - pub line_hits: usize, - /// The number of statements in the source file. - pub statement_count: usize, - /// The number of statements that were hit. - pub statement_hits: usize, - /// The number of branches in the source file. - pub branch_count: usize, - /// The number of branches that were hit. - pub branch_hits: usize, - /// The number of functions in the source file. - pub function_count: usize, - /// The number of functions hit. - pub function_hits: usize, -} - -impl AddAssign<&Self> for CoverageSummary { - fn add_assign(&mut self, other: &Self) { - self.line_count += other.line_count; - self.line_hits += other.line_hits; - self.statement_count += other.statement_count; - self.statement_hits += other.statement_hits; - self.branch_count += other.branch_count; - self.branch_hits += other.branch_hits; - self.function_count += other.function_count; - self.function_hits += other.function_hits; - } -} - -impl AddAssign<&CoverageItem> for 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; - } - } - } - } -} - -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/src/debug.rs b/crates/evm/src/debug.rs deleted file mode 100644 index 885830abb4da1..0000000000000 --- a/crates/evm/src/debug.rs +++ /dev/null @@ -1,190 +0,0 @@ -use crate::{abi::HEVM_ABI, CallKind}; -use ethers::types::{Address, U256}; -use revm::interpreter::{Memory, OpCode}; -use serde::{Deserialize, Serialize}; -use std::fmt::Display; - -/// An arena of [DebugNode]s -#[derive(Default, Debug, Clone)] -pub struct DebugArena { - /// The arena of nodes - pub arena: Vec, -} - -impl DebugArena { - /// 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<(Address, Vec, CallKind)> { - let node = &self.arena[entry]; - - let mut flattened = vec![]; - if !node.steps.is_empty() { - flattened.push((node.address, node.steps.clone(), node.kind)); - } - flattened.extend(node.children.iter().flat_map(|child| self.flatten(*child))); - - flattened - } -} - -/// A node in the arena -#[derive(Default, Debug, Clone)] -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 - pub depth: usize, - /// The debug steps - pub steps: Vec, -} - -impl DebugNode { - pub fn new(address: Address, depth: usize, steps: Vec) -> Self { - Self { address, depth, steps, ..Default::default() } - } -} - -/// 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(Debug, Clone)] -pub struct DebugStep { - /// Stack *prior* to running the associated opcode - pub stack: Vec, - /// Memory *prior* to running the associated opcode - pub memory: Memory, - /// Opcode to be executed - pub instruction: Instruction, - /// Optional bytes that are being pushed onto the stack - pub push_bytes: Option>, - /// 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: Memory::new(), - instruction: Instruction::OpCode(revm::interpreter::opcode::INVALID), - push_bytes: None, - pc: 0, - total_gas_used: 0, - } - } -} - -impl DebugStep { - /// Pretty print the step's opcode - pub fn pretty_opcode(&self) -> String { - if let Some(push_bytes) = &self.push_bytes { - format!("{}(0x{})", self.instruction, hex::encode(push_bytes)) - } else { - self.instruction.to_string() - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum Instruction { - OpCode(u8), - Cheatcode([u8; 4]), -} - -impl From for Instruction { - fn from(op: u8) -> Instruction { - Instruction::OpCode(op) - } -} - -impl Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Instruction::OpCode(op) => write!( - f, - "{}", - OpCode::try_from_u8(*op).map_or_else( - || format!("UNDEFINED(0x{op:02x})"), - |opcode| opcode.as_str().to_string(), - ) - ), - Instruction::Cheatcode(cheat) => write!( - f, - "VM_{}", - &*HEVM_ABI - .functions() - .find(|func| func.short_signature() == *cheat) - .expect("unknown cheatcode found in debugger") - .name - .to_uppercase() - ), - } - } -} diff --git a/crates/evm/src/decode.rs b/crates/evm/src/decode.rs deleted file mode 100644 index bf65f2bcedd88..0000000000000 --- a/crates/evm/src/decode.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! Various utilities to decode test results -use crate::{ - abi::ConsoleEvents::{self, *}, - executor::inspector::cheatcodes::util::MAGIC_SKIP_BYTES, -}; -use ethers::{ - abi::{decode, AbiDecode, Contract as Abi, ParamType, RawLog, Token}, - contract::EthLogDecode, - prelude::U256, - types::Log, -}; -use foundry_common::{abi::format_token, SELECTOR_LEN}; -use foundry_utils::error::ERROR_PREFIX; -use itertools::Itertools; -use once_cell::sync::Lazy; -use revm::interpreter::{return_ok, InstructionResult}; - -/// Decode a set of logs, only returning logs from DSTest logging events and Hardhat's `console.log` -pub fn decode_console_logs(logs: &[Log]) -> Vec { - logs.iter().filter_map(decode_console_log).collect() -} - -/// Decode a single log. -/// -/// This function returns [None] if it is not a DSTest log or the result of a Hardhat -/// `console.log`. -pub fn decode_console_log(log: &Log) -> Option { - // NOTE: We need to do this conversion because ethers-rs does not - // support passing `Log`s - let raw_log = RawLog { topics: log.topics.clone(), data: log.data.to_vec() }; - let decoded = match ConsoleEvents::decode_log(&raw_log).ok()? { - LogsFilter(inner) => format!("{}", inner.0), - LogBytesFilter(inner) => format!("{}", inner.0), - LogNamedAddressFilter(inner) => format!("{}: {:?}", inner.key, inner.val), - LogNamedBytes32Filter(inner) => { - format!("{}: 0x{}", inner.key, hex::encode(inner.val)) - } - LogNamedDecimalIntFilter(inner) => { - let (sign, val) = inner.val.into_sign_and_abs(); - format!( - "{}: {}{}", - inner.key, - sign, - ethers::utils::format_units(val, inner.decimals.as_u32()).unwrap() - ) - } - LogNamedDecimalUintFilter(inner) => { - format!( - "{}: {}", - inner.key, - ethers::utils::format_units(inner.val, inner.decimals.as_u32()).unwrap() - ) - } - LogNamedIntFilter(inner) => format!("{}: {:?}", inner.key, inner.val), - LogNamedUintFilter(inner) => format!("{}: {:?}", inner.key, inner.val), - LogNamedBytesFilter(inner) => { - format!("{}: 0x{}", inner.key, hex::encode(inner.val)) - } - LogNamedStringFilter(inner) => format!("{}: {}", inner.key, inner.val), - LogNamedArray1Filter(inner) => format!("{}: {:?}", inner.key, inner.val), - LogNamedArray2Filter(inner) => format!("{}: {:?}", inner.key, inner.val), - LogNamedArray3Filter(inner) => format!("{}: {:?}", inner.key, inner.val), - - e => e.to_string(), - }; - Some(decoded) -} - -/// Given an ABI encoded error string with the function signature `Error(string)`, it decodes -/// it and returns the revert error message. -pub fn decode_revert( - err: &[u8], - maybe_abi: Option<&Abi>, - status: Option, -) -> eyre::Result { - if err.len() < SELECTOR_LEN { - if let Some(status) = status { - if !matches!(status, return_ok!()) { - return Ok(format!("EvmError: {status:?}")) - } - } - eyre::bail!("Not enough error data to decode") - } - match err[..SELECTOR_LEN] { - // keccak(Panic(uint256)) - [78, 72, 123, 113] => { - // ref: https://soliditydeveloper.com/solidity-0.8 - match err[err.len() - 1] { - 1 => { - // assert - Ok("Assertion violated".to_string()) - } - 17 => { - // safemath over/underflow - Ok("Arithmetic over/underflow".to_string()) - } - 18 => { - // divide by 0 - Ok("Division or modulo by 0".to_string()) - } - 33 => { - // conversion into non-existent enum type - Ok("Conversion into non-existent enum type".to_string()) - } - 34 => { - // incorrectly encoded storage byte array - Ok("Incorrectly encoded storage byte array".to_string()) - } - 49 => { - // pop() on empty array - Ok("`pop()` on empty array".to_string()) - } - 50 => { - // index out of bounds - Ok("Index out of bounds".to_string()) - } - 65 => { - // allocating too much memory or creating too large array - Ok("Memory allocation overflow".to_string()) - } - 81 => { - // calling a zero initialized variable of internal function type - Ok("Calling a zero initialized variable of internal function type".to_string()) - } - _ => { - eyre::bail!("Unsupported solidity builtin panic") - } - } - } - // keccak(Error(string)) - [8, 195, 121, 160] => { - String::decode(&err[SELECTOR_LEN..]).map_err(|_| eyre::eyre!("Bad string decode")) - } - // keccak(expectRevert(bytes)) - [242, 141, 206, 179] => { - let err_data = &err[SELECTOR_LEN..]; - if err_data.len() > 64 { - let len = U256::from(&err_data[32..64]).as_usize(); - if err_data.len() > 64 + len { - let actual_err = &err_data[64..64 + len]; - if let Ok(decoded) = decode_revert(actual_err, maybe_abi, None) { - // check if it's a builtin - return Ok(decoded) - } else if let Ok(as_str) = String::from_utf8(actual_err.to_vec()) { - // check if it's a true string - return Ok(as_str) - } - } - } - eyre::bail!("Non-native error and not string") - } - // keccak(expectRevert(bytes4)) - [195, 30, 176, 224] => { - let err_data = &err[SELECTOR_LEN..]; - if err_data.len() == 32 { - let actual_err = &err_data[..SELECTOR_LEN]; - if let Ok(decoded) = decode_revert(actual_err, maybe_abi, None) { - // it's a known selector - return Ok(decoded) - } - } - eyre::bail!("Unknown error selector") - } - _ => { - // See if the revert is caused by a skip() call. - if err == MAGIC_SKIP_BYTES { - return Ok("SKIPPED".to_string()) - } - // try to decode a custom error if provided an abi - if let Some(abi) = maybe_abi { - for abi_error in abi.errors() { - if abi_error.signature()[..SELECTOR_LEN] == err[..SELECTOR_LEN] { - // if we don't decode, don't return an error, try to decode as a - // string later - if let Ok(decoded) = abi_error.decode(&err[SELECTOR_LEN..]) { - let inputs = decoded - .iter() - .map(foundry_common::abi::format_token) - .collect::>() - .join(", "); - return Ok(format!("{}({inputs})", abi_error.name)) - } - } - } - } - // optimistically try to decode as string, unknown selector or `CheatcodeError` - String::decode(err) - .ok() - .or_else(|| { - // try decoding as cheatcode error - if err.starts_with(ERROR_PREFIX.as_slice()) { - String::decode(&err[ERROR_PREFIX.len()..]).ok() - } else { - None - } - }) - .or_else(|| { - // try decoding as unknown err - String::decode(&err[SELECTOR_LEN..]) - .map(|err_str| format!("{}:{err_str}", hex::encode(&err[..SELECTOR_LEN]))) - .ok() - }) - .or_else(|| { - // try to decode possible variations of custom error types - decode_custom_error(err).map(|token| { - let s = format!("Custom Error {}:", hex::encode(&err[..SELECTOR_LEN])); - - let err_str = format_token(&token); - if err_str.starts_with('(') { - format!("{s}{err_str}") - } else { - format!("{s}({err_str})") - } - }) - }) - .ok_or_else(|| eyre::eyre!("Non-native error and not string")) - } - } -} - -/// Tries to optimistically decode a custom solc error, with at most 4 arguments -pub fn decode_custom_error(err: &[u8]) -> Option { - decode_custom_error_args(err, 4) -} - -/// Tries to optimistically decode a custom solc error with a maximal amount of arguments -/// -/// This will brute force decoding of custom errors with up to `args` arguments -pub fn decode_custom_error_args(err: &[u8], args: usize) -> Option { - if err.len() <= SELECTOR_LEN { - return None - } - - let err = &err[SELECTOR_LEN..]; - /// types we check against - static TYPES: Lazy> = Lazy::new(|| { - vec![ - ParamType::Address, - ParamType::Bool, - ParamType::Uint(256), - ParamType::Int(256), - ParamType::Bytes, - ParamType::String, - ] - }); - - macro_rules! try_decode { - ($ty:ident) => { - if let Ok(mut decoded) = decode(&[$ty], err) { - return Some(decoded.remove(0)) - } - }; - } - - // check if single param, but only if it's a single word - if err.len() == 32 { - for ty in TYPES.iter().cloned() { - try_decode!(ty); - } - return None - } - - // brute force decode all possible combinations - for num in (2..=args).rev() { - for candidate in TYPES.iter().cloned().combinations(num) { - if let Ok(decoded) = decode(&candidate, err) { - return Some(Token::Tuple(decoded)) - } - } - } - - // try as array - for ty in TYPES.iter().cloned().map(|ty| ParamType::Array(Box::new(ty))) { - try_decode!(ty); - } - - None -} - -#[cfg(test)] -mod tests { - use super::*; - use ethers::{ - abi::{AbiEncode, Address}, - contract::EthError, - }; - - #[test] - fn test_decode_custom_error_address() { - #[derive(Debug, Clone, EthError)] - struct AddressErr(Address); - let err = AddressErr(Address::random()); - - let encoded = err.clone().encode(); - let decoded = decode_custom_error(&encoded).unwrap(); - assert_eq!(decoded, Token::Address(err.0)); - } - - #[test] - fn test_decode_custom_error_args3() { - #[derive(Debug, Clone, EthError)] - struct MyError(Address, bool, U256); - let err = MyError(Address::random(), true, 100u64.into()); - - let encoded = err.clone().encode(); - let decoded = decode_custom_error(&encoded).unwrap(); - assert_eq!( - decoded, - Token::Tuple(vec![ - Token::Address(err.0), - Token::Bool(err.1), - Token::Uint(100u64.into()), - ]) - ); - } -} diff --git a/crates/evm/src/executor/abi/mod.rs b/crates/evm/src/executor/abi/mod.rs deleted file mode 100644 index 276fd27d917da..0000000000000 --- a/crates/evm/src/executor/abi/mod.rs +++ /dev/null @@ -1,573 +0,0 @@ -use ethers::types::{Address, Selector, H160}; -pub use foundry_abi::{ - console::{self, ConsoleEvents, CONSOLE_ABI}, - hardhat_console::{self, HardhatConsoleCalls, HARDHATCONSOLE_ABI as HARDHAT_CONSOLE_ABI}, - hevm::{self, HEVMCalls, HEVM_ABI}, -}; -use once_cell::sync::Lazy; -use std::collections::HashMap; - -/// The cheatcode handler address (0x7109709ECfa91a80626fF3989D68f67F5b1DD12D). -/// -/// This is the same address as the one used in DappTools's HEVM. -/// `address(bytes20(uint160(uint256(keccak256('hevm cheat code')))))` -pub const CHEATCODE_ADDRESS: Address = H160([ - 0x71, 0x09, 0x70, 0x9E, 0xcf, 0xa9, 0x1a, 0x80, 0x62, 0x6f, 0xf3, 0x98, 0x9d, 0x68, 0xf6, 0x7f, - 0x5b, 0x1d, 0xd1, 0x2d, -]); - -/// The Hardhat console address (0x000000000000000000636F6e736F6c652e6c6f67). -/// -/// See: https://github.com/nomiclabs/hardhat/blob/master/packages/hardhat-core/console.sol -pub const HARDHAT_CONSOLE_ADDRESS: Address = H160([ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, - 0x2e, 0x6c, 0x6f, 0x67, -]); - -/// If the input starts with a known `hardhat/console.log` `uint` selector, then this will replace -/// it with the selector `abigen!` bindings expect. -pub fn patch_hardhat_console_selector(input: &mut [u8]) { - if input.len() < 4 { - return - } - - // SAFETY: length checked above; see [<[T]>::split_array_mut]. - let selector = unsafe { &mut *(input.get_unchecked_mut(..4) as *mut [u8] as *mut [u8; 4]) }; - if let Some(abigen_selector) = HARDHAT_CONSOLE_SELECTOR_PATCHES.get(selector) { - *selector = *abigen_selector; - } -} - -/// This contains a map with all the `hardhat/console.log` log selectors that use `uint` or `int` -/// as key and the selector of the call with `uint256`, -/// -/// This is a bit terrible but a workaround for the differing selectors used by hardhat and the call -/// bindings which `abigen!` creates. `hardhat/console.log` logs its events in functions that accept -/// `uint` manually as `abi.encodeWithSignature("log(int)", p0)`, but `abigen!` uses `uint256` for -/// its call bindings (`HardhatConsoleCalls`) as generated by solc. -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_path_works() { - for (hh, abigen) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { - let mut hh = *hh; - patch_hardhat_console_selector(&mut hh); - assert_eq!(*abigen, hh); - } - } -} diff --git a/crates/evm/src/executor/backend/error.rs b/crates/evm/src/executor/backend/error.rs deleted file mode 100644 index eb3a595b10c2d..0000000000000 --- a/crates/evm/src/executor/backend/error.rs +++ /dev/null @@ -1,113 +0,0 @@ -use ethers::types::{Address, BlockId, H256, U256}; -use foundry_utils::error::SolError; -use futures::channel::mpsc::{SendError, TrySendError}; -use std::{ - convert::Infallible, - fmt, - sync::{mpsc::RecvError, Arc}, -}; - -/// Result alias with `DatabaseError` as error -pub type DatabaseResult = Result; - -/// Errors that can happen when working with [`revm::Database`] -#[derive(Debug, thiserror::Error)] -pub enum DatabaseError { - #[error("Failed to fetch AccountInfo {0:?}")] - MissingAccount(Address), - #[error("Could should already be loaded: {0:?}")] - MissingCode(H256), - #[error(transparent)] - Recv(#[from] RecvError), - #[error(transparent)] - Send(#[from] SendError), - #[error("{0}")] - Message(String), - #[error("Failed to get account for {0:?}: {0:?}")] - GetAccount(Address, Arc), - #[error("Failed to get storage for {0:?} at {1:?}: {2:?}")] - GetStorage(Address, U256, Arc), - #[error("Failed to get block hash for {0}: {1:?}")] - GetBlockHash(u64, Arc), - #[error("Failed to get full block for {0:?}: {1:?}")] - GetFullBlock(BlockId, Arc), - #[error("Block {0:?} does not exist")] - BlockNotFound(BlockId), - #[error("Failed to get transaction {0:?}: {1:?}")] - GetTransaction(H256, Arc), - #[error("Transaction {0:?} not found")] - TransactionNotFound(H256), - #[error( - "CREATE2 Deployer not present on this chain. [0x4e59b44847b379578588920ca78fbf26c0b4956c]" - )] - MissingCreate2Deployer, - #[error(transparent)] - JoinError(#[from] tokio::task::JoinError), -} - -impl DatabaseError { - /// Create a new error with a message - pub fn msg(msg: impl Into) -> Self { - DatabaseError::Message(msg.into()) - } - - fn get_rpc_error(&self) -> Option<&eyre::Error> { - match self { - Self::GetAccount(_, err) => Some(err), - Self::GetStorage(_, _, err) => Some(err), - Self::GetBlockHash(_, err) => Some(err), - Self::GetFullBlock(_, err) => Some(err), - Self::GetTransaction(_, err) => Some(err), - // Enumerate explicitly to make sure errors are updated if a new one is added. - Self::MissingAccount(_) | - Self::MissingCode(_) | - Self::Recv(_) | - Self::Send(_) | - Self::Message(_) | - Self::BlockNotFound(_) | - Self::TransactionNotFound(_) | - Self::MissingCreate2Deployer | - Self::JoinError(_) => None, - } - } - - /// Whether the error is potentially caused by the user forking from an older block in a - /// non-archive node. - pub fn is_possibly_non_archive_node_error(&self) -> bool { - static GETH_MESSAGE: &str = "missing trie node"; - - self.get_rpc_error() - .map(|err| err.to_string().to_lowercase().contains(GETH_MESSAGE)) - .unwrap_or(false) - } -} - -impl SolError for DatabaseError {} - -impl From> for DatabaseError { - fn from(err: TrySendError) -> Self { - err.into_send_error().into() - } -} - -impl From for DatabaseError { - fn from(never: Infallible) -> Self { - match never {} - } -} - -/// Error thrown when the address is not allowed to execute cheatcodes -/// -/// See also [`DatabaseExt`](crate::executor::DatabaseExt) -#[derive(Debug, Clone, Copy)] -pub struct NoCheatcodeAccessError(pub Address); - -impl fmt::Display for NoCheatcodeAccessError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "No cheatcode access granted for: {:?}, see `vm.allowCheatcodes()`", self.0) - } -} - -impl std::error::Error for NoCheatcodeAccessError {} - -impl SolError for NoCheatcodeAccessError {} diff --git a/crates/evm/src/executor/backend/fuzz.rs b/crates/evm/src/executor/backend/fuzz.rs deleted file mode 100644 index b1c98bfbf2b2e..0000000000000 --- a/crates/evm/src/executor/backend/fuzz.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::{ - executor::{ - backend::{ - diagnostic::RevertDiagnostic, error::DatabaseError, Backend, DatabaseExt, LocalForkId, - }, - fork::{CreateFork, ForkId}, - inspector::cheatcodes::Cheatcodes, - }, - Address, -}; -use ethers::prelude::{H256, U256}; - -use revm::{ - db::DatabaseRef, - primitives::{AccountInfo, Bytecode, Env, ResultAndState, B160, B256, U256 as rU256}, - Database, Inspector, JournaledState, -}; -use std::borrow::Cow; - -/// 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 -/// will result in a clone of the initial Database. Therefore, this backend type is basically -/// a clone-on-write `Backend`, where cloning is only necessary if cheatcodes will modify the -/// `Backend` -/// -/// Entire purpose of this type is for fuzzing. A test function fuzzer will repeatedly execute the -/// function via immutable raw (no state changes) calls. -/// -/// **N.B.**: we're assuming cheatcodes that alter the state (like multi fork swapping) are niche. -/// If they executed during fuzzing, it will require a clone of the initial input database. This way -/// we can support these cheatcodes in fuzzing cheaply without adding overhead for fuzz tests that -/// don't make use of them. Alternatively each test case would require its own `Backend` clone, -/// which would add significant overhead for large fuzz sets even if the Database is not big after -/// setup. -#[derive(Debug, Clone)] -pub struct FuzzBackendWrapper<'a> { - /// The underlying immutable `Backend` - /// - /// No calls on the `FuzzBackendWrapper` will ever persistently modify the `backend`'s state. - pub backend: Cow<'a, Backend>, - /// Keeps track of whether the backed is already intialized - is_initialized: bool, - /// Keeps track of wheter there was a snapshot failure. - /// - /// Necessary as the backend is dropped after usage, but we'll need to persist - /// the snapshot failure anyhow. - has_snapshot_failure: bool, -} - -// === impl FuzzBackendWrapper === - -impl<'a> FuzzBackendWrapper<'a> { - pub fn new(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), is_initialized: false, has_snapshot_failure: false } - } - - /// Executes the configured transaction of the `env` without committing state changes - pub fn inspect_ref( - &mut self, - env: &mut Env, - mut inspector: INSP, - ) -> eyre::Result - where - INSP: Inspector, - { - // this is a new call to inspect with a new env, so even if we've cloned the backend - // already, we reset the initialized state - self.is_initialized = false; - match revm::evm_inner::(env, self, &mut inspector).transact() { - Ok(result) => Ok(result), - Err(e) => eyre::bail!("fuzz: failed to inspect: {:?}", e), - } - } - - /// Returns whether there was a snapshot failure in the fuzz backend. - /// - /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. - pub fn has_snapshot_failure(&self) -> bool { - self.has_snapshot_failure - } - - /// Sets whether there was a snapshot failure in the fuzz backend. - /// - /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. - pub fn set_snapshot_failure(&mut self, has_snapshot_failure: bool) { - self.has_snapshot_failure = has_snapshot_failure; - } - - /// Returns a mutable instance of the Backend. - /// - /// If this is the first time this is called, the backed is cloned and initialized. - fn backend_mut(&mut self, env: &Env) -> &mut Backend { - if !self.is_initialized { - let backend = self.backend.to_mut(); - backend.initialize(env); - self.is_initialized = true; - return backend - } - self.backend.to_mut() - } -} - -impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { - trace!("fuzz: create snapshot"); - self.backend_mut(env).snapshot(journaled_state, env) - } - - fn revert( - &mut self, - id: U256, - journaled_state: &JournaledState, - current: &mut Env, - ) -> Option { - trace!(?id, "fuzz: revert snapshot"); - let journaled_state = self.backend_mut(current).revert(id, journaled_state, current); - // Persist the snapshot failure in the fuzz backend, as the underlying backend state is lost - // after the call. - self.set_snapshot_failure(self.has_snapshot_failure || self.backend.has_snapshot_failure()); - journaled_state - } - - fn create_fork(&mut self, fork: CreateFork) -> eyre::Result { - trace!("fuzz: create fork"); - self.backend.to_mut().create_fork(fork) - } - - fn create_fork_at_transaction( - &mut self, - fork: CreateFork, - transaction: H256, - ) -> eyre::Result { - trace!(?transaction, "fuzz: create fork at"); - self.backend.to_mut().create_fork_at_transaction(fork, transaction) - } - - fn select_fork( - &mut self, - id: LocalForkId, - env: &mut Env, - journaled_state: &mut JournaledState, - ) -> eyre::Result<()> { - trace!(?id, "fuzz: select fork"); - self.backend_mut(env).select_fork(id, env, journaled_state) - } - - fn roll_fork( - &mut self, - id: Option, - block_number: U256, - env: &mut Env, - journaled_state: &mut JournaledState, - ) -> eyre::Result<()> { - trace!(?id, ?block_number, "fuzz: roll fork"); - self.backend_mut(env).roll_fork(id, block_number, env, journaled_state) - } - - fn roll_fork_to_transaction( - &mut self, - id: Option, - transaction: H256, - env: &mut Env, - journaled_state: &mut JournaledState, - ) -> eyre::Result<()> { - trace!(?id, ?transaction, "fuzz: roll fork to transaction"); - self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) - } - - fn transact( - &mut self, - id: Option, - transaction: H256, - env: &mut Env, - journaled_state: &mut JournaledState, - cheatcodes_inspector: Option<&mut Cheatcodes>, - ) -> eyre::Result<()> { - trace!(?id, ?transaction, "fuzz: execute transaction"); - self.backend_mut(env).transact(id, transaction, env, journaled_state, cheatcodes_inspector) - } - - fn active_fork_id(&self) -> Option { - self.backend.active_fork_id() - } - - fn active_fork_url(&self) -> Option { - self.backend.active_fork_url() - } - - fn ensure_fork(&self, id: Option) -> eyre::Result { - self.backend.ensure_fork(id) - } - - fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { - self.backend.ensure_fork_id(id) - } - - fn diagnose_revert( - &self, - callee: Address, - journaled_state: &JournaledState, - ) -> Option { - self.backend.diagnose_revert(callee, journaled_state) - } - - fn is_persistent(&self, acc: &Address) -> bool { - self.backend.is_persistent(acc) - } - - fn remove_persistent_account(&mut self, account: &Address) -> bool { - self.backend.to_mut().remove_persistent_account(account) - } - - fn add_persistent_account(&mut self, account: Address) -> bool { - self.backend.to_mut().add_persistent_account(account) - } - - fn allow_cheatcode_access(&mut self, account: Address) -> bool { - self.backend.to_mut().allow_cheatcode_access(account) - } - - fn revoke_cheatcode_access(&mut self, account: Address) -> bool { - self.backend.to_mut().revoke_cheatcode_access(account) - } - - fn has_cheatcode_access(&self, account: Address) -> bool { - self.backend.has_cheatcode_access(account) - } -} - -impl<'a> DatabaseRef for FuzzBackendWrapper<'a> { - type Error = DatabaseError; - - fn basic(&self, address: B160) -> Result, Self::Error> { - DatabaseRef::basic(self.backend.as_ref(), address) - } - - fn code_by_hash(&self, code_hash: B256) -> Result { - DatabaseRef::code_by_hash(self.backend.as_ref(), code_hash) - } - - fn storage(&self, address: B160, index: rU256) -> Result { - DatabaseRef::storage(self.backend.as_ref(), address, index) - } - - fn block_hash(&self, number: rU256) -> Result { - DatabaseRef::block_hash(self.backend.as_ref(), number) - } -} - -impl<'a> Database for FuzzBackendWrapper<'a> { - type Error = DatabaseError; - - fn basic(&mut self, address: B160) -> Result, Self::Error> { - DatabaseRef::basic(self, address) - } - - fn code_by_hash(&mut self, code_hash: B256) -> Result { - DatabaseRef::code_by_hash(self, code_hash) - } - - fn storage(&mut self, address: B160, index: rU256) -> Result { - DatabaseRef::storage(self, address, index) - } - - fn block_hash(&mut self, number: rU256) -> Result { - DatabaseRef::block_hash(self, number) - } -} diff --git a/crates/evm/src/executor/backend/snapshot.rs b/crates/evm/src/executor/backend/snapshot.rs deleted file mode 100644 index 36f1aa611788c..0000000000000 --- a/crates/evm/src/executor/backend/snapshot.rs +++ /dev/null @@ -1,43 +0,0 @@ -use revm::{ - primitives::{AccountInfo, Env, HashMap as Map, B160, B256, U256}, - JournaledState, -}; -use serde::{Deserialize, Serialize}; - -/// A minimal abstraction of a state at a certain point in time -#[derive(Default, Clone, Debug, Serialize, Deserialize)] -pub struct StateSnapshot { - pub accounts: Map, - pub storage: Map>, - pub block_hashes: Map, -} - -/// Represents a snapshot taken during evm execution -#[derive(Clone, Debug)] -pub struct BackendSnapshot { - pub db: T, - /// The journaled_state state at a specific point - pub journaled_state: JournaledState, - /// Contains the env at the time of the snapshot - pub env: Env, -} - -// === impl BackendSnapshot === - -impl BackendSnapshot { - /// Takes a new snapshot - pub fn new(db: T, journaled_state: JournaledState, env: Env) -> Self { - Self { db, journaled_state, env } - } - - /// Called when this snapshot is reverted. - /// - /// Since we want to keep all additional logs that were emitted since the snapshot was taken - /// we'll merge additional logs into the snapshot's `revm::JournaledState`. Additional logs are - /// those logs that are missing in the snapshot's journaled_state, since the current - /// journaled_state includes the same logs, we can simply replace use that See also - /// `DatabaseExt::revert` - pub fn merge(&mut self, current: &JournaledState) { - self.journaled_state.logs = current.logs.clone(); - } -} diff --git a/crates/evm/src/executor/builder.rs b/crates/evm/src/executor/builder.rs deleted file mode 100644 index 5e021c7658c65..0000000000000 --- a/crates/evm/src/executor/builder.rs +++ /dev/null @@ -1,112 +0,0 @@ -use super::{ - inspector::{Cheatcodes, Fuzzer, InspectorStackConfig}, - Executor, -}; -use crate::{ - executor::{backend::Backend, inspector::CheatsConfig}, - fuzz::{invariant::RandomCallGenerator, strategies::EvmFuzzState}, -}; -use ethers::types::U256; -use revm::primitives::{Env, SpecId}; - -/// The builder that allows to configure an evm [`Executor`] which a stack of optional -/// [`revm::Inspector`]s, such as [`Cheatcodes`] -/// -/// By default, the [`Executor`] will be configured with an empty [`InspectorStack`] -#[derive(Default, Debug)] -pub struct ExecutorBuilder { - /// The execution environment configuration. - env: Env, - /// The configuration used to build an [InspectorStack]. - inspector_config: InspectorStackConfig, - gas_limit: Option, -} - -// === impl ExecutorBuilder === - -impl ExecutorBuilder { - /// Enables cheatcodes on the executor. - #[must_use] - pub fn with_cheatcodes(mut self, config: CheatsConfig) -> Self { - self.inspector_config.cheatcodes = - Some(Cheatcodes::new(self.env.block.clone(), self.env.tx.gas_price.into(), config)); - self - } - - /// Enables or disables tracing - #[must_use] - pub fn set_tracing(mut self, enable: bool) -> Self { - self.inspector_config.tracing = enable; - self - } - - /// Enables or disables the debugger - #[must_use] - pub fn set_debugger(mut self, enable: bool) -> Self { - self.inspector_config.debugger = enable; - self - } - - /// Enables or disables coverage collection - #[must_use] - pub fn set_coverage(mut self, enable: bool) -> Self { - self.inspector_config.coverage = enable; - self - } - - /// Enables or disabled trace printer. - #[must_use] - pub fn set_trace_printer(mut self, enable: bool) -> Self { - self.inspector_config.trace_printer = enable; - self - } - - /// Enables the fuzzer for data collection and maybe call overriding - #[must_use] - pub fn with_fuzzer( - mut self, - call_generator: Option, - fuzz_state: EvmFuzzState, - ) -> Self { - self.inspector_config.fuzzer = Some(Fuzzer { call_generator, fuzz_state, collect: false }); - self - } - - /// Sets the EVM spec to use - #[must_use] - pub fn with_spec(mut self, spec: SpecId) -> Self { - self.env.cfg.spec_id = spec; - self - } - - /// Sets the executor gas limit. - /// - /// See [Executor::gas_limit] for more info on why you might want to set this. - #[must_use] - pub fn with_gas_limit(mut self, gas_limit: U256) -> Self { - self.gas_limit = Some(gas_limit); - self - } - - /// Configure the execution environment (gas limit, chain spec, ...) - #[must_use] - pub fn with_config(mut self, env: Env) -> Self { - self.inspector_config.block = env.block.clone(); - self.inspector_config.gas_price = env.tx.gas_price.into(); - self.env = env; - self - } - - /// Enable the chisel state inspector - #[must_use] - pub fn with_chisel_state(mut self, final_pc: usize) -> Self { - self.inspector_config.chisel_state = Some(final_pc); - self - } - - /// Builds the executor as configured. - pub fn build(self, db: Backend) -> Executor { - let gas_limit = self.gas_limit.unwrap_or(self.env.block.gas_limit.into()); - Executor::new(db, self.env, self.inspector_config, gas_limit) - } -} diff --git a/crates/evm/src/executor/fork/backend.rs b/crates/evm/src/executor/fork/backend.rs deleted file mode 100644 index cb5f9f9593964..0000000000000 --- a/crates/evm/src/executor/fork/backend.rs +++ /dev/null @@ -1,805 +0,0 @@ -//! Smart caching and deduplication of requests when using a forking provider -use crate::{ - executor::{ - backend::error::{DatabaseError, DatabaseResult}, - fork::{cache::FlushJsonBlockCacheDB, BlockchainDb}, - }, - utils::{b160_to_h160, b256_to_h256, h160_to_b160, h256_to_b256, ru256_to_u256, u256_to_ru256}, -}; -use ethers::{ - core::abi::ethereum_types::BigEndianHash, - providers::Middleware, - types::{Address, Block, BlockId, Bytes, Transaction, H256, U256}, - utils::keccak256, -}; -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, B160, B256, KECCAK_EMPTY, U256 as rU256}, -}; -use std::{ - collections::{hash_map::Entry, HashMap, VecDeque}, - 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< - Box< - dyn Future>, Err>, BlockId)> - + Send, - >, ->; -type TransactionFuture = Pin< - Box, Err>, H256)> + Send>, ->; - -type AccountInfoSender = OneshotSender>; -type StorageSender = OneshotSender>; -type BlockHashSender = OneshotSender>; -type FullBlockSender = OneshotSender>>; -type TransactionSender = OneshotSender>; - -/// Request variants that are executed by the provider -enum ProviderRequest { - Account(AccountFuture), - Storage(StorageFuture), - BlockHash(BlockHashFuture), - FullBlock(FullBlockFuture), - Transaction(TransactionFuture), -} - -/// 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(H256, TransactionSender), - /// Sets the pinned block to fetch data from - SetPinnedBlock(BlockId), -} - -/// 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 = "BackendHandler does nothing unless polled."] -pub struct BackendHandler { - provider: M, - /// 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: HashMap>, - /// 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, -} - -impl BackendHandler -where - M: Middleware + Clone + 'static, -{ - fn new( - provider: M, - 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, - } - } - - /// 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(&h160_to_b160(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(&rU256::from(number)).cloned(); - if let Some(hash) = hash { - let _ = sender.send(Ok(hash.into())); - } 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(&h160_to_b160(addr)) - .and_then(|acc| acc.get(&u256_to_ru256(idx)).copied()); - if let Some(value) = value { - let _ = sender.send(Ok(ru256_to_u256(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); - } - } - } - - /// 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", "preparing storage request, address={:?}, idx={}", address, idx); - entry.insert(vec![listener]); - let provider = self.provider.clone(); - let block_id = self.block_id; - let fut = Box::pin(async move { - // serialize & deserialize back to U256 - let idx_req = H256::from_uint(&idx); - let storage = provider.get_storage_at(address, idx_req, block_id).await; - let storage = storage.map(|storage| storage.into_uint()); - (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; - let fut = Box::pin(async move { - let balance = provider.get_balance(address, block_id); - let nonce = provider.get_transaction_count(address, block_id); - let code = provider.get_code(address, block_id); - let resp = tokio::try_join!(balance, nonce, code); - (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_with_txs(number).await; - (sender, block, number) - }); - - self.pending_requests.push(ProviderRequest::FullBlock(fut)); - } - - /// process a request for a transactions - fn request_transaction(&mut self, tx: H256, sender: TransactionSender) { - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider.get_transaction(tx).await; - (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", "preparing block hash request, number={}", number); - entry.insert(vec![listener]); - let provider = self.provider.clone(); - let fut = Box::pin(async move { - let block = provider.get_block(number).await; - - let block_hash = match block { - Ok(Some(block)) => Ok(block - .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(b256_to_h256(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)); - } - } - } -} - -impl Future for BackendHandler -where - M: Middleware + 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(eyre::Error::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.0.is_empty() { - (Some(code.0.clone()), keccak256(&code).into()) - } else { - (Some(bytes::Bytes::default()), KECCAK_EMPTY) - }; - - // update the cache - let acc = AccountInfo { - nonce: nonce.as_u64(), - balance: balance.into(), - code: code.map(|bytes| Bytecode::new_raw(bytes).to_checked()), - code_hash, - }; - pin.db.accounts().write().insert(addr.into(), 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(eyre::Error::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.into()) - .or_default() - .insert(idx.into(), value.into()); - - // 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(eyre::Error::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(rU256::from(number), value.into()); - - // 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(eyre::Error::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(Some(tx)) => Ok(tx), - Ok(None) => Err(DatabaseError::TransactionNotFound(tx_hash)), - Err(err) => { - let err = Arc::new(eyre::Error::new(err)); - Err(DatabaseError::GetTransaction(tx_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 an ethers `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(Debug, Clone)] -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: M, db: BlockchainDb, pin_block: Option) -> Self - where - M: Middleware + 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: M, - db: BlockchainDb, - pin_block: Option, - ) -> Self - where - M: Middleware + 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-thread".to_string()) - .spawn(move || { - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("failed to create fork-backend-thread tokio runtime"); - - rt.block_on(handler); - }) - .expect("failed to spawn backendhandler thread"); - trace!(target: "backendhandler", "spawned Backendhandler thread"); - - shared - } - - /// Returns a new `SharedBackend` and the `BackendHandler` - pub fn new( - provider: M, - db: BlockchainDb, - pin_block: Option, - ) -> (Self, BackendHandler) - where - M: Middleware + 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: H256) -> 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()? - }) - } - - /// 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(&self, address: B160) -> Result, Self::Error> { - trace!( target: "sharedbackend", "request basic {:?}", address); - self.do_get_basic(b160_to_h160(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(&self, hash: B256) -> Result { - Err(DatabaseError::MissingCode(b256_to_h256(hash))) - } - - fn storage(&self, address: B160, index: rU256) -> Result { - trace!( target: "sharedbackend", "request storage {:?} at {:?}", address, index); - match self.do_get_storage(b160_to_h160(address), index.into()).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 - }) { - Ok(val) => Ok(val.into()), - Err(err) => Err(err), - } - } - - fn block_hash(&self, number: rU256) -> Result { - if number > rU256::from(u64::MAX) { - return Ok(KECCAK_EMPTY) - } - let number: U256 = number.into(); - let number = number.as_u64(); - trace!( target: "sharedbackend", "request block hash for number {:?}", number); - match 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 - }) { - Ok(val) => Ok(h256_to_b256(val)), - Err(err) => Err(err), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::executor::{ - fork::{BlockchainDbMeta, CreateFork, JsonBlockCacheDB}, - opts::EvmOpts, - Backend, - }; - use ethers::types::Chain; - use foundry_common::get_http_provider; - use foundry_config::Config; - use std::{collections::BTreeSet, path::PathBuf, sync::Arc}; - const ENDPOINT: &str = "https://mainnet.infura.io/v3/40bee2d557ed4b52908c3e62345a3d8b"; - - #[tokio::test(flavor = "multi_thread")] - async fn shared_backend() { - 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: B160 = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); - - let idx = rU256::from(0u64); - let value = backend.storage(address, idx).unwrap(); - let account = backend.basic(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 = rU256::from(10u64); - let hash = backend.block_hash(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 = rU256::from(i); - let _ = backend.storage(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 provider = get_http_provider(ENDPOINT); - - let block_num = provider.get_block_number().await.unwrap().as_u64(); - - 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)).await; - - // some rng contract from etherscan - let address: B160 = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); - - let idx = rU256::from(0u64); - let _value = backend.storage(address, idx); - let _account = backend.basic(address); - - // fill some slots - let num_slots = 10u64; - for idx in 1..num_slots { - let _ = backend.storage(address, rU256::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(Chain::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/src/executor/fork/cache.rs b/crates/evm/src/executor/fork/cache.rs deleted file mode 100644 index dbd4cfe747c8e..0000000000000 --- a/crates/evm/src/executor/fork/cache.rs +++ /dev/null @@ -1,537 +0,0 @@ -//! Cache related abstraction -use crate::executor::backend::snapshot::StateSnapshot; -use hashbrown::HashMap as Map; -use parking_lot::RwLock; -use revm::{ - primitives::{Account, AccountInfo, B160, B256, KECCAK_EMPTY, U256}, - 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 [revm::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(Debug, Clone, 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); - - BlockchainDbMeta { - 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::CfgEnv] - /// - /// This prevents deserialization errors of cache files caused by breaking changes to the - /// default [revm::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() { - // additional field `disable_eip3607` enabled by the `optional_eip3607` feature - let key = "disable_eip3607"; - if !obj.contains_key(key) { - obj.insert(key.to_string(), true.into()); - } - // additional field `disable_block_gas_limit` enabled by the - // `optional_block_gas_limit` feature - let key = "disable_block_gas_limit"; - if !obj.contains_key(key) { - // keep default value - obj.insert(key.to_string(), false.into()); - } - let key = "disable_base_fee"; - if !obj.contains_key(key) { - // keep default value - obj.insert(key.to_string(), false.into()); - } - } - - let cfg_env: revm::primitives::CfgEnv = - 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: revm::primitives::BlockEnv, - /// 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, - 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: B160, 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_destroyed { - 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) - { - 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.storage_cleared { - acc_storage.clear(); - } - for (index, value) in acc.storage { - if value.present_value() == U256::from(0) { - 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 [BlockCacheDB] 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. - #[tracing::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))?; - - let meta = self.meta.read(); - map.serialize_entry("meta", &*meta)?; - drop(meta); - - let accounts = self.data.accounts.read(); - map.serialize_entry("accounts", &*accounts)?; - drop(accounts); - - let storage = self.data.storage.read(); - map.serialize_entry("storage", &*storage)?; - drop(storage); - - let block_hashes = self.data.block_hashes.read(); - map.serialize_entry("block_hashes", &*block_hashes)?; - drop(block_hashes); - - 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(JsonBlockCacheData { - 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": "0x539", - "spec_id": "LATEST", - "perf_all_precompiles_have_balance": false, - "perf_analyse_created_bytecodes": "Analyse", - "limit_contract_code_size": 18446744073709551615, - "memory_limit": 4294967295 - }, - "block_env": { - "number": "0xed3ddf", - "coinbase": "0x0000000000000000000000000000000000000000", - "timestamp": "0x6324bc3f", - "difficulty": "0x0", - "basefee": "0x2e5fda223", - "gas_limit": "0x1c9c380" - }, - "hosts": [ - "eth-mainnet.alchemyapi.io" - ] - }, - "accounts": { - "0xb8ffc3cd6e7cf5a098a1c92f48009765b24088dc": { - "balance": "0x0", - "nonce": 10, - "code_hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad", - "code": { - "bytecode": "0x60806040526004361061006c5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416634555d5c9811461012b5780634558850c1461015257806348a0c8dd146101965780635c60da1b146101bf57806386070cfe146101d4575b6127107f665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea6000825a10156100e15760003411361583541616156100dc576040513381523460208201527f15eeaa57c7bd188c1388020bcadc2c436ec60d647d36ef5b9eb3c742217ddee1604082a1005b600080fd5b6100e96101e9565b9050610126816000368080601f0160208091040260200160405190810160405280939291908181526020018383808284375061026c945050505050565b505050005b34801561013757600080fd5b506101406102ad565b60408051918252519081900360200190f35b34801561015e57600080fd5b5061016d6004356024356102b2565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156101a257600080fd5b506101ab6102e2565b604080519115158252519081900360200190f35b3480156101cb57600080fd5b5061016d6101e9565b3480156101e057600080fd5b50610140610312565b7f3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c6000527fc67454ed56db7ff90a4bb32fc9a8de1ab3174b221e5fecea22b7503a3111791f6020527f8e2ed18767e9c33b25344c240cdf92034fae56be99e2c07f3d9946d949ffede45473ffffffffffffffffffffffffffffffffffffffff1690565b600061027783610318565b151561028257600080fd5b612710905060008083516020850186855a03f43d604051816000823e8280156102a9578282f35b8282fd5b600290565b600060208181529281526040808220909352908152205473ffffffffffffffffffffffffffffffffffffffff1681565b600061030d7f665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea610352565b905090565b60015481565b60008073ffffffffffffffffffffffffffffffffffffffff83161515610341576000915061034c565b823b90506000811191505b50919050565b54905600a165627a7a72305820968d404e148c1ec7bb58c8df6cbdcaad4978b93a804e00a1f0e97a5e789eacd40029000000000000000000000000000000000000000000000000000000000000000000", - "hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad", - "state": { - "Checked": { - "len": 898 - } - } - } - } - }, - "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(); - } -} diff --git a/crates/evm/src/executor/fork/init.rs b/crates/evm/src/executor/fork/init.rs deleted file mode 100644 index 849b46f51cf00..0000000000000 --- a/crates/evm/src/executor/fork/init.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::utils::{ - apply_chain_and_block_specific_env_changes, h160_to_b160, h256_to_b256, u256_to_ru256, -}; -use ethers::{ - providers::Middleware, - types::{Address, Block, TxHash, U256}, -}; -use eyre::WrapErr; -use foundry_common::NON_ARCHIVE_NODE_WARNING; -use futures::TryFutureExt; -use revm::primitives::{BlockEnv, CfgEnv, Env, TxEnv}; - -/// Initializes a REVM block environment based on a forked -/// ethereum provider. -pub async fn environment( - provider: &M, - memory_limit: u64, - gas_price: Option, - override_chain_id: Option, - pin_block: Option, - origin: Address, -) -> eyre::Result<(Env, Block)> -where - M::Error: 'static, -{ - let block_number = if let Some(pin_block) = pin_block { - pin_block - } else { - provider.get_block_number().await.wrap_err("Failed to get latest block number")?.as_u64() - }; - let (fork_gas_price, rpc_chain_id, block) = tokio::try_join!( - provider - .get_gas_price() - .map_err(|err| { eyre::Error::new(err).wrap_err("Failed to get gas price") }), - provider - .get_chainid() - .map_err(|err| { eyre::Error::new(err).wrap_err("Failed to get chain id") }), - provider.get_block(block_number).map_err(|err| { - eyre::Error::new(err).wrap_err(format!("Failed to get block {block_number}")) - }) - )?; - let block = if let Some(block) = block { - block - } else { - if let Ok(latest_block) = provider.get_block_number().await { - // If the `eth_getBlockByNumber` call succeeds, but returns null instead of - // the block, and the block number is less than equal the latest block, then - // the user is forking from a non-archive node with an older block number. - if block_number <= latest_block.as_u64() { - error!("{NON_ARCHIVE_NODE_WARNING}"); - } - eyre::bail!( - "Failed to get block for block number: {}\nlatest block number: {}", - block_number, - latest_block - ); - } - eyre::bail!("Failed to get block for block number: {}", block_number) - }; - - let mut env = Env { - cfg: CfgEnv { - chain_id: u256_to_ru256(override_chain_id.unwrap_or(rpc_chain_id.as_u64()).into()), - memory_limit, - limit_contract_code_size: Some(usize::MAX), - // EIP-3607 rejects transactions from senders with deployed code. - // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller - // is a contract. So we disable the check by default. - disable_eip3607: true, - ..Default::default() - }, - block: BlockEnv { - number: u256_to_ru256(block.number.expect("block number not found").as_u64().into()), - timestamp: block.timestamp.into(), - coinbase: h160_to_b160(block.author.unwrap_or_default()), - difficulty: block.difficulty.into(), - prevrandao: Some(block.mix_hash.map(h256_to_b256).unwrap_or_default()), - basefee: block.base_fee_per_gas.unwrap_or_default().into(), - gas_limit: block.gas_limit.into(), - }, - tx: TxEnv { - caller: h160_to_b160(origin), - gas_price: gas_price.map(U256::from).unwrap_or(fork_gas_price).into(), - chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id.as_u64())), - gas_limit: block.gas_limit.as_u64(), - ..Default::default() - }, - }; - - apply_chain_and_block_specific_env_changes(&mut env, &block); - - Ok((env, block)) -} diff --git a/crates/evm/src/executor/inspector/access_list.rs b/crates/evm/src/executor/inspector/access_list.rs deleted file mode 100644 index 2dbd4a3dfb4a4..0000000000000 --- a/crates/evm/src/executor/inspector/access_list.rs +++ /dev/null @@ -1,101 +0,0 @@ -use ethers::{ - abi::{ethereum_types::BigEndianHash, Address}, - types::{ - transaction::eip2930::{AccessList, AccessListItem}, - H256, - }, -}; -use hashbrown::{HashMap, HashSet}; -use revm::{ - interpreter::{opcode, InstructionResult, Interpreter}, - Database, EVMData, Inspector, -}; - -use crate::utils::{b160_to_h160, ru256_to_u256}; - -/// An inspector that collects touched accounts and storage slots. -#[derive(Default, Debug)] -pub struct AccessListTracer { - excluded: HashSet
, - access_list: HashMap>, -} - -impl AccessListTracer { - pub fn new( - access_list: AccessList, - from: Address, - to: Address, - precompiles: Vec
, - ) -> Self { - AccessListTracer { - excluded: [from, to].iter().chain(precompiles.iter()).copied().collect(), - access_list: access_list - .0 - .iter() - .map(|v| (v.address, v.storage_keys.iter().copied().collect())) - .collect(), - } - } - - pub fn access_list(&self) -> AccessList { - AccessList::from( - self.access_list - .iter() - .map(|(address, slots)| AccessListItem { - address: *address, - storage_keys: slots.iter().copied().collect(), - }) - .collect::>(), - ) - } -} - -impl Inspector for AccessListTracer -where - DB: Database, -{ - fn step( - &mut self, - interpreter: &mut Interpreter, - _data: &mut EVMData<'_, DB>, - _is_static: bool, - ) -> InstructionResult { - let pc = interpreter.program_counter(); - let op = interpreter.contract.bytecode.bytecode()[pc]; - - match op { - opcode::SLOAD | opcode::SSTORE => { - if let Ok(slot) = interpreter.stack().peek(0) { - let cur_contract = interpreter.contract.address; - self.access_list - .entry(b160_to_h160(cur_contract)) - .or_default() - .insert(H256::from_uint(&ru256_to_u256(slot))); - } - } - opcode::EXTCODECOPY | - opcode::EXTCODEHASH | - opcode::EXTCODESIZE | - opcode::BALANCE | - opcode::SELFDESTRUCT => { - if let Ok(slot) = interpreter.stack().peek(0) { - let addr: Address = H256::from_uint(&ru256_to_u256(slot)).into(); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - opcode::DELEGATECALL | opcode::CALL | opcode::STATICCALL | opcode::CALLCODE => { - if let Ok(slot) = interpreter.stack().peek(1) { - let addr: Address = H256::from_uint(&ru256_to_u256(slot)).into(); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - _ => (), - } - - InstructionResult::Continue - } -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/env.rs b/crates/evm/src/executor/inspector/cheatcodes/env.rs deleted file mode 100644 index bd122387697e2..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/env.rs +++ /dev/null @@ -1,708 +0,0 @@ -use super::{ensure, fmt_err, Cheatcodes, Result}; -use crate::{ - abi::HEVMCalls, - executor::{ - backend::DatabaseExt, - inspector::cheatcodes::{ - mapping::{get_mapping_key_and_parent, get_mapping_length, get_mapping_slot_at}, - util::{is_potential_precompile, with_journaled_account}, - DealRecord, - }, - }, - utils::{b160_to_h160, h160_to_b160, ru256_to_u256, u256_to_ru256}, -}; -use ethers::{ - abi::{self, AbiEncode, RawLog, Token, Tokenizable, Tokenize}, - signers::{LocalWallet, Signer}, - types::{Address, Bytes, U256}, -}; -use foundry_config::Config; -use revm::{ - primitives::{Bytecode, SpecId, B256, KECCAK_EMPTY}, - Database, EVMData, -}; -use std::collections::BTreeMap; - -#[derive(Clone, Debug, Default)] -pub struct Broadcast { - /// Address of the transaction origin - pub new_origin: Address, - /// Original caller - pub original_caller: Address, - /// Original `tx.origin` - pub original_origin: Address, - /// Depth of the broadcast - pub depth: u64, - /// Whether the prank stops by itself after the next call - pub single_call: bool, -} - -#[derive(Clone, Debug, Default)] -pub struct Prank { - /// Address of the contract that initiated the prank - pub prank_caller: Address, - /// Address of `tx.origin` when the prank was initiated - pub prank_origin: Address, - /// The address to assign to `msg.sender` - pub new_caller: Address, - /// The address to assign to `tx.origin` - pub new_origin: Option
, - /// The depth at which the prank was called - pub depth: u64, - /// Whether the prank stops by itself after the next call - pub single_call: bool, - /// Whether the prank has been used yet (false if unused) - pub used: bool, -} - -impl Prank { - pub fn new( - prank_caller: Address, - prank_origin: Address, - new_caller: Address, - new_origin: Option
, - depth: u64, - single_call: bool, - ) -> Prank { - Prank { - prank_caller, - prank_origin, - new_caller, - new_origin, - depth, - single_call, - used: false, - } - } - - /// Apply the prank by setting `used` to true iff it is false - /// Only returns self in the case it is updated (first application) - pub fn first_time_applied(&self) -> Option { - if self.used { - None - } else { - Some(Prank { used: true, ..self.clone() }) - } - } -} - -/// Represents the possible caller modes for the readCallers() cheat code return value -enum CallerMode { - /// No caller modification is currently active - None, - /// A one time broadcast triggered by a `vm.broadcast()` call is currently active - Broadcast, - /// A recurrent broadcast triggered by a `vm.startBroadcast()` call is currently active - RecurrentBroadcast, - /// A one time prank triggered by a `vm.prank()` call is currently active - Prank, - /// A recurrent prank triggered by a `vm.startPrank()` call is currently active - RecurrentPrank, -} - -impl From for U256 { - fn from(value: CallerMode) -> Self { - (value as i8).into() - } -} - -/// Sets up broadcasting from a script using `origin` as the sender -fn broadcast( - state: &mut Cheatcodes, - new_origin: Address, - original_caller: Address, - original_origin: Address, - depth: u64, - single_call: bool, -) -> Result { - ensure!( - state.prank.is_none(), - "You have an active prank. Broadcasting and pranks are not compatible. \ - Disable one or the other" - ); - ensure!(state.broadcast.is_none(), "You have an active broadcast already."); - - let broadcast = Broadcast { new_origin, original_origin, original_caller, depth, single_call }; - state.broadcast = Some(broadcast); - Ok(Bytes::new()) -} - -/// Sets up broadcasting from a script with the sender derived from `private_key` -/// Adds this private key to `state`'s `script_wallets` vector to later be used for signing -/// iff broadcast is successful -fn broadcast_key( - state: &mut Cheatcodes, - private_key: U256, - original_caller: Address, - original_origin: Address, - chain_id: U256, - depth: u64, - single_call: bool, -) -> Result { - let key = super::util::parse_private_key(private_key)?; - let wallet = LocalWallet::from(key).with_chain_id(chain_id.as_u64()); - let new_origin = wallet.address(); - - let result = broadcast(state, new_origin, original_caller, original_origin, depth, single_call); - if result.is_ok() { - state.script_wallets.push(wallet); - } - result -} - -fn prank( - state: &mut Cheatcodes, - prank_caller: Address, - prank_origin: Address, - new_caller: Address, - new_origin: Option
, - depth: u64, - single_call: bool, -) -> Result { - let prank = Prank::new(prank_caller, prank_origin, new_caller, new_origin, depth, single_call); - - if let Some(Prank { used, single_call: current_single_call, .. }) = state.prank { - ensure!(used, "You cannot overwrite `prank` until it is applied at least once"); - // This case can only fail if the user calls `vm.startPrank` and then `vm.prank` later on. - // This should not be possible without first calling `stopPrank` - ensure!(single_call == current_single_call, "You cannot override an ongoing prank with a single vm.prank. Use vm.startPrank to override the current prank."); - } - - ensure!( - state.broadcast.is_none(), - "You cannot `prank` for a broadcasted transaction.\ - Pass the desired tx.origin into the broadcast cheatcode call" - ); - - state.prank = Some(prank); - Ok(Bytes::new()) -} - -/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and -/// `tx.origin`. -/// -/// Depending on the current caller mode, one of the following results will be returned: -/// - If there is an active prank: -/// - caller_mode will be equal to: -/// - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`. -/// - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`. -/// - `msg.sender` will be equal to the address set for the prank. -/// - `tx.origin` will be equal to the default sender address unless an alternative one has been -/// set when configuring the prank. -/// -/// - If there is an active broadcast: -/// - caller_mode will be equal to: -/// - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`. -/// - [CallerMode::RecurrentBroadcast] if the broadcast has been set with -/// `vm.startBroadcast(..)`. -/// - `msg.sender` and `tx.origin` will be equal to the address provided when setting the -/// broadcast. -/// -/// - If no caller modification is active: -/// - caller_mode will be equal to [CallerMode::None], -/// - `msg.sender` and `tx.origin` will be equal to the default sender address. -fn read_callers(state: &Cheatcodes, default_sender: Address) -> Bytes { - let Cheatcodes { prank, broadcast, .. } = &state; - - let data = if let Some(prank) = prank { - let caller_mode = - if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank }; - - [ - Token::Uint(caller_mode.into()), - Token::Address(prank.new_caller), - Token::Address(prank.new_origin.unwrap_or(default_sender)), - ] - } else if let Some(broadcast) = broadcast { - let caller_mode = if broadcast.single_call { - CallerMode::Broadcast - } else { - CallerMode::RecurrentBroadcast - }; - - [ - Token::Uint(caller_mode.into()), - Token::Address(broadcast.new_origin), - Token::Address(broadcast.new_origin), - ] - } else { - [ - Token::Uint(CallerMode::None.into()), - Token::Address(default_sender), - Token::Address(default_sender), - ] - }; - - abi::encode(&data).into() -} - -#[derive(Clone, Debug, Default)] -pub struct RecordAccess { - pub reads: BTreeMap>, - pub writes: BTreeMap>, -} - -fn start_record(state: &mut Cheatcodes) { - state.accesses = Some(Default::default()); -} - -fn accesses(state: &mut Cheatcodes, address: Address) -> Bytes { - if let Some(storage_accesses) = &mut state.accesses { - let first_token = - |x: Option>| x.unwrap_or_default().into_tokens().into_iter().next().unwrap(); - ethers::abi::encode(&[ - first_token(storage_accesses.reads.remove(&address)), - first_token(storage_accesses.writes.remove(&address)), - ]) - .into() - } else { - ethers::abi::encode(&[Token::Array(vec![]), Token::Array(vec![])]).into() - } -} - -#[derive(Clone, Debug, Default)] -pub struct RecordedLogs { - pub entries: Vec, -} - -#[derive(Clone, Debug)] -pub struct Log { - pub emitter: Address, - pub inner: RawLog, -} - -fn start_record_logs(state: &mut Cheatcodes) { - state.recorded_logs = Some(Default::default()); -} - -fn get_recorded_logs(state: &mut Cheatcodes) -> Bytes { - if let Some(recorded_logs) = state.recorded_logs.replace(Default::default()) { - abi::encode( - &recorded_logs - .entries - .iter() - .map(|entry| { - Token::Tuple(vec![ - entry.inner.topics.clone().into_token(), - Token::Bytes(entry.inner.data.clone()), - entry.emitter.into_token(), - ]) - }) - .collect::>() - .into_tokens(), - ) - .into() - } else { - abi::encode(&[Token::Array(vec![])]).into() - } -} - -/// Entry point of the breakpoint cheatcode. Adds the called breakpoint to the state. -fn add_breakpoint(state: &mut Cheatcodes, caller: Address, inner: &str, add: bool) -> Result { - let mut chars = inner.chars(); - let point = chars.next(); - - let point = - point.ok_or_else(|| fmt_err!("Please provide at least one char for the breakpoint"))?; - - ensure!(chars.next().is_none(), "Provide only one character for the breakpoint"); - ensure!(point.is_alphabetic(), "Only alphabetic characters are accepted as breakpoints"); - - // add a breakpoint from the interpreter - if add { - state.breakpoints.insert(point, (caller, state.pc)); - } else { - state.breakpoints.remove(&point); - } - - Ok(Bytes::new()) -} - -#[instrument(level = "error", name = "env", target = "evm::cheatcodes", skip_all)] -pub fn apply( - state: &mut Cheatcodes, - data: &mut EVMData<'_, DB>, - caller: Address, - call: &HEVMCalls, -) -> Result> { - let result = match call { - HEVMCalls::Warp(inner) => { - data.env.block.timestamp = inner.0.into(); - Bytes::new() - } - HEVMCalls::Difficulty(inner) => { - ensure!( - data.env.cfg.spec_id < SpecId::MERGE, - "`difficulty` is not supported after the Paris hard fork, \ - use `prevrandao` instead. \ - For more information, please see https://eips.ethereum.org/EIPS/eip-4399" - ); - data.env.block.difficulty = inner.0.into(); - Bytes::new() - } - HEVMCalls::Prevrandao(inner) => { - ensure!( - data.env.cfg.spec_id >= SpecId::MERGE, - "`prevrandao` is not supported before the Paris hard fork, \ - use `difficulty` instead. \ - For more information, please see https://eips.ethereum.org/EIPS/eip-4399" - ); - data.env.block.prevrandao = Some(B256::from(inner.0)); - Bytes::new() - } - HEVMCalls::Roll(inner) => { - data.env.block.number = inner.0.into(); - Bytes::new() - } - HEVMCalls::Fee(inner) => { - data.env.block.basefee = inner.0.into(); - Bytes::new() - } - HEVMCalls::Coinbase(inner) => { - data.env.block.coinbase = h160_to_b160(inner.0); - Bytes::new() - } - HEVMCalls::Store(inner) => { - ensure!(!is_potential_precompile(inner.0), "Store cannot be used on precompile addresses (N < 10). Please use an address bigger than 10 instead"); - data.journaled_state.load_account(h160_to_b160(inner.0), data.db)?; - // ensure the account is touched - data.journaled_state.touch(&h160_to_b160(inner.0)); - - data.journaled_state.sstore( - h160_to_b160(inner.0), - u256_to_ru256(inner.1.into()), - u256_to_ru256(inner.2.into()), - data.db, - )?; - Bytes::new() - } - HEVMCalls::Load(inner) => { - ensure!(!is_potential_precompile(inner.0), "Load cannot be used on precompile addresses (N < 10). Please use an address bigger than 10 instead"); - // TODO: Does this increase gas usage? - data.journaled_state.load_account(h160_to_b160(inner.0), data.db)?; - let (val, _) = data.journaled_state.sload( - h160_to_b160(inner.0), - u256_to_ru256(inner.1.into()), - data.db, - )?; - ru256_to_u256(val).encode().into() - } - HEVMCalls::Breakpoint0(inner) => add_breakpoint(state, caller, &inner.0, true)?, - HEVMCalls::Breakpoint1(inner) => add_breakpoint(state, caller, &inner.0, inner.1)?, - HEVMCalls::Etch(inner) => { - ensure!(!is_potential_precompile(inner.0), "Etch cannot be used on precompile addresses (N < 10). Please use an address bigger than 10 instead"); - let code = inner.1.clone(); - trace!(address=?inner.0, code=?hex::encode(&code), "etch cheatcode"); - // TODO: Does this increase gas usage? - data.journaled_state.load_account(h160_to_b160(inner.0), data.db)?; - data.journaled_state - .set_code(h160_to_b160(inner.0), Bytecode::new_raw(code.0).to_checked()); - Bytes::new() - } - HEVMCalls::Deal(inner) => { - let who = inner.0; - let value = inner.1; - trace!(?who, ?value, "deal cheatcode"); - with_journaled_account(&mut data.journaled_state, data.db, who, |account| { - // record the deal - let record = DealRecord { - address: who, - old_balance: account.info.balance.into(), - new_balance: value, - }; - state.eth_deals.push(record); - - account.info.balance = value.into(); - })?; - Bytes::new() - } - HEVMCalls::Prank0(inner) => prank( - state, - caller, - b160_to_h160(data.env.tx.caller), - inner.0, - None, - data.journaled_state.depth(), - true, - )?, - HEVMCalls::Prank1(inner) => prank( - state, - caller, - b160_to_h160(data.env.tx.caller), - inner.0, - Some(inner.1), - data.journaled_state.depth(), - true, - )?, - HEVMCalls::StartPrank0(inner) => prank( - state, - caller, - b160_to_h160(data.env.tx.caller), - inner.0, - None, - data.journaled_state.depth(), - false, - )?, - HEVMCalls::StartPrank1(inner) => prank( - state, - caller, - b160_to_h160(data.env.tx.caller), - inner.0, - Some(inner.1), - data.journaled_state.depth(), - false, - )?, - HEVMCalls::StopPrank(_) => { - ensure!(state.prank.is_some(), "No prank in progress to stop"); - state.prank = None; - Bytes::new() - } - HEVMCalls::ReadCallers(_) => read_callers(state, b160_to_h160(data.env.tx.caller)), - HEVMCalls::Record(_) => { - start_record(state); - Bytes::new() - } - HEVMCalls::Accesses(inner) => accesses(state, inner.0), - HEVMCalls::RecordLogs(_) => { - start_record_logs(state); - Bytes::new() - } - HEVMCalls::GetRecordedLogs(_) => get_recorded_logs(state), - HEVMCalls::SetNonce(inner) => { - with_journaled_account( - &mut data.journaled_state, - data.db, - inner.0, - |account| -> Result { - // nonce must increment only - let current = account.info.nonce; - let new = inner.1; - ensure!( - new >= current, - "New nonce ({new}) must be strictly equal to or higher than the \ - account's current nonce ({current})." - ); - account.info.nonce = new; - Ok(Bytes::new()) - }, - )?? - } - HEVMCalls::SetNonceUnsafe(inner) => with_journaled_account( - &mut data.journaled_state, - data.db, - inner.0, - |account| -> Result { - let new = inner.1; - account.info.nonce = new; - Ok(Bytes::new()) - }, - )??, - HEVMCalls::ResetNonce(inner) => with_journaled_account( - &mut data.journaled_state, - data.db, - inner.0, - |account| -> Result { - // Per EIP-161, EOA nonces start at 0, but contract nonces - // start at 1. Comparing by code_hash instead of code - // to avoid hitting the case where account's code is None. - let empty = account.info.code_hash == KECCAK_EMPTY; - let nonce = if empty { 0 } else { 1 }; - account.info.nonce = nonce; - Ok(Bytes::new()) - }, - )??, - // [function getNonce(address)] returns the current nonce of a given ETH address - HEVMCalls::GetNonce1(inner) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - - // TODO: this is probably not a good long-term solution since it might mess up the gas - // calculations - data.journaled_state.load_account(h160_to_b160(inner.0), data.db)?; - - // we can safely unwrap because `load_account` insert inner.0 to DB. - let account = data.journaled_state.state().get(&h160_to_b160(inner.0)).unwrap(); - abi::encode(&[Token::Uint(account.info.nonce.into())]).into() - } - // [function getNonce(Wallet)] returns the current nonce of the Wallet's ETH address - HEVMCalls::GetNonce0(inner) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - - // TODO: this is probably not a good long-term solution since it might mess up the gas - // calculations - data.journaled_state.load_account(h160_to_b160(inner.0.addr), data.db)?; - - // we can safely unwrap because `load_account` insert inner.0 to DB. - let account = data.journaled_state.state().get(&h160_to_b160(inner.0.addr)).unwrap(); - abi::encode(&[Token::Uint(account.info.nonce.into())]).into() - } - HEVMCalls::ChainId(inner) => { - ensure!(inner.0 <= U256::from(u64::MAX), "Chain ID must be less than 2^64 - 1"); - data.env.cfg.chain_id = inner.0.into(); - Bytes::new() - } - HEVMCalls::TxGasPrice(inner) => { - data.env.tx.gas_price = inner.0.into(); - Bytes::new() - } - HEVMCalls::Broadcast0(_) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - broadcast( - state, - b160_to_h160(data.env.tx.caller), - caller, - b160_to_h160(data.env.tx.caller), - data.journaled_state.depth(), - true, - )? - } - HEVMCalls::Broadcast1(inner) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - broadcast( - state, - inner.0, - caller, - b160_to_h160(data.env.tx.caller), - data.journaled_state.depth(), - true, - )? - } - HEVMCalls::Broadcast2(inner) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - broadcast_key( - state, - inner.0, - caller, - b160_to_h160(data.env.tx.caller), - data.env.cfg.chain_id.into(), - data.journaled_state.depth(), - true, - )? - } - HEVMCalls::StartBroadcast0(_) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - broadcast( - state, - b160_to_h160(data.env.tx.caller), - caller, - b160_to_h160(data.env.tx.caller), - data.journaled_state.depth(), - false, - )? - } - HEVMCalls::StartBroadcast1(inner) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - broadcast( - state, - inner.0, - caller, - b160_to_h160(data.env.tx.caller), - data.journaled_state.depth(), - false, - )? - } - HEVMCalls::StartBroadcast2(inner) => { - correct_sender_nonce( - b160_to_h160(data.env.tx.caller), - &mut data.journaled_state, - &mut data.db, - state, - )?; - broadcast_key( - state, - inner.0, - caller, - b160_to_h160(data.env.tx.caller), - data.env.cfg.chain_id.into(), - data.journaled_state.depth(), - false, - )? - } - HEVMCalls::StopBroadcast(_) => { - ensure!(state.broadcast.is_some(), "No broadcast in progress to stop"); - state.broadcast = None; - Bytes::new() - } - HEVMCalls::PauseGasMetering(_) => { - if state.gas_metering.is_none() { - state.gas_metering = Some(None); - } - Bytes::new() - } - HEVMCalls::ResumeGasMetering(_) => { - state.gas_metering = None; - Bytes::new() - } - HEVMCalls::StartMappingRecording(_) => { - if state.mapping_slots.is_none() { - state.mapping_slots = Some(Default::default()); - } - Bytes::new() - } - HEVMCalls::StopMappingRecording(_) => { - state.mapping_slots = None; - Bytes::new() - } - HEVMCalls::GetMappingLength(inner) => get_mapping_length(state, inner.0, inner.1.into()), - HEVMCalls::GetMappingSlotAt(inner) => { - get_mapping_slot_at(state, inner.0, inner.1.into(), inner.2) - } - HEVMCalls::GetMappingKeyAndParentOf(inner) => { - get_mapping_key_and_parent(state, inner.0, inner.1.into()) - } - _ => return Ok(None), - }; - Ok(Some(result)) -} - -/// When using `forge script`, the script method is called using the address from `--sender`. -/// That leads to its nonce being incremented by `call_raw`. In a `broadcast` scenario this is -/// undesirable. Therefore, we make sure to fix the sender's nonce **once**. -fn correct_sender_nonce( - sender: Address, - journaled_state: &mut revm::JournaledState, - db: &mut DB, - state: &mut Cheatcodes, -) -> Result<(), DB::Error> { - if !state.corrected_nonce && sender != Config::DEFAULT_SENDER { - with_journaled_account(journaled_state, db, sender, |account| { - account.info.nonce = account.info.nonce.saturating_sub(1); - state.corrected_nonce = true; - })?; - } - Ok(()) -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/error.rs b/crates/evm/src/executor/inspector/cheatcodes/error.rs deleted file mode 100644 index 5461a9fa6a0bd..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/error.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::executor::backend::{error::NoCheatcodeAccessError, DatabaseError}; -use ethers::{ - abi::AbiEncode, prelude::k256::ecdsa::signature::Error as SignatureError, types::Bytes, -}; -use foundry_common::errors::FsPathError; -use foundry_config::UnresolvedEnvVarError; -use foundry_utils::error::{encode_error, SolError}; -use std::{borrow::Cow, fmt::Arguments}; - -/// Type alias with a default Ok type of [`Bytes`], and default Err type of [`Error`]. -pub type Result = std::result::Result; - -macro_rules! fmt_err { - ($msg:literal $(,)?) => { - $crate::executor::inspector::cheatcodes::Error::fmt(::std::format_args!($msg)) - }; - ($err:expr $(,)?) => { - <$crate::executor::inspector::cheatcodes::Error as ::std::convert::From<_>>::from($err) - }; - ($fmt:expr, $($arg:tt)*) => { - $crate::executor::inspector::cheatcodes::Error::fmt(::std::format_args!($fmt, $($arg)*)) - }; -} - -macro_rules! bail { - ($msg:literal $(,)?) => { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::fmt_err!($msg)) - }; - ($err:expr $(,)?) => { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::fmt_err!($err)) - }; - ($fmt:expr, $($arg:tt)*) => { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::fmt_err!($fmt, $($arg)*)) - }; -} - -macro_rules! ensure { - ($cond:expr $(,)?) => { - if !$cond { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::Error::custom( - ::std::concat!("Condition failed: `", ::std::stringify!($cond), "`") - )); - } - }; - ($cond:expr, $msg:literal $(,)?) => { - if !$cond { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::fmt_err!($msg)); - } - }; - ($cond:expr, $err:expr $(,)?) => { - if !$cond { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::fmt_err!($err)); - } - }; - ($cond:expr, $fmt:expr, $($arg:tt)*) => { - if !$cond { - return ::std::result::Result::Err($crate::executor::inspector::cheatcodes::fmt_err!($fmt, $($arg)*)); - } - }; -} - -pub(crate) use bail; -pub(crate) use ensure; -pub(crate) use fmt_err; - -/// Errors that can happen when working with [`Cheacodes`]. -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("You need to stop broadcasting before you can select forks.")] - SelectForkDuringBroadcast, - - #[error(transparent)] - Eyre(#[from] eyre::Report), - - #[error(transparent)] - Signature(#[from] SignatureError), - - #[error(transparent)] - Database(#[from] DatabaseError), - - #[error(transparent)] - FsPath(#[from] FsPathError), - - #[error(transparent)] - NoCheatcodeAccess(#[from] NoCheatcodeAccessError), - - #[error(transparent)] - UnresolvedEnvVar(#[from] UnresolvedEnvVarError), - - #[error(transparent)] - Abi(#[from] ethers::abi::Error), - - #[error(transparent)] - Abi2(#[from] ethers::abi::AbiError), - - #[error(transparent)] - Wallet(#[from] ethers::signers::WalletError), - - #[error(transparent)] - EthersSignature(#[from] ethers::core::types::SignatureError), - - #[error(transparent)] - Json(#[from] serde_json::Error), - - #[error(transparent)] - JsonPath(#[from] jsonpath_lib::JsonPathError), - - #[error(transparent)] - Hex(#[from] hex::FromHexError), - - #[error(transparent)] - Utf8(#[from] std::str::Utf8Error), - - #[error(transparent)] - FromUtf8(#[from] std::string::FromUtf8Error), - - #[error(transparent)] - Io(#[from] std::io::Error), - - #[error(transparent)] - TryFromInt(#[from] std::num::TryFromIntError), - - /// Custom error. - #[error("{0}")] - Custom(Cow<'static, str>), - - /// Custom bytes. Will not get encoded with the error prefix. - #[error("{}", hex::encode(_0))] // ignored in SolError implementation - CustomBytes(Cow<'static, [u8]>), -} - -impl Error { - /// Creates a new error with a custom message. - pub fn custom(msg: impl Into>) -> Self { - Self::Custom(msg.into()) - } - - /// Creates a new error with a custom `fmt::Arguments` message. - pub fn fmt(args: Arguments<'_>) -> Self { - let cow = match args.as_str() { - Some(s) => Cow::Borrowed(s), - None => Cow::Owned(std::fmt::format(args)), - }; - Self::Custom(cow) - } - - /// Creates a new error with the given bytes. - pub fn custom_bytes(bytes: impl Into>) -> Self { - Self::CustomBytes(bytes.into()) - } -} - -impl From> for Error { - fn from(value: Cow<'static, str>) -> Self { - Self::Custom(value) - } -} - -impl From for Error { - fn from(value: String) -> Self { - Self::Custom(value.into()) - } -} - -impl From<&'static str> for Error { - fn from(value: &'static str) -> Self { - Self::Custom(value.into()) - } -} - -impl SolError for Error { - fn encode_error(&self) -> Bytes { - match self { - Self::CustomBytes(cow) => cow_to_bytes(cow), - e => encode_error(e), - } - } - - fn encode_string(&self) -> Bytes { - match self { - Self::CustomBytes(cow) => cow_to_bytes(cow), - e => e.to_string().encode().into(), - } - } -} - -#[allow(clippy::ptr_arg)] // need to match on the Cow. -fn cow_to_bytes(cow: &Cow<'static, [u8]>) -> Bytes { - match cow { - Cow::Borrowed(slice) => Bytes::from_static(slice), - Cow::Owned(vec) => Bytes(vec.clone().into()), - } -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/expect.rs b/crates/evm/src/executor/inspector/cheatcodes/expect.rs deleted file mode 100644 index 0f15381144bd6..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/expect.rs +++ /dev/null @@ -1,556 +0,0 @@ -use super::{bail, ensure, fmt_err, Cheatcodes, Result}; -use crate::{abi::HEVMCalls, executor::backend::DatabaseExt, utils::h160_to_b160}; -use ethers::{ - abi::{AbiDecode, RawLog}, - contract::Lazy, - types::{Address, Bytes, H160, U256}, -}; -use foundry_utils::error::{ERROR_PREFIX, REVERT_PREFIX}; -use revm::{ - interpreter::{return_ok, InstructionResult}, - primitives::Bytecode, - EVMData, -}; -use std::cmp::Ordering; - -/// For some cheatcodes we may internally change the status of the call, i.e. in `expectRevert`. -/// Solidity will see a successful call and attempt to decode the return data. Therefore, we need -/// to populate the return with dummy bytes so the decode doesn't fail. -/// -/// 8912 bytes was arbitrarily chosen because it is long enough for return values up to 256 words in -/// size. -static DUMMY_CALL_OUTPUT: Lazy = Lazy::new(|| Bytes::from_static(&[0u8; 8192])); - -/// Same reasoning as [DUMMY_CALL_OUTPUT], but for creates. -static DUMMY_CREATE_ADDRESS: Address = - H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); - -#[derive(Clone, Debug, Default)] -pub struct ExpectedRevert { - /// The expected data returned by the revert, None being any - pub reason: Option, - /// The depth at which the revert is expected - pub depth: u64, -} - -fn expect_revert(state: &mut Cheatcodes, reason: Option, depth: u64) -> Result { - ensure!( - state.expected_revert.is_none(), - "You must call another function prior to expecting a second revert." - ); - state.expected_revert = Some(ExpectedRevert { reason, depth }); - Ok(Bytes::new()) -} - -#[instrument(skip_all, fields(expected_revert, status, retdata = hex::encode(&retdata)))] -pub fn handle_expect_revert( - is_create: bool, - expected_revert: Option<&Bytes>, - status: InstructionResult, - retdata: Bytes, -) -> Result<(Option
, Bytes)> { - trace!("handle expect revert"); - - ensure!(!matches!(status, return_ok!()), "Call did not revert as expected"); - - macro_rules! success_return { - () => { - Ok(if is_create { - (Some(DUMMY_CREATE_ADDRESS), Bytes::new()) - } else { - trace!("successfully handled expected revert"); - (None, DUMMY_CALL_OUTPUT.clone()) - }) - }; - } - - // If None, accept any revert - let expected_revert = match expected_revert { - Some(x) => x, - None => return success_return!(), - }; - - if !expected_revert.is_empty() && retdata.is_empty() { - bail!("Call reverted as expected, but without data"); - } - - let mut actual_revert = retdata; - if actual_revert.len() >= 4 && - matches!(actual_revert[..4].try_into(), Ok(ERROR_PREFIX | REVERT_PREFIX)) - { - if let Ok(bytes) = Bytes::decode(&actual_revert[4..]) { - actual_revert = bytes; - } - } - - if actual_revert == *expected_revert { - success_return!() - } else { - let stringify = |data: &[u8]| { - String::decode(data) - .ok() - .or_else(|| std::str::from_utf8(data).ok().map(ToOwned::to_owned)) - .unwrap_or_else(|| format!("0x{}", hex::encode(data))) - }; - Err(fmt_err!( - "Error != expected error: {} != {}", - stringify(&actual_revert), - stringify(expected_revert), - )) - } -} - -#[derive(Clone, Debug, Default)] -pub struct ExpectedEmit { - /// The depth at which we expect this emit to have occurred - pub depth: u64, - /// The log we expect - pub log: Option, - /// The checks to perform: - /// - /// ┌───────┬───────┬───────┬────┐ - /// │topic 1│topic 2│topic 3│data│ - /// └───────┴───────┴───────┴────┘ - pub checks: [bool; 4], - /// If present, check originating address against this - pub address: Option
, - /// Whether the log was actually found in the subcalls - pub found: bool, -} - -pub fn handle_expect_emit(state: &mut Cheatcodes, log: RawLog, address: &Address) { - // Fill or check the expected emits. - // We expect for emit checks to be filled as they're declared (from oldest to newest), - // so we fill them and push them to the back of the queue. - // If the user has properly filled all the emits, they'll end up in their original order. - // If not, the queue will not be in the order the events will be intended to be filled, - // and we'll be able to later detect this and bail. - - // First, we can return early if all events have been matched. - // This allows a contract to arbitrarily emit more events than expected (additive behavior), - // as long as all the previous events were matched in the order they were expected to be. - if state.expected_emits.iter().all(|expected| expected.found) { - return - } - - // if there's anything to fill, we need to pop back. - let event_to_fill_or_check = - if state.expected_emits.iter().any(|expected| expected.log.is_none()) { - state.expected_emits.pop_back() - // Else, 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). - } else { - state.expected_emits.pop_front() - }; - - let mut event_to_fill_or_check = - event_to_fill_or_check.expect("We should have an emit to fill or check. This is a bug"); - - match event_to_fill_or_check.log { - Some(ref expected) => { - let expected_topic_0 = expected.topics.get(0); - let log_topic_0 = log.topics.get(0); - - // same topic0 and equal number of topics should be verified further, others are a no - // match - 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 - .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 == *address; - } - - // Maybe match data - if event_to_fill_or_check.checks[3] { - event_to_fill_or_check.found &= expected.data == log.data; - } - } - - // If we found the event, we can push it to the back of the queue - // and begin expecting the next event. - if event_to_fill_or_check.found { - state.expected_emits.push_back(event_to_fill_or_check); - } else { - // We did not match this event, so we need to keep waiting for the right one to - // appear. - state.expected_emits.push_front(event_to_fill_or_check); - } - } - // Fill the event. - None => { - event_to_fill_or_check.log = Some(log); - state.expected_emits.push_back(event_to_fill_or_check); - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct ExpectedCallData { - /// The expected value sent in the call - pub value: Option, - /// The expected gas supplied to the call - pub gas: Option, - /// The expected *minimum* gas supplied to the call - pub min_gas: Option, - /// The number of times the call is expected to be made. - /// If the type of call is `NonCount`, this is the lower bound for the number of calls - /// that must be seen. - /// If the type of call is `Count`, this is the exact number of calls that must be seen. - pub count: u64, - /// The type of call - pub call_type: ExpectedCallType, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub enum ExpectedCallType { - #[default] - Count, - NonCount, -} - -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct MockCallDataContext { - /// The partial calldata to match for mock - pub calldata: Bytes, - /// The value to match for mock - pub value: Option, -} - -#[derive(Clone, Debug)] -pub struct MockCallReturnData { - /// The return type for the mocked call - pub ret_type: InstructionResult, - /// Return data or error - pub data: Bytes, -} - -impl Ord for MockCallDataContext { - fn cmp(&self, other: &Self) -> Ordering { - // Calldata matching is reversed to ensure that a tighter match is - // returned if an exact match is not found. In case, there is - // a partial match to calldata that is more specific than - // a match to a msg.value, then the more specific calldata takes - // precedence. - self.calldata.cmp(&other.calldata).reverse().then(self.value.cmp(&other.value).reverse()) - } -} - -impl PartialOrd for MockCallDataContext { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -fn expect_safe_memory(state: &mut Cheatcodes, start: u64, end: u64, depth: u64) -> Result { - ensure!(start < end, "Invalid memory range: [{start}:{end}]"); - #[allow(clippy::single_range_in_vec_init)] - let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]); - offsets.push(start..end); - Ok(Bytes::new()) -} - -/// Handles expected calls specified by the `vm.expectCall` cheatcode. -/// -/// It can handle calls in two ways: -/// - If the cheatcode was used with a `count` argument, it will expect the call to be made exactly -/// `count` times. -/// e.g. `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f), 4)` will expect the -/// call to address(0xc4f3) with selector `0xd34db33f` to be made exactly 4 times. If the amount of -/// calls is less or more than 4, the test will fail. Note that the `count` argument cannot be -/// overwritten with another `vm.expectCall`. If this is attempted, `expectCall` will revert. -/// - If the cheatcode was used without a `count` argument, it will expect the call to be made at -/// least the amount of times the cheatcode -/// was called. This means that `vm.expectCall` without a count argument can be called many times, -/// but cannot be called with a `count` argument after it was called without one. If the latter -/// happens, `expectCall` will revert. e.g `vm.expectCall(address(0xc4f3), -/// abi.encodeWithSelector(0xd34db33f))` will expect the call to address(0xc4f3) and selector -/// `0xd34db33f` to be made at least once. If the amount of calls is 0, the test will fail. If the -/// call is made more than once, the test will pass. -#[allow(clippy::too_many_arguments)] -fn expect_call( - state: &mut Cheatcodes, - target: H160, - calldata: Vec, - value: Option, - gas: Option, - min_gas: Option, - count: u64, - call_type: ExpectedCallType, -) -> Result { - match call_type { - ExpectedCallType::Count => { - // Get the expected calls for this target. - let expecteds = state.expected_calls.entry(target).or_default(); - // In this case, as we're using counted expectCalls, we should not be able to set them - // more than once. - ensure!( - !expecteds.contains_key(&calldata), - "Counted expected calls can only bet set once." - ); - expecteds - .insert(calldata, (ExpectedCallData { value, gas, min_gas, count, call_type }, 0)); - Ok(Bytes::new()) - } - ExpectedCallType::NonCount => { - let expecteds = state.expected_calls.entry(target).or_default(); - // Check if the expected calldata exists. - // If it does, increment the count by one as we expect to see it one more time. - if let Some(expected) = expecteds.get_mut(&calldata) { - // Ensure we're not overwriting a counted expectCall. - ensure!( - expected.0.call_type == ExpectedCallType::NonCount, - "Cannot overwrite a counted expectCall with a non-counted expectCall." - ); - expected.0.count += 1; - } else { - // If it does not exist, then create it. - expecteds.insert( - calldata, - (ExpectedCallData { value, gas, min_gas, count, call_type }, 0), - ); - } - Ok(Bytes::new()) - } - } -} - -#[instrument(level = "error", name = "expect", target = "evm::cheatcodes", skip_all)] -pub fn apply( - state: &mut Cheatcodes, - data: &mut EVMData<'_, DB>, - call: &HEVMCalls, -) -> Option { - let result = match call { - HEVMCalls::ExpectRevert0(_) => expect_revert(state, None, data.journaled_state.depth()), - HEVMCalls::ExpectRevert1(inner) => { - expect_revert(state, Some(inner.0.clone()), data.journaled_state.depth()) - } - HEVMCalls::ExpectRevert2(inner) => { - expect_revert(state, Some(inner.0.into()), data.journaled_state.depth()) - } - HEVMCalls::ExpectEmit0(_) => { - state.expected_emits.push_back(ExpectedEmit { - depth: data.journaled_state.depth(), - checks: [true, true, true, true], - ..Default::default() - }); - Ok(Bytes::new()) - } - HEVMCalls::ExpectEmit1(inner) => { - state.expected_emits.push_back(ExpectedEmit { - depth: data.journaled_state.depth(), - checks: [true, true, true, true], - address: Some(inner.0), - ..Default::default() - }); - Ok(Bytes::new()) - } - HEVMCalls::ExpectEmit2(inner) => { - state.expected_emits.push_back(ExpectedEmit { - depth: data.journaled_state.depth(), - checks: [inner.0, inner.1, inner.2, inner.3], - ..Default::default() - }); - Ok(Bytes::new()) - } - HEVMCalls::ExpectEmit3(inner) => { - state.expected_emits.push_back(ExpectedEmit { - depth: data.journaled_state.depth(), - checks: [inner.0, inner.1, inner.2, inner.3], - address: Some(inner.4), - ..Default::default() - }); - Ok(Bytes::new()) - } - HEVMCalls::ExpectCall0(inner) => expect_call( - state, - inner.0, - inner.1.to_vec(), - None, - None, - None, - 1, - ExpectedCallType::NonCount, - ), - HEVMCalls::ExpectCall1(inner) => expect_call( - state, - inner.0, - inner.1.to_vec(), - None, - None, - None, - inner.2, - ExpectedCallType::Count, - ), - HEVMCalls::ExpectCall2(inner) => expect_call( - state, - inner.0, - inner.2.to_vec(), - Some(inner.1), - None, - None, - 1, - ExpectedCallType::NonCount, - ), - HEVMCalls::ExpectCall3(inner) => expect_call( - state, - inner.0, - inner.2.to_vec(), - Some(inner.1), - None, - None, - inner.3, - ExpectedCallType::Count, - ), - HEVMCalls::ExpectCall4(inner) => { - let value = inner.1; - // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas - // to ensure that the basic fallback function can be called. - let positive_value_cost_stipend = if value > U256::zero() { 2300 } else { 0 }; - - expect_call( - state, - inner.0, - inner.3.to_vec(), - Some(value), - Some(inner.2 + positive_value_cost_stipend), - None, - 1, - ExpectedCallType::NonCount, - ) - } - HEVMCalls::ExpectCall5(inner) => { - let value = inner.1; - // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas - // to ensure that the basic fallback function can be called. - let positive_value_cost_stipend = if value > U256::zero() { 2300 } else { 0 }; - - expect_call( - state, - inner.0, - inner.3.to_vec(), - Some(value), - Some(inner.2 + positive_value_cost_stipend), - None, - inner.4, - ExpectedCallType::Count, - ) - } - HEVMCalls::ExpectCallMinGas0(inner) => { - let value = inner.1; - // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas - // to ensure that the basic fallback function can be called. - let positive_value_cost_stipend = if value > U256::zero() { 2300 } else { 0 }; - - expect_call( - state, - inner.0, - inner.3.to_vec(), - Some(value), - None, - Some(inner.2 + positive_value_cost_stipend), - 1, - ExpectedCallType::NonCount, - ) - } - HEVMCalls::ExpectCallMinGas1(inner) => { - let value = inner.1; - // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas - // to ensure that the basic fallback function can be called. - let positive_value_cost_stipend = if value > U256::zero() { 2300 } else { 0 }; - - expect_call( - state, - inner.0, - inner.3.to_vec(), - Some(value), - None, - Some(inner.2 + positive_value_cost_stipend), - inner.4, - ExpectedCallType::Count, - ) - } - HEVMCalls::MockCall0(inner) => { - // TODO: Does this increase gas usage? - if let Err(err) = data.journaled_state.load_account(h160_to_b160(inner.0), data.db) { - return Some(Err(err.into())) - } - - // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` - // check Solidity might perform. - let empty_bytecode = data - .journaled_state - .account(h160_to_b160(inner.0)) - .info - .code - .as_ref() - .map_or(true, Bytecode::is_empty); - if empty_bytecode { - let code = Bytecode::new_raw(bytes::Bytes::from_static(&[0u8])).to_checked(); - data.journaled_state.set_code(h160_to_b160(inner.0), code); - } - state.mocked_calls.entry(inner.0).or_default().insert( - MockCallDataContext { calldata: inner.1.clone(), value: None }, - MockCallReturnData { data: inner.2.clone(), ret_type: InstructionResult::Return }, - ); - Ok(Bytes::new()) - } - HEVMCalls::MockCall1(inner) => { - if let Err(err) = data.journaled_state.load_account(h160_to_b160(inner.0), data.db) { - return Some(Err(err.into())) - } - - state.mocked_calls.entry(inner.0).or_default().insert( - MockCallDataContext { calldata: inner.2.to_vec().into(), value: Some(inner.1) }, - MockCallReturnData { - data: inner.3.to_vec().into(), - ret_type: InstructionResult::Return, - }, - ); - Ok(Bytes::new()) - } - HEVMCalls::MockCallRevert0(inner) => { - state.mocked_calls.entry(inner.0).or_default().insert( - MockCallDataContext { calldata: inner.1.to_vec().into(), value: None }, - MockCallReturnData { - data: inner.2.to_vec().into(), - ret_type: InstructionResult::Revert, - }, - ); - Ok(Bytes::new()) - } - HEVMCalls::MockCallRevert1(inner) => { - state.mocked_calls.entry(inner.0).or_default().insert( - MockCallDataContext { calldata: inner.2.to_vec().into(), value: Some(inner.1) }, - MockCallReturnData { - data: inner.3.to_vec().into(), - ret_type: InstructionResult::Revert, - }, - ); - Ok(Bytes::new()) - } - HEVMCalls::ClearMockedCalls(_) => { - state.mocked_calls = Default::default(); - Ok(Bytes::new()) - } - HEVMCalls::ExpectSafeMemory(inner) => { - expect_safe_memory(state, inner.0, inner.1, data.journaled_state.depth()) - } - HEVMCalls::ExpectSafeMemoryCall(inner) => { - expect_safe_memory(state, inner.0, inner.1, data.journaled_state.depth() + 1) - } - _ => return None, - }; - Some(result) -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/ext.rs b/crates/evm/src/executor/inspector/cheatcodes/ext.rs deleted file mode 100644 index ee4707019de58..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/ext.rs +++ /dev/null @@ -1,682 +0,0 @@ -use super::{bail, ensure, fmt_err, Cheatcodes, Result}; -use crate::{abi::HEVMCalls, executor::inspector::cheatcodes::util}; -use ethers::{ - abi::{self, AbiEncode, JsonAbi, ParamType, Token}, - prelude::artifacts::CompactContractBytecode, - types::*, -}; -use foundry_common::{fmt::*, fs, get_artifact_path}; -use foundry_config::fs_permissions::FsAccessKind; -use serde::Deserialize; -use serde_json::Value; -use std::{collections::BTreeMap, env, path::Path, process::Command}; - -/// Invokes a `Command` with the given args and returns the abi encoded response -/// -/// If the output of the command is valid hex, it returns the hex decoded value -fn ffi(state: &Cheatcodes, args: &[String]) -> Result { - if args.is_empty() || args[0].is_empty() { - bail!("Can't execute empty command"); - } - let name = &args[0]; - let mut cmd = Command::new(name); - if args.len() > 1 { - cmd.args(&args[1..]); - } - - debug!(target: "evm::cheatcodes", ?args, "invoking ffi"); - - let output = cmd - .current_dir(&state.config.root) - .output() - .map_err(|err| fmt_err!("Failed to execute command: {err}"))?; - - if !output.stderr.is_empty() { - let stderr = String::from_utf8_lossy(&output.stderr); - error!(target: "evm::cheatcodes", ?args, ?stderr, "non-empty stderr"); - } - - let output = String::from_utf8(output.stdout)?; - let trimmed = output.trim(); - if let Ok(hex) = hex::decode(trimmed.strip_prefix("0x").unwrap_or(trimmed)) { - Ok(abi::encode(&[Token::Bytes(hex)]).into()) - } else { - Ok(trimmed.encode().into()) - } -} - -/// An enum which unifies the deserialization of Hardhat-style artifacts with Forge-style artifacts -/// to get their bytecode. -#[derive(Deserialize)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] -enum ArtifactBytecode { - Hardhat(HardhatArtifact), - Solc(JsonAbi), - Forge(CompactContractBytecode), - Huff(HuffArtifact), -} - -impl ArtifactBytecode { - fn into_bytecode(self) -> Option { - match self { - ArtifactBytecode::Hardhat(inner) => Some(inner.bytecode), - ArtifactBytecode::Forge(inner) => { - inner.bytecode.and_then(|bytecode| bytecode.object.into_bytes()) - } - ArtifactBytecode::Solc(inner) => inner.bytecode(), - ArtifactBytecode::Huff(inner) => Some(inner.bytecode), - } - } - - fn into_deployed_bytecode(self) -> Option { - match self { - ArtifactBytecode::Hardhat(inner) => Some(inner.deployed_bytecode), - ArtifactBytecode::Forge(inner) => inner.deployed_bytecode.and_then(|bytecode| { - bytecode.bytecode.and_then(|bytecode| bytecode.object.into_bytes()) - }), - ArtifactBytecode::Solc(inner) => inner.deployed_bytecode(), - ArtifactBytecode::Huff(inner) => Some(inner.runtime), - } - } -} - -/// A thin wrapper around a Hardhat-style artifact that only extracts the bytecode. -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct HardhatArtifact { - bytecode: Bytes, - deployed_bytecode: Bytes, -} - -#[derive(Deserialize)] -struct HuffArtifact { - bytecode: Bytes, - runtime: Bytes, -} - -/// Returns the _deployed_ bytecode (`bytecode`) of the matching artifact -fn get_code(state: &Cheatcodes, path: &str) -> Result { - let bytecode = read_bytecode(state, path)?; - if let Some(bin) = bytecode.into_bytecode() { - Ok(bin.encode().into()) - } else { - Err(fmt_err!("No bytecode for contract. Is it abstract or unlinked?")) - } -} - -/// Returns the _deployed_ bytecode (`bytecode`) of the matching artifact -fn get_deployed_code(state: &Cheatcodes, path: &str) -> Result { - let bytecode = read_bytecode(state, path)?; - if let Some(bin) = bytecode.into_deployed_bytecode() { - Ok(bin.encode().into()) - } else { - Err(fmt_err!("No deployed bytecode for contract. Is it abstract or unlinked?")) - } -} - -/// Reads the bytecode object(s) from the matching artifact -fn read_bytecode(state: &Cheatcodes, path: &str) -> Result { - let path = get_artifact_path(&state.config.paths, path); - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let data = fs::read_to_string(path)?; - serde_json::from_str::(&data).map_err(Into::into) -} - -fn set_env(key: &str, val: &str) -> Result { - // `std::env::set_var` may panic in the following situations - // ref: https://doc.rust-lang.org/std/env/fn.set_var.html - if key.is_empty() { - Err(fmt_err!("Environment variable key can't be empty")) - } else if key.contains('=') { - Err(fmt_err!("Environment variable key can't contain equal sign `=`")) - } else if key.contains('\0') { - Err(fmt_err!("Environment variable key can't contain NUL character `\\0`")) - } else if val.contains('\0') { - Err(fmt_err!("Environment variable value can't contain NUL character `\\0`")) - } else { - env::set_var(key, val); - Ok(Bytes::new()) - } -} - -fn get_env(key: &str, ty: ParamType, delim: Option<&str>, default: Option) -> Result { - let val = env::var(key).or_else(|e| { - default.ok_or_else(|| { - fmt_err!("Failed to get environment variable `{key}` as type `{ty}`: {e}") - }) - })?; - if let Some(d) = delim { - util::parse_array(val.split(d).map(str::trim), &ty) - } else { - util::parse(&val, &ty) - } -} - -/// Converts a JSON [`Value`] to a [`Token`]. -/// -/// 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 -/// Tuple -fn value_to_token(value: &Value) -> Result { - match value { - Value::Null => Ok(Token::FixedBytes(vec![0; 32])), - Value::Bool(boolean) => Ok(Token::Bool(*boolean)), - Value::Array(array) => { - let values = array.iter().map(value_to_token).collect::>>()?; - Ok(Token::Array(values)) - } - value @ Value::Object(_) => { - // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) - let ordered_object: BTreeMap = - serde_json::from_value(value.clone()).unwrap(); - let values = ordered_object.values().map(value_to_token).collect::>>()?; - Ok(Token::Tuple(values)) - } - Value::Number(number) => { - if let Some(f) = number.as_f64() { - // Check if the number has decimal digits because the EVM does not support floating - // point math - if f.fract() == 0.0 { - // Use the string representation of the `serde_json` Number type instead of - // calling f.to_string(), because some numbers are wrongly rounded up after - // being convented to f64. - // Example: 18446744073709551615 becomes 18446744073709552000 after parsing it - // to f64. - let s = number.to_string(); - - // Calling Number::to_string with powers of ten formats the number using - // scientific notation and causes from_dec_str to fail. Using format! with f64 - // keeps the full number representation. - // Example: 100000000000000000000 becomes 1e20 when Number::to_string is - // used. - let fallback_s = format!("{f}"); - - if let Ok(n) = U256::from_dec_str(&s) { - return Ok(Token::Uint(n)) - } - if let Ok(n) = I256::from_dec_str(&s) { - return Ok(Token::Int(n.into_raw())) - } - if let Ok(n) = U256::from_dec_str(&fallback_s) { - return Ok(Token::Uint(n)) - } - if let Ok(n) = I256::from_dec_str(&fallback_s) { - return Ok(Token::Int(n.into_raw())) - } - } - } - - Err(fmt_err!("Unsupported value: {number:?}")) - } - Value::String(string) => { - if let Some(mut val) = string.strip_prefix("0x") { - let s; - if val.len() % 2 != 0 { - s = format!("0{}", val); - val = &s[..]; - } - let bytes = hex::decode(val)?; - Ok(match bytes.len() { - 20 => Token::Address(Address::from_slice(&bytes)), - 32 => Token::FixedBytes(bytes), - _ => Token::Bytes(bytes), - }) - } else { - Ok(Token::String(string.to_owned())) - } - } - } -} - -/// Canonicalize a json path key to always start from the root of the document. -/// Read more about json path syntax: https://goessner.net/articles/JsonPath/ -fn canonicalize_json_key(key: &str) -> String { - if !key.starts_with('$') { - format!("${key}") - } else { - key.to_owned() - } -} - -/// Encodes a vector of [`Token`] into a vector of bytes. -fn encode_abi_values(values: Vec) -> Vec { - if values.is_empty() { - abi::encode(&[Token::Bytes(Vec::new())]) - } else if values.len() == 1 { - abi::encode(&[Token::Bytes(abi::encode(&values))]) - } else { - abi::encode(&[Token::Bytes(abi::encode(&[Token::Array(values)]))]) - } -} - -/// Parses a vector of [`Value`]s into a vector of [`Token`]s. -fn parse_json_values(values: Vec<&Value>, key: &str) -> Result> { - values - .iter() - .map(|inner| { - value_to_token(inner).map_err(|err| fmt_err!("Failed to parse key \"{key}\": {err}")) - }) - .collect::>>() -} - -/// Parses a JSON and returns a single value, an array or an entire JSON object encoded as tuple. -/// As the JSON object is parsed serially, with the keys ordered alphabetically, they must be -/// deserialized in the same order. That means that the solidity `struct` should order it's fields -/// alphabetically and not by efficient packing or some other taxonomy. -fn parse_json(json_str: &str, key: &str, coerce: Option) -> Result { - let json = - serde_json::from_str(json_str).map_err(|err| fmt_err!("Failed to parse JSON: {err}"))?; - match key { - // Handle the special case of the root key. We want to return the entire JSON object - // in this case. - "." => { - let values = jsonpath_lib::select(&json, "$")?; - let res = parse_json_values(values, key)?; - - // encode the bytes as the 'bytes' solidity type - let abi_encoded = encode_abi_values(res); - Ok(abi_encoded.into()) - } - _ => { - let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?; - - // values is an array of items. Depending on the JsonPath key, they - // can be many or a single item. An item can be a single value or - // an entire JSON object. - if let Some(coercion_type) = coerce { - ensure!( - values.iter().all(|value| !value.is_object()), - "You can only coerce values or arrays, not JSON objects. The key '{key}' returns an object", - ); - - ensure!(!values.is_empty(), "No matching value or array found for key {key}"); - - let to_string = |v: &Value| { - let mut s = v.to_string(); - s.retain(|c: char| c != '"'); - s - }; - trace!(target : "forge::evm", ?values, "parsign values"); - return if let Some(array) = values[0].as_array() { - util::parse_array(array.iter().map(to_string), &coercion_type) - } else { - util::parse(&to_string(values[0]), &coercion_type) - } - } - - let res = parse_json_values(values, key)?; - - // encode the bytes as the 'bytes' solidity type - let abi_encoded = encode_abi_values(res); - Ok(abi_encoded.into()) - } - } -} - -// returns JSON keys of given object as a string array -fn parse_json_keys(json_str: &str, key: &str) -> Result { - let json = serde_json::from_str(json_str)?; - let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?; - - // We need to check that values contains just one JSON-object and not an array of objects - ensure!( - values.len() == 1, - "You can only get keys for a single JSON-object. The key '{key}' returns a value or an array of JSON-objects", - ); - - let value = values[0]; - - ensure!( - value.is_object(), - "You can only get keys for JSON-object. The key '{key}' does not return an object", - ); - - let res = value - .as_object() - .ok_or(eyre::eyre!("Unexpected error while extracting JSON-object"))? - .keys() - .map(|key| Token::String(key.to_owned())) - .collect::>(); - - // encode the bytes as the 'bytes' solidity type - let abi_encoded = abi::encode(&[Token::Array(res)]); - Ok(abi_encoded.into()) -} - -/// Serializes a key:value pair to a specific object. By calling this function multiple times, -/// the user can serialize multiple KV pairs to the same object. The value can be of any type, even -/// a new object in itself. The function will return -/// a stringified version of the object, so that the user can use that as a value to a new -/// invocation of the same function with a new object key. This enables the user to reuse the same -/// function to crate arbitrarily complex object structures (JSON). -fn serialize_json( - state: &mut Cheatcodes, - object_key: &str, - value_key: &str, - value: &str, -) -> Result { - let parsed_value = - serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_string())); - let json = if let Some(serialization) = state.serialized_jsons.get_mut(object_key) { - serialization.insert(value_key.to_string(), parsed_value); - serialization.clone() - } else { - let mut serialization = BTreeMap::new(); - serialization.insert(value_key.to_string(), parsed_value); - state.serialized_jsons.insert(object_key.to_string(), serialization.clone()); - serialization.clone() - }; - let stringified = serde_json::to_string(&json) - .map_err(|err| fmt_err!("Failed to stringify hashmap: {err}"))?; - Ok(abi::encode(&[Token::String(stringified)]).into()) -} - -/// Converts an array to it's stringified version, adding the appropriate quotes around it's -/// ellements. This is to signify that the elements of the array are string themselves. -fn array_str_to_str(array: &Vec) -> String { - format!( - "[{}]", - array - .iter() - .enumerate() - .map(|(index, value)| { - if index == array.len() - 1 { - format!("\"{}\"", value.pretty()) - } else { - format!("\"{}\",", value.pretty()) - } - }) - .collect::() - ) -} - -/// Converts an array to it's stringified version. It will not add quotes around the values of the -/// array, enabling serde_json to parse the values of the array as types (e.g numbers, booleans, -/// etc.) -fn array_eval_to_str(array: &Vec) -> String { - format!( - "[{}]", - array - .iter() - .enumerate() - .map(|(index, value)| { - if index == array.len() - 1 { - value.pretty() - } else { - format!("{},", value.pretty()) - } - }) - .collect::() - ) -} - -/// Write an object to a new file OR replace the value of an existing JSON file with the supplied -/// object. -fn write_json( - state: &Cheatcodes, - object: &str, - path: impl AsRef, - json_path_or_none: Option<&str>, -) -> Result { - let json: Value = - serde_json::from_str(object).unwrap_or_else(|_| Value::String(object.to_owned())); - let json_string = serde_json::to_string_pretty(&if let Some(json_path) = json_path_or_none { - let path = state.config.ensure_path_allowed(&path, FsAccessKind::Read)?; - let data = serde_json::from_str(&fs::read_to_string(path)?)?; - jsonpath_lib::replace_with(data, &canonicalize_json_key(json_path), &mut |_| { - Some(json.clone()) - })? - } else { - json - })?; - super::fs::write_file(state, path, json_string)?; - Ok(Bytes::new()) -} - -/// Checks if a key exists in a JSON object. -fn key_exists(json_str: &str, key: &str) -> Result { - let json: Value = - serde_json::from_str(json_str).map_err(|e| format!("Could not convert to JSON: {e}"))?; - let values = jsonpath_lib::select(&json, &canonicalize_json_key(key))?; - let exists = util::parse(&(!values.is_empty()).to_string(), &ParamType::Bool)?; - Ok(exists) -} - -/// Sleeps for a given amount of milliseconds. -fn sleep(milliseconds: &U256) -> Result { - let sleep_duration = std::time::Duration::from_millis(milliseconds.as_u64()); - std::thread::sleep(sleep_duration); - - Ok(Default::default()) -} - -#[instrument(level = "error", name = "ext", target = "evm::cheatcodes", skip_all)] -pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { - Some(match call { - HEVMCalls::Ffi(inner) => { - if state.config.ffi { - ffi(state, &inner.0) - } else { - Err(fmt_err!("FFI disabled: run again with `--ffi` if you want to allow tests to call external scripts.")) - } - } - HEVMCalls::GetCode(inner) => get_code(state, &inner.0), - HEVMCalls::GetDeployedCode(inner) => get_deployed_code(state, &inner.0), - HEVMCalls::SetEnv(inner) => set_env(&inner.0, &inner.1), - HEVMCalls::EnvBool0(inner) => get_env(&inner.0, ParamType::Bool, None, None), - HEVMCalls::EnvUint0(inner) => get_env(&inner.0, ParamType::Uint(256), None, None), - HEVMCalls::EnvInt0(inner) => get_env(&inner.0, ParamType::Int(256), None, None), - HEVMCalls::EnvAddress0(inner) => get_env(&inner.0, ParamType::Address, None, None), - HEVMCalls::EnvBytes320(inner) => get_env(&inner.0, ParamType::FixedBytes(32), None, None), - HEVMCalls::EnvString0(inner) => get_env(&inner.0, ParamType::String, None, None), - HEVMCalls::EnvBytes0(inner) => get_env(&inner.0, ParamType::Bytes, None, None), - HEVMCalls::EnvBool1(inner) => get_env(&inner.0, ParamType::Bool, Some(&inner.1), None), - HEVMCalls::EnvUint1(inner) => get_env(&inner.0, ParamType::Uint(256), Some(&inner.1), None), - HEVMCalls::EnvInt1(inner) => get_env(&inner.0, ParamType::Int(256), Some(&inner.1), None), - HEVMCalls::EnvAddress1(inner) => { - get_env(&inner.0, ParamType::Address, Some(&inner.1), None) - } - HEVMCalls::EnvBytes321(inner) => { - get_env(&inner.0, ParamType::FixedBytes(32), Some(&inner.1), None) - } - HEVMCalls::EnvString1(inner) => get_env(&inner.0, ParamType::String, Some(&inner.1), None), - HEVMCalls::EnvBytes1(inner) => get_env(&inner.0, ParamType::Bytes, Some(&inner.1), None), - HEVMCalls::EnvOr0(inner) => { - get_env(&inner.0, ParamType::Bool, None, Some(inner.1.to_string())) - } - HEVMCalls::EnvOr1(inner) => { - get_env(&inner.0, ParamType::Uint(256), None, Some(inner.1.to_string())) - } - HEVMCalls::EnvOr2(inner) => { - get_env(&inner.0, ParamType::Int(256), None, Some(inner.1.to_string())) - } - HEVMCalls::EnvOr3(inner) => { - get_env(&inner.0, ParamType::Address, None, Some(hex::encode(inner.1))) - } - HEVMCalls::EnvOr4(inner) => { - get_env(&inner.0, ParamType::FixedBytes(32), None, Some(hex::encode(inner.1))) - } - HEVMCalls::EnvOr5(inner) => { - get_env(&inner.0, ParamType::String, None, Some(inner.1.to_string())) - } - HEVMCalls::EnvOr6(inner) => { - get_env(&inner.0, ParamType::Bytes, None, Some(hex::encode(&inner.1))) - } - HEVMCalls::EnvOr7(inner) => get_env( - &inner.0, - ParamType::Bool, - Some(&inner.1), - Some(inner.2.iter().map(|v| v.to_string()).collect::>().join(&inner.1)), - ), - HEVMCalls::EnvOr8(inner) => get_env( - &inner.0, - ParamType::Uint(256), - Some(&inner.1), - Some(inner.2.iter().map(|v| v.to_string()).collect::>().join(&inner.1)), - ), - HEVMCalls::EnvOr9(inner) => get_env( - &inner.0, - ParamType::Int(256), - Some(&inner.1), - Some(inner.2.iter().map(|v| v.to_string()).collect::>().join(&inner.1)), - ), - HEVMCalls::EnvOr10(inner) => get_env( - &inner.0, - ParamType::Address, - Some(&inner.1), - Some(inner.2.iter().map(hex::encode).collect::>().join(&inner.1)), - ), - HEVMCalls::EnvOr11(inner) => get_env( - &inner.0, - ParamType::FixedBytes(32), - Some(&inner.1), - Some(inner.2.iter().map(hex::encode).collect::>().join(&inner.1)), - ), - HEVMCalls::EnvOr12(inner) => { - get_env(&inner.0, ParamType::String, Some(&inner.1), Some(inner.2.join(&inner.1))) - } - HEVMCalls::EnvOr13(inner) => get_env( - &inner.0, - ParamType::Bytes, - Some(&inner.1), - Some(inner.2.iter().map(hex::encode).collect::>().join(&inner.1)), - ), - - // If no key argument is passed, return the whole JSON object. - // "$" is the JSONPath key for the root of the object - HEVMCalls::ParseJson0(inner) => parse_json(&inner.0, "$", None), - HEVMCalls::ParseJson1(inner) => parse_json(&inner.0, &inner.1, None), - HEVMCalls::ParseJsonBool(inner) => parse_json(&inner.0, &inner.1, Some(ParamType::Bool)), - HEVMCalls::ParseJsonKeys(inner) => parse_json_keys(&inner.0, &inner.1), - HEVMCalls::ParseJsonBoolArray(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Bool)) - } - HEVMCalls::ParseJsonUint(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Uint(256))) - } - HEVMCalls::ParseJsonUintArray(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Uint(256))) - } - HEVMCalls::ParseJsonInt(inner) => parse_json(&inner.0, &inner.1, Some(ParamType::Int(256))), - HEVMCalls::ParseJsonIntArray(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Int(256))) - } - HEVMCalls::ParseJsonString(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::String)) - } - HEVMCalls::ParseJsonStringArray(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::String)) - } - HEVMCalls::ParseJsonAddress(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Address)) - } - HEVMCalls::ParseJsonAddressArray(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Address)) - } - HEVMCalls::ParseJsonBytes(inner) => parse_json(&inner.0, &inner.1, Some(ParamType::Bytes)), - HEVMCalls::ParseJsonBytesArray(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::Bytes)) - } - HEVMCalls::ParseJsonBytes32(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::FixedBytes(32))) - } - HEVMCalls::ParseJsonBytes32Array(inner) => { - parse_json(&inner.0, &inner.1, Some(ParamType::FixedBytes(32))) - } - HEVMCalls::SerializeBool0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeBool1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_eval_to_str(&inner.2)) - } - HEVMCalls::SerializeUint0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeUint1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_eval_to_str(&inner.2)) - } - HEVMCalls::SerializeInt0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeInt1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_eval_to_str(&inner.2)) - } - HEVMCalls::SerializeAddress0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeAddress1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) - } - HEVMCalls::SerializeBytes320(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeBytes321(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) - } - HEVMCalls::SerializeString0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeString1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) - } - HEVMCalls::SerializeBytes0(inner) => { - serialize_json(state, &inner.0, &inner.1, &inner.2.pretty()) - } - HEVMCalls::SerializeBytes1(inner) => { - serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) - } - HEVMCalls::Sleep(inner) => sleep(&inner.0), - HEVMCalls::WriteJson0(inner) => write_json(state, &inner.0, &inner.1, None), - HEVMCalls::WriteJson1(inner) => write_json(state, &inner.0, &inner.1, Some(&inner.2)), - HEVMCalls::KeyExists(inner) => key_exists(&inner.0, &inner.1), - _ => return None, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::executor::inspector::CheatsConfig; - use ethers::core::abi::AbiDecode; - use std::{path::PathBuf, sync::Arc}; - - fn cheats() -> Cheatcodes { - let config = - CheatsConfig { root: PathBuf::from(&env!("CARGO_MANIFEST_DIR")), ..Default::default() }; - Cheatcodes { config: Arc::new(config), ..Default::default() } - } - - #[test] - fn test_ffi_hex() { - let msg = "gm"; - let cheats = cheats(); - let args = ["echo".to_string(), hex::encode(msg)]; - let output = ffi(&cheats, &args).unwrap(); - - let output = String::decode(&output).unwrap(); - assert_eq!(output, msg); - } - - #[test] - fn test_ffi_string() { - let msg = "gm"; - let cheats = cheats(); - - let args = ["echo".to_string(), msg.to_string()]; - let output = ffi(&cheats, &args).unwrap(); - - let output = String::decode(&output).unwrap(); - assert_eq!(output, msg); - } - - #[test] - fn test_artifact_parsing() { - let s = include_str!("../../../../test-data/solc-obj.json"); - let artifact: ArtifactBytecode = serde_json::from_str(s).unwrap(); - assert!(artifact.into_bytecode().is_some()); - - let artifact: ArtifactBytecode = serde_json::from_str(s).unwrap(); - assert!(artifact.into_deployed_bytecode().is_some()); - } -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/fork.rs b/crates/evm/src/executor/inspector/cheatcodes/fork.rs deleted file mode 100644 index 8734071108395..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/fork.rs +++ /dev/null @@ -1,248 +0,0 @@ -use super::{fmt_err, Cheatcodes, Error, Result}; -use crate::{ - abi::HEVMCalls, - executor::{backend::DatabaseExt, fork::CreateFork}, -}; -use ethers::{ - abi::AbiEncode, - prelude::U256, - types::{Bytes, H256}, -}; -use revm::EVMData; - -fn empty(_: T) -> Bytes { - Bytes::new() -} - -/// Handles fork related cheatcodes -#[instrument(level = "error", name = "fork", target = "evm::cheatcodes", skip_all)] -pub fn apply( - state: &mut Cheatcodes, - data: &mut EVMData<'_, DB>, - call: &HEVMCalls, -) -> Option { - let result = match call { - HEVMCalls::CreateFork0(fork) => create_fork(state, data, fork.0.clone(), None), - HEVMCalls::CreateFork1(fork) => { - create_fork(state, data, fork.0.clone(), Some(fork.1.as_u64())) - } - HEVMCalls::CreateFork2(fork) => { - create_fork_at_transaction(state, data, fork.0.clone(), fork.1.into()) - } - HEVMCalls::CreateSelectFork0(fork) => create_select_fork(state, data, fork.0.clone(), None), - HEVMCalls::CreateSelectFork1(fork) => { - create_select_fork(state, data, fork.0.clone(), Some(fork.1.as_u64())) - } - HEVMCalls::CreateSelectFork2(fork) => { - create_select_fork_at_transaction(state, data, fork.0.clone(), fork.1.into()) - } - HEVMCalls::SelectFork(fork_id) => select_fork(state, data, fork_id.0), - HEVMCalls::MakePersistent0(acc) => { - data.db.add_persistent_account(acc.0); - Ok(Bytes::new()) - } - HEVMCalls::MakePersistent1(acc) => { - data.db.extend_persistent_accounts(acc.0.clone()); - Ok(Bytes::new()) - } - HEVMCalls::MakePersistent2(acc) => { - data.db.add_persistent_account(acc.0); - data.db.add_persistent_account(acc.1); - Ok(Bytes::new()) - } - HEVMCalls::MakePersistent3(acc) => { - data.db.add_persistent_account(acc.0); - data.db.add_persistent_account(acc.1); - data.db.add_persistent_account(acc.2); - Ok(Bytes::new()) - } - HEVMCalls::IsPersistent(acc) => Ok(data.db.is_persistent(&acc.0).encode().into()), - HEVMCalls::RevokePersistent0(acc) => { - data.db.remove_persistent_account(&acc.0); - Ok(Bytes::new()) - } - HEVMCalls::RevokePersistent1(acc) => { - data.db.remove_persistent_accounts(acc.0.clone()); - Ok(Bytes::new()) - } - HEVMCalls::ActiveFork(_) => data - .db - .active_fork_id() - .map(|id| id.encode().into()) - .ok_or_else(|| fmt_err!("No active fork")), - HEVMCalls::RollFork0(fork) => data - .db - .roll_fork(None, fork.0, data.env, &mut data.journaled_state) - .map(empty) - .map_err(Into::into), - HEVMCalls::RollFork1(fork) => data - .db - .roll_fork_to_transaction(None, fork.0.into(), data.env, &mut data.journaled_state) - .map(empty) - .map_err(Into::into), - HEVMCalls::RollFork2(fork) => data - .db - .roll_fork(Some(fork.0), fork.1, data.env, &mut data.journaled_state) - .map(empty) - .map_err(Into::into), - HEVMCalls::RollFork3(fork) => data - .db - .roll_fork_to_transaction( - Some(fork.0), - fork.1.into(), - data.env, - &mut data.journaled_state, - ) - .map(empty) - .map_err(Into::into), - HEVMCalls::RpcUrl(rpc) => state.config.get_rpc_url(&rpc.0).map(|url| url.encode().into()), - HEVMCalls::RpcUrls(_) => { - let mut urls = Vec::with_capacity(state.config.rpc_endpoints.len()); - for alias in state.config.rpc_endpoints.keys().cloned() { - match state.config.get_rpc_url(&alias) { - Ok(url) => { - urls.push([alias, url]); - } - Err(err) => return Some(Err(err)), - } - } - Ok(urls.encode().into()) - } - HEVMCalls::RpcUrlStructs(_) => { - let mut urls = Vec::with_capacity(state.config.rpc_endpoints.len()); - for alias in state.config.rpc_endpoints.keys() { - match state.config.get_rpc_url(alias) { - Ok(url) => { - urls.push([alias.clone(), url]); - } - Err(err) => return Some(Err(err)), - } - } - Ok(urls.encode().into()) - } - HEVMCalls::AllowCheatcodes(addr) => { - data.db.allow_cheatcode_access(addr.0); - Ok(Bytes::new()) - } - HEVMCalls::Transact0(inner) => data - .db - .transact(None, inner.0.into(), data.env, &mut data.journaled_state, Some(state)) - .map(empty) - .map_err(Into::into), - HEVMCalls::Transact1(inner) => data - .db - .transact( - Some(inner.0), - inner.1.into(), - data.env, - &mut data.journaled_state, - Some(state), - ) - .map(empty) - .map_err(Into::into), - _ => return None, - }; - Some(result) -} - -/// Selects the given fork id -fn select_fork( - state: &mut Cheatcodes, - data: &mut EVMData, - fork_id: U256, -) -> Result { - if state.broadcast.is_some() { - return Err(Error::SelectForkDuringBroadcast) - } - - // No need to correct since the sender's nonce does not get incremented when selecting a fork. - state.corrected_nonce = true; - - data.db.select_fork(fork_id, data.env, &mut data.journaled_state)?; - Ok(Bytes::new()) -} - -/// Creates and then also selects the new fork -fn create_select_fork( - state: &mut Cheatcodes, - data: &mut EVMData<'_, DB>, - url_or_alias: String, - block: Option, -) -> Result { - if state.broadcast.is_some() { - return Err(Error::SelectForkDuringBroadcast) - } - - // No need to correct since the sender's nonce does not get incremented when selecting a fork. - state.corrected_nonce = true; - - let fork = create_fork_request(state, url_or_alias, block, data)?; - let id = data.db.create_select_fork(fork, data.env, &mut data.journaled_state)?; - Ok(id.encode().into()) -} - -/// Creates a new fork -fn create_fork( - state: &Cheatcodes, - data: &mut EVMData<'_, DB>, - url_or_alias: String, - block: Option, -) -> Result { - let fork = create_fork_request(state, url_or_alias, block, data)?; - let id = data.db.create_fork(fork)?; - Ok(id.encode().into()) -} -/// Creates and then also selects the new fork at the given transaction -fn create_select_fork_at_transaction( - state: &mut Cheatcodes, - data: &mut EVMData<'_, DB>, - url_or_alias: String, - transaction: H256, -) -> Result { - if state.broadcast.is_some() { - return Err(Error::SelectForkDuringBroadcast) - } - - // No need to correct since the sender's nonce does not get incremented when selecting a fork. - state.corrected_nonce = true; - - let fork = create_fork_request(state, url_or_alias, None, data)?; - let id = data.db.create_select_fork_at_transaction( - fork, - data.env, - &mut data.journaled_state, - transaction, - )?; - Ok(id.encode().into()) -} - -/// Creates a new fork at the given transaction -fn create_fork_at_transaction( - state: &Cheatcodes, - data: &mut EVMData<'_, DB>, - url_or_alias: String, - transaction: H256, -) -> Result { - let fork = create_fork_request(state, url_or_alias, None, data)?; - let id = data.db.create_fork_at_transaction(fork, transaction)?; - Ok(id.encode().into()) -} - -/// Creates the request object for a new fork request -fn create_fork_request( - state: &Cheatcodes, - url_or_alias: String, - block: Option, - data: &EVMData, -) -> Result { - let url = state.config.get_rpc_url(url_or_alias)?; - let mut evm_opts = state.config.evm_opts.clone(); - evm_opts.fork_block_number = block; - let fork = CreateFork { - enable_caching: state.config.rpc_storage_caching.enable_for_endpoint(&url), - url, - env: data.env.clone(), - evm_opts, - }; - Ok(fork) -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/fs.rs b/crates/evm/src/executor/inspector/cheatcodes/fs.rs deleted file mode 100644 index e6616c30341fa..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/fs.rs +++ /dev/null @@ -1,277 +0,0 @@ -use super::{Cheatcodes, Result}; -use crate::abi::hevm::{DirEntry, FsMetadata, HEVMCalls}; -use ethers::{ - abi::{self, AbiEncode, Token, Tokenize}, - types::Bytes, -}; -use foundry_common::fs; -use foundry_config::fs_permissions::FsAccessKind; -use std::{ - io::{BufRead, BufReader, Write}, - path::Path, - time::UNIX_EPOCH, -}; -use walkdir::WalkDir; - -fn project_root(state: &Cheatcodes) -> Result { - let root = state.config.root.display().to_string(); - Ok(abi::encode(&[Token::String(root)]).into()) -} - -fn read_file(state: &Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let data = fs::read_to_string(path)?; - Ok(abi::encode(&[Token::String(data)]).into()) -} - -fn read_file_binary(state: &Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let data = fs::read(path)?; - Ok(abi::encode(&[Token::Bytes(data)]).into()) -} - -fn read_line(state: &mut Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - - // Get reader for previously opened file to continue reading OR initialize new reader - let reader = state - .context - .opened_read_files - .entry(path.clone()) - .or_insert(BufReader::new(fs::open(path)?)); - - let mut line: String = String::new(); - reader.read_line(&mut line)?; - - // Remove trailing newline character, preserving others for cases where it may be important - if line.ends_with('\n') { - line.pop(); - if line.ends_with('\r') { - line.pop(); - } - } - - Ok(abi::encode(&[Token::String(line)]).into()) -} - -/// Writes `content` to `path`. -/// -/// This function will create a file if it does not exist, and will entirely replace its contents if -/// it does. -/// -/// Caution: writing files is only allowed if the targeted path is allowed, (inside `/` by -/// default) -pub(super) fn write_file( - state: &Cheatcodes, - path: impl AsRef, - content: impl AsRef<[u8]>, -) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; - // write access to foundry.toml is not allowed - state.config.ensure_not_foundry_toml(&path)?; - - if state.fs_commit { - fs::write(path, content.as_ref())?; - } - - Ok(Bytes::new()) -} - -/// Writes a single line to the file. -/// -/// This will create a file if it does not exist, and append the `line` if it does. -fn write_line(state: &Cheatcodes, path: impl AsRef, line: &str) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; - state.config.ensure_not_foundry_toml(&path)?; - - if state.fs_commit { - let mut file = std::fs::OpenOptions::new().append(true).create(true).open(path)?; - - writeln!(file, "{line}")?; - } - - Ok(Bytes::new()) -} - -fn copy_file(state: &Cheatcodes, from: impl AsRef, to: impl AsRef) -> Result { - let from = state.config.ensure_path_allowed(from, FsAccessKind::Read)?; - let to = state.config.ensure_path_allowed(to, FsAccessKind::Write)?; - state.config.ensure_not_foundry_toml(&to)?; - - let n = fs::copy(from, to)?; - Ok(abi::encode(&[Token::Uint(n.into())]).into()) -} - -fn close_file(state: &mut Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - - state.context.opened_read_files.remove(&path); - - Ok(Bytes::new()) -} - -/// Removes a file from the filesystem. -/// -/// Only files inside `/` can be removed, `foundry.toml` excluded. -/// -/// This will return an error if the path points to a directory, or the file does not exist -fn remove_file(state: &mut Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; - state.config.ensure_not_foundry_toml(&path)?; - - // also remove from the set if opened previously - state.context.opened_read_files.remove(&path); - - if state.fs_commit { - fs::remove_file(&path)?; - } - - Ok(Bytes::new()) -} - -/// Creates a new, empty directory at the provided path. -/// -/// If `recursive` is true, it will also create all the parent directories if they don't exist. -/// -/// # Errors -/// -/// This function will return an error in the following situations, but is not limited to just these -/// cases: -/// -/// - User lacks permissions to modify `path`. -/// - A parent of the given path doesn't exist and `recursive` is false. -/// - `path` already exists and `recursive` is false. -fn create_dir(state: &Cheatcodes, path: impl AsRef, recursive: bool) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; - if recursive { fs::create_dir_all(path) } else { fs::create_dir(path) }?; - Ok(Bytes::new()) -} - -/// Removes a directory at the provided path. -/// -/// This will also remove all the directory's contents recursively if `recursive` is true. -/// -/// # Errors -/// -/// This function will return an error in the following situations, but is not limited to just these -/// cases: -/// -/// - `path` doesn't exist. -/// - `path` isn't a directory. -/// - User lacks permissions to modify `path`. -/// - The directory is not empty and `recursive` is false. -fn remove_dir(state: &Cheatcodes, path: impl AsRef, recursive: bool) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Write)?; - if recursive { fs::remove_dir_all(path) } else { fs::remove_dir(path) }?; - Ok(Bytes::new()) -} - -/// Reads the directory at the given path recursively, up to `max_depth`. -/// -/// Follows symbolic links if `follow_links` is true. -fn read_dir( - state: &Cheatcodes, - path: impl AsRef, - max_depth: u64, - follow_links: bool, -) -> Result { - let root = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - let paths: Vec = WalkDir::new(root) - .min_depth(1) - .max_depth(max_depth.try_into()?) - .follow_links(follow_links) - .contents_first(false) - .same_file_system(true) - .sort_by_file_name() - .into_iter() - .map(|entry| { - let entry = match entry { - Ok(entry) => DirEntry { - error_message: String::new(), - path: entry.path().display().to_string(), - depth: entry.depth() as u64, - is_dir: entry.file_type().is_dir(), - is_symlink: entry.path_is_symlink(), - }, - Err(e) => DirEntry { - error_message: e.to_string(), - path: e.path().map(|p| p.display().to_string()).unwrap_or_default(), - depth: e.depth() as u64, - is_dir: false, - is_symlink: false, - }, - }; - Token::Tuple(entry.into_tokens()) - }) - .collect(); - Ok(abi::encode(&[Token::Array(paths)]).into()) -} - -/// Reads a symbolic link, returning the path that the link points to. -/// -/// # Errors -/// -/// This function will return an error in the following situations, but is not limited to just these -/// cases: -/// -/// - `path` is not a symbolic link. -/// - `path` does not exist. -fn read_link(state: &Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - - let target = fs::read_link(path)?; - - Ok(abi::encode(&[Token::String(target.display().to_string())]).into()) -} - -/// Gets the metadata of a file/directory -/// -/// This will return an error if no file/directory is found, or if the target path isn't allowed -fn fs_metadata(state: &Cheatcodes, path: impl AsRef) -> Result { - let path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; - - let metadata = path.metadata()?; - - // These fields not available on all platforms; default to 0 - let [modified, accessed, created] = - [metadata.modified(), metadata.accessed(), metadata.created()].map(|time| { - time.unwrap_or(UNIX_EPOCH).duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() - }); - - let metadata = FsMetadata { - is_dir: metadata.is_dir(), - is_symlink: metadata.is_symlink(), - length: metadata.len().into(), - read_only: metadata.permissions().readonly(), - modified: modified.into(), - accessed: accessed.into(), - created: created.into(), - }; - Ok(metadata.encode().into()) -} - -#[instrument(level = "error", name = "fs", target = "evm::cheatcodes", skip_all)] -pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { - let res = match call { - HEVMCalls::ProjectRoot(_) => project_root(state), - HEVMCalls::ReadFile(inner) => read_file(state, &inner.0), - HEVMCalls::ReadFileBinary(inner) => read_file_binary(state, &inner.0), - HEVMCalls::ReadLine(inner) => read_line(state, &inner.0), - HEVMCalls::WriteFile(inner) => write_file(state, &inner.0, &inner.1), - HEVMCalls::WriteFileBinary(inner) => write_file(state, &inner.0, &inner.1), - HEVMCalls::WriteLine(inner) => write_line(state, &inner.0, &inner.1), - HEVMCalls::CopyFile(inner) => copy_file(state, &inner.0, &inner.1), - HEVMCalls::CloseFile(inner) => close_file(state, &inner.0), - HEVMCalls::RemoveFile(inner) => remove_file(state, &inner.0), - HEVMCalls::FsMetadata(inner) => fs_metadata(state, &inner.0), - HEVMCalls::ReadLink(inner) => read_link(state, &inner.0), - HEVMCalls::CreateDir(inner) => create_dir(state, &inner.0, inner.1), - HEVMCalls::RemoveDir(inner) => remove_dir(state, &inner.0, inner.1), - HEVMCalls::ReadDir0(inner) => read_dir(state, &inner.0, 1, false), - HEVMCalls::ReadDir1(inner) => read_dir(state, &inner.0, inner.1, false), - HEVMCalls::ReadDir2(inner) => read_dir(state, &inner.0, inner.1, inner.2), - - _ => return None, - }; - Some(res) -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/fuzz.rs b/crates/evm/src/executor/inspector/cheatcodes/fuzz.rs deleted file mode 100644 index 6ed597d574d49..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/fuzz.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::{Error, Result}; -use crate::{abi::HEVMCalls, fuzz::error::ASSUME_MAGIC_RETURN_CODE}; -use ethers::types::Bytes; - -#[instrument(level = "error", name = "fuzz", target = "evm::cheatcodes", skip_all)] -pub fn apply(call: &HEVMCalls) -> Option { - if let HEVMCalls::Assume(inner) = call { - let bytes = if inner.0 { - Ok(Bytes::new()) - } else { - // `custom_bytes` will not encode with the error prefix. - Err(Error::custom_bytes(ASSUME_MAGIC_RETURN_CODE)) - }; - Some(bytes) - } else { - None - } -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/mapping.rs b/crates/evm/src/executor/inspector/cheatcodes/mapping.rs deleted file mode 100644 index cf81dc447eb1c..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/mapping.rs +++ /dev/null @@ -1,120 +0,0 @@ -use super::Cheatcodes; -use crate::utils::{b160_to_h160, ru256_to_u256}; -use ethers::{ - abi::{self, Token}, - types::{Address, Bytes, U256}, - utils::keccak256, -}; -use revm::{ - interpreter::{opcode, Interpreter}, - Database, EVMData, -}; -use std::collections::BTreeMap; - -#[derive(Clone, Debug, Default)] -pub struct MappingSlots { - /// Holds mapping parent (slots => slots) - pub parent_slots: BTreeMap, - - /// Holds mapping key (slots => key) - pub keys: BTreeMap, - - /// Holds mapping child (slots => slots[]) - pub children: BTreeMap>, - - /// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record - /// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in - /// `data_low`, higher 256 bits in `data_high`. - /// This is needed for mapping_key detect if the slot is for some mapping and record that. - pub seen_sha3: BTreeMap, -} - -impl MappingSlots { - pub fn insert(&mut self, slot: U256) -> bool { - match self.seen_sha3.get(&slot).copied() { - Some((key, parent)) => { - if self.keys.contains_key(&slot) { - return false - } - self.keys.insert(slot, key); - self.parent_slots.insert(slot, parent); - self.children.entry(parent).or_default().push(slot); - self.insert(parent); - true - } - None => false, - } - } -} - -pub fn get_mapping_length(state: &Cheatcodes, address: Address, slot: U256) -> Bytes { - let result = match state.mapping_slots.as_ref().and_then(|dict| dict.get(&address)) { - Some(mapping_slots) => { - mapping_slots.children.get(&slot).map(|set| set.len()).unwrap_or_default() - } - None => 0, - }; - abi::encode(&[Token::Uint(result.into())]).into() -} - -pub fn get_mapping_slot_at(state: &Cheatcodes, address: Address, slot: U256, index: U256) -> Bytes { - let result = match state.mapping_slots.as_ref().and_then(|dict| dict.get(&address)) { - Some(mapping_slots) => mapping_slots - .children - .get(&slot) - .and_then(|set| set.get(index.as_usize())) - .copied() - .unwrap_or_default(), - None => 0.into(), - }; - abi::encode(&[Token::Uint(result)]).into() -} - -pub fn get_mapping_key_and_parent(state: &Cheatcodes, address: Address, slot: U256) -> Bytes { - let (found, key, parent) = - match state.mapping_slots.as_ref().and_then(|dict| dict.get(&address)) { - Some(mapping_slots) => match mapping_slots.keys.get(&slot) { - Some(key) => (true, *key, mapping_slots.parent_slots[&slot]), - None => match mapping_slots.seen_sha3.get(&slot).copied() { - Some(maybe_info) => (true, maybe_info.0, maybe_info.1), - None => (false, U256::zero(), U256::zero()), - }, - }, - None => (false, U256::zero(), U256::zero()), - }; - abi::encode(&[Token::Bool(found), Token::Uint(key), Token::Uint(parent)]).into() -} - -pub fn on_evm_step( - mapping_slots: &mut BTreeMap, - interpreter: &Interpreter, - _data: &mut EVMData<'_, DB>, -) { - match interpreter.contract.bytecode.bytecode()[interpreter.program_counter()] { - opcode::SHA3 => { - if interpreter.stack.peek(1) == Ok(revm::primitives::U256::from(0x40)) { - let address = interpreter.contract.address; - let offset = interpreter.stack.peek(0).expect("stack size > 1").to::(); - let low = U256::from(interpreter.memory.get_slice(offset, 0x20)); - let high = U256::from(interpreter.memory.get_slice(offset + 0x20, 0x20)); - let result = U256::from(keccak256(interpreter.memory.get_slice(offset, 0x40))); - - mapping_slots - .entry(b160_to_h160(address)) - .or_default() - .seen_sha3 - .insert(result, (low, high)); - } - } - opcode::SSTORE => { - if let Some(mapping_slots) = - mapping_slots.get_mut(&b160_to_h160(interpreter.contract.address)) - { - if let Ok(slot) = interpreter.stack.peek(0) { - mapping_slots.insert(ru256_to_u256(slot)); - } - } - } - _ => {} - } -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/mod.rs b/crates/evm/src/executor/inspector/cheatcodes/mod.rs deleted file mode 100644 index afc14138d9a44..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/mod.rs +++ /dev/null @@ -1,1159 +0,0 @@ -use self::{ - env::Broadcast, - expect::{handle_expect_emit, handle_expect_revert, ExpectedCallType}, - mapping::MappingSlots, - util::{check_if_fixed_gas_limit, process_create, BroadcastableTransactions, MAGIC_SKIP_BYTES}, -}; -use crate::{ - abi::HEVMCalls, - executor::{ - backend::DatabaseExt, inspector::cheatcodes::env::RecordedLogs, CHEATCODE_ADDRESS, - HARDHAT_CONSOLE_ADDRESS, - }, - utils::{b160_to_h160, b256_to_h256, h160_to_b160, ru256_to_u256}, -}; -use ethers::{ - abi::{AbiDecode, AbiEncode, RawLog}, - signers::LocalWallet, - types::{ - transaction::eip2718::TypedTransaction, Address, Bytes, NameOrAddress, TransactionRequest, - U256, - }, -}; -use foundry_common::evm::Breakpoints; -use foundry_utils::error::SolError; -use itertools::Itertools; -use revm::{ - interpreter::{opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::{BlockEnv, TransactTo, B160, B256}, - EVMData, Inspector, -}; -use serde_json::Value; -use std::{ - collections::{BTreeMap, HashMap, VecDeque}, - fs::File, - io::BufReader, - ops::Range, - path::PathBuf, - sync::Arc, -}; - -/// Cheatcodes related to the execution environment. -mod env; -pub use env::{Log, Prank, RecordAccess}; -/// Assertion helpers (such as `expectEmit`) -mod expect; -pub use expect::{ - ExpectedCallData, ExpectedEmit, ExpectedRevert, MockCallDataContext, MockCallReturnData, -}; - -/// Cheatcodes that interact with the external environment (FFI etc.) -mod ext; -/// Fork related cheatcodes -mod fork; -/// File-system related cheatcodes -mod fs; -/// Cheatcodes that configure the fuzzer -mod fuzz; -/// Mapping related cheatcodes -mod mapping; -/// Snapshot related cheatcodes -mod snapshot; -/// Utility cheatcodes (`sign` etc.) -pub mod util; -pub use util::{BroadcastableTransaction, DEFAULT_CREATE2_DEPLOYER}; - -mod config; -use crate::executor::{backend::RevertDiagnostic, inspector::utils::get_create_address}; -pub use config::CheatsConfig; - -mod error; -pub(crate) use error::{bail, ensure, fmt_err}; -pub use error::{Error, Result}; - -/// Tracks the expected calls per address. -/// For each address, we track the expected calls per call data. We track it in such manner -/// so that we don't mix together calldatas that only contain selectors and calldatas that contain -/// selector and arguments (partial and full matches). -/// This then allows us to customize the matching behavior for each call data on the -/// `ExpectedCallData` struct and track how many times we've actually seen the call on the second -/// element of the tuple. -pub type ExpectedCallTracker = BTreeMap, (ExpectedCallData, u64)>>; - -/// An inspector that handles calls to various cheatcodes, each with their own behavior. -/// -/// Cheatcodes can be called by contracts during execution to modify the VM environment, such as -/// mocking addresses, signatures and altering call reverts. -/// -/// Executing cheatcodes can be very powerful. Most cheatcodes are limited to evm internals, but -/// there are also cheatcodes like `ffi` which can execute arbitrary commands or `writeFile` and -/// `readFile` which can manipulate files of the filesystem. Therefore, several restrictions are -/// implemented for these cheatcodes: -/// -/// - `ffi`, and file cheatcodes are _always_ opt-in (via foundry config) and never enabled by -/// default: all respective cheatcode handlers implement the appropriate checks -/// - File cheatcodes require explicit permissions which paths are allowed for which operation, -/// see `Config.fs_permission` -/// - Only permitted accounts are allowed to execute cheatcodes in forking mode, this ensures no -/// contract deployed on the live network is able to execute cheatcodes by simply calling the -/// cheatcode address: by default, the caller, test contract and newly deployed contracts are -/// allowed to execute cheatcodes -#[derive(Clone, Debug, Default)] -pub struct Cheatcodes { - /// The block environment - /// - /// Used in the cheatcode handler to overwrite the block environment separately from the - /// execution block environment. - pub block: Option, - - /// The gas price - /// - /// Used in the cheatcode handler to overwrite the gas price separately from the gas price - /// in the execution environment. - pub gas_price: Option, - - /// Address labels - pub labels: BTreeMap, - - /// Rememebered private keys - pub script_wallets: Vec, - - /// Whether the skip cheatcode was activated - pub skip: bool, - - /// Prank information - pub prank: Option, - - /// Expected revert information - pub expected_revert: Option, - - /// Additional diagnostic for reverts - pub fork_revert_diagnostic: Option, - - /// Recorded storage reads and writes - pub accesses: Option, - - /// Recorded logs - pub recorded_logs: Option, - - /// Mocked calls - pub mocked_calls: BTreeMap>, - - /// Expected calls - pub expected_calls: ExpectedCallTracker, - - /// Expected emits - pub expected_emits: VecDeque, - - /// Map of context depths to memory offset ranges that may be written to within the call depth. - pub allowed_mem_writes: BTreeMap>>, - - /// Current broadcasting information - pub broadcast: Option, - - /// Used to correct the nonce of --sender after the initiating call. For more, check - /// `docs/scripting`. - pub corrected_nonce: bool, - - /// Scripting based transactions - pub broadcastable_transactions: BroadcastableTransactions, - - /// Additional, user configurable context this Inspector has access to when inspecting a call - pub config: Arc, - - /// Test-scoped context holding data that needs to be reset every test run - pub context: Context, - - // Commit FS changes such as file creations, writes and deletes. - // Used to prevent duplicate changes file executing non-committing calls. - pub fs_commit: bool, - - pub serialized_jsons: BTreeMap>, - - /// Records all eth deals - pub eth_deals: Vec, - - /// Holds the stored gas info for when we pause gas metering. It is an `Option>` - /// because the `call` callback in an `Inspector` doesn't get access to - /// the `revm::Interpreter` which holds the `revm::Gas` struct that - /// we need to copy. So we convert it to a `Some(None)` in `apply_cheatcode`, and once we have - /// the interpreter, we copy the gas struct. Then each time there is an execution of an - /// operation, we reset the gas. - pub gas_metering: Option>, - - /// Holds stored gas info for when we pause gas metering, and we're entering/inside - /// CREATE / CREATE2 frames. This is needed to make gas meter pausing work correctly when - /// paused and creating new contracts. - pub gas_metering_create: Option>, - - /// Holds mapping slots info - pub mapping_slots: Option>, - - /// current program counter - pub pc: usize, - /// Breakpoints supplied by the `vm.breakpoint("")` cheatcode - /// char -> pc - pub breakpoints: Breakpoints, -} - -impl Cheatcodes { - /// Creates a new `Cheatcodes` based on the given settings - pub fn new(block: BlockEnv, gas_price: U256, config: CheatsConfig) -> Self { - Self { - corrected_nonce: false, - block: Some(block), - gas_price: Some(gas_price), - config: Arc::new(config), - fs_commit: true, - ..Default::default() - } - } - - #[instrument(level = "error", name = "apply", target = "evm::cheatcodes", skip_all)] - fn apply_cheatcode( - &mut self, - data: &mut EVMData<'_, DB>, - caller: Address, - call: &CallInputs, - ) -> Result { - // Decode the cheatcode call - let decoded = HEVMCalls::decode(&call.input)?; - - // ensure the caller is allowed to execute cheatcodes, - // but only if the backend is in forking mode - data.db.ensure_cheatcode_access_forking_mode(caller)?; - - // TODO: Log the opcode for the debugger - let opt = env::apply(self, data, caller, &decoded) - .transpose() - .or_else(|| util::apply(self, data, &decoded)) - .or_else(|| expect::apply(self, data, &decoded)) - .or_else(|| fuzz::apply(&decoded)) - .or_else(|| ext::apply(self, &decoded)) - .or_else(|| fs::apply(self, &decoded)) - .or_else(|| snapshot::apply(data, &decoded)) - .or_else(|| fork::apply(self, data, &decoded)); - match opt { - Some(res) => res, - None => Err(fmt_err!("Unhandled cheatcode: {decoded:?}. This is a bug.")), - } - } - - /// Determines the address of the contract and marks it as allowed - /// - /// There may be cheatcodes in the constructor of the new contract, in order to allow them - /// automatically we need to determine the new address - fn allow_cheatcodes_on_create( - &self, - data: &mut EVMData<'_, DB>, - inputs: &CreateInputs, - ) { - let old_nonce = data - .journaled_state - .state - .get(&inputs.caller) - .map(|acc| acc.info.nonce) - .unwrap_or_default(); - let created_address = get_create_address(inputs, old_nonce); - - if data.journaled_state.depth > 1 && - !data.db.has_cheatcode_access(b160_to_h160(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 - } - - data.db.allow_cheatcode_access(created_address); - } - - /// Called when there was a revert. - /// - /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's - /// revert would run into issues. - pub fn on_revert(&mut self, data: &mut EVMData<'_, DB>) { - trace!(deals=?self.eth_deals.len(), "Rolling back deals"); - - // Delay revert clean up until expected revert is handled, if set. - if self.expected_revert.is_some() { - return - } - - // we only want to apply cleanup top level - if data.journaled_state.depth() > 0 { - return - } - - // Roll back all previously applied deals - // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine - // which rolls back any transfers. - while let Some(record) = self.eth_deals.pop() { - if let Some(acc) = data.journaled_state.state.get_mut(&h160_to_b160(record.address)) { - acc.info.balance = record.old_balance.into(); - } - } - } -} - -impl Inspector for Cheatcodes -where - DB: DatabaseExt, -{ - fn initialize_interp( - &mut self, - _: &mut Interpreter, - data: &mut EVMData<'_, DB>, - _: bool, - ) -> InstructionResult { - // 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() { - data.env.block = block; - } - if let Some(gas_price) = self.gas_price.take() { - data.env.tx.gas_price = gas_price.into(); - } - - InstructionResult::Continue - } - - fn step( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - _: bool, - ) -> InstructionResult { - self.pc = interpreter.program_counter(); - - // 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.contract.bytecode.bytecode()[interpreter.program_counter()] { - 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 = revm::interpreter::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 = revm::interpreter::Gas::new(gas.limit()); - - // 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)) - } - - // dont monitor gas changes, keep it constant - interpreter.gas = gas; - } - } - } - _ => {} - } - - // Record writes and reads if `record` has been called - if let Some(storage_accesses) = &mut self.accesses { - match interpreter.contract.bytecode.bytecode()[interpreter.program_counter()] { - opcode::SLOAD => { - let key = try_or_continue!(interpreter.stack().peek(0)); - storage_accesses - .reads - .entry(b160_to_h160(interpreter.contract().address)) - .or_insert_with(Vec::new) - .push(key.into()); - } - opcode::SSTORE => { - let key = try_or_continue!(interpreter.stack().peek(0)); - - // An SSTORE does an SLOAD internally - storage_accesses - .reads - .entry(b160_to_h160(interpreter.contract().address)) - .or_insert_with(Vec::new) - .push(key.into()); - storage_accesses - .writes - .entry(b160_to_h160(interpreter.contract().address)) - .or_insert_with(Vec::new) - .push(key.into()); - } - _ => (), - } - } - - // 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(&data.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.contract.bytecode.bytecode()[interpreter.program_counter()] { - //////////////////////////////////////////////////////////////// - // 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 = ru256_to_u256(try_or_continue!(interpreter.stack().peek(0))).as_u64(); - - // 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)) - }) { - revert_helper::disallowed_mem_write(offset, 32, interpreter, ranges); - return InstructionResult::Revert - } - } - opcode::MSTORE8 => { - // The offset of the mstore8 operation is at the top of the stack. - let offset = ru256_to_u256(try_or_continue!(interpreter.stack().peek(0))).as_u64(); - - // If none of the allowed ranges contain the offset, memory has been - // unexpectedly mutated. - if !ranges.iter().any(|range| range.contains(&offset)) { - revert_helper::disallowed_mem_write(offset, 1, interpreter, ranges); - return InstructionResult::Revert - } - } - - //////////////////////////////////////////////////////////////// - // OPERATIONS THAT CAN EXPAND MEMORY BY READING // - //////////////////////////////////////////////////////////////// - - opcode::MLOAD => { - // The offset of the mload operation is at the top of the stack - let offset = ru256_to_u256(try_or_continue!(interpreter.stack().peek(0))).as_u64(); - - // 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.memory.len() as u64 && !ranges.iter().any(|range| { - range.contains(&offset) && range.contains(&(offset + 31)) - }) { - revert_helper::disallowed_mem_write(offset, 32, interpreter, ranges); - return InstructionResult::Revert - } - } - - //////////////////////////////////////////////////////////////// - // OPERATIONS WITH OFFSET AND SIZE ON STACK // - //////////////////////////////////////////////////////////////// - - $(opcode::$opcode => { - // The destination offset of the operation is at the top of the stack. - let dest_offset = ru256_to_u256(try_or_continue!(interpreter.stack().peek($offset_depth))).as_u64(); - - // The size of the data that will be copied is the third item on the stack. - let size = ru256_to_u256(try_or_continue!(interpreter.stack().peek($size_depth))).as_u64(); - - // 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.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 { - revert_helper::disallowed_mem_write(dest_offset, size, interpreter, ranges); - return InstructionResult::Revert - } - })* - _ => () - } - } - } - - // 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), - (CALL, 5, 6, true), - (CALLCODE, 5, 6, true), - (STATICCALL, 4, 5, true), - (DELEGATECALL, 4, 5, true), - (SHA3, 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::on_evm_step(mapping_slots, interpreter, data) - } - - InstructionResult::Continue - } - - fn log( - &mut self, - _: &mut EVMData<'_, DB>, - address: &B160, - topics: &[B256], - data: &bytes::Bytes, - ) { - if !self.expected_emits.is_empty() { - handle_expect_emit( - self, - RawLog { - topics: topics.iter().copied().map(b256_to_h256).collect_vec(), - data: data.to_vec(), - }, - &b160_to_h160(*address), - ); - } - - // Stores this log if `recordLogs` has been called - if let Some(storage_recorded_logs) = &mut self.recorded_logs { - storage_recorded_logs.entries.push(Log { - emitter: b160_to_h160(*address), - inner: RawLog { - topics: topics.iter().copied().map(b256_to_h256).collect_vec(), - data: data.to_vec(), - }, - }); - } - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - is_static: bool, - ) -> (InstructionResult, Gas, bytes::Bytes) { - if call.contract == h160_to_b160(CHEATCODE_ADDRESS) { - let gas = Gas::new(call.gas_limit); - match self.apply_cheatcode(data, b160_to_h160(call.context.caller), call) { - Ok(retdata) => (InstructionResult::Return, gas, retdata.0), - Err(err) => (InstructionResult::Revert, gas, err.encode_error().0), - } - } else if call.contract != h160_to_b160(HARDHAT_CONSOLE_ADDRESS) { - // Handle expected calls - - // Grab the different calldatas expected. - if let Some(expected_calls_for_target) = - self.expected_calls.get_mut(&(b160_to_h160(call.contract))) - { - // Match every partial/full calldata - for (calldata, (expected, actual_count)) in expected_calls_for_target.iter_mut() { - // Increment actual times seen if... - // The calldata is at most, as big as this call's input, and - if calldata.len() <= call.input.len() && - // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and - *calldata == call.input[..calldata.len()] && - // The value matches, if provided - expected - .value - .map_or(true, |value| value == call.transfer.value.into()) && - // The gas matches, if provided - expected.gas.map_or(true, |gas| gas == call.gas_limit) && - // The minimum gas matches, if provided - expected.min_gas.map_or(true, |min_gas| min_gas <= call.gas_limit) - { - *actual_count += 1; - } - } - } - - // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get(&b160_to_h160(call.contract)) { - let ctx = MockCallDataContext { - calldata: call.input.clone().into(), - value: Some(call.transfer.value.into()), - }; - if let Some(mock_retdata) = mocks.get(&ctx) { - return ( - mock_retdata.ret_type, - Gas::new(call.gas_limit), - mock_retdata.data.clone().0, - ) - } else if let Some((_, mock_retdata)) = mocks.iter().find(|(mock, _)| { - mock.calldata.len() <= call.input.len() && - *mock.calldata == call.input[..mock.calldata.len()] && - mock.value.map_or(true, |value| value == call.transfer.value.into()) - }) { - return ( - mock_retdata.ret_type, - Gas::new(call.gas_limit), - mock_retdata.data.0.clone(), - ) - } - } - - // Apply our prank - if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && - call.context.caller == h160_to_b160(prank.prank_caller) - { - let mut prank_applied = false; - // At the target depth we set `msg.sender` - if data.journaled_state.depth() == prank.depth { - call.context.caller = h160_to_b160(prank.new_caller); - call.transfer.source = h160_to_b160(prank.new_caller); - prank_applied = true; - } - - // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - data.env.tx.caller = h160_to_b160(new_origin); - prank_applied = true; - } - - // If prank applied for first time, then update - if prank_applied { - if let Some(applied_prank) = prank.first_time_applied() { - self.prank = Some(applied_prank); - } - } - } - } - - // Apply our broadcast - if let Some(broadcast) = &self.broadcast { - // We only apply a broadcast *to a specific depth*. - // - // We do this because any subsequent contract calls *must* exist on chain and - // we only want to grab *this* call, not internal ones - if data.journaled_state.depth() == broadcast.depth && - call.context.caller == h160_to_b160(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. - data.env.tx.caller = h160_to_b160(broadcast.new_origin); - - call.context.caller = h160_to_b160(broadcast.new_origin); - call.transfer.source = h160_to_b160(broadcast.new_origin); - // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here - // because we only need the from, to, value, and data. We can later change this - // into 1559, in the cli package, relatively easily once we - // know the target chain supports EIP-1559. - if !is_static { - if let Err(err) = data - .journaled_state - .load_account(h160_to_b160(broadcast.new_origin), data.db) - { - return ( - InstructionResult::Revert, - Gas::new(call.gas_limit), - err.encode_string().0, - ) - } - - let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); - - let account = data - .journaled_state - .state() - .get_mut(&h160_to_b160(broadcast.new_origin)) - .unwrap(); - - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: data.db.active_fork_url(), - transaction: TypedTransaction::Legacy(TransactionRequest { - from: Some(broadcast.new_origin), - to: Some(NameOrAddress::Address(b160_to_h160(call.contract))), - value: Some(call.transfer.value.into()), - data: Some(call.input.clone().into()), - nonce: Some(account.info.nonce.into()), - gas: if is_fixed_gas_limit { - Some(call.gas_limit.into()) - } else { - None - }, - ..Default::default() - }), - }); - - // call_inner does not increase nonces, so we have to do it ourselves - account.info.nonce += 1; - } else if broadcast.single_call { - return ( - InstructionResult::Revert, - Gas::new(0), - "Staticcalls are not allowed after vm.broadcast. Either remove it, or use vm.startBroadcast instead." - .to_string() - .encode() - .into() - ); - } - } - } - - (InstructionResult::Continue, Gas::new(call.gas_limit), bytes::Bytes::new()) - } else { - (InstructionResult::Continue, Gas::new(call.gas_limit), bytes::Bytes::new()) - } - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: bytes::Bytes, - _: bool, - ) -> (InstructionResult, Gas, bytes::Bytes) { - if call.contract == h160_to_b160(CHEATCODE_ADDRESS) || - call.contract == h160_to_b160(HARDHAT_CONSOLE_ADDRESS) - { - return (status, remaining_gas, retdata) - } - - if data.journaled_state.depth() == 0 && self.skip { - return ( - InstructionResult::Revert, - remaining_gas, - Error::custom_bytes(MAGIC_SKIP_BYTES).encode_error().0, - ) - } - - // Clean up pranks - if let Some(prank) = &self.prank { - if data.journaled_state.depth() == prank.depth { - data.env.tx.caller = h160_to_b160(prank.prank_origin); - } - if prank.single_call { - std::mem::take(&mut self.prank); - } - } - - // Clean up broadcast - if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() == broadcast.depth { - data.env.tx.caller = h160_to_b160(broadcast.original_origin); - } - - if broadcast.single_call { - std::mem::take(&mut self.broadcast); - } - } - - // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert { - if data.journaled_state.depth() <= expected_revert.depth { - let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match handle_expect_revert( - false, - expected_revert.reason.as_ref(), - status, - retdata.into(), - ) { - Err(error) => { - trace!(expected=?expected_revert, ?error, ?status, "Expected revert mismatch"); - (InstructionResult::Revert, remaining_gas, error.encode_error().0) - } - Ok((_, retdata)) => (InstructionResult::Return, remaining_gas, retdata.0), - } - } - } - - // At the end of the call, - // we need to check if we've found all the emits. - // We know we've found all the expected emits in the right order - // if the queue is fully matched. - // If it's not fully matched, then either: - // 1. Not enough events were emitted (we'll know this because the amount of times we - // inspected events will be less than the size of the queue) 2. The wrong events - // were emitted (The inspected events should match the size of the queue, but still some - // events will not be matched) - - // First, check that we're at the call depth where the emits were declared from. - let should_check_emits = self - .expected_emits - .iter() - .any(|expected| expected.depth == data.journaled_state.depth()) && - // Ignore staticcalls - !call.is_static; - // If so, check the emits - if should_check_emits { - // Not all emits were matched. - if self.expected_emits.iter().any(|expected| !expected.found) { - return ( - InstructionResult::Revert, - remaining_gas, - "Log != expected log".to_string().encode().into(), - ) - } else { - // All emits were found, we're good. - // Clear the queue, as we expect the user to declare more events for the next call - // if they wanna match further events. - self.expected_emits.clear() - } - } - - // If the depth is 0, then this is the root call terminating - if data.journaled_state.depth() == 0 { - // Match expected calls - for (address, calldatas) in &self.expected_calls { - // Loop over each address, and for each address, loop over each calldata it expects. - for (calldata, (expected, actual_count)) in calldatas { - // Grab the values we expect to see - let ExpectedCallData { gas, min_gas, value, count, call_type } = expected; - let calldata = Bytes::from(calldata.clone()); - - // We must match differently depending on the type of call we expect. - match call_type { - // If the cheatcode was called with a `count` argument, - // we must check that the EVM performed a CALL with this calldata exactly - // `count` times. - ExpectedCallType::Count => { - if *count != *actual_count { - let expected_values = [ - Some(format!("data {calldata}")), - value.map(|v| format!("value {v}")), - gas.map(|g| format!("gas {g}")), - min_gas.map(|g| format!("minimum gas {g}")), - ] - .into_iter() - .flatten() - .join(" and "); - let failure_message = match status { - InstructionResult::Continue | InstructionResult::Stop | InstructionResult::Return | InstructionResult::SelfDestruct => - format!("Expected call to {address:?} with {expected_values} to be called {count} time(s), but was called {actual_count} time(s)"), - _ => format!("Expected call to {address:?} with {expected_values} to be called {count} time(s), but the call reverted instead. Ensure you're testing the happy path when using the expectCall cheatcode"), - }; - return ( - InstructionResult::Revert, - remaining_gas, - failure_message.encode().into(), - ) - } - } - // If the cheatcode was called without a `count` argument, - // we must check that the EVM performed a CALL with this calldata at least - // `count` times. The amount of times to check was - // the amount of time the cheatcode was called. - ExpectedCallType::NonCount => { - if *count > *actual_count { - let expected_values = [ - Some(format!("data {calldata}")), - value.map(|v| format!("value {v}")), - gas.map(|g| format!("gas {g}")), - min_gas.map(|g| format!("minimum gas {g}")), - ] - .into_iter() - .flatten() - .join(" and "); - let failure_message = match status { - InstructionResult::Continue | InstructionResult::Stop | InstructionResult::Return | InstructionResult::SelfDestruct => - format!("Expected call to {address:?} with {expected_values} to be called {count} time(s), but was called {actual_count} time(s)"), - _ => format!("Expected call to {address:?} with {expected_values} to be called {count} time(s), but the call reverted instead. Ensure you're testing the happy path when using the expectCall cheatcode"), - }; - return ( - InstructionResult::Revert, - remaining_gas, - failure_message.encode().into(), - ) - } - } - } - } - } - - // Check if we have any leftover expected emits - // First, if any emits were found at the root call, then we its ok and we remove them. - self.expected_emits.retain(|expected| !expected.found); - // If not empty, we got mismatched emits - if !self.expected_emits.is_empty() { - let failure_message = match status { - InstructionResult::Continue | InstructionResult::Stop | InstructionResult::Return | InstructionResult::SelfDestruct => - "Expected an emit, but no logs were emitted afterward. You might have mismatched events or not enough events were emitted.", - _ => "Expected an emit, but the call reverted instead. Ensure you're testing the happy path when using the `expectEmit` cheatcode.", - }; - return ( - InstructionResult::Revert, - remaining_gas, - failure_message.to_string().encode().into(), - ) - } - } - - // if there's a revert and a previous call was diagnosed as fork related revert then we can - // return a better error here - if status == InstructionResult::Revert { - if let Some(err) = self.fork_revert_diagnostic.take() { - return (status, remaining_gas, err.to_error_msg(self).encode().into()) - } - } - - // this will ensure we don't have false positives when trying to diagnose reverts in fork - // mode - let _ = self.fork_revert_diagnostic.take(); - - // 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) = data.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 data.db.is_forked_mode() && - status == InstructionResult::Stop && - call.contract != test_contract - { - self.fork_revert_diagnostic = - data.db.diagnose_revert(b160_to_h160(call.contract), &data.journaled_state); - } - } - - (status, remaining_gas, retdata) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option, Gas, bytes::Bytes) { - // allow cheatcodes from the address of the new contract - self.allow_cheatcodes_on_create(data, call); - - // Apply our prank - if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && - call.caller == h160_to_b160(prank.prank_caller) - { - // At the target depth we set `msg.sender` - if data.journaled_state.depth() == prank.depth { - call.caller = h160_to_b160(prank.new_caller); - } - - // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - data.env.tx.caller = h160_to_b160(new_origin); - } - } - } - - // Apply our broadcast - if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() >= broadcast.depth && - call.caller == h160_to_b160(broadcast.original_caller) - { - if let Err(err) = - data.journaled_state.load_account(h160_to_b160(broadcast.new_origin), data.db) - { - return ( - InstructionResult::Revert, - None, - Gas::new(call.gas_limit), - err.encode_string().0, - ) - } - - data.env.tx.caller = h160_to_b160(broadcast.new_origin); - - if data.journaled_state.depth() == broadcast.depth { - let (bytecode, to, nonce) = match process_create( - broadcast.new_origin, - call.init_code.clone(), - data, - call, - ) { - Ok(val) => val, - Err(err) => { - return ( - InstructionResult::Revert, - None, - Gas::new(call.gas_limit), - err.encode_string().0, - ) - } - }; - - let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); - - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: data.db.active_fork_url(), - transaction: TypedTransaction::Legacy(TransactionRequest { - from: Some(broadcast.new_origin), - to, - value: Some(call.value.into()), - data: Some(bytecode.into()), - nonce: Some(nonce.into()), - gas: if is_fixed_gas_limit { - Some(call.gas_limit.into()) - } else { - None - }, - ..Default::default() - }), - }); - } - } - } - - (InstructionResult::Continue, None, Gas::new(call.gas_limit), bytes::Bytes::new()) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: InstructionResult, - address: Option, - remaining_gas: Gas, - retdata: bytes::Bytes, - ) -> (InstructionResult, Option, Gas, bytes::Bytes) { - // Clean up pranks - if let Some(prank) = &self.prank { - if data.journaled_state.depth() == prank.depth { - data.env.tx.caller = h160_to_b160(prank.prank_origin); - } - if prank.single_call { - std::mem::take(&mut self.prank); - } - } - - // Clean up broadcasts - if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() == broadcast.depth { - data.env.tx.caller = h160_to_b160(broadcast.original_origin); - } - - if broadcast.single_call { - std::mem::take(&mut self.broadcast); - } - } - - // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert { - if data.journaled_state.depth() <= expected_revert.depth { - let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match handle_expect_revert( - true, - expected_revert.reason.as_ref(), - status, - retdata.into(), - ) { - Ok((address, retdata)) => ( - InstructionResult::Return, - address.map(h160_to_b160), - remaining_gas, - retdata.0, - ), - Err(err) => { - (InstructionResult::Revert, None, remaining_gas, err.encode_error().0) - } - } - } - } - - (status, address, remaining_gas, retdata) - } -} - -/// Contains additional, test specific resources that should be kept for the duration of the test -#[derive(Debug, Default)] -pub struct Context { - //// Buffered readers for files opened for reading (path => BufReader mapping) - pub opened_read_files: HashMap>, -} - -/// Every time we clone `Context`, we want it to be empty -impl Clone for Context { - fn clone(&self) -> Self { - Default::default() - } -} - -/// Records `deal` cheatcodes -#[derive(Debug, Clone)] -pub struct DealRecord { - /// Target of the deal. - pub address: Address, - /// The balance of the address before deal was applied - pub old_balance: U256, - /// Balance after deal was applied - pub new_balance: U256, -} - -/// Helper module to store revert strings in memory -mod revert_helper { - use super::*; - - /// Helper that expands memory, stores a revert string pertaining to a disallowed memory write, - /// and sets the return range to the revert string's location in memory. - pub fn disallowed_mem_write( - dest_offset: u64, - size: u64, - interpreter: &mut Interpreter, - ranges: &[Range], - ) { - let revert_string: Bytes = format!( - "Memory write at offset 0x{:02X} of size 0x{:02X} not allowed. Safe range: {}", - dest_offset, - size, - ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" ∪ ") - ) - .encode() - .into(); - mstore_revert_string(revert_string, interpreter); - } - - /// Expands memory, stores a revert string, and sets the return range to the revert - /// string's location in memory. - fn mstore_revert_string(bytes: Bytes, interpreter: &mut Interpreter) { - let starting_offset = interpreter.memory.len(); - interpreter.memory.resize(starting_offset + bytes.len()); - interpreter.memory.set_data(starting_offset, 0, bytes.len(), &bytes); - interpreter.return_range = starting_offset..interpreter.memory.len(); - } -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/snapshot.rs b/crates/evm/src/executor/inspector/cheatcodes/snapshot.rs deleted file mode 100644 index f34d61e54038e..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/snapshot.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::Result; -use crate::{abi::HEVMCalls, executor::backend::DatabaseExt}; -use ethers::abi::AbiEncode; -use revm::EVMData; - -/// Handles fork related cheatcodes -#[instrument(level = "error", name = "snapshot", target = "evm::cheatcodes", skip_all)] -pub fn apply(data: &mut EVMData<'_, DB>, call: &HEVMCalls) -> Option { - Some(match call { - HEVMCalls::Snapshot(_) => { - Ok(data.db.snapshot(&data.journaled_state, data.env).encode().into()) - } - HEVMCalls::RevertTo(snapshot) => { - let res = if let Some(journaled_state) = - data.db.revert(snapshot.0, &data.journaled_state, data.env) - { - // we reset the evm's journaled_state to the state of the snapshot previous state - data.journaled_state = journaled_state; - true - } else { - false - }; - Ok(res.encode().into()) - } - _ => return None, - }) -} diff --git a/crates/evm/src/executor/inspector/cheatcodes/util.rs b/crates/evm/src/executor/inspector/cheatcodes/util.rs deleted file mode 100644 index b3863c0a55c5d..0000000000000 --- a/crates/evm/src/executor/inspector/cheatcodes/util.rs +++ /dev/null @@ -1,512 +0,0 @@ -use super::{ensure, fmt_err, Cheatcodes, Error, Result}; -use crate::{ - abi::HEVMCalls, - executor::backend::{ - error::{DatabaseError, DatabaseResult}, - DatabaseExt, - }, - utils::{h160_to_b160, h256_to_u256_be, ru256_to_u256, u256_to_ru256}, -}; -use bytes::{BufMut, Bytes, BytesMut}; -use ethers::{ - abi::{AbiEncode, Address, ParamType, Token}, - core::k256::elliptic_curve::Curve, - prelude::{ - k256::{ - ecdsa::SigningKey, - elliptic_curve::{bigint::Encoding, sec1::ToEncodedPoint}, - Secp256k1, - }, - LocalWallet, Signer, H160, *, - }, - signers::{ - coins_bip39::{ - ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, - Korean, Portuguese, Spanish, Wordlist, - }, - MnemonicBuilder, - }, - types::{transaction::eip2718::TypedTransaction, NameOrAddress, H256, U256}, - utils::{self, keccak256}, -}; -use foundry_common::{fmt::*, RpcUrl}; -use revm::{ - interpreter::CreateInputs, - primitives::{Account, TransactTo}, - Database, EVMData, JournaledState, -}; -use std::{collections::VecDeque, str::FromStr}; - -const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; - -/// Address of the default CREATE2 deployer 0x4e59b44847b379578588920ca78fbf26c0b4956c -pub const DEFAULT_CREATE2_DEPLOYER: H160 = H160([ - 78, 89, 180, 72, 71, 179, 121, 87, 133, 136, 146, 12, 167, 143, 191, 38, 192, 180, 149, 108, -]); - -pub const MAGIC_SKIP_BYTES: &[u8] = b"FOUNDRY::SKIP"; - -/// Helps collecting transactions from different forks. -#[derive(Debug, Clone, Default)] -pub struct BroadcastableTransaction { - pub rpc: Option, - pub transaction: TypedTransaction, -} - -pub type BroadcastableTransactions = VecDeque; - -/// Configures the env for the transaction -pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { - env.tx.caller = h160_to_b160(tx.from); - env.tx.gas_limit = tx.gas.as_u64(); - env.tx.gas_price = tx.gas_price.unwrap_or_default().into(); - env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(Into::into); - env.tx.nonce = Some(tx.nonce.as_u64()); - env.tx.access_list = tx - .access_list - .clone() - .unwrap_or_default() - .0 - .into_iter() - .map(|item| { - ( - h160_to_b160(item.address), - item.storage_keys.into_iter().map(h256_to_u256_be).map(u256_to_ru256).collect(), - ) - }) - .collect(); - env.tx.value = tx.value.into(); - env.tx.data = tx.input.0.clone(); - env.tx.transact_to = - tx.to.map(h160_to_b160).map(TransactTo::Call).unwrap_or_else(TransactTo::create) -} - -/// Applies the given function `f` to the `revm::Account` belonging to the `addr` -/// -/// This will ensure the `Account` is loaded and `touched`, see [`JournaledState::touch`] -pub fn with_journaled_account( - journaled_state: &mut JournaledState, - db: &mut DB, - addr: Address, - mut f: F, -) -> Result -where - F: FnMut(&mut Account) -> R, -{ - let addr = h160_to_b160(addr); - journaled_state.load_account(addr, db)?; - journaled_state.touch(&addr); - let account = journaled_state.state.get_mut(&addr).expect("account loaded;"); - Ok(f(account)) -} - -fn addr(private_key: U256) -> Result { - let key = parse_private_key(private_key)?; - let addr = utils::secret_key_to_address(&key); - Ok(addr.encode().into()) -} - -fn sign(private_key: U256, digest: H256, chain_id: U256) -> Result { - let key = parse_private_key(private_key)?; - let wallet = LocalWallet::from(key).with_chain_id(chain_id.as_u64()); - - // The `ecrecover` precompile does not use EIP-155 - let sig = wallet.sign_hash(digest)?; - let recovered = sig.recover(digest)?; - - assert_eq!(recovered, wallet.address()); - - let mut r_bytes = [0u8; 32]; - let mut s_bytes = [0u8; 32]; - sig.r.to_big_endian(&mut r_bytes); - sig.s.to_big_endian(&mut s_bytes); - - Ok((sig.v, r_bytes, s_bytes).encode().into()) -} - -/// Using a given private key, return its public ETH address, its public key affine x and y -/// coodinates, and its private key (see the 'Wallet' struct) -/// -/// If 'label' is set to 'Some()', assign that label to the associated ETH address in state -fn create_wallet(private_key: U256, label: Option, state: &mut Cheatcodes) -> Result { - let key = parse_private_key(private_key)?; - let addr = utils::secret_key_to_address(&key); - - let pub_key = key.verifying_key().as_affine().to_encoded_point(false); - let pub_key_x = pub_key.x().ok_or("No x coordinate was found")?; - let pub_key_y = pub_key.y().ok_or("No y coordinate was found")?; - - let pub_key_x = U256::from(pub_key_x.as_slice()); - let pub_key_y = U256::from(pub_key_y.as_slice()); - - if let Some(label) = label { - state.labels.insert(addr, label); - } - - Ok((addr, pub_key_x, pub_key_y, private_key).encode().into()) -} - -enum WordlistLang { - ChineseSimplified, - ChineseTraditional, - Czech, - English, - French, - Italian, - Japanese, - Korean, - Portuguese, - Spanish, -} - -impl FromStr for WordlistLang { - type Err = String; - - fn from_str(input: &str) -> Result { - match input { - "chinese_simplified" => Ok(Self::ChineseSimplified), - "chinese_traditional" => Ok(Self::ChineseTraditional), - "czech" => Ok(Self::Czech), - "english" => Ok(Self::English), - "french" => Ok(Self::French), - "italian" => Ok(Self::Italian), - "japanese" => Ok(Self::Japanese), - "korean" => Ok(Self::Korean), - "portuguese" => Ok(Self::Portuguese), - "spanish" => Ok(Self::Spanish), - _ => Err(format!("the language `{}` has no wordlist", input)), - } - } -} - -fn derive_key(mnemonic: &str, path: &str, index: u32) -> Result { - let derivation_path = - if path.ends_with('/') { format!("{path}{index}") } else { format!("{path}/{index}") }; - - let wallet = MnemonicBuilder::::default() - .phrase(mnemonic) - .derivation_path(&derivation_path)? - .build()?; - - let private_key = U256::from_big_endian(wallet.signer().to_bytes().as_slice()); - - Ok(private_key.encode().into()) -} - -fn derive_key_with_wordlist(mnemonic: &str, path: &str, index: u32, lang: &str) -> Result { - let lang = WordlistLang::from_str(lang)?; - match lang { - WordlistLang::ChineseSimplified => derive_key::(mnemonic, path, index), - WordlistLang::ChineseTraditional => derive_key::(mnemonic, path, index), - WordlistLang::Czech => derive_key::(mnemonic, path, index), - WordlistLang::English => derive_key::(mnemonic, path, index), - WordlistLang::French => derive_key::(mnemonic, path, index), - WordlistLang::Italian => derive_key::(mnemonic, path, index), - WordlistLang::Japanese => derive_key::(mnemonic, path, index), - WordlistLang::Korean => derive_key::(mnemonic, path, index), - WordlistLang::Portuguese => derive_key::(mnemonic, path, index), - WordlistLang::Spanish => derive_key::(mnemonic, path, index), - } -} - -fn remember_key(state: &mut Cheatcodes, private_key: U256, chain_id: U256) -> Result { - let key = parse_private_key(private_key)?; - let wallet = LocalWallet::from(key).with_chain_id(chain_id.as_u64()); - let address = wallet.address(); - - state.script_wallets.push(wallet); - - Ok(address.encode().into()) -} - -pub fn parse(s: &str, ty: &ParamType) -> Result { - parse_token(s, ty) - .map(|token| abi::encode(&[token]).into()) - .map_err(|e| fmt_err!("Failed to parse `{s}` as type `{ty}`: {e}")) -} - -pub fn skip(state: &mut Cheatcodes, depth: u64, skip: bool) -> Result { - if !skip { - return Ok(b"".into()) - } - - // Skip should not work if called deeper than at test level. - // As we're not returning the magic skip bytes, this will cause a test failure. - if depth > 1 { - return Err(Error::custom("The skip cheatcode can only be used at test level")) - } - - state.skip = true; - Err(Error::custom_bytes(MAGIC_SKIP_BYTES)) -} - -#[instrument(level = "error", name = "util", target = "evm::cheatcodes", skip_all)] -pub fn apply( - state: &mut Cheatcodes, - data: &mut EVMData<'_, DB>, - call: &HEVMCalls, -) -> Option { - Some(match call { - HEVMCalls::Addr(inner) => addr(inner.0), - // [function sign(uint256,bytes32)] Used to sign bytes32 digests using the given private key - HEVMCalls::Sign0(inner) => sign(inner.0, inner.1.into(), data.env.cfg.chain_id.into()), - // [function createWallet(string)] Used to derive private key and label the wallet with the - // same string - HEVMCalls::CreateWallet0(inner) => { - create_wallet(U256::from(keccak256(&inner.0)), Some(inner.0.clone()), state) - } - // [function createWallet(uint256)] creates a new wallet with the given private key - HEVMCalls::CreateWallet1(inner) => create_wallet(inner.0, None, state), - // [function createWallet(uint256,string)] creates a new wallet with the given private key - // and labels it with the given string - HEVMCalls::CreateWallet2(inner) => create_wallet(inner.0, Some(inner.1.clone()), state), - // [function sign(uint256,bytes32)] Used to sign bytes32 digests using the given Wallet's - // private key - HEVMCalls::Sign1(inner) => { - sign(inner.0.private_key, inner.1.into(), data.env.cfg.chain_id.into()) - } - HEVMCalls::DeriveKey0(inner) => { - derive_key::(&inner.0, DEFAULT_DERIVATION_PATH_PREFIX, inner.1) - } - HEVMCalls::DeriveKey1(inner) => derive_key::(&inner.0, &inner.1, inner.2), - HEVMCalls::DeriveKey2(inner) => { - derive_key_with_wordlist(&inner.0, DEFAULT_DERIVATION_PATH_PREFIX, inner.1, &inner.2) - } - HEVMCalls::DeriveKey3(inner) => { - derive_key_with_wordlist(&inner.0, &inner.1, inner.2, &inner.3) - } - HEVMCalls::RememberKey(inner) => remember_key(state, inner.0, data.env.cfg.chain_id.into()), - HEVMCalls::Label(inner) => { - state.labels.insert(inner.0, inner.1.clone()); - Ok(Default::default()) - } - HEVMCalls::GetLabel(inner) => { - let label = state - .labels - .get(&inner.0) - .cloned() - .unwrap_or_else(|| format!("unlabeled:{:?}", inner.0)); - Ok(ethers::abi::encode(&[Token::String(label)]).into()) - } - HEVMCalls::ToString0(inner) => { - Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into()) - } - HEVMCalls::ToString1(inner) => { - Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into()) - } - HEVMCalls::ToString2(inner) => { - Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into()) - } - HEVMCalls::ToString3(inner) => { - Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into()) - } - HEVMCalls::ToString4(inner) => { - Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into()) - } - HEVMCalls::ToString5(inner) => { - Ok(ethers::abi::encode(&[Token::String(inner.0.pretty())]).into()) - } - HEVMCalls::ParseBytes(inner) => parse(&inner.0, &ParamType::Bytes), - HEVMCalls::ParseAddress(inner) => parse(&inner.0, &ParamType::Address), - HEVMCalls::ParseUint(inner) => parse(&inner.0, &ParamType::Uint(256)), - HEVMCalls::ParseInt(inner) => parse(&inner.0, &ParamType::Int(256)), - HEVMCalls::ParseBytes32(inner) => parse(&inner.0, &ParamType::FixedBytes(32)), - HEVMCalls::ParseBool(inner) => parse(&inner.0, &ParamType::Bool), - HEVMCalls::Skip(inner) => skip(state, data.journaled_state.depth(), inner.0), - _ => return None, - }) -} - -pub fn process_create( - broadcast_sender: Address, - bytecode: Bytes, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, -) -> DatabaseResult<(Bytes, Option, u64)> -where - DB: Database, -{ - let broadcast_sender = h160_to_b160(broadcast_sender); - match call.scheme { - revm::primitives::CreateScheme::Create => { - call.caller = broadcast_sender; - - Ok((bytecode, None, data.journaled_state.account(broadcast_sender).info.nonce)) - } - revm::primitives::CreateScheme::Create2 { salt } => { - // Sanity checks for our CREATE2 deployer - data.journaled_state.load_account(h160_to_b160(DEFAULT_CREATE2_DEPLOYER), data.db)?; - - let info = &data.journaled_state.account(h160_to_b160(DEFAULT_CREATE2_DEPLOYER)).info; - match &info.code { - Some(code) => { - if code.is_empty() { - trace!(create2=?DEFAULT_CREATE2_DEPLOYER, "Empty Create 2 deployer code"); - return Err(DatabaseError::MissingCreate2Deployer) - } - } - None => { - // forked db - trace!(create2=?DEFAULT_CREATE2_DEPLOYER, "Missing Create 2 deployer code"); - if data.db.code_by_hash(info.code_hash)?.is_empty() { - return Err(DatabaseError::MissingCreate2Deployer) - } - } - } - - call.caller = h160_to_b160(DEFAULT_CREATE2_DEPLOYER); - - // We have to increment the nonce of the user address, since this create2 will be done - // by the create2_deployer - let account = data.journaled_state.state().get_mut(&broadcast_sender).unwrap(); - let nonce = account.info.nonce; - account.info.nonce += 1; - - // Proxy deployer requires the data to be on the following format `salt.init_code` - let mut calldata = BytesMut::with_capacity(32 + bytecode.len()); - let salt = ru256_to_u256(salt); - let mut salt_bytes = [0u8; 32]; - salt.to_big_endian(&mut salt_bytes); - calldata.put_slice(&salt_bytes); - calldata.put(bytecode); - - Ok((calldata.freeze(), Some(NameOrAddress::Address(DEFAULT_CREATE2_DEPLOYER)), nonce)) - } - } -} - -pub fn parse_array(values: I, ty: &ParamType) -> Result -where - I: IntoIterator, - T: AsRef, -{ - let mut values = values.into_iter(); - match values.next() { - Some(first) if !first.as_ref().is_empty() => { - let tokens = std::iter::once(first) - .chain(values) - .map(|v| parse_token(v.as_ref(), ty)) - .collect::, _>>()?; - Ok(abi::encode(&[Token::Array(tokens)]).into()) - } - // return the empty encoded Bytes when values is empty or the first element is empty - _ => Ok(abi::encode(&[Token::String(String::new())]).into()), - } -} - -fn parse_token(s: &str, ty: &ParamType) -> Result { - match ty { - ParamType::Bool => { - s.to_ascii_lowercase().parse().map(Token::Bool).map_err(|e| e.to_string()) - } - ParamType::Uint(256) => parse_uint(s).map(Token::Uint), - ParamType::Int(256) => parse_int(s).map(Token::Int), - ParamType::Address => s.parse().map(Token::Address).map_err(|e| e.to_string()), - ParamType::FixedBytes(32) => parse_bytes(s).map(Token::FixedBytes), - ParamType::Bytes => parse_bytes(s).map(Token::Bytes), - ParamType::String => Ok(Token::String(s.to_string())), - _ => Err("unsupported type".into()), - } -} - -fn parse_int(s: &str) -> Result { - // hex string may start with "0x", "+0x", or "-0x" which needs to be stripped for - // `I256::from_hex_str` - if s.starts_with("0x") || s.starts_with("+0x") || s.starts_with("-0x") { - s.replacen("0x", "", 1).parse::().map_err(|err| err.to_string()) - } else { - match I256::from_dec_str(s) { - Ok(val) => Ok(val), - Err(dec_err) => s.parse::().map_err(|hex_err| { - format!("could not parse value as decimal or hex: {dec_err}, {hex_err}") - }), - } - } - .map(|v| v.into_raw()) -} - -fn parse_uint(s: &str) -> Result { - if s.starts_with("0x") { - s.parse::().map_err(|err| err.to_string()) - } else { - match U256::from_dec_str(s) { - Ok(val) => Ok(val), - Err(dec_err) => s.parse::().map_err(|hex_err| { - format!("could not parse value as decimal or hex: {dec_err}, {hex_err}") - }), - } - } -} - -fn parse_bytes(s: &str) -> Result, String> { - hex::decode(s.strip_prefix("0x").unwrap_or(s)).map_err(|e| e.to_string()) -} - -pub fn parse_private_key(private_key: U256) -> Result { - ensure!(!private_key.is_zero(), "Private key cannot be 0."); - ensure!( - private_key < U256::from_big_endian(&Secp256k1::ORDER.to_be_bytes()), - "Private key must be less than the secp256k1 curve order \ - (115792089237316195423570985008687907852837564279074904382605163141518161494337).", - ); - let mut bytes: [u8; 32] = [0; 32]; - private_key.to_big_endian(&mut bytes); - SigningKey::from_bytes((&bytes).into()).map_err(Into::into) -} - -// Determines if the gas limit on a given call was manually set in the script and should therefore -// not be overwritten by later estimations -pub fn check_if_fixed_gas_limit( - data: &EVMData<'_, DB>, - call_gas_limit: u64, -) -> bool { - // If the gas limit was not set in the source code it is set to the estimated gas left at the - // time of the call, which should be rather close to configured gas limit. - // TODO: Find a way to reliably make this determination. (for example by - // generating it in the compilation or evm simulation process) - U256::from(data.env.tx.gas_limit) > data.env.block.gas_limit.into() && - U256::from(call_gas_limit) <= data.env.block.gas_limit.into() - // Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic - // gas too low" failure when simulated on chain - && call_gas_limit > 2300 -} - -/// Small utility function that checks if an address is a potential precompile. -pub fn is_potential_precompile(address: H160) -> bool { - address < H160::from_low_u64_be(10) && address != H160::zero() -} - -#[cfg(test)] -mod tests { - use super::*; - use ethers::abi::AbiDecode; - - #[test] - fn test_uint_env() { - let pk = "0x10532cc9d0d992825c3f709c62c969748e317a549634fb2a9fa949326022e81f"; - let val: U256 = pk.parse().unwrap(); - let parsed = parse(pk, &ParamType::Uint(256)).unwrap(); - let decoded = U256::decode(&parsed).unwrap(); - assert_eq!(val, decoded); - - let parsed = parse(pk.strip_prefix("0x").unwrap(), &ParamType::Uint(256)).unwrap(); - let decoded = U256::decode(&parsed).unwrap(); - assert_eq!(val, decoded); - - let parsed = parse("1337", &ParamType::Uint(256)).unwrap(); - let decoded = U256::decode(&parsed).unwrap(); - assert_eq!(U256::from(1337u64), decoded); - } - - #[test] - fn test_int_env() { - let val = U256::from(100u64); - let parsed = parse(&val.to_string(), &ParamType::Int(256)).unwrap(); - let decoded = I256::decode(parsed).unwrap(); - assert_eq!(val, decoded.try_into().unwrap()); - - let parsed = parse("100", &ParamType::Int(256)).unwrap(); - let decoded = I256::decode(parsed).unwrap(); - assert_eq!(U256::from(100u64), decoded.try_into().unwrap()); - } -} diff --git a/crates/evm/src/executor/inspector/chisel_state.rs b/crates/evm/src/executor/inspector/chisel_state.rs deleted file mode 100644 index e10720bba85a2..0000000000000 --- a/crates/evm/src/executor/inspector/chisel_state.rs +++ /dev/null @@ -1,39 +0,0 @@ -use revm::{ - interpreter::{InstructionResult, Interpreter, Memory, Stack}, - Database, Inspector, -}; - -/// An inspector for Chisel -#[derive(Default)] -pub struct ChiselState { - /// The PC of the final instruction - pub final_pc: usize, - /// The final state of the REPL contract call - pub state: Option<(Stack, Memory, InstructionResult)>, -} - -impl ChiselState { - pub fn new(final_pc: usize) -> Self { - Self { final_pc, state: None } - } -} - -impl Inspector for ChiselState -where - DB: Database, -{ - fn step_end( - &mut self, - interp: &mut Interpreter, - _: &mut revm::EVMData<'_, DB>, - _: bool, - eval: InstructionResult, - ) -> InstructionResult { - // If we are at the final pc of the REPL contract execution, set the state. - if self.final_pc == interp.program_counter() - 1 { - self.state = Some((interp.stack().clone(), interp.memory.clone(), eval)) - } - // Pass on [revm::Return] from arguments - eval - } -} diff --git a/crates/evm/src/executor/inspector/coverage.rs b/crates/evm/src/executor/inspector/coverage.rs deleted file mode 100644 index 67b949a8c9720..0000000000000 --- a/crates/evm/src/executor/inspector/coverage.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{ - coverage::{HitMap, HitMaps}, - utils::b256_to_h256, -}; -use bytes::Bytes; -use revm::{ - interpreter::{InstructionResult, Interpreter}, - Database, EVMData, Inspector, -}; - -#[derive(Default, Debug)] -pub struct CoverageCollector { - /// Maps that track instruction hit data. - pub maps: HitMaps, -} - -impl Inspector for CoverageCollector -where - DB: Database, -{ - fn initialize_interp( - &mut self, - interpreter: &mut Interpreter, - _: &mut EVMData<'_, DB>, - _: bool, - ) -> InstructionResult { - let hash = b256_to_h256(interpreter.contract.bytecode.hash()); - self.maps.entry(hash).or_insert_with(|| { - HitMap::new(Bytes::copy_from_slice( - interpreter.contract.bytecode.original_bytecode_slice(), - )) - }); - - InstructionResult::Continue - } - - fn step( - &mut self, - interpreter: &mut Interpreter, - _: &mut EVMData<'_, DB>, - _: bool, - ) -> InstructionResult { - let hash = b256_to_h256(interpreter.contract.bytecode.hash()); - self.maps.entry(hash).and_modify(|map| map.hit(interpreter.program_counter())); - - InstructionResult::Continue - } -} diff --git a/crates/evm/src/executor/inspector/debugger.rs b/crates/evm/src/executor/inspector/debugger.rs deleted file mode 100644 index 24fce86c2ca54..0000000000000 --- a/crates/evm/src/executor/inspector/debugger.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::{ - debug::{DebugArena, DebugNode, DebugStep, Instruction}, - executor::{ - backend::DatabaseExt, - inspector::utils::{gas_used, get_create_address}, - CHEATCODE_ADDRESS, - }, - utils::b160_to_h160, - CallKind, -}; -use bytes::Bytes; -use ethers::types::Address; -use foundry_utils::error::SolError; -use revm::{ - inspectors::GasInspector, - interpreter::{ - opcode::{self, spec_opcode_gas}, - CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, Memory, - }, - primitives::B160, - EVMData, Inspector, -}; -use std::{cell::RefCell, rc::Rc}; - -/// An inspector that collects debug nodes on every step of the interpreter. -#[derive(Debug)] -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, - - gas_inspector: Rc>, -} - -impl Debugger { - pub fn new(gas_inspector: Rc>) -> Self { - Self { - arena: Default::default(), - head: Default::default(), - context: Default::default(), - gas_inspector, - } - } - - /// 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.context = address; - self.head = - self.arena.push_node(DebugNode { depth, address, kind, ..Default::default() }); - } - } -} - -impl Inspector for Debugger -where - DB: DatabaseExt, -{ - fn step( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - _: bool, - ) -> InstructionResult { - let pc = interpreter.program_counter(); - let op = interpreter.contract.bytecode.bytecode()[pc]; - - // Get opcode information - let opcode_infos = spec_opcode_gas(data.env.cfg.spec_id); - let opcode_info = &opcode_infos[op as usize]; - - // Extract the push bytes - let push_size = if opcode_info.is_push() { (op - opcode::PUSH1 + 1) as usize } else { 0 }; - let push_bytes = match push_size { - 0 => None, - n => { - let start = pc + 1; - let end = start + n; - Some(interpreter.contract.bytecode.bytecode()[start..end].to_vec()) - } - }; - - let total_gas_used = gas_used( - data.env.cfg.spec_id, - interpreter.gas.limit().saturating_sub(self.gas_inspector.borrow().gas_remaining()), - interpreter.gas.refunded() as u64, - ); - - self.arena.arena[self.head].steps.push(DebugStep { - pc, - stack: interpreter.stack().data().iter().copied().map(|d| d.into()).collect(), - memory: interpreter.memory.clone(), - instruction: Instruction::OpCode(op), - push_bytes, - total_gas_used, - }); - - InstructionResult::Continue - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - self.enter( - data.journaled_state.depth() as usize, - b160_to_h160(call.context.code_address), - call.context.scheme.into(), - ); - if CHEATCODE_ADDRESS == b160_to_h160(call.contract) { - self.arena.arena[self.head].steps.push(DebugStep { - memory: Memory::new(), - instruction: Instruction::Cheatcode( - call.input[0..4].try_into().expect("malformed cheatcode call"), - ), - ..Default::default() - }); - } - - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - gas: Gas, - status: InstructionResult, - retdata: Bytes, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - self.exit(); - - (status, gas, retdata) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option, Gas, Bytes) { - // TODO: Does this increase gas cost? - if let Err(err) = data.journaled_state.load_account(call.caller, data.db) { - let gas = Gas::new(call.gas_limit); - return (InstructionResult::Revert, None, gas, err.encode_string().0) - } - - let nonce = data.journaled_state.account(call.caller).info.nonce; - self.enter( - data.journaled_state.depth() as usize, - get_create_address(call, nonce), - CallKind::Create, - ); - - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) - } - - fn create_end( - &mut self, - _: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: InstructionResult, - address: Option, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option, Gas, Bytes) { - self.exit(); - - (status, address, gas, retdata) - } -} diff --git a/crates/evm/src/executor/inspector/fuzzer.rs b/crates/evm/src/executor/inspector/fuzzer.rs deleted file mode 100644 index 021ac4f35f6a9..0000000000000 --- a/crates/evm/src/executor/inspector/fuzzer.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::{ - fuzz::{invariant::RandomCallGenerator, strategies::EvmFuzzState}, - utils::{self, b160_to_h160, h160_to_b160}, -}; -use bytes::Bytes; -use revm::{ - interpreter::{CallInputs, CallScheme, Gas, InstructionResult, Interpreter}, - Database, EVMData, Inspector, -}; - -/// An inspector that can fuzz and collect data for that effect. -#[derive(Clone, Debug)] -pub struct Fuzzer { - /// Given a strategy, it generates a random call. - pub call_generator: Option, - /// If set, it collects `stack` and `memory` values for fuzzing purposes. - pub collect: bool, - /// If `collect` is set, we store the collected values in this fuzz dictionary. - pub fuzz_state: EvmFuzzState, -} - -impl Inspector for Fuzzer -where - DB: Database, -{ - fn step( - &mut self, - interpreter: &mut Interpreter, - _: &mut EVMData<'_, DB>, - _: bool, - ) -> InstructionResult { - // We only collect `stack` and `memory` data before and after calls. - if self.collect { - self.collect_data(interpreter); - self.collect = false; - } - InstructionResult::Continue - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - // We don't want to override the very first call made to the test contract. - if self.call_generator.is_some() && data.env.tx.caller != call.context.caller { - self.override_call(call); - } - - // We only collect `stack` and `memory` data before and after calls. - // this will be turned off on the next `step` - self.collect = true; - - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - if let Some(ref mut call_generator) = self.call_generator { - call_generator.used = false; - } - - // We only collect `stack` and `memory` data before and after calls. - // this will be turned off on the next `step` - self.collect = true; - - (status, remaining_gas, retdata) - } -} - -impl Fuzzer { - /// Collects `stack` and `memory` values into the fuzz dictionary. - fn collect_data(&mut self, interpreter: &Interpreter) { - let mut state = self.fuzz_state.write(); - - for slot in interpreter.stack().data() { - state.values_mut().insert(utils::u256_to_h256_be(utils::ru256_to_u256(*slot)).into()); - } - - // TODO: disabled for now since it's flooding the dictionary - // for index in 0..interpreter.memory.len() / 32 { - // let mut slot = [0u8; 32]; - // slot.clone_from_slice(interpreter.memory.get_slice(index * 32, 32)); - - // state.insert(slot); - // } - } - - /// Overrides an external call and tries to call any method of msg.sender. - fn override_call(&mut self, call: &mut CallInputs) { - if let Some(ref mut call_generator) = self.call_generator { - // We only override external calls which are not coming from the test contract. - if call.context.caller != h160_to_b160(call_generator.test_address) && - call.context.scheme == CallScheme::Call && - !call_generator.used - { - // There's only a 30% chance that an override happens. - if let Some((sender, (contract, input))) = call_generator - .next(b160_to_h160(call.context.caller), b160_to_h160(call.contract)) - { - call.input = input.0; - call.context.caller = h160_to_b160(sender); - call.contract = h160_to_b160(contract); - - // TODO: in what scenarios can the following be problematic - call.context.code_address = h160_to_b160(contract); - call.context.address = h160_to_b160(contract); - - call_generator.used = true; - } - } - } - } -} diff --git a/crates/evm/src/executor/inspector/logs.rs b/crates/evm/src/executor/inspector/logs.rs deleted file mode 100644 index 5c92d2a355a53..0000000000000 --- a/crates/evm/src/executor/inspector/logs.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::{ - executor::{patch_hardhat_console_selector, HardhatConsoleCalls, HARDHAT_CONSOLE_ADDRESS}, - utils::{b160_to_h160, b256_to_h256, h160_to_b160}, -}; -use bytes::Bytes; -use ethers::{ - abi::{AbiDecode, Token}, - types::{Log, H256}, -}; -use foundry_macros::ConsoleFmt; -use revm::{ - interpreter::{CallInputs, Gas, InstructionResult}, - primitives::{B160, B256}, - Database, EVMData, Inspector, -}; - -/// An inspector that collects logs during execution. -/// -/// The inspector collects logs from the LOG opcodes as well as Hardhat-style logs. -#[derive(Debug, Clone, Default)] -pub struct LogCollector { - pub logs: Vec, -} - -impl LogCollector { - fn hardhat_log(&mut self, mut input: Vec) -> (InstructionResult, Bytes) { - // Patch the Hardhat-style selectors - patch_hardhat_console_selector(&mut input); - let decoded = match HardhatConsoleCalls::decode(input) { - Ok(inner) => inner, - Err(err) => { - return ( - InstructionResult::Revert, - ethers::abi::encode(&[Token::String(err.to_string())]).into(), - ) - } - }; - - // Convert it to a DS-style `emit log(string)` event - self.logs.push(convert_hh_log_to_event(decoded)); - - (InstructionResult::Continue, Bytes::new()) - } -} - -impl Inspector for LogCollector -where - DB: Database, -{ - fn log(&mut self, _: &mut EVMData<'_, DB>, address: &B160, topics: &[B256], data: &Bytes) { - self.logs.push(Log { - address: b160_to_h160(*address), - topics: topics.iter().copied().map(b256_to_h256).collect(), - data: data.clone().into(), - ..Default::default() - }); - } - - fn call( - &mut self, - _: &mut EVMData<'_, DB>, - call: &mut CallInputs, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - if call.contract == h160_to_b160(HARDHAT_CONSOLE_ADDRESS) { - let (status, reason) = self.hardhat_log(call.input.to_vec()); - (status, Gas::new(call.gas_limit), reason) - } else { - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) - } - } -} - -/// Topic 0 of DSTest's `log(string)`. -/// -/// `0x41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50` -const TOPIC: H256 = H256([ - 0x41, 0x30, 0x4f, 0xac, 0xd9, 0x32, 0x3d, 0x75, 0xb1, 0x1b, 0xcd, 0xd6, 0x09, 0xcb, 0x38, 0xef, - 0xff, 0xfd, 0xb0, 0x57, 0x10, 0xf7, 0xca, 0xf0, 0xe9, 0xb1, 0x6c, 0x6d, 0x9d, 0x70, 0x9f, 0x50, -]); - -/// Converts a call to Hardhat's `console.log` to a DSTest `log(string)` event. -fn convert_hh_log_to_event(call: HardhatConsoleCalls) -> Log { - // Convert the parameters of the call to their string representation using `ConsoleFmt`. - let fmt = call.fmt(Default::default()); - let token = Token::String(fmt); - let data = ethers::abi::encode(&[token]).into(); - Log { topics: vec![TOPIC], data, ..Default::default() } -} diff --git a/crates/evm/src/executor/inspector/mod.rs b/crates/evm/src/executor/inspector/mod.rs deleted file mode 100644 index f4e5dd8c5acb5..0000000000000 --- a/crates/evm/src/executor/inspector/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -#[macro_use] -mod utils; - -mod logs; - -pub use logs::LogCollector; -use std::{cell::RefCell, rc::Rc}; - -mod access_list; -pub use access_list::AccessListTracer; - -mod tracer; -pub use tracer::Tracer; - -mod debugger; -pub use debugger::Debugger; - -mod coverage; -pub use coverage::CoverageCollector; - -mod stack; -pub use stack::{InspectorData, InspectorStack}; - -pub mod cheatcodes; -pub use cheatcodes::{Cheatcodes, CheatsConfig, DEFAULT_CREATE2_DEPLOYER}; - -mod chisel_state; -pub use chisel_state::ChiselState; - -use ethers::types::U256; - -use revm::{inspectors::GasInspector, primitives::BlockEnv}; - -mod fuzzer; -pub use fuzzer::Fuzzer; - -mod printer; -pub use printer::TracePrinter; - -#[derive(Default, Clone, Debug)] -pub struct InspectorStackConfig { - /// The cheatcode inspector and its state, if cheatcodes are enabled. - /// Whether cheatcodes are enabled - pub cheatcodes: Option, - /// The block environment - /// - /// Used in the cheatcode handler to overwrite the block environment separately from the - /// execution block environment. - pub block: BlockEnv, - /// The gas price - /// - /// Used in the cheatcode handler to overwrite the gas price separately from the gas price - /// in the execution environment. - pub gas_price: U256, - /// Whether tracing is enabled - pub tracing: bool, - /// Whether the debugger is enabled - pub debugger: bool, - /// The fuzzer inspector and its state, if it exists. - pub fuzzer: Option, - /// Whether coverage info should be collected - pub coverage: bool, - /// Should we print all opcode traces into console. Useful for debugging of EVM. - pub trace_printer: bool, - /// The chisel state inspector. - /// - /// If the inspector is enabled, Some(final_pc) - /// If not, None - pub chisel_state: Option, -} - -impl InspectorStackConfig { - /// Returns the stack of inspectors to use when transacting/committing on the EVM - /// - /// See also [`revm::Evm::inspect_ref`] and [`revm::Evm::commit_ref`] - pub fn stack(&self) -> InspectorStack { - let mut stack = - InspectorStack { log_collector: Some(LogCollector::default()), ..Default::default() }; - - stack.cheatcodes = self.create_cheatcodes(); - if let Some(ref mut cheatcodes) = stack.cheatcodes { - cheatcodes.block = Some(self.block.clone()); - cheatcodes.gas_price = Some(self.gas_price); - } - - if self.tracing { - stack.tracer = Some(Tracer::default()); - } - if self.debugger { - let gas_inspector = Rc::new(RefCell::new(GasInspector::default())); - stack.gas = Some(gas_inspector.clone()); - stack.debugger = Some(Debugger::new(gas_inspector)); - } - stack.fuzzer = self.fuzzer.clone(); - - if self.coverage { - stack.coverage = Some(CoverageCollector::default()); - } - - if self.trace_printer { - stack.printer = Some(TracePrinter::default()); - } - - if let Some(final_pc) = self.chisel_state { - stack.chisel_state = Some(ChiselState::new(final_pc)); - } - stack - } - - /// Configures the cheatcode inspector with a new and empty context - /// - /// Returns `None` if no cheatcodes inspector is set - fn create_cheatcodes(&self) -> Option { - let cheatcodes = self.cheatcodes.clone(); - - cheatcodes.map(|cheatcodes| Cheatcodes { context: Default::default(), ..cheatcodes }) - } -} diff --git a/crates/evm/src/executor/inspector/printer.rs b/crates/evm/src/executor/inspector/printer.rs deleted file mode 100644 index b9a1abc7f19d6..0000000000000 --- a/crates/evm/src/executor/inspector/printer.rs +++ /dev/null @@ -1,127 +0,0 @@ -use bytes::Bytes; -use revm::{ - inspectors::GasInspector, - interpreter::{opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::B160, - Database, EVMData, Inspector, -}; - -#[derive(Clone, Default)] -pub struct TracePrinter { - gas_inspector: GasInspector, -} - -impl Inspector for TracePrinter { - fn initialize_interp( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> InstructionResult { - self.gas_inspector.initialize_interp(interp, data, is_static); - InstructionResult::Continue - } - - // get opcode by calling `interp.contract.opcode(interp.program_counter())`. - // all other information can be obtained from interp. - fn step( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> InstructionResult { - let opcode = interp.current_opcode(); - let opcode_str = opcode::OPCODE_JUMPMAP[opcode as usize]; - - let gas_remaining = self.gas_inspector.gas_remaining(); - - println!( - "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data size:{}, Data: 0x{}", - data.journaled_state.depth(), - interp.program_counter(), - gas_remaining, - gas_remaining, - opcode_str.unwrap(), - opcode, - interp.gas.refunded(), - interp.gas.refunded(), - interp.stack.data(), - interp.memory.data().len(), - hex::encode(interp.memory.data()), - ); - - self.gas_inspector.step(interp, data, is_static); - - InstructionResult::Continue - } - - fn step_end( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - eval: InstructionResult, - ) -> InstructionResult { - self.gas_inspector.step_end(interp, data, is_static, eval); - InstructionResult::Continue - } - - fn call( - &mut self, - _data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { - println!( - "SM CALL: {:?},context:{:?}, is_static:{:?}, transfer:{:?}, input_size:{:?}", - inputs.contract, - inputs.context, - is_static, - inputs.transfer, - inputs.input.len(), - ); - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { - self.gas_inspector.call_end(data, inputs, remaining_gas, ret, out.clone(), is_static); - (ret, remaining_gas, out) - } - - fn create( - &mut self, - _data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option, Gas, Bytes) { - println!( - "CREATE CALL: caller:{:?}, scheme:{:?}, value:{:?}, init_code:{:?}, gas:{:?}", - inputs.caller, - inputs.scheme, - inputs.value, - hex::encode(&inputs.init_code), - inputs.gas_limit - ); - (InstructionResult::Continue, None, Gas::new(0), Bytes::new()) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &CreateInputs, - ret: InstructionResult, - address: Option, - remaining_gas: Gas, - out: Bytes, - ) -> (InstructionResult, Option, Gas, Bytes) { - self.gas_inspector.create_end(data, inputs, ret, address, remaining_gas, out.clone()); - (ret, address, remaining_gas, out) - } -} diff --git a/crates/evm/src/executor/inspector/stack.rs b/crates/evm/src/executor/inspector/stack.rs deleted file mode 100644 index 2a3763ce53924..0000000000000 --- a/crates/evm/src/executor/inspector/stack.rs +++ /dev/null @@ -1,383 +0,0 @@ -use super::{Cheatcodes, ChiselState, Debugger, Fuzzer, LogCollector, TracePrinter, Tracer}; -use crate::{ - coverage::HitMaps, - debug::DebugArena, - executor::{backend::DatabaseExt, inspector::CoverageCollector}, - trace::CallTraceArena, -}; -use bytes::Bytes; -use ethers::{ - signers::LocalWallet, - types::{Address, Log}, -}; -use revm::{ - inspectors::GasInspector, - interpreter::{ - return_revert, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, Memory, Stack, - }, - primitives::{B160, B256}, - EVMData, Inspector, -}; -use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; - -/// Helper macro to call the same method on multiple inspectors without resorting to dynamic -/// dispatch -#[macro_export] -macro_rules! call_inspectors { - ($id:ident, [ $($inspector:expr),+ ], $call:block) => { - $({ - if let Some($id) = $inspector { - $call; - } - })+ - } -} - -pub struct InspectorData { - pub logs: Vec, - pub labels: BTreeMap, - pub traces: Option, - pub debug: Option, - pub coverage: Option, - pub gas: Option, - pub cheatcodes: Option, - pub script_wallets: Vec, - pub chisel_state: Option<(Stack, Memory, InstructionResult)>, -} - -/// An inspector that calls multiple inspectors in sequence. -/// -/// If a call to an inspector returns a value other than [InstructionResult::Continue] (or -/// equivalent) the remaining inspectors are not called. -#[derive(Default)] -pub struct InspectorStack { - pub tracer: Option, - pub log_collector: Option, - pub cheatcodes: Option, - pub gas: Option>>, - pub debugger: Option, - pub fuzzer: Option, - pub coverage: Option, - pub printer: Option, - pub chisel_state: Option, -} - -impl InspectorStack { - pub fn collect_inspector_states(self) -> InspectorData { - InspectorData { - logs: self.log_collector.map(|logs| logs.logs).unwrap_or_default(), - labels: self - .cheatcodes - .as_ref() - .map(|cheatcodes| cheatcodes.labels.clone()) - .unwrap_or_default(), - traces: self.tracer.map(|tracer| tracer.traces), - debug: self.debugger.map(|debugger| debugger.arena), - coverage: self.coverage.map(|coverage| coverage.maps), - gas: self.gas.map(|gas| gas.borrow().gas_remaining()), - script_wallets: self - .cheatcodes - .as_ref() - .map(|cheatcodes| cheatcodes.script_wallets.clone()) - .unwrap_or_default(), - cheatcodes: self.cheatcodes, - chisel_state: self.chisel_state.unwrap_or_default().state, - } - } - - fn do_call_end( - &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - { - let (new_status, new_gas, new_retdata) = inspector.call_end( - data, - call, - remaining_gas, - status, - retdata.clone(), - is_static, - ); - - // If the inspector returns a different status or a revert with a non-empty message, - // we assume it wants to tell us something - if new_status != status || - (new_status == InstructionResult::Revert && new_retdata != retdata) - { - return (new_status, new_gas, new_retdata) - } - } - ); - - (status, remaining_gas, retdata) - } -} - -impl Inspector for InspectorStack -where - DB: DatabaseExt, -{ - fn initialize_interp( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> InstructionResult { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.debugger, - &mut self.coverage, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - { - let status = inspector.initialize_interp(interpreter, data, is_static); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return status - } - } - ); - - InstructionResult::Continue - } - - fn step( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - ) -> InstructionResult { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - { - let status = inspector.step(interpreter, data, is_static); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return status - } - } - ); - - InstructionResult::Continue - } - - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &B160, - topics: &[B256], - data: &Bytes, - ) { - call_inspectors!( - inspector, - [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - { - inspector.log(evm_data, address, topics, data); - } - ); - } - - fn step_end( - &mut self, - interpreter: &mut Interpreter, - data: &mut EVMData<'_, DB>, - is_static: bool, - status: InstructionResult, - ) -> InstructionResult { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer, - &mut self.chisel_state - ], - { - let status = inspector.step_end(interpreter, data, is_static, status); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return status - } - } - ); - - InstructionResult::Continue - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.fuzzer, - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - { - let (status, gas, retdata) = inspector.call(data, call, is_static); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return (status, gas, retdata) - } - } - ); - - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - is_static: bool, - ) -> (InstructionResult, Gas, Bytes) { - let res = self.do_call_end(data, call, remaining_gas, status, retdata, is_static); - - if matches!(res.0, return_revert!()) { - // Encountered a revert, since cheatcodes may have altered the evm state in such a way - // that violates some constraints, e.g. `deal`, we need to manually roll back on revert - // before revm reverts the state itself - if let Some(cheats) = self.cheatcodes.as_mut() { - cheats.on_revert(data); - } - } - - res - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - { - let (status, addr, gas, retdata) = inspector.create(data, call); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - return (status, addr, gas, retdata) - } - } - ); - - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - call: &CreateInputs, - status: InstructionResult, - address: Option, - remaining_gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option, Gas, Bytes) { - call_inspectors!( - inspector, - [ - &mut self.gas.as_deref().map(|gas| gas.borrow_mut()), - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - { - let (new_status, new_address, new_gas, new_retdata) = inspector.create_end( - data, - call, - status, - address, - remaining_gas, - retdata.clone(), - ); - - if new_status != status { - return (new_status, new_address, new_gas, new_retdata) - } - } - ); - - (status, address, remaining_gas, retdata) - } - - fn selfdestruct(&mut self, contract: B160, target: B160) { - call_inspectors!( - inspector, - [ - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer, - &mut self.chisel_state - ], - { - Inspector::::selfdestruct(inspector, contract, target); - } - ); - } -} diff --git a/crates/evm/src/executor/inspector/tracer.rs b/crates/evm/src/executor/inspector/tracer.rs deleted file mode 100644 index 0b4edf0482712..0000000000000 --- a/crates/evm/src/executor/inspector/tracer.rs +++ /dev/null @@ -1,295 +0,0 @@ -use crate::{ - debug::Instruction::OpCode, - executor::inspector::utils::{gas_used, get_create_address}, - trace::{ - CallTrace, CallTraceArena, CallTraceStep, LogCallOrder, RawOrDecodedCall, RawOrDecodedLog, - RawOrDecodedReturnData, - }, - utils::{b160_to_h160, b256_to_h256, ru256_to_u256}, - CallKind, -}; -use bytes::Bytes; -use ethers::{ - abi::RawLog, - types::{Address, U256}, -}; -use revm::{ - inspectors::GasInspector, - interpreter::{ - opcode, return_ok, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, - Interpreter, - }, - primitives::{B160, B256}, - Database, EVMData, Inspector, JournalEntry, -}; -use std::{cell::RefCell, rc::Rc}; - -/// An inspector that collects call traces. -#[derive(Default, Debug, Clone)] -pub struct Tracer { - record_steps: bool, - - pub traces: CallTraceArena, - trace_stack: Vec, - step_stack: Vec<(usize, usize)>, // (trace_idx, step_idx) - - gas_inspector: Rc>, -} - -impl Tracer { - /// Enables step recording and uses [revm::GasInspector] to report gas costs for each step. - /// - /// Gas Inspector should be called externally **before** [Tracer], this is why we need it as - /// `Rc>` here. - pub fn with_steps_recording(mut self, gas_inspector: Rc>) -> Self { - self.record_steps = true; - self.gas_inspector = gas_inspector; - self - } - - fn start_trace( - &mut self, - depth: usize, - address: Address, - data: Vec, - value: U256, - kind: CallKind, - caller: Address, - ) { - self.trace_stack.push(self.traces.push_trace( - 0, - CallTrace { - depth, - address, - kind, - data: RawOrDecodedCall::Raw(data.into()), - value, - status: InstructionResult::Continue, - caller, - ..Default::default() - }, - )); - } - - fn fill_trace( - &mut self, - status: InstructionResult, - cost: u64, - output: Vec, - address: Option
, - ) { - let success = matches!(status, return_ok!()); - let trace = &mut self.traces.arena - [self.trace_stack.pop().expect("more traces were filled than started")] - .trace; - trace.status = status; - trace.success = success; - trace.gas_cost = cost; - trace.output = RawOrDecodedReturnData::Raw(output.into()); - - if let Some(address) = address { - trace.address = address; - } - } - - fn start_step(&mut self, interp: &Interpreter, data: &EVMData<'_, DB>) { - let trace_idx = - *self.trace_stack.last().expect("can't start step without starting a trace first"); - let trace = &mut self.traces.arena[trace_idx]; - - self.step_stack.push((trace_idx, trace.trace.steps.len())); - - let pc = interp.program_counter(); - - trace.trace.steps.push(CallTraceStep { - depth: data.journaled_state.depth(), - pc, - op: OpCode(interp.contract.bytecode.bytecode()[pc]), - contract: b160_to_h160(interp.contract.address), - stack: interp.stack.clone(), - memory: interp.memory.clone(), - gas: self.gas_inspector.borrow().gas_remaining(), - gas_refund_counter: interp.gas.refunded() as u64, - gas_cost: 0, - state_diff: None, - error: None, - }); - } - - fn fill_step( - &mut self, - interp: &Interpreter, - data: &EVMData<'_, DB>, - status: InstructionResult, - ) { - let (trace_idx, step_idx) = - self.step_stack.pop().expect("can't fill step without starting a step first"); - let step = &mut self.traces.arena[trace_idx].trace.steps[step_idx]; - - if let Some(pc) = interp.program_counter().checked_sub(1) { - let op = interp.contract.bytecode.bytecode()[pc]; - - let journal_entry = data - .journaled_state - .journal - .last() - // This should always work because revm initializes it as `vec![vec![]]` - .unwrap() - .last(); - - step.state_diff = match (op, journal_entry) { - ( - opcode::SLOAD | opcode::SSTORE, - Some(JournalEntry::StorageChange { address, key, .. }), - ) => { - let value = data.journaled_state.state[address].storage[key].present_value(); - Some((ru256_to_u256(*key), value.into())) - } - _ => None, - }; - - step.gas_cost = step.gas - self.gas_inspector.borrow().gas_remaining(); - } - - // Error codes only - if status as u8 > InstructionResult::OutOfGas as u8 { - step.error = Some(format!("{status:?}")); - } - } -} - -impl Inspector for Tracer -where - DB: Database, -{ - fn step( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - _is_static: bool, - ) -> InstructionResult { - if !self.record_steps { - return InstructionResult::Continue - } - - self.start_step(interp, data); - - InstructionResult::Continue - } - - fn log(&mut self, _: &mut EVMData<'_, DB>, _: &B160, topics: &[B256], data: &Bytes) { - let node = &mut self.traces.arena[*self.trace_stack.last().expect("no ongoing trace")]; - let topics: Vec<_> = topics.iter().copied().map(b256_to_h256).collect(); - node.ordering.push(LogCallOrder::Log(node.logs.len())); - node.logs.push(RawOrDecodedLog::Raw(RawLog { topics, data: data.to_vec() })); - } - - fn step_end( - &mut self, - interp: &mut Interpreter, - data: &mut EVMData<'_, DB>, - _: bool, - status: InstructionResult, - ) -> InstructionResult { - if !self.record_steps { - return InstructionResult::Continue - } - - self.fill_step(interp, data, status); - - status - } - - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - let (from, to) = match inputs.context.scheme { - CallScheme::DelegateCall | CallScheme::CallCode => { - (inputs.context.address, inputs.context.code_address) - } - _ => (inputs.context.caller, inputs.context.address), - }; - - self.start_trace( - data.journaled_state.depth() as usize, - b160_to_h160(to), - inputs.input.to_vec(), - inputs.transfer.value.into(), - inputs.context.scheme.into(), - b160_to_h160(from), - ); - - (InstructionResult::Continue, Gas::new(inputs.gas_limit), Bytes::new()) - } - - fn call_end( - &mut self, - data: &mut EVMData<'_, DB>, - _inputs: &CallInputs, - gas: Gas, - status: InstructionResult, - retdata: Bytes, - _: bool, - ) -> (InstructionResult, Gas, Bytes) { - self.fill_trace( - status, - gas_used(data.env.cfg.spec_id, gas.spend(), gas.refunded() as u64), - retdata.to_vec(), - None, - ); - - (status, gas, retdata) - } - - fn create( - &mut self, - data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option, Gas, Bytes) { - // TODO: Does this increase gas cost? - let _ = data.journaled_state.load_account(inputs.caller, data.db); - let nonce = data.journaled_state.account(inputs.caller).info.nonce; - self.start_trace( - data.journaled_state.depth() as usize, - get_create_address(inputs, nonce), - inputs.init_code.to_vec(), - inputs.value.into(), - inputs.scheme.into(), - b160_to_h160(inputs.caller), - ); - - (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::new()) - } - - fn create_end( - &mut self, - data: &mut EVMData<'_, DB>, - _inputs: &CreateInputs, - status: InstructionResult, - address: Option, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option, Gas, Bytes) { - let code = match address { - Some(address) => data - .journaled_state - .account(address) - .info - .code - .as_ref() - .map_or(vec![], |code| code.bytes()[..code.len()].to_vec()), - None => vec![], - }; - self.fill_trace( - status, - gas_used(data.env.cfg.spec_id, gas.spend(), gas.refunded() as u64), - code, - address.map(b160_to_h160), - ); - - (status, address, gas, retdata) - } -} diff --git a/crates/evm/src/executor/inspector/utils.rs b/crates/evm/src/executor/inspector/utils.rs deleted file mode 100644 index 22fecc6b7b05d..0000000000000 --- a/crates/evm/src/executor/inspector/utils.rs +++ /dev/null @@ -1,42 +0,0 @@ -use ethers::{ - types::Address, - utils::{get_contract_address, get_create2_address}, -}; -use revm::{ - interpreter::CreateInputs, - primitives::{CreateScheme, SpecId}, -}; - -use crate::utils::{b160_to_h160, ru256_to_u256}; - -/// Returns [InstructionResult::Continue] on an error, discarding the error. -/// -/// Useful for inspectors that read state that might be invalid, but do not want to emit -/// appropriate errors themselves, instead opting to continue. -macro_rules! try_or_continue { - ($e:expr) => { - match $e { - Ok(v) => v, - Err(_) => return InstructionResult::Continue, - } - }; -} - -/// Get the address of a contract creation -pub fn get_create_address(call: &CreateInputs, nonce: u64) -> Address { - match call.scheme { - CreateScheme::Create => get_contract_address(b160_to_h160(call.caller), nonce), - CreateScheme::Create2 { salt } => { - let salt = ru256_to_u256(salt); - let mut salt_bytes = [0u8; 32]; - salt.to_big_endian(&mut salt_bytes); - get_create2_address(b160_to_h160(call.caller), salt_bytes, call.init_code.clone()) - } - } -} - -/// Get the gas used, accounting for refunds -pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { - let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 }; - spent - (refunded).min(spent / refund_quotient) -} diff --git a/crates/evm/src/executor/mod.rs b/crates/evm/src/executor/mod.rs deleted file mode 100644 index 3de78fbc868a3..0000000000000 --- a/crates/evm/src/executor/mod.rs +++ /dev/null @@ -1,926 +0,0 @@ -use self::inspector::{ - cheatcodes::util::BroadcastableTransactions, Cheatcodes, InspectorData, InspectorStackConfig, -}; -use crate::{ - debug::DebugArena, - decode, - trace::CallTraceArena, - utils::{b160_to_h160, eval_to_instruction_result, h160_to_b160, halt_to_instruction_result}, - CALLER, -}; -pub use abi::{ - patch_hardhat_console_selector, HardhatConsoleCalls, CHEATCODE_ADDRESS, CONSOLE_ABI, - HARDHAT_CONSOLE_ABI, HARDHAT_CONSOLE_ADDRESS, -}; -use backend::FuzzBackendWrapper; -use bytes::Bytes; -use ethers::{ - abi::{Abi, Contract, Detokenize, Function, Tokenize}, - prelude::{decode_function_data, encode_function_data, Address, U256}, - signers::LocalWallet, - types::Log, -}; -use foundry_common::{abi::IntoFunction, evm::Breakpoints}; -use revm::primitives::hex_literal::hex; -/// Reexport commonly used revm types -pub use revm::primitives::{Env, SpecId}; -pub use revm::{ - db::{DatabaseCommit, DatabaseRef}, - interpreter::{return_ok, CreateScheme, InstructionResult, Memory, Stack}, - primitives::{ - Account, BlockEnv, Bytecode, ExecutionResult, HashMap, Output, ResultAndState, TransactTo, - TxEnv, B160, U256 as rU256, - }, -}; -use std::collections::BTreeMap; - -/// ABIs used internally in the executor -pub mod abi; -/// custom revm database implementations -pub mod backend; -pub use backend::Backend; -/// Executor builder -pub mod builder; -/// Forking provider -pub mod fork; -/// Executor inspectors -pub mod inspector; -/// Executor configuration -pub mod opts; -pub mod snapshot; - -use crate::{ - coverage::HitMaps, - executor::{ - backend::{ - error::{DatabaseError, DatabaseResult}, - DatabaseExt, - }, - inspector::{InspectorStack, DEFAULT_CREATE2_DEPLOYER}, - }, -}; -pub use builder::ExecutorBuilder; - -/// A mapping of addresses to their changed state. -pub type StateChangeset = HashMap; - -pub const DEFAULT_CREATE2_DEPLOYER_CODE: &[u8] = &hex!("604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"); - -/// A type that can execute calls -/// -/// The executor can be configured with various `revm::Inspector`s, like `Cheatcodes`. -/// -/// There are two ways of executing calls: -/// - `committing`: any state changes made during the call are recorded and are persisting -/// - `raw`: state changes only exist for the duration of the call and are discarded afterwards, in -/// other words: the state of the underlying database remains unchanged. -#[derive(Debug, Clone)] -pub struct Executor { - /// The underlying `revm::Database` that contains the EVM storage - // Note: We do not store an EVM here, since we are really - // only interested in the database. REVM's `EVM` is a thin - // wrapper around spawning a new EVM on every call anyway, - // so the performance difference should be negligible. - backend: Backend, - env: Env, - inspector_config: InspectorStackConfig, - /// 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, -} - -// === impl Executor === - -impl Executor { - pub fn new( - mut backend: Backend, - env: Env, - inspector_config: InspectorStackConfig, - gas_limit: U256, - ) -> Self { - // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks - // does not fail - backend.insert_account_info( - CHEATCODE_ADDRESS, - revm::primitives::AccountInfo { - code: Some(Bytecode::new_raw(vec![0u8].into()).to_checked()), - ..Default::default() - }, - ); - - Executor { backend, env, inspector_config, gas_limit } - } - - /// Returns a reference to the Env - pub fn env(&self) -> &Env { - &self.env - } - - /// Returns a mutable reference to the Env - pub fn env_mut(&mut self) -> &mut Env { - &mut self.env - } - - /// Returns a mutable reference to the Backend - pub fn backend_mut(&mut self) -> &mut Backend { - &mut self.backend - } - - pub fn backend(&self) -> &Backend { - &self.backend - } - - /// Returns an immutable reference to the InspectorStackConfig - pub fn inspector_config(&self) -> &InspectorStackConfig { - &self.inspector_config - } - - /// Returns a mutable reference to the InspectorStackConfig - pub fn inspector_config_mut(&mut self) -> &mut InspectorStackConfig { - &mut self.inspector_config - } - - /// 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_mut() - .basic(h160_to_b160(DEFAULT_CREATE2_DEPLOYER))? - .ok_or(DatabaseError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?; - - // 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(); - - // 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"); - - self.set_balance(creator, initial_balance)?; - } - Ok(()) - } - - /// Set the balance of an account. - pub fn set_balance(&mut self, address: Address, amount: U256) -> DatabaseResult<&mut Self> { - trace!(?address, ?amount, "setting account balance"); - let mut account = self.backend_mut().basic(h160_to_b160(address))?.unwrap_or_default(); - account.balance = amount.into(); - - self.backend_mut().insert_account_info(address, account); - Ok(self) - } - - /// Gets the balance of an account - pub fn get_balance(&self, address: Address) -> DatabaseResult { - Ok(self - .backend() - .basic(h160_to_b160(address))? - .map(|acc| acc.balance.into()) - .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_mut().basic(h160_to_b160(address))?.unwrap_or_default(); - account.nonce = nonce; - - self.backend_mut().insert_account_info(address, account); - Ok(self) - } - - pub fn set_tracing(&mut self, tracing: bool) -> &mut Self { - self.inspector_config.tracing = tracing; - self - } - - pub fn set_debugger(&mut self, debugger: bool) -> &mut Self { - self.inspector_config.debugger = debugger; - self - } - - pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self { - self.inspector_config.trace_printer = trace_printer; - self - } - - pub fn set_gas_limit(&mut self, gas_limit: U256) -> &mut Self { - self.gas_limit = gas_limit; - self - } - - /// Calls the `setUp()` function on a contract. - /// - /// This will commit any state changes to the underlying database. - /// - /// 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. - pub fn setup( - &mut self, - from: Option
, - to: Address, - ) -> Result, EvmError> { - trace!(?from, ?to, "setting up contract"); - - let from = from.unwrap_or(CALLER); - self.backend_mut().set_test_contract(to).set_caller(from); - let res = self.call_committing::<(), _, _>(from, to, "setUp()", (), 0.into(), None)?; - - // record any changes made to the block's environment during setup - self.env.block = res.env.block.clone(); - // and also the chainid, which can be set manually - self.env.cfg.chain_id = res.env.cfg.chain_id; - - match res.state_changeset.as_ref() { - Some(changeset) => { - let success = self - .ensure_success(to, res.reverted, changeset.clone(), false) - .map_err(|err| EvmError::Eyre(eyre::eyre!(err.to_string())))?; - if success { - Ok(res) - } else { - Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: res.reverted, - reason: "execution error".to_owned(), - traces: res.traces, - gas_used: res.gas_used, - gas_refunded: res.gas_refunded, - stipend: res.stipend, - logs: res.logs, - debug: res.debug, - labels: res.labels, - state_changeset: None, - transactions: None, - script_wallets: res.script_wallets, - }))) - } - } - None => Ok(res), - } - } - - /// Performs a call to an account on the current state of the VM. - /// - /// The state after the call is persisted. - pub fn call_committing( - &mut self, - from: Address, - to: Address, - func: F, - args: T, - value: U256, - abi: Option<&Abi>, - ) -> Result, EvmError> { - let func = func.into(); - let calldata = Bytes::from(encode_function_data(&func, args)?.to_vec()); - let result = self.call_raw_committing(from, to, calldata, value)?; - convert_call_result(abi, &func, result) - } - - /// Performs a raw call to an account on the current state of the VM. - /// - /// The state after the call is persisted. - pub fn call_raw_committing( - &mut self, - from: Address, - to: Address, - calldata: Bytes, - value: U256, - ) -> eyre::Result { - let env = self.build_test_env(from, TransactTo::Call(h160_to_b160(to)), calldata, value); - let mut result = self.call_raw_with_env(env)?; - self.commit(&mut result); - Ok(result) - } - - /// Executes the test function call - pub fn execute_test( - &mut self, - from: Address, - test_contract: Address, - func: F, - args: T, - value: U256, - abi: Option<&Abi>, - ) -> Result, EvmError> { - let func = func.into(); - let calldata = Bytes::from(encode_function_data(&func, args)?.to_vec()); - - // execute the call - let env = self.build_test_env( - from, - TransactTo::Call(h160_to_b160(test_contract)), - calldata, - value, - ); - let call_result = self.call_raw_with_env(env)?; - convert_call_result(abi, &func, call_result) - } - - /// Performs a call to an account on the current state of the VM. - /// - /// The state after the call is not persisted. - pub fn call( - &self, - from: Address, - to: Address, - func: F, - args: T, - value: U256, - abi: Option<&Abi>, - ) -> Result, EvmError> { - let func = func.into(); - let calldata = Bytes::from(encode_function_data(&func, args)?.to_vec()); - let call_result = self.call_raw(from, to, calldata, value)?; - convert_call_result(abi, &func, call_result) - } - - /// Performs a raw call to an account on the current state of the VM. - /// - /// Any state modifications made by the call are not committed. - pub fn call_raw( - &self, - from: Address, - to: Address, - calldata: Bytes, - value: U256, - ) -> eyre::Result { - // execute the call - let mut inspector = self.inspector_config.stack(); - // Build VM - let mut env = - self.build_test_env(from, TransactTo::Call(h160_to_b160(to)), calldata, value); - let mut db = FuzzBackendWrapper::new(self.backend()); - let result = db.inspect_ref(&mut env, &mut inspector)?; - - // Persist the snapshot failure recorded on the fuzz backend wrapper. - self.backend().set_snapshot_failure( - self.backend().has_snapshot_failure() || db.has_snapshot_failure(), - ); - - convert_executed_result(env, inspector, result) - } - - /// Execute the transaction configured in `env.tx` and commit the changes - pub fn commit_tx_with_env(&mut self, env: Env) -> eyre::Result { - let mut result = self.call_raw_with_env(env)?; - self.commit(&mut result); - Ok(result) - } - - /// Execute the transaction configured in `env.tx` - pub fn call_raw_with_env(&mut self, mut env: Env) -> eyre::Result { - // execute the call - let mut inspector = self.inspector_config.stack(); - let result = self.backend_mut().inspect_ref(&mut env, &mut inspector)?; - convert_executed_result(env, inspector, result) - } - - /// Commit the changeset to the database and adjust `self.inspector_config` - /// values according to the executed call result - fn commit(&mut self, result: &mut RawCallResult) { - // persist changes to db - if let Some(changes) = result.state_changeset.as_ref() { - self.backend_mut().commit(changes.clone()); - } - // Persist the changed block environment - self.inspector_config.block = result.env.block.clone(); - // Persist cheatcode state - let mut cheatcodes = result.cheatcodes.take(); - if let Some(cheats) = cheatcodes.as_mut() { - // Clear broadcastable transactions - cheats.broadcastable_transactions.clear(); - - // corrected_nonce value is needed outside of this context (setUp), so we don't - // reset it. - } - self.inspector_config.cheatcodes = cheatcodes; - } - - /// Deploys a contract using the given `env` and commits the new state to the underlying - /// database - pub fn deploy_with_env( - &mut self, - env: Env, - abi: Option<&Abi>, - ) -> Result { - debug_assert!( - matches!(env.tx.transact_to, TransactTo::Create(_)), - "Expect create transaction" - ); - trace!(sender=?env.tx.caller, "deploying contract"); - - let mut result = self.call_raw_with_env(env)?; - self.commit(&mut result); - - let RawCallResult { - exit_reason, - out, - gas_used, - gas_refunded, - logs, - labels, - traces, - debug, - script_wallets, - env, - .. - } = result; - - let result = match &out { - Some(Output::Create(data, _)) => data.to_owned(), - _ => Bytes::default(), - }; - - let address = match exit_reason { - return_ok!() => { - if let Some(Output::Create(_, Some(addr))) = out { - addr - } else { - return Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: true, - reason: "Deployment succeeded, but no address was returned. This is a bug, please report it".to_string(), - traces, - gas_used, - gas_refunded: 0, - stipend: 0, - logs, - debug, - labels, - state_changeset: None, - transactions: None, - script_wallets - }))); - } - } - _ => { - let reason = decode::decode_revert(result.as_ref(), abi, Some(exit_reason)) - .unwrap_or_else(|_| format!("{exit_reason:?}")); - return Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: true, - reason, - traces, - gas_used, - gas_refunded, - stipend: 0, - logs, - debug, - labels, - state_changeset: None, - transactions: None, - script_wallets, - }))) - } - }; - - // 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(b160_to_h160(address)); - - trace!(address=?address, "deployed contract"); - - Ok(DeployResult { - address: b160_to_h160(address), - gas_used, - gas_refunded, - logs, - traces, - debug, - env, - }) - } - - /// Deploys a contract and commits the new state to the underlying database. - /// - /// Executes a CREATE transaction with the contract `code` and persistent database state - /// modifications - pub fn deploy( - &mut self, - from: Address, - code: Bytes, - value: U256, - abi: Option<&Abi>, - ) -> Result { - let env = self.build_test_env(from, TransactTo::Create(CreateScheme::Create), code, value); - self.deploy_with_env(env, abi) - } - - /// Check if a call to a test contract was 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. - /// - /// DSTest will not revert inside its `assertEq`-like functions which allows - /// to test multiple assertions in 1 test function while also preserving logs. - /// - /// 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. - /// - /// 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()`. - pub fn is_success( - &self, - address: Address, - reverted: bool, - state_changeset: StateChangeset, - should_fail: bool, - ) -> bool { - self.ensure_success(address, reverted, state_changeset, should_fail).unwrap_or_default() - } - - fn ensure_success( - &self, - address: Address, - reverted: bool, - state_changeset: StateChangeset, - should_fail: bool, - ) -> Result { - if self.backend().has_snapshot_failure() { - // a failure occurred in a reverted snapshot, which is considered a failed test - return Ok(should_fail) - } - - // Construct a new VM with the state changeset - 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 addr in [address, CHEATCODE_ADDRESS] { - let acc = self.backend().basic(h160_to_b160(addr))?.unwrap_or_default(); - backend.insert_account_info(addr, acc); - } - - // If this test failed any asserts, then this changeset will contain changes `false -> true` - // for the contract's `failed` variable and the `globalFailure` flag in the state of the - // cheatcode address which are both read when call `"failed()(bool)"` in the next step - backend.commit(state_changeset); - let executor = - Executor::new(backend, self.env.clone(), self.inspector_config.clone(), self.gas_limit); - - let mut success = !reverted; - if success { - // Check if a DSTest assertion failed - let call = - executor.call::(CALLER, address, "failed()(bool)", (), 0.into(), None); - - if let Ok(CallResult { result: failed, .. }) = call { - success = !failed; - } - } - - Ok(should_fail ^ success) - } - - /// Creates the environment to use when executing a transaction in a test context - /// - /// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by - /// the cheatcode state inbetween calls. - fn build_test_env( - &self, - caller: Address, - transact_to: TransactTo, - data: Bytes, - value: U256, - ) -> Env { - Env { - 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: rU256::from(0), - gas_limit: self.gas_limit.into(), - ..self.env.block.clone() - }, - tx: TxEnv { - caller: h160_to_b160(caller), - transact_to, - data, - value: value.into(), - // As above, we set the gas price to 0. - gas_price: rU256::from(0), - gas_priority_fee: None, - gas_limit: self.gas_limit.as_u64(), - ..self.env.tx.clone() - }, - } - } -} - -/// Represents the context after an execution error occurred. -#[derive(thiserror::Error, Debug)] -#[error("Execution reverted: {reason} (gas: {gas_used})")] -pub struct ExecutionErr { - pub reverted: bool, - pub reason: String, - pub gas_used: u64, - pub gas_refunded: u64, - pub stipend: u64, - pub logs: Vec, - pub traces: Option, - pub debug: Option, - pub labels: BTreeMap, - pub transactions: Option, - pub state_changeset: Option, - pub script_wallets: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum EvmError { - /// Error which occurred during execution of a transaction - #[error(transparent)] - Execution(Box), - /// Error which occurred during ABI encoding/decoding - #[error(transparent)] - AbiError(#[from] ethers::contract::AbiError), - /// Error caused which occurred due to calling the skip() cheatcode. - #[error("Skipped")] - SkipError, - /// Any other error. - #[error(transparent)] - Eyre(#[from] eyre::Error), -} - -/// The result of a deployment. -#[derive(Debug)] -pub struct DeployResult { - /// The address of the deployed contract - pub address: Address, - /// The gas cost of the deployment - pub gas_used: u64, - /// The refunded gas - pub gas_refunded: u64, - /// The logs emitted during the deployment - pub logs: Vec, - /// The traces of the deployment - pub traces: Option, - /// The debug nodes of the call - pub debug: Option, - /// The `revm::Env` after deployment - pub env: Env, -} - -/// The result of a call. -#[derive(Debug)] -pub struct CallResult { - pub skipped: bool, - /// Whether the call reverted or not - pub reverted: bool, - /// The decoded result of the call - pub result: D, - /// The gas used for the call - pub gas_used: u64, - /// The refunded gas for the call - pub gas_refunded: u64, - /// The initial gas stipend for the transaction - pub stipend: u64, - /// The logs emitted during the call - pub logs: Vec, - /// The labels assigned to addresses during the call - pub labels: BTreeMap, - /// The traces of the call - 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. - /// - /// This is only present if the changed state was not committed to the database (i.e. if you - /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). - pub state_changeset: Option, - /// The wallets added during the call using the `rememberKey` cheatcode - pub script_wallets: Vec, - /// The `revm::Env` after the call - pub env: Env, - /// breakpoints - pub breakpoints: Breakpoints, -} - -/// The result of a raw call. -#[derive(Debug)] -pub struct RawCallResult { - /// The status of the call - pub exit_reason: InstructionResult, - /// Whether the call reverted or not - pub reverted: bool, - /// The raw result of the call - pub result: Bytes, - /// The gas used for the call - pub gas_used: u64, - /// Refunded gas - pub gas_refunded: u64, - /// The initial gas stipend for the transaction - pub stipend: u64, - /// The logs emitted during the call - pub logs: Vec, - /// The labels assigned to addresses during the call - pub labels: BTreeMap, - /// The traces of the call - 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. - /// - /// This is only present if the changed state was not committed to the database (i.e. if you - /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). - pub state_changeset: Option, - /// The wallets added during the call using the `rememberKey` cheatcode - pub script_wallets: Vec, - /// The `revm::Env` after the call - pub env: Env, - /// The cheatcode states after execution - pub cheatcodes: Option, - /// The raw output of the execution - pub out: Option, - /// The chisel state - pub chisel_state: Option<(Stack, Memory, InstructionResult)>, -} - -impl Default for RawCallResult { - fn default() -> Self { - Self { - exit_reason: InstructionResult::Continue, - reverted: false, - result: Bytes::new(), - gas_used: 0, - gas_refunded: 0, - stipend: 0, - logs: Vec::new(), - labels: BTreeMap::new(), - traces: None, - coverage: None, - debug: None, - transactions: None, - state_changeset: None, - script_wallets: Vec::new(), - env: Default::default(), - cheatcodes: Default::default(), - out: None, - chisel_state: None, - } - } -} - -/// Calculates the initial gas stipend for a transaction -fn calc_stipend(calldata: &[u8], spec: SpecId) -> u64 { - let non_zero_data_cost = if SpecId::enabled(spec, SpecId::ISTANBUL) { 16 } else { 68 }; - calldata.iter().fold(21000, |sum, byte| sum + if *byte == 0 { 4 } else { non_zero_data_cost }) -} - -/// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` -fn convert_executed_result( - env: Env, - inspector: InspectorStack, - result: ResultAndState, -) -> 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, .. } => { - (eval_to_instruction_result(reason), gas_refunded, gas_used, Some(output)) - } - ExecutionResult::Revert { gas_used, output } => { - // Need to fetch the unused gas - (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output))) - } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), 0_u64, gas_used, None) - } - }; - let stipend = calc_stipend(&env.tx.data, env.cfg.spec_id); - - let result = match out { - Some(Output::Call(ref data)) => data.to_owned(), - _ => Bytes::default(), - }; - - let InspectorData { - logs, - labels, - traces, - gas, - coverage, - debug, - cheatcodes, - script_wallets, - chisel_state, - } = inspector.collect_inspector_states(); - - let gas_refunded = gas.unwrap_or(gas_refunded); - let transactions = match cheatcodes.as_ref() { - Some(cheats) if !cheats.broadcastable_transactions.is_empty() => { - Some(cheats.broadcastable_transactions.clone()) - } - _ => None, - }; - - Ok(RawCallResult { - exit_reason, - reverted: !matches!(exit_reason, return_ok!()), - result, - gas_used, - gas_refunded, - stipend, - logs: logs.to_vec(), - labels, - traces, - coverage, - debug, - transactions, - state_changeset: Some(state_changeset), - script_wallets, - env, - cheatcodes, - out, - chisel_state, - }) -} - -fn convert_call_result( - abi: Option<&Contract>, - func: &Function, - call_result: RawCallResult, -) -> Result, EvmError> { - let RawCallResult { - result, - exit_reason: status, - reverted, - gas_used, - gas_refunded, - stipend, - logs, - labels, - traces, - coverage, - debug, - transactions, - state_changeset, - script_wallets, - env, - .. - } = call_result; - - let breakpoints = if let Some(c) = call_result.cheatcodes { - c.breakpoints - } else { - std::collections::HashMap::new() - }; - - match status { - return_ok!() => { - let result = decode_function_data(func, result, false)?; - Ok(CallResult { - reverted, - result, - gas_used, - gas_refunded, - stipend, - logs, - labels, - traces, - coverage, - debug, - transactions, - state_changeset, - script_wallets, - env, - breakpoints, - skipped: false, - }) - } - _ => { - let reason = decode::decode_revert(result.as_ref(), abi, Some(status)) - .unwrap_or_else(|_| format!("{status:?}")); - if reason == "SKIPPED" { - return Err(EvmError::SkipError) - } - Err(EvmError::Execution(Box::new(ExecutionErr { - reverted, - reason, - gas_used, - gas_refunded, - stipend, - logs, - traces, - debug, - labels, - transactions, - state_changeset, - script_wallets, - }))) - } - } -} diff --git a/crates/evm/src/executor/opts.rs b/crates/evm/src/executor/opts.rs deleted file mode 100644 index 944a46206dfe0..0000000000000 --- a/crates/evm/src/executor/opts.rs +++ /dev/null @@ -1,277 +0,0 @@ -use crate::{ - executor::fork::CreateFork, - utils::{h160_to_b160, h256_to_b256, RuntimeOrHandle}, -}; -use ethers::{ - providers::{Middleware, Provider}, - types::{Address, Block, Chain, TxHash, H256, U256}, -}; -use eyre::WrapErr; -use foundry_common::{self, ProviderBuilder, RpcUrl, ALCHEMY_FREE_TIER_CUPS}; -use foundry_config::Config; -use revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv, U256 as rU256}; -use serde::{Deserialize, Deserializer, Serialize}; - -use super::fork::environment; - -#[derive(Debug, Clone, Serialize, Deserialize, Default)] -pub struct EvmOpts { - #[serde(flatten)] - pub env: Env, - - /// Fetch state over a remote instead of starting from empty state - #[serde(rename = "eth_rpc_url")] - pub fork_url: Option, - - /// pins the block number for the state fork - pub fork_block_number: Option, - - /// initial retry backoff - pub fork_retry_backoff: Option, - - /// The available compute units per second - pub compute_units_per_second: Option, - - /// Disables rate limiting entirely. - pub no_rpc_rate_limit: bool, - - /// Disables storage caching entirely. - pub no_storage_caching: bool, - - /// the initial balance of each deployed test contract - pub initial_balance: U256, - - /// the address which will be executing all tests - pub sender: Address, - - /// enables the FFI cheatcode - pub ffi: bool, - - /// Verbosity mode of EVM output as number of occurrences - pub verbosity: u8, - - /// The memory limit of the EVM in bytes. - pub memory_limit: u64, -} - -impl EvmOpts { - /// Configures a new `revm::Env` - /// - /// If a `fork_url` is set, it gets configured with settings fetched from the endpoint (chain - /// id, ) - pub async fn evm_env(&self) -> eyre::Result { - if let Some(ref fork_url) = self.fork_url { - Ok(self.fork_evm_env(fork_url).await?.0) - } else { - Ok(self.local_evm_env()) - } - } - - /// Returns the `revm::Env` that is configured with settings retrieved from the endpoint. - /// And the block that was used to configure the environment. - pub async fn fork_evm_env( - &self, - fork_url: impl AsRef, - ) -> eyre::Result<(revm::primitives::Env, Block)> { - let fork_url = fork_url.as_ref(); - let provider = ProviderBuilder::new(fork_url) - .compute_units_per_second(self.get_compute_units_per_second()) - .build()?; - environment( - &provider, - self.memory_limit, - self.env.gas_price, - self.env.chain_id, - self.fork_block_number, - self.sender, - ) - .await - .wrap_err_with(|| { - format!("Could not instantiate forked environment with fork url: {fork_url}") - }) - } - - /// Returns the `revm::Env` configured with only local settings - pub fn local_evm_env(&self) -> revm::primitives::Env { - revm::primitives::Env { - block: BlockEnv { - number: rU256::from(self.env.block_number), - coinbase: h160_to_b160(self.env.block_coinbase), - timestamp: rU256::from(self.env.block_timestamp), - difficulty: rU256::from(self.env.block_difficulty), - prevrandao: Some(h256_to_b256(self.env.block_prevrandao)), - basefee: rU256::from(self.env.block_base_fee_per_gas), - gas_limit: self.gas_limit().into(), - }, - cfg: CfgEnv { - chain_id: rU256::from(self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID)), - spec_id: SpecId::MERGE, - limit_contract_code_size: self.env.code_size_limit.or(Some(usize::MAX)), - memory_limit: self.memory_limit, - // EIP-3607 rejects transactions from senders with deployed code. - // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the - // caller is a contract. So we disable the check by default. - disable_eip3607: true, - ..Default::default() - }, - tx: TxEnv { - gas_price: rU256::from(self.env.gas_price.unwrap_or_default()), - gas_limit: self.gas_limit().as_u64(), - caller: h160_to_b160(self.sender), - ..Default::default() - }, - } - } - - /// Helper function that returns the [CreateFork] to use, if any. - /// - /// storage caching for the [CreateFork] will be enabled if - /// - `fork_url` is present - /// - `fork_block_number` is present - /// - [StorageCachingConfig] allows the `fork_url` + chain id pair - /// - storage is allowed (`no_storage_caching = false`) - /// - /// If all these criteria are met, then storage caching is enabled and storage info will be - /// written to [Config::foundry_cache_dir()]///storage.json - /// - /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will - /// be at `~/.foundry/cache/mainnet/14435000/storage.json` - pub fn get_fork(&self, config: &Config, env: revm::primitives::Env) -> Option { - let url = self.fork_url.clone()?; - let enable_caching = config.enable_caching(&url, env.cfg.chain_id.to::()); - Some(CreateFork { url, enable_caching, env, evm_opts: self.clone() }) - } - - /// Returns the gas limit to use - pub fn gas_limit(&self) -> U256 { - self.env.block_gas_limit.unwrap_or(self.env.gas_limit).into() - } - - /// Returns the configured chain id, which will be - /// - the value of `chain_id` if set - /// - mainnet if `fork_url` contains "mainnet" - /// - the chain if `fork_url` is set and the endpoints returned its chain id successfully - /// - mainnet otherwise - pub fn get_chain_id(&self) -> u64 { - if let Some(id) = self.env.chain_id { - return id - } - self.get_remote_chain_id().map_or(Chain::Mainnet as u64, |id| id as u64) - } - - /// Returns the available compute units per second, which will be - /// - u64::MAX, if `no_rpc_rate_limit` if set (as rate limiting is disabled) - /// - the assigned compute units, if `compute_units_per_second` is set - /// - ALCHEMY_FREE_TIER_CUPS (330) otherwise - pub fn get_compute_units_per_second(&self) -> u64 { - if self.no_rpc_rate_limit { - u64::MAX - } else if let Some(cups) = self.compute_units_per_second { - return cups - } else { - ALCHEMY_FREE_TIER_CUPS - } - } - - /// Returns the chain ID from the RPC, if any. - pub fn get_remote_chain_id(&self) -> Option { - if let Some(ref url) = self.fork_url { - if url.contains("mainnet") { - trace!(?url, "auto detected mainnet chain"); - return Some(Chain::Mainnet) - } - trace!(?url, "retrieving chain via eth_chainId"); - let provider = Provider::try_from(url.as_str()) - .unwrap_or_else(|_| panic!("Failed to establish provider to {url}")); - - if let Ok(id) = RuntimeOrHandle::new().block_on(provider.get_chainid()) { - return Chain::try_from(id.as_u64()).ok() - } - } - - None - } -} - -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct Env { - /// the block gas limit - #[serde(deserialize_with = "string_or_number")] - pub gas_limit: u64, - - /// the chainid opcode value - pub chain_id: Option, - - /// the tx.gasprice value during EVM execution - /// - /// This is an Option, so we can determine in fork mode whether to use the config's gas price - /// (if set by user) or the remote client's gas price. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub gas_price: Option, - - /// the base fee in a block - pub block_base_fee_per_gas: u64, - - /// the tx.origin value during EVM execution - pub tx_origin: Address, - - /// the block.coinbase value during EVM execution - pub block_coinbase: Address, - - /// the block.timestamp value during EVM execution - pub block_timestamp: u64, - - /// the block.number value during EVM execution" - pub block_number: u64, - - /// the block.difficulty value during EVM execution - pub block_difficulty: u64, - - /// Previous block beacon chain random value. Before merge this field is used for mix_hash - pub block_prevrandao: H256, - - /// the block.gaslimit value during EVM execution - #[serde( - default, - skip_serializing_if = "Option::is_none", - deserialize_with = "string_or_number_opt" - )] - pub block_gas_limit: Option, - - /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub code_size_limit: Option, -} - -#[derive(Deserialize)] -#[serde(untagged)] -enum Gas { - Number(u64), - Text(String), -} - -fn string_or_number<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - use serde::de::Error; - match Gas::deserialize(deserializer)? { - Gas::Number(num) => Ok(num), - Gas::Text(s) => s.parse().map_err(D::Error::custom), - } -} - -fn string_or_number_opt<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - use serde::de::Error; - - match Option::::deserialize(deserializer)? { - Some(gas) => match gas { - Gas::Number(num) => Ok(Some(num)), - Gas::Text(s) => s.parse().map(Some).map_err(D::Error::custom), - }, - _ => Ok(None), - } -} diff --git a/crates/evm/src/executor/snapshot.rs b/crates/evm/src/executor/snapshot.rs deleted file mode 100644 index a549362b101ae..0000000000000 --- a/crates/evm/src/executor/snapshot.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! support for snapshotting different states - -use ethers::types::U256; -use std::collections::HashMap; - -/// Represents all snapshots -#[derive(Debug, Clone)] -pub struct Snapshots { - id: U256, - snapshots: HashMap, -} - -// === impl Snapshots === - -impl Snapshots { - fn next_id(&mut self) -> U256 { - let id = self.id; - self.id = id.saturating_add(U256::one()); - id - } - - /// Returns the snapshot with the given id `id` - pub fn get(&self, id: U256) -> Option<&T> { - self.snapshots.get(&id) - } - - /// Removes the snapshot with the given `id`. - /// - /// This will also remove any snapshots taken after the snapshot with the `id`. e.g.: reverting - /// to id 1 will delete snapshots with ids 1, 2, 3, etc.) - pub fn remove(&mut self, id: U256) -> Option { - let snapshot = self.snapshots.remove(&id); - - // revert all snapshots taken after the snapshot - let mut to_revert = id + 1; - while to_revert < self.id { - self.snapshots.remove(&to_revert); - to_revert = to_revert + 1; - } - - snapshot - } - - /// Removes the snapshot with the given `id`. - /// - /// Does not remove snapshots after it. - pub fn remove_at(&mut self, id: U256) -> Option { - self.snapshots.remove(&id) - } - - /// Inserts the new snapshot and returns the id - pub fn insert(&mut self, snapshot: T) -> U256 { - let id = self.next_id(); - self.snapshots.insert(id, snapshot); - id - } - - /// Inserts the new snapshot at the given `id`. - /// - /// Does not auto-increment the next `id`. - pub fn insert_at(&mut self, snapshot: T, id: U256) -> U256 { - self.snapshots.insert(id, snapshot); - id - } -} - -impl Default for Snapshots { - fn default() -> Self { - Self { id: U256::zero(), snapshots: HashMap::new() } - } -} diff --git a/crates/evm/src/fuzz/error.rs b/crates/evm/src/fuzz/error.rs deleted file mode 100644 index 3165ededdd6c3..0000000000000 --- a/crates/evm/src/fuzz/error.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! errors related to fuzz tests -use proptest::test_runner::Reason; - -/// Magic return code for the `assume` cheatcode -pub const ASSUME_MAGIC_RETURN_CODE: &[u8] = b"FOUNDRY::ASSUME"; - -/// Possible errors when running fuzz tests -#[derive(Debug, thiserror::Error)] -pub enum FuzzError { - #[error("Couldn't call unknown contract")] - UnknownContract, - #[error("Couldn't find function")] - UnknownFunction, - #[error("Failed to decode fuzzer inputs")] - FailedDecodeInput, - #[error("Failed contract call")] - FailedContractCall, - #[error("Empty state changeset")] - EmptyChangeset, - #[error("`vm.assume` reject")] - AssumeReject, - #[error("The `vm.assume` cheatcode rejected too many inputs ({0} allowed)")] - TooManyRejects(u32), -} - -impl From for Reason { - fn from(error: FuzzError) -> Self { - error.to_string().into() - } -} diff --git a/crates/evm/src/fuzz/invariant/error.rs b/crates/evm/src/fuzz/invariant/error.rs deleted file mode 100644 index c09e52e85ab3c..0000000000000 --- a/crates/evm/src/fuzz/invariant/error.rs +++ /dev/null @@ -1,256 +0,0 @@ -use super::{BasicTxDetails, InvariantContract}; -use crate::{ - decode::decode_revert, - executor::{Executor, RawCallResult}, - fuzz::{invariant::set_up_inner_replay, *}, - trace::{load_contracts, TraceKind, Traces}, - CALLER, -}; -use ethers::{ - abi::Function, - types::{Address, U256}, -}; -use eyre::{Result, WrapErr}; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use proptest::test_runner::TestError; - -#[derive(Debug, Clone)] -pub struct InvariantFuzzError { - pub logs: Vec, - pub traces: Option, - /// The proptest error occurred as a result of a test case. - pub test_error: TestError>, - /// The return reason of the offending call. - pub return_reason: Reason, - /// The revert string of the offending call. - pub revert_reason: String, - /// Address of the invariant asserter. - pub addr: Address, - /// Function data for invariant check. - pub func: Option, - /// Inner fuzzing Sequence coming from overriding calls. - pub inner_sequence: Vec>, - /// Shrink the failed test case to the smallest sequence. - pub shrink: bool, -} - -impl InvariantFuzzError { - pub fn new( - invariant_contract: &InvariantContract, - error_func: Option<&Function>, - calldata: &[BasicTxDetails], - call_result: RawCallResult, - inner_sequence: &[Option], - shrink_sequence: bool, - ) -> Self { - let mut func = None; - let origin: String; - - if let Some(f) = error_func { - func = Some(f.short_signature().into()); - origin = f.name.clone(); - } else { - origin = "Revert".to_string(); - } - - InvariantFuzzError { - logs: call_result.logs, - traces: call_result.traces, - test_error: proptest::test_runner::TestError::Fail( - format!( - "{}, reason: '{}'", - origin, - match decode_revert( - call_result.result.as_ref(), - Some(invariant_contract.abi), - Some(call_result.exit_reason) - ) { - Ok(e) => e, - Err(e) => e.to_string(), - } - ) - .into(), - calldata.to_vec(), - ), - return_reason: "".into(), - revert_reason: decode_revert( - call_result.result.as_ref(), - Some(invariant_contract.abi), - Some(call_result.exit_reason), - ) - .unwrap_or_default(), - addr: invariant_contract.address, - func, - inner_sequence: inner_sequence.to_vec(), - shrink: shrink_sequence, - } - } - - /// Replays the error case and collects all necessary traces. - pub fn replay( - &self, - mut executor: Executor, - known_contracts: Option<&ContractsByArtifact>, - mut ided_contracts: ContractsByAddress, - logs: &mut Vec, - traces: &mut Traces, - ) -> Result> { - let mut counterexample_sequence = vec![]; - let calls = match self.test_error { - // Don't use at the moment. - TestError::Abort(_) => return Ok(None), - TestError::Fail(_, ref calls) => calls, - }; - - if self.shrink { - let _ = self.try_shrinking(calls, &executor); - } else { - trace!(target: "forge::test", "Shrinking disabled."); - } - - // We want traces for a failed case. - executor.set_tracing(true); - - set_up_inner_replay(&mut executor, &self.inner_sequence); - - // Replay each call from the sequence until we break the invariant. - for (sender, (addr, bytes)) in calls.iter() { - let call_result = executor - .call_raw_committing(*sender, *addr, bytes.0.clone(), U256::zero()) - .expect("bad call to evm"); - - logs.extend(call_result.logs); - traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - - // Identify newly generated contracts, if they exist. - ided_contracts.extend(load_contracts( - vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], - known_contracts, - )); - - counterexample_sequence.push( - BaseCounterExample::create( - *sender, - *addr, - bytes, - &ided_contracts, - call_result.traces, - ) - .wrap_err("Failed to create counter example")?, - ); - - // Checks the invariant. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.0.clone(), U256::zero()) - .expect("bad call to evm"); - - traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); - - logs.extend(error_call_result.logs); - if error_call_result.reverted { - break - } - } - } - - Ok((!counterexample_sequence.is_empty()) - .then_some(CounterExample::Sequence(counterexample_sequence))) - } - - /// Tests that the modified sequence of calls successfully reverts on the error function. - fn fails_successfully<'a>( - &self, - mut executor: Executor, - calls: &'a [BasicTxDetails], - anchor: usize, - removed_calls: &[usize], - ) -> Result, ()> { - let calls = calls.iter().enumerate().filter_map(|(index, element)| { - if anchor > index || removed_calls.contains(&index) { - return None - } - Some(element) - }); - - let mut new_sequence = vec![]; - for details in calls { - new_sequence.push(details); - - let (sender, (addr, bytes)) = details; - - executor - .call_raw_committing(*sender, *addr, bytes.0.clone(), 0.into()) - .expect("bad call to evm"); - - // Checks the invariant. If we exit before the last call, all the better. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.0.clone(), 0.into()) - .expect("bad call to evm"); - - if error_call_result.reverted { - return Ok(new_sequence) - } - } - } - - Err(()) - } - - /// Tries to shrink the failure case to its smallest sequence of calls. - /// - /// Sets an anchor at the beginning (index=0) and tries to remove all other calls one by one, - /// until it reaches the last one. The elements which were removed and lead to a failure are - /// kept in the removal list. The removed ones that didn't lead to a failure are inserted - /// back into the sequence. - /// - /// Once it reaches the end, it increments the anchor, resets the removal list and starts the - /// same process again. - /// - /// Returns the smallest sequence found. - fn try_shrinking<'a>( - &self, - calls: &'a [BasicTxDetails], - executor: &Executor, - ) -> Vec<&'a BasicTxDetails> { - let mut anchor = 0; - let mut removed_calls = vec![]; - let mut shrunk = calls.iter().collect::>(); - trace!(target: "forge::test", "Shrinking."); - - while anchor != calls.len() { - // Get the latest removed element, so we know which one to remove next. - let removed = - match self.fails_successfully(executor.clone(), calls, anchor, &removed_calls) { - Ok(new_sequence) => { - if shrunk.len() > new_sequence.len() { - shrunk = new_sequence; - } - removed_calls.last().cloned() - } - Err(_) => removed_calls.pop(), - }; - - if let Some(last_removed) = removed { - // If we haven't reached the end of the sequence, then remove the next element. - // Otherwise, restart the process with an incremented anchor. - - let next_removed = last_removed + 1; - - if next_removed > calls.len() - 1 { - anchor += 1; - removed_calls = vec![]; - continue - } - - removed_calls.push(next_removed); - } else { - // When the process is restarted, `removed_calls` will be empty. - removed_calls.push(anchor + 1); - } - } - - shrunk - } -} diff --git a/crates/evm/src/fuzz/invariant/executor.rs b/crates/evm/src/fuzz/invariant/executor.rs deleted file mode 100644 index acbffe60347f9..0000000000000 --- a/crates/evm/src/fuzz/invariant/executor.rs +++ /dev/null @@ -1,669 +0,0 @@ -use super::{ - assert_invariants, - filters::{ArtifactFilters, SenderFilters}, - BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract, InvariantFuzzError, - InvariantFuzzTestResult, RandomCallGenerator, TargetedContracts, -}; -use crate::{ - executor::{ - inspector::Fuzzer, Executor, RawCallResult, StateChangeset, CHEATCODE_ADDRESS, - HARDHAT_CONSOLE_ADDRESS, - }, - fuzz::{ - strategies::{ - build_initial_state, collect_created_contracts, collect_state_from_call, - invariant_strat, override_call_strat, EvmFuzzState, - }, - FuzzCase, FuzzedCases, - }, - utils::{get_function, h160_to_b160}, - CALLER, -}; -use ethers::{ - abi::{Abi, Address, Detokenize, FixedBytes, Function, Tokenizable, TokenizableItem}, - prelude::U256, -}; -use eyre::ContextCompat; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_config::{FuzzDictionaryConfig, InvariantConfig}; -use parking_lot::{Mutex, RwLock}; -use proptest::{ - strategy::{BoxedStrategy, Strategy, ValueTree}, - test_runner::{TestCaseError, TestRunner}, -}; -use revm::{ - primitives::{HashMap, B160}, - DatabaseCommit, -}; -use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; - -/// Alias for (Dictionary for fuzzing, initial contracts to fuzz and an InvariantStrategy). -type InvariantPreparation = - (EvmFuzzState, FuzzRunIdentifiedContracts, BoxedStrategy>); - -/// Enriched results of an invariant run check. -/// -/// Contains the success condition and call results of the last run -struct RichInvariantResults { - success: bool, - call_results: Option>, -} - -impl RichInvariantResults { - fn new(success: bool, call_results: Option>) -> Self { - Self { success, call_results } - } -} - -/// Wrapper around any [`Executor`] implementor which provides fuzzing support using [`proptest`](https://docs.rs/proptest/1.0.0/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](https://docs.rs/proptest/1.0.0/proptest/test_runner/struct.Config.html) -pub struct InvariantExecutor<'a> { - pub executor: &'a mut Executor, - /// Proptest runner. - runner: TestRunner, - /// The invariant configuration - config: InvariantConfig, - /// Contracts deployed with `setUp()` - setup_contracts: &'a ContractsByAddress, - /// Contracts that are part of the project but have not been deployed yet. We need the bytecode - /// to identify them from the stateset changes. - project_contracts: &'a ContractsByArtifact, - /// Filters contracts to be fuzzed through their artifact identifiers. - artifact_filters: ArtifactFilters, -} - -impl<'a> InvariantExecutor<'a> { - /// Instantiates a fuzzed executor EVM given a testrunner - pub fn new( - executor: &'a mut Executor, - runner: TestRunner, - config: InvariantConfig, - setup_contracts: &'a ContractsByAddress, - project_contracts: &'a ContractsByArtifact, - ) -> Self { - Self { - executor, - runner, - config, - setup_contracts, - project_contracts, - artifact_filters: ArtifactFilters::default(), - } - } - - /// Fuzzes any deployed contract and checks any broken invariant at `invariant_address` - /// Returns a list of all the consumed gas and calldata of every invariant fuzz case - pub fn invariant_fuzz( - &mut self, - invariant_contract: InvariantContract, - ) -> eyre::Result { - let (fuzz_state, targeted_contracts, strat) = self.prepare_fuzzing(&invariant_contract)?; - - // 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(&invariant_contract.invariant_functions)); - - let blank_executor = RefCell::new(&mut *self.executor); - - let last_call_results = RefCell::new( - assert_invariants( - &invariant_contract, - &blank_executor.borrow(), - &[], - &mut failures.borrow_mut(), - self.config.shrink_sequence, - ) - .ok(), - ); - let last_run_calldata: RefCell> = RefCell::new(vec![]); - // Make sure invariants are sound even before starting to fuzz - if last_call_results.borrow().is_none() { - fuzz_cases.borrow_mut().push(FuzzedCases::new(vec![])); - } - - if failures.borrow().broken_invariants_count < invariant_contract.invariant_functions.len() - { - // 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, |mut inputs| { - // Scenarios where we want to fail as soon as possible. - { - if self.config.fail_on_revert && failures.borrow().reverts == 1 { - return Err(TestCaseError::fail("Revert occurred.")) - } - - if failures.borrow().broken_invariants_count == - invariant_contract.invariant_functions.len() - { - return Err(TestCaseError::fail("All invariants have been broken.")) - } - } - - // Before each run, we must reset the backend state. - let mut executor = blank_executor.borrow().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![]; - - 'fuzz_run: for current_run in 0..self.config.depth { - let (sender, (address, calldata)) = - inputs.last().expect("to have the next randomly generated input."); - - // Executes the call from the randomly generated sequence. - let call_result = executor - .call_raw(*sender, *address, calldata.0.clone(), U256::zero()) - .expect("could not make raw evm call"); - - // Collect data for fuzzing from the state changeset. - let mut state_changeset = - call_result.state_changeset.to_owned().expect("no changesets"); - - collect_data( - &mut state_changeset, - sender, - &call_result, - fuzz_state.clone(), - &self.config.dictionary, - ); - - if let Err(error) = collect_created_contracts( - &state_changeset, - self.project_contracts, - self.setup_contracts, - &self.artifact_filters, - targeted_contracts.clone(), - &mut created_contracts, - ) { - warn!(target: "forge::test", "{error}"); - } - - // Commit changes to the database. - executor.backend_mut().commit(state_changeset.clone()); - - fuzz_runs.push(FuzzCase { - calldata: calldata.clone(), - gas: call_result.gas_used, - stipend: call_result.stipend, - }); - - let RichInvariantResults { success: can_continue, call_results } = can_continue( - &invariant_contract, - call_result, - &executor, - &inputs, - &mut failures.borrow_mut(), - &targeted_contracts, - state_changeset, - self.config.fail_on_revert, - self.config.shrink_sequence, - ); - - if !can_continue || current_run == self.config.depth - 1 { - *last_run_calldata.borrow_mut() = inputs.clone(); - } - - if !can_continue { - break 'fuzz_run - } - - *last_call_results.borrow_mut() = call_results; - - // Generates the next call from the run using the recently updated - // dictionary. - inputs.extend( - strat - .new_tree(&mut branch_runner.borrow_mut()) - .map_err(|_| TestCaseError::Fail("Could not generate case".into()))? - .current(), - ); - } - - // We clear all the targeted contracts created during this run. - if !created_contracts.is_empty() { - let mut writable_targeted = targeted_contracts.lock(); - for addr in created_contracts.iter() { - writable_targeted.remove(addr); - } - } - - fuzz_cases.borrow_mut().push(FuzzedCases::new(fuzz_runs)); - - Ok(()) - }); - } - - trace!(target: "forge::test::invariant::dictionary", "{:?}", fuzz_state.read().values().iter().map(hex::encode).collect::>()); - - let (reverts, invariants) = failures.into_inner().into_inner(); - - Ok(InvariantFuzzTestResult { - invariants, - cases: fuzz_cases.into_inner(), - reverts, - last_run_inputs: last_run_calldata.take(), - }) - } - - /// Prepares certain structures to execute the invariant tests: - /// * Fuzz dictionary - /// * Targeted contracts - /// * Invariant Strategy - fn prepare_fuzzing( - &mut self, - invariant_contract: &InvariantContract, - ) -> eyre::Result { - // Finds out the chosen deployed contracts and/or senders. - self.select_contract_artifacts(invariant_contract.address, invariant_contract.abi)?; - let (targeted_senders, targeted_contracts) = - self.select_contracts_and_senders(invariant_contract.address, invariant_contract.abi)?; - - if targeted_contracts.is_empty() { - eyre::bail!("No contracts to fuzz."); - } - - // Stores fuzz state for use with [fuzz_calldata_from_state]. - let fuzz_state: EvmFuzzState = - build_initial_state(self.executor.backend().mem_db(), &self.config.dictionary); - - // During execution, any newly created contract is added here and used through the rest of - // the fuzz run. - let targeted_contracts: FuzzRunIdentifiedContracts = - Arc::new(Mutex::new(targeted_contracts)); - - // Creates the invariant strategy. - let strat = invariant_strat( - fuzz_state.clone(), - targeted_senders, - targeted_contracts.clone(), - self.config.dictionary.dictionary_weight, - ) - .no_shrink() - .boxed(); - - // Allows `override_call_strat` to use the address given by the Fuzzer inspector during - // EVM execution. - let mut call_generator = None; - if self.config.call_override { - let target_contract_ref = Arc::new(RwLock::new(Address::zero())); - - call_generator = Some(RandomCallGenerator::new( - invariant_contract.address, - self.runner.clone(), - override_call_strat( - fuzz_state.clone(), - targeted_contracts.clone(), - target_contract_ref.clone(), - ), - target_contract_ref, - )); - } - - self.executor.inspector_config_mut().fuzzer = - Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true }); - - Ok((fuzz_state, targeted_contracts, strat)) - } - - /// Fills the `InvariantExecutor` with the artifact identifier filters (in `path:name` string - /// format). They will be used to filter contracts after the `setUp`, and more importantly, - /// during the runs. - /// - /// Also excludes any contract without any mutable functions. - /// - /// Priority: - /// - /// targetArtifactSelectors > excludeArtifacts > targetArtifacts - pub fn select_contract_artifacts( - &mut self, - invariant_address: Address, - abi: &Abi, - ) -> eyre::Result<()> { - // targetArtifactSelectors -> (string, bytes4[])[]. - let targeted_abi = self - .get_list::<(String, Vec)>( - invariant_address, - abi, - "targetArtifactSelectors", - ) - .into_iter() - .map(|(contract, functions)| (contract, functions)) - .collect::>(); - - // Insert them into the executor `targeted_abi`. - for (contract, selectors) in targeted_abi { - let identifier = self.validate_selected_contract(contract, &selectors)?; - - self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors); - } - - // targetArtifacts -> string[] - // excludeArtifacts -> string[]. - let [selected_abi, excluded_abi] = ["targetArtifacts", "excludeArtifacts"] - .map(|method| self.get_list::(invariant_address, abi, method)); - - // Insert `excludeArtifacts` into the executor `excluded_abi`. - for contract in excluded_abi { - let identifier = self.validate_selected_contract(contract, &[])?; - - if !self.artifact_filters.excluded.contains(&identifier) { - self.artifact_filters.excluded.push(identifier); - } - } - - // Exclude any artifact without mutable functions. - for (artifact, (abi, _)) in self.project_contracts.iter() { - if abi - .functions() - .filter(|func| { - !matches!( - func.state_mutability, - ethers::abi::StateMutability::Pure | ethers::abi::StateMutability::View - ) - }) - .count() == - 0 && - !self.artifact_filters.excluded.contains(&artifact.identifier()) - { - self.artifact_filters.excluded.push(artifact.identifier()); - } - } - - // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen - // before. - for contract in selected_abi { - let identifier = self.validate_selected_contract(contract, &[])?; - - if !self.artifact_filters.targeted.contains_key(&identifier) && - !self.artifact_filters.excluded.contains(&identifier) - { - self.artifact_filters.targeted.insert(identifier, vec![]); - } - } - - Ok(()) - } - - /// Makes sure that the contract exists in the project. If so, it returns its artifact - /// identifier. - fn validate_selected_contract( - &mut self, - contract: String, - selectors: &[FixedBytes], - ) -> eyre::Result { - if let Some((artifact, (abi, _))) = - self.project_contracts.find_by_name_or_identifier(&contract)? - { - // Check that the selectors really exist for this contract. - for selector in selectors { - abi.functions() - .find(|func| func.short_signature().as_slice() == selector.as_slice()) - .wrap_err(format!("{contract} does not have the selector {selector:?}"))?; - } - - return Ok(artifact.identifier()) - } - eyre::bail!("{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`."); - } - - /// Selects senders and contracts based on the contract methods `targetSenders() -> address[]`, - /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`. - pub fn select_contracts_and_senders( - &self, - invariant_address: Address, - abi: &Abi, - ) -> eyre::Result<(SenderFilters, TargetedContracts)> { - let [targeted_senders, excluded_senders, selected, excluded] = - ["targetSenders", "excludeSenders", "targetContracts", "excludeContracts"] - .map(|method| self.get_list::
(invariant_address, abi, method)); - - let mut contracts: TargetedContracts = self - .setup_contracts - .clone() - .into_iter() - .filter(|(addr, (identifier, _))| { - *addr != invariant_address && - *addr != CHEATCODE_ADDRESS && - *addr != HARDHAT_CONSOLE_ADDRESS && - (selected.is_empty() || selected.contains(addr)) && - (self.artifact_filters.targeted.is_empty() || - self.artifact_filters.targeted.contains_key(identifier)) && - (excluded.is_empty() || !excluded.contains(addr)) && - (self.artifact_filters.excluded.is_empty() || - !self.artifact_filters.excluded.contains(identifier)) - }) - .map(|(addr, (identifier, abi))| (addr, (identifier, abi, vec![]))) - .collect(); - - self.select_selectors(invariant_address, abi, &mut contracts)?; - - Ok((SenderFilters::new(targeted_senders, excluded_senders), contracts)) - } - - /// Selects the functions to fuzz based on the contract method `targetSelectors()` and - /// `targetArtifactSelectors()`. - pub fn select_selectors( - &self, - address: Address, - abi: &Abi, - targeted_contracts: &mut TargetedContracts, - ) -> eyre::Result<()> { - // `targetArtifactSelectors() -> (string, bytes4[])[]`. - let some_abi_selectors = self - .artifact_filters - .targeted - .iter() - .filter(|(_, selectors)| !selectors.is_empty()) - .collect::>(); - - for (address, (identifier, _)) in self.setup_contracts.iter() { - if let Some(selectors) = some_abi_selectors.get(identifier) { - self.add_address_with_functions( - *address, - (*selectors).clone(), - targeted_contracts, - )?; - } - } - - // `targetSelectors() -> (address, bytes4[])[]`. - let selectors = - self.get_list::<(Address, Vec)>(address, abi, "targetSelectors"); - - for (address, bytes4_array) in selectors.into_iter() { - self.add_address_with_functions(address, bytes4_array, targeted_contracts)?; - } - Ok(()) - } - - /// Adds the address and fuzzable functions to `TargetedContracts`. - fn add_address_with_functions( - &self, - address: Address, - bytes4_array: Vec>, - targeted_contracts: &mut TargetedContracts, - ) -> eyre::Result<()> { - if let Some((name, abi, address_selectors)) = targeted_contracts.get_mut(&address) { - // The contract is already part of our filter, and all we do is specify that we're - // only looking at specific functions coming from `bytes4_array`. - for selector in bytes4_array { - address_selectors.push(get_function(name, &selector, abi)?); - } - } else { - let (name, abi) = self.setup_contracts.get(&address).wrap_err(format!( - "[targetSelectors] address does not have an associated contract: {address}" - ))?; - - let functions = bytes4_array - .into_iter() - .map(|selector| get_function(name, &selector, abi)) - .collect::, _>>()?; - - targeted_contracts.insert(address, (name.to_string(), abi.clone(), functions)); - } - Ok(()) - } - - /// Gets list of `T` by calling the contract `method_name` function. - fn get_list(&self, address: Address, abi: &Abi, method_name: &str) -> Vec - where - T: Tokenizable + Detokenize + TokenizableItem, - { - if let Some(func) = abi.functions().find(|func| func.name == method_name) { - if let Ok(call_result) = self.executor.call::, _, _>( - CALLER, - address, - func.clone(), - (), - U256::zero(), - Some(abi), - ) { - return call_result.result - } else { - warn!( - "The function {} was found but there was an error querying its data.", - method_name - ); - } - }; - - Vec::new() - } -} - -/// Collects data from call for fuzzing. However, it first verifies that the sender is not an EOA -/// before inserting it into the dictionary. Otherwise, we flood the dictionary with -/// randomly generated addresses. -fn collect_data( - state_changeset: &mut HashMap, - sender: &Address, - call_result: &RawCallResult, - fuzz_state: EvmFuzzState, - config: &FuzzDictionaryConfig, -) { - // Verify it has no code. - let mut has_code = false; - if let Some(Some(code)) = - state_changeset.get(&h160_to_b160(*sender)).map(|account| account.info.code.as_ref()) - { - has_code = !code.is_empty(); - } - - // We keep the nonce changes to apply later. - let mut sender_changeset = None; - if !has_code { - sender_changeset = state_changeset.remove(&h160_to_b160(*sender)); - } - - collect_state_from_call(&call_result.logs, &*state_changeset, fuzz_state, config); - - // Re-add changes - if let Some(changed) = sender_changeset { - state_changeset.insert(h160_to_b160(*sender), changed); - } -} - -/// 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)] -fn can_continue( - invariant_contract: &InvariantContract, - call_result: RawCallResult, - executor: &Executor, - calldata: &[BasicTxDetails], - failures: &mut InvariantFailures, - targeted_contracts: &FuzzRunIdentifiedContracts, - state_changeset: StateChangeset, - fail_on_revert: bool, - shrink_sequence: bool, -) -> RichInvariantResults { - let mut call_results = None; - - // Detect handler assertion failures first. - let handlers_failed = targeted_contracts - .lock() - .iter() - .any(|contract| !executor.is_success(*contract.0, false, state_changeset.clone(), false)); - - // Assert invariants IFF the call did not revert and the handlers did not fail. - if !call_result.reverted && !handlers_failed { - call_results = - assert_invariants(invariant_contract, executor, calldata, failures, shrink_sequence) - .ok(); - if call_results.is_none() { - return RichInvariantResults::new(false, None) - } - } else { - failures.reverts += 1; - - // The user might want to stop all execution if a revert happens to - // better bound their testing space. - if fail_on_revert { - let error = InvariantFuzzError::new( - invariant_contract, - None, - calldata, - call_result, - &[], - shrink_sequence, - ); - - failures.revert_reason = Some(error.revert_reason.clone()); - - // Hacky to provide the full error to the user. - for invariant in invariant_contract.invariant_functions.iter() { - failures.failed_invariants.insert( - invariant.name.clone(), - (Some(error.clone()), invariant.to_owned().clone()), - ); - } - - return RichInvariantResults::new(false, None) - } - } - RichInvariantResults::new(true, call_results) -} - -#[derive(Clone)] -/// Stores information about failures and reverts of the invariant tests. -pub struct InvariantFailures { - /// The latest revert reason of a run. - pub revert_reason: Option, - /// Total number of reverts. - pub reverts: usize, - /// How many different invariants have been broken. - pub broken_invariants_count: usize, - /// Maps a broken invariant to its specific error. - pub failed_invariants: BTreeMap, Function)>, -} - -impl InvariantFailures { - fn new(invariants: &[&Function]) -> Self { - InvariantFailures { - reverts: 0, - broken_invariants_count: 0, - failed_invariants: invariants - .iter() - .map(|f| (f.name.to_string(), (None, f.to_owned().clone()))) - .collect(), - revert_reason: None, - } - } - - /// Moves `reverts` and `failed_invariants` out of the struct. - fn into_inner(self) -> (usize, BTreeMap, Function)>) { - (self.reverts, self.failed_invariants) - } -} diff --git a/crates/evm/src/fuzz/invariant/mod.rs b/crates/evm/src/fuzz/invariant/mod.rs deleted file mode 100644 index 1b6ed27ee3b5e..0000000000000 --- a/crates/evm/src/fuzz/invariant/mod.rs +++ /dev/null @@ -1,197 +0,0 @@ -//! Fuzzing support abstracted over the [`Evm`](crate::Evm) used -use crate::{ - fuzz::*, - trace::{load_contracts, TraceKind, Traces}, - CALLER, -}; -mod error; -pub use error::InvariantFuzzError; -mod filters; -pub use filters::{ArtifactFilters, SenderFilters}; -mod call_override; -pub use call_override::{set_up_inner_replay, RandomCallGenerator}; -use foundry_common::ContractsByArtifact; -mod executor; -use crate::executor::Executor; -use ethers::{ - abi::{Abi, Function}, - types::{Address, Bytes, U256}, -}; -pub use executor::{InvariantExecutor, InvariantFailures}; -use parking_lot::Mutex; -pub use proptest::test_runner::Config as FuzzConfig; -use std::{collections::BTreeMap, sync::Arc}; - -pub type TargetedContracts = BTreeMap)>; -pub type FuzzRunIdentifiedContracts = Arc>; - -/// (Sender, (TargetContract, Calldata)) -pub type BasicTxDetails = (Address, (Address, Bytes)); - -/// Test contract which is testing its invariants. -#[derive(Debug, Clone)] -pub struct InvariantContract<'a> { - /// Address of the test contract. - pub address: Address, - /// Invariant functions present in the test contract. - pub invariant_functions: Vec<&'a Function>, - /// Abi of the test contract. - pub abi: &'a Abi, -} - -/// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the -/// external `invariant_failures.failed_invariant` map and returns a generic error. -/// Returns the mapping of (Invariant Function Name -> Call Result). -pub fn assert_invariants( - invariant_contract: &InvariantContract, - executor: &Executor, - calldata: &[BasicTxDetails], - invariant_failures: &mut InvariantFailures, - shrink_sequence: bool, -) -> eyre::Result> { - let mut found_case = false; - let mut inner_sequence = vec![]; - - if let Some(ref fuzzer) = executor.inspector_config().fuzzer { - if let Some(ref call_generator) = fuzzer.call_generator { - inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); - } - } - - let mut call_results = BTreeMap::new(); - for func in &invariant_contract.invariant_functions { - let mut call_result = executor - .call_raw( - CALLER, - invariant_contract.address, - func.encode_input(&[]).expect("invariant should have no inputs").into(), - U256::zero(), - ) - .expect("EVM error"); - - let err = if call_result.reverted { - Some(*func) - } else { - // This will panic and get caught by the executor - if !executor.is_success( - invariant_contract.address, - call_result.reverted, - call_result.state_changeset.take().expect("we should have a state changeset"), - false, - ) { - Some(*func) - } else { - None - } - }; - - if let Some(broken_invariant) = err { - let invariant_error = invariant_failures - .failed_invariants - .get(&broken_invariant.name) - .expect("to have been initialized."); - - // We only care about invariants which we haven't broken yet. - if invariant_error.0.is_none() { - invariant_failures.failed_invariants.insert( - broken_invariant.name.clone(), - ( - Some(InvariantFuzzError::new( - invariant_contract, - Some(broken_invariant), - calldata, - call_result, - &inner_sequence, - shrink_sequence, - )), - broken_invariant.clone().to_owned(), - ), - ); - found_case = true; - } else { - call_results.insert(func.name.clone(), call_result); - } - } else { - call_results.insert(func.name.clone(), call_result); - } - } - - if found_case { - let before = invariant_failures.broken_invariants_count; - - invariant_failures.broken_invariants_count = invariant_failures - .failed_invariants - .iter() - .filter(|(_function, error)| error.0.is_some()) - .count(); - - eyre::bail!( - "{} new invariants have been broken.", - invariant_failures.broken_invariants_count - before - ); - } - - Ok(call_results) -} - -/// Replays the provided invariant run for collecting the logs and traces from all depths. -#[allow(clippy::too_many_arguments)] -pub fn replay_run( - invariant_contract: &InvariantContract, - mut executor: Executor, - known_contracts: Option<&ContractsByArtifact>, - mut ided_contracts: ContractsByAddress, - logs: &mut Vec, - traces: &mut Traces, - func: Function, - inputs: Vec, -) { - // We want traces for a failed case. - executor.set_tracing(true); - - // set_up_inner_replay(&mut executor, &inputs); - - // Replay each call from the sequence until we break the invariant. - for (sender, (addr, bytes)) in inputs.iter() { - let call_result = executor - .call_raw_committing(*sender, *addr, bytes.0.clone(), U256::zero()) - .expect("bad call to evm"); - - logs.extend(call_result.logs); - traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - - // Identify newly generated contracts, if they exist. - ided_contracts.extend(load_contracts( - vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], - known_contracts, - )); - - // Checks the invariant. - let error_call_result = executor - .call_raw( - CALLER, - invariant_contract.address, - func.encode_input(&[]).expect("invariant should have no inputs").into(), - U256::zero(), - ) - .expect("bad call to evm"); - - traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); - - logs.extend(error_call_result.logs); - } -} - -/// The outcome of an invariant fuzz test -#[derive(Debug)] -pub struct InvariantFuzzTestResult { - pub invariants: BTreeMap, Function)>, - /// Every successful fuzz test case - pub cases: Vec, - /// Number of reverted fuzz calls - pub reverts: usize, - - /// The entire inputs of the last run of the invariant campaign, used for - /// replaying the run for collecting traces. - pub last_run_inputs: Vec, -} diff --git a/crates/evm/src/fuzz/mod.rs b/crates/evm/src/fuzz/mod.rs deleted file mode 100644 index d8d4edbe64daa..0000000000000 --- a/crates/evm/src/fuzz/mod.rs +++ /dev/null @@ -1,446 +0,0 @@ -use crate::{ - coverage::HitMaps, - decode::{self, decode_console_logs}, - executor::{Executor, RawCallResult}, - trace::CallTraceArena, -}; -use error::{FuzzError, ASSUME_MAGIC_RETURN_CODE}; -use ethers::{ - abi::{Abi, Function, Token}, - types::{Address, Bytes, Log}, -}; -use eyre::Result; -use foundry_common::{calc, contracts::ContractsByAddress}; -use foundry_config::FuzzConfig; -pub use proptest::test_runner::Reason; -use proptest::test_runner::{TestCaseError, TestError, TestRunner}; -use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, collections::BTreeMap, fmt}; -use strategies::{ - build_initial_state, collect_state_from_call, fuzz_calldata, fuzz_calldata_from_state, - EvmFuzzState, -}; - -pub mod error; -pub mod invariant; -pub mod strategies; - -/// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`](https://docs.rs/proptest/1.0.0/proptest/). -/// -/// After instantiation, calling `fuzz` will proceed to hammer the deployed smart contract with -/// inputs, until it finds a counterexample. The provided [`TestRunner`] contains all the -/// configuration which can be overridden via [environment variables](https://docs.rs/proptest/1.0.0/proptest/test_runner/struct.Config.html) -pub struct FuzzedExecutor<'a> { - /// The VM - executor: &'a Executor, - /// The fuzzer - runner: TestRunner, - /// The account that calls tests - sender: Address, - /// The fuzz configuration - config: FuzzConfig, -} - -impl<'a> FuzzedExecutor<'a> { - /// Instantiates a fuzzed executor given a testrunner - pub fn new( - executor: &'a Executor, - runner: TestRunner, - sender: Address, - config: FuzzConfig, - ) -> Self { - Self { executor, runner, sender, config } - } - - /// Fuzzes the provided function, assuming it is available at the contract at `address` - /// If `should_fail` is set to `true`, then it will stop only when there's a success - /// test case. - /// - /// Returns a list of all the consumed gas and calldata of every fuzz case - pub fn fuzz( - &self, - func: &Function, - address: Address, - should_fail: bool, - errors: Option<&Abi>, - ) -> 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(); - - // Stores the last successful call trace - let traces: RefCell> = RefCell::default(); - - // Stores coverage information for all fuzz cases - let coverage: RefCell> = RefCell::default(); - - // Stores fuzz state for use with [fuzz_calldata_from_state] - let state: EvmFuzzState = if let Some(fork_db) = self.executor.backend().active_fork_db() { - build_initial_state(fork_db, &self.config.dictionary) - } else { - build_initial_state(self.executor.backend().mem_db(), &self.config.dictionary) - }; - - let mut weights = vec![]; - let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); - if self.config.dictionary.dictionary_weight < 100 { - weights.push((100 - dictionary_weight, fuzz_calldata(func.clone()))); - } - if dictionary_weight > 0 { - weights.push(( - self.config.dictionary.dictionary_weight, - fuzz_calldata_from_state(func.clone(), state.clone()), - )); - } - - let strat = proptest::strategy::Union::new_weighted(weights); - debug!(func = ?func.name, should_fail, "fuzzing"); - let run_result = self.runner.clone().run(&strat, |calldata| { - let call = self - .executor - .call_raw(self.sender, address, calldata.0.clone(), 0.into()) - .map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?; - let state_changeset = call - .state_changeset - .as_ref() - .ok_or_else(|| TestCaseError::fail(FuzzError::EmptyChangeset))?; - - // Build fuzzer state - collect_state_from_call( - &call.logs, - state_changeset, - state.clone(), - &self.config.dictionary, - ); - - // When assume cheat code is triggered return a special string "FOUNDRY::ASSUME" - if call.result.as_ref() == ASSUME_MAGIC_RETURN_CODE { - return Err(TestCaseError::reject(FuzzError::AssumeReject)) - } - - let success = self.executor.is_success( - address, - call.reverted, - state_changeset.clone(), - should_fail, - ); - - if success { - let mut first_case = first_case.borrow_mut(); - if first_case.is_none() { - first_case.replace(FuzzCase { - calldata, - gas: call.gas_used, - stipend: call.stipend, - }); - } - gas_by_case.borrow_mut().push((call.gas_used, call.stipend)); - - traces.replace(call.traces); - - if let Some(prev) = coverage.take() { - // Safety: If `Option::or` evaluates to `Some`, then `call.coverage` must - // necessarily also be `Some` - coverage.replace(Some(prev.merge(call.coverage.unwrap()))); - } else { - coverage.replace(call.coverage); - } - - Ok(()) - } else { - let status = call.exit_reason; - // We cannot use the calldata returned by the test runner in `TestError::Fail`, - // since that input represents the last run case, which may not correspond with our - // failure - when a fuzz case fails, proptest will try to run at least one more - // case to find a minimal failure case. - *counterexample.borrow_mut() = (calldata, call); - Err(TestCaseError::fail( - decode::decode_revert( - counterexample.borrow().1.result.as_ref(), - errors, - Some(status), - ) - .unwrap_or_default(), - )) - } - }); - - let (calldata, call) = counterexample.into_inner(); - let mut result = FuzzTestResult { - first_case: first_case.take().unwrap_or_default(), - gas_by_case: gas_by_case.take(), - success: run_result.is_ok(), - reason: None, - counterexample: None, - decoded_logs: decode_console_logs(&call.logs), - logs: call.logs, - labeled_addresses: call.labels, - traces: if run_result.is_ok() { traces.into_inner() } else { call.traces.clone() }, - coverage: coverage.into_inner(), - }; - - match run_result { - // Currently the only operation that can trigger proptest global rejects is the - // `vm.assume` cheatcode, thus we surface this info to the user when the fuzz test - // aborts due to too many global rejects, making the error message more actionable. - Err(TestError::Abort(reason)) if reason.message() == "Too many global rejects" => { - result.reason = Some( - FuzzError::TooManyRejects(self.runner.config().max_global_rejects).to_string(), - ); - } - Err(TestError::Abort(reason)) => { - result.reason = Some(reason.to_string()); - } - Err(TestError::Fail(reason, _)) => { - let reason = reason.to_string(); - result.reason = if reason.is_empty() { None } else { Some(reason) }; - - let args = func.decode_input(&calldata.as_ref()[4..]).unwrap_or_default(); - result.counterexample = Some(CounterExample::Single(BaseCounterExample { - sender: None, - addr: None, - signature: None, - contract_name: None, - traces: call.traces, - calldata, - args, - })); - } - _ => {} - } - - result - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum CounterExample { - /// Call used as a counter example for fuzz tests. - Single(BaseCounterExample), - /// Sequence of calls used as a counter example for invariant tests. - Sequence(Vec), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct BaseCounterExample { - /// Address which makes the call - pub sender: Option
, - /// Address to which to call to - pub addr: Option
, - /// The data to provide - pub calldata: Bytes, - /// Function signature if it exists - pub signature: Option, - /// Contract name if it exists - pub contract_name: Option, - /// Traces - pub traces: Option, - // Token does not implement Serde (lol), so we just serialize the calldata - #[serde(skip)] - pub args: Vec, -} - -impl BaseCounterExample { - pub fn create( - sender: Address, - addr: Address, - bytes: &Bytes, - contracts: &ContractsByAddress, - traces: Option, - ) -> Result { - let (name, abi) = &contracts.get(&addr).ok_or(FuzzError::UnknownContract)?; - - let func = abi - .functions() - .find(|f| f.short_signature() == bytes.0.as_ref()[0..4]) - .ok_or(FuzzError::UnknownFunction)?; - - // skip the function selector when decoding - let args = - func.decode_input(&bytes.0.as_ref()[4..]).map_err(|_| FuzzError::FailedDecodeInput)?; - - Ok(BaseCounterExample { - sender: Some(sender), - addr: Some(addr), - calldata: bytes.clone(), - signature: Some(func.signature()), - contract_name: Some(name.clone()), - traces, - args, - }) - } -} - -impl fmt::Display for BaseCounterExample { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let args = foundry_common::abi::format_tokens(&self.args).collect::>().join(", "); - - if let Some(sender) = self.sender { - write!(f, "sender={sender:?} addr=")? - } - - if let Some(name) = &self.contract_name { - write!(f, "[{name}]")? - } - - if let Some(addr) = &self.addr { - write!(f, "{addr:?} ")? - } - - if let Some(sig) = &self.signature { - write!(f, "calldata={}", &sig)? - } else { - write!(f, "calldata=0x{}", hex::encode(&self.calldata))? - } - - write!(f, ", args=[{args}]") - } -} - -/// The outcome of a fuzz test -#[derive(Debug)] -pub struct FuzzTestResult { - /// we keep this for the debugger - pub first_case: FuzzCase, - /// Gas usage (gas_used, call_stipend) per cases - pub gas_by_case: Vec<(u64, u64)>, - /// Whether the test case was successful. This means that the transaction executed - /// properly, or that there was a revert and that the test was expected to fail - /// (prefixed with `testFail`) - pub success: bool, - - /// If there was a revert, this field will be populated. Note that the test can - /// still be successful (i.e self.success == true) when it's expected to fail. - pub reason: Option, - - /// Minimal reproduction test case for failing fuzz tests - pub counterexample: Option, - - /// Any captured & parsed as strings logs along the test's execution which should - /// 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: BTreeMap, - - /// Exemplary traces for a fuzz run of the test function - /// - /// **Note** We only store a single trace of a successful fuzz call, otherwise we would get - /// `num(fuzz_cases)` traces, one for each run, which is neither helpful nor performant. - pub traces: Option, - - /// Raw coverage info - pub coverage: Option, -} - -impl FuzzTestResult { - /// Returns the median gas of all test cases - pub fn median_gas(&self, with_stipend: bool) -> u64 { - let mut values = self.gas_values(with_stipend); - values.sort_unstable(); - calc::median_sorted(&values) - } - - /// Returns the average gas use of all test cases - pub fn mean_gas(&self, with_stipend: bool) -> u64 { - let mut values = self.gas_values(with_stipend); - values.sort_unstable(); - calc::mean(&values).as_u64() - } - - fn gas_values(&self, with_stipend: bool) -> Vec { - self.gas_by_case - .iter() - .map(|gas| if with_stipend { gas.0 } else { gas.0.saturating_sub(gas.1) }) - .collect() - } -} - -/// Container type for all successful test cases -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct FuzzedCases { - cases: Vec, -} - -impl FuzzedCases { - pub fn new(mut cases: Vec) -> Self { - cases.sort_by_key(|c| c.gas); - Self { cases } - } - - pub fn cases(&self) -> &[FuzzCase] { - &self.cases - } - - pub fn into_cases(self) -> Vec { - self.cases - } - - /// Get the last [FuzzCase] - pub fn last(&self) -> Option<&FuzzCase> { - self.cases.last() - } - - /// Returns the median gas of all test cases - pub fn median_gas(&self, with_stipend: bool) -> u64 { - let mut values = self.gas_values(with_stipend); - values.sort_unstable(); - calc::median_sorted(&values) - } - - /// Returns the average gas use of all test cases - pub fn mean_gas(&self, with_stipend: bool) -> u64 { - let mut values = self.gas_values(with_stipend); - values.sort_unstable(); - calc::mean(&values).as_u64() - } - - fn gas_values(&self, with_stipend: bool) -> Vec { - self.cases - .iter() - .map(|c| if with_stipend { c.gas } else { c.gas.saturating_sub(c.stipend) }) - .collect() - } - - /// Returns the case with the highest gas usage - pub fn highest(&self) -> Option<&FuzzCase> { - self.cases.last() - } - - /// Returns the case with the lowest gas usage - pub fn lowest(&self) -> Option<&FuzzCase> { - self.cases.first() - } - - /// Returns the highest amount of gas spent on a fuzz case - pub fn highest_gas(&self, with_stipend: bool) -> u64 { - self.highest() - .map(|c| if with_stipend { c.gas } else { c.gas - c.stipend }) - .unwrap_or_default() - } - - /// Returns the lowest amount of gas spent on a fuzz case - pub fn lowest_gas(&self) -> u64 { - self.lowest().map(|c| c.gas).unwrap_or_default() - } -} - -/// Data of a single fuzz test case -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -pub struct FuzzCase { - /// The calldata used for this fuzz test - pub calldata: Bytes, - /// Consumed gas - pub gas: u64, - /// The initial gas stipend for the transaction - pub stipend: u64, -} diff --git a/crates/evm/src/fuzz/strategies/calldata.rs b/crates/evm/src/fuzz/strategies/calldata.rs deleted file mode 100644 index b8499dc003d03..0000000000000 --- a/crates/evm/src/fuzz/strategies/calldata.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::fuzz_param; -use ethers::{abi::Function, types::Bytes}; -use proptest::prelude::{BoxedStrategy, Strategy}; - -/// Given a function, it returns a strategy which generates valid calldata -/// for that function's input types. -pub fn fuzz_calldata(func: Function) -> BoxedStrategy { - // We need to compose all the strategies generated for each parameter in all - // possible combinations - let strats = func.inputs.iter().map(|input| fuzz_param(&input.kind)).collect::>(); - - strats - .prop_map(move |tokens| { - trace!(input = ?tokens); - func.encode_input(&tokens).unwrap().into() - }) - .boxed() -} diff --git a/crates/evm/src/fuzz/strategies/invariants.rs b/crates/evm/src/fuzz/strategies/invariants.rs deleted file mode 100644 index 4b9d2f458c3ed..0000000000000 --- a/crates/evm/src/fuzz/strategies/invariants.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::fuzz::{ - fuzz_calldata, fuzz_calldata_from_state, - invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, SenderFilters}, - strategies::fuzz_param, - EvmFuzzState, -}; -use ethers::{ - abi::{Abi, Function, ParamType}, - types::{Address, Bytes}, -}; -use parking_lot::RwLock; -use proptest::prelude::*; -pub use proptest::test_runner::Config as FuzzConfig; -use std::{rc::Rc, sync::Arc}; - -use super::fuzz_param_from_state; - -/// Given a target address, we generate random calldata. -pub fn override_call_strat( - fuzz_state: EvmFuzzState, - contracts: FuzzRunIdentifiedContracts, - target: Arc>, -) -> SBoxedStrategy<(Address, Bytes)> { - let contracts_ref = contracts.clone(); - - let random_contract = any::() - .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())); - let target = any::().prop_map(move |_| *target.read()); - - proptest::strategy::Union::new_weighted(vec![ - (80, target.sboxed()), - (20, random_contract.sboxed()), - ]) - .prop_flat_map(move |target_address| { - let fuzz_state = fuzz_state.clone(); - let (_, abi, functions) = contracts.lock().get(&target_address).unwrap().clone(); - - let func = select_random_function(abi, functions); - func.prop_flat_map(move |func| { - fuzz_contract_with_calldata(fuzz_state.clone(), target_address, func) - }) - }) - .sboxed() -} - -/// Creates the invariant strategy. -/// -/// Given the known and future contracts, it generates the next call by fuzzing the `caller`, -/// `calldata` and `target`. The generated data is evaluated lazily for every single call to fully -/// leverage the evolving fuzz dictionary. -/// -/// The fuzzed parameters can be filtered through different methods implemented in the test -/// contract: -/// -/// `targetContracts()`, `targetSenders()`, `excludeContracts()`, `targetSelectors()` -pub fn invariant_strat( - fuzz_state: EvmFuzzState, - senders: SenderFilters, - contracts: FuzzRunIdentifiedContracts, - dictionary_weight: u32, -) -> impl Strategy> { - // We only want to seed the first value, since we want to generate the rest as we mutate the - // state - generate_call(fuzz_state, senders, contracts, dictionary_weight).prop_map(|x| vec![x]) -} - -/// Strategy to generate a transaction where the `sender`, `target` and `calldata` are all generated -/// through specific strategies. -fn generate_call( - fuzz_state: EvmFuzzState, - senders: SenderFilters, - contracts: FuzzRunIdentifiedContracts, - dictionary_weight: u32, -) -> BoxedStrategy { - let random_contract = select_random_contract(contracts); - let senders = Rc::new(senders); - random_contract - .prop_flat_map(move |(contract, abi, functions)| { - let func = select_random_function(abi, functions); - let senders = senders.clone(); - let fuzz_state = fuzz_state.clone(); - func.prop_flat_map(move |func| { - let sender = - select_random_sender(fuzz_state.clone(), senders.clone(), dictionary_weight); - (sender, fuzz_contract_with_calldata(fuzz_state.clone(), contract, func)) - }) - }) - .boxed() -} - -/// Strategy to select a sender address: -/// * If `senders` is empty, then it's either a random address (10%) or from the dictionary (90%). -/// * If `senders` is not empty, a random address is chosen from the list of senders. -fn select_random_sender( - fuzz_state: EvmFuzzState, - senders: Rc, - dictionary_weight: u32, -) -> BoxedStrategy
{ - let senders_ref = senders.clone(); - let fuzz_strategy = proptest::strategy::Union::new_weighted(vec![ - ( - 100 - dictionary_weight, - fuzz_param(&ParamType::Address) - .prop_map(move |addr| addr.into_address().unwrap()) - .boxed(), - ), - ( - dictionary_weight, - fuzz_param_from_state(&ParamType::Address, fuzz_state) - .prop_map(move |addr| addr.into_address().unwrap()) - .boxed(), - ), - ]) - // Too many exclusions can slow down testing. - .prop_filter("senders not allowed", move |addr| !senders_ref.excluded.contains(addr)) - .boxed(); - - if !senders.targeted.is_empty() { - any::() - .prop_map(move |selector| *selector.select(&*senders.targeted)) - .boxed() - } else { - fuzz_strategy - } -} - -/// Strategy to randomly select a contract from the `contracts` list that has at least 1 function -fn select_random_contract( - contracts: FuzzRunIdentifiedContracts, -) -> impl Strategy)> { - let selectors = any::(); - - selectors.prop_map(move |selector| { - let contracts = contracts.lock(); - let (addr, (_, abi, functions)) = - selector.select(contracts.iter().filter(|(_, (_, abi, _))| !abi.functions.is_empty())); - (*addr, abi.clone(), functions.clone()) - }) -} - -/// Strategy to select a random mutable function from the abi. -/// -/// If `targeted_functions` is not empty, select one from it. Otherwise, take any -/// of the available abi functions. -fn select_random_function(abi: Abi, targeted_functions: Vec) -> BoxedStrategy { - let selectors = any::(); - let possible_funcs: Vec = abi - .functions() - .filter(|func| { - !matches!( - func.state_mutability, - ethers::abi::StateMutability::Pure | ethers::abi::StateMutability::View - ) - }) - .cloned() - .collect(); - - let total_random = selectors.prop_map(move |selector| { - let func = selector.select(&possible_funcs); - func.clone() - }); - - if !targeted_functions.is_empty() { - let selector = any::() - .prop_map(move |selector| selector.select(targeted_functions.clone())); - - selector.boxed() - } else { - total_random.boxed() - } -} - -/// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata -/// for that function's input types. -pub fn fuzz_contract_with_calldata( - fuzz_state: EvmFuzzState, - contract: Address, - func: Function, -) -> impl Strategy { - // We need to compose all the strategies generated for each parameter in all - // possible combinations - - // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning - #[allow(clippy::arc_with_non_send_sync)] - let strats = prop_oneof![ - 60 => fuzz_calldata(func.clone()), - 40 => fuzz_calldata_from_state(func, fuzz_state), - ]; - strats.prop_map(move |calldata| { - trace!(input = ?calldata); - (contract, calldata) - }) -} diff --git a/crates/evm/src/fuzz/strategies/mod.rs b/crates/evm/src/fuzz/strategies/mod.rs deleted file mode 100644 index 8add232c89532..0000000000000 --- a/crates/evm/src/fuzz/strategies/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod int; -mod uint; -pub use int::IntStrategy; -pub use uint::UintStrategy; - -mod param; -pub use param::{fuzz_param, fuzz_param_from_state}; - -mod calldata; -pub use calldata::fuzz_calldata; - -mod state; -pub use state::{ - build_initial_state, collect_created_contracts, collect_state_from_call, - fuzz_calldata_from_state, EvmFuzzState, -}; - -mod invariants; -pub use invariants::*; diff --git a/crates/evm/src/fuzz/strategies/param.rs b/crates/evm/src/fuzz/strategies/param.rs deleted file mode 100644 index f251466971418..0000000000000 --- a/crates/evm/src/fuzz/strategies/param.rs +++ /dev/null @@ -1,149 +0,0 @@ -use super::state::EvmFuzzState; -use ethers::{ - abi::{ParamType, Token, Tokenizable}, - types::{Address, Bytes, H160, I256, U256}, -}; -use proptest::prelude::*; - -/// The max length of arrays we fuzz for is 256. -pub const MAX_ARRAY_LEN: usize = 256; - -/// Given a parameter type, returns a strategy for generating values for that type. -/// -/// Works with ABI Encoder v2 tuples. -pub fn fuzz_param(param: &ParamType) -> BoxedStrategy { - match param { - ParamType::Address => { - // The key to making this work is the `boxed()` call which type erases everything - // https://altsysrq.github.io/proptest-book/proptest/tutorial/transforming-strategies.html - any::<[u8; 20]>().prop_map(|x| H160(x).into_token()).boxed() - } - ParamType::Bytes => any::>().prop_map(|x| Bytes::from(x).into_token()).boxed(), - ParamType::Int(n) => { - super::IntStrategy::new(*n, vec![]).prop_map(|x| x.into_token()).boxed() - } - ParamType::Uint(n) => { - super::UintStrategy::new(*n, vec![]).prop_map(|x| x.into_token()).boxed() - } - ParamType::Bool => any::().prop_map(|x| x.into_token()).boxed(), - ParamType::String => any::>() - .prop_map(|x| Token::String(unsafe { String::from_utf8_unchecked(x) })) - .boxed(), - ParamType::Array(param) => proptest::collection::vec(fuzz_param(param), 0..MAX_ARRAY_LEN) - .prop_map(Token::Array) - .boxed(), - ParamType::FixedBytes(size) => { - prop::collection::vec(any::(), *size).prop_map(Token::FixedBytes).boxed() - } - ParamType::FixedArray(param, size) => { - prop::collection::vec(fuzz_param(param), *size).prop_map(Token::FixedArray).boxed() - } - ParamType::Tuple(params) => { - params.iter().map(fuzz_param).collect::>().prop_map(Token::Tuple).boxed() - } - } -} - -/// Given a parameter type, returns a strategy for generating values for that type, given some EVM -/// fuzz state. -/// -/// Works with ABI Encoder v2 tuples. -pub fn fuzz_param_from_state(param: &ParamType, arc_state: EvmFuzzState) -> BoxedStrategy { - // These are to comply with lifetime requirements - let state_len = arc_state.read().values().len(); - - // Select a value from the state - let st = arc_state.clone(); - let value = any::() - .prop_map(move |index| index.index(state_len)) - .prop_map(move |index| *st.read().values().iter().nth(index).unwrap()); - - // Convert the value based on the parameter type - match param { - ParamType::Address => { - value.prop_map(move |value| Address::from_slice(&value[12..]).into_token()).boxed() - } - ParamType::Bytes => value.prop_map(move |value| Bytes::from(value).into_token()).boxed(), - ParamType::Int(n) => match n / 8 { - 32 => { - value.prop_map(move |value| I256::from_raw(U256::from(value)).into_token()).boxed() - } - y @ 1..=31 => value - .prop_map(move |value| { - // Generate a uintN in the correct range, then shift it to the range of intN - // by subtracting 2^(N-1) - let uint = U256::from(value) % U256::from(2usize).pow(U256::from(y * 8)); - let max_int_plus1 = U256::from(2usize).pow(U256::from(y * 8 - 1)); - let num = I256::from_raw(uint.overflowing_sub(max_int_plus1).0); - num.into_token() - }) - .boxed(), - _ => panic!("unsupported solidity type int{n}"), - }, - ParamType::Uint(n) => match n / 8 { - 32 => value.prop_map(move |value| U256::from(value).into_token()).boxed(), - y @ 1..=31 => value - .prop_map(move |value| { - (U256::from(value) % (U256::from(2usize).pow(U256::from(y * 8)))).into_token() - }) - .boxed(), - _ => panic!("unsupported solidity type uint{n}"), - }, - ParamType::Bool => value.prop_map(move |value| Token::Bool(value[31] == 1)).boxed(), - ParamType::String => value - .prop_map(move |value| { - Token::String( - String::from_utf8_lossy(&value[..]).trim().trim_end_matches('\0').to_string(), - ) - }) - .boxed(), - ParamType::Array(param) => { - proptest::collection::vec(fuzz_param_from_state(param, arc_state), 0..MAX_ARRAY_LEN) - .prop_map(Token::Array) - .boxed() - } - ParamType::FixedBytes(size) => { - let size = *size; - value.prop_map(move |value| Token::FixedBytes(value[32 - size..].to_vec())).boxed() - } - ParamType::FixedArray(param, size) => { - let fixed_size = *size; - proptest::collection::vec(fuzz_param_from_state(param, arc_state), fixed_size) - .prop_map(Token::FixedArray) - .boxed() - } - ParamType::Tuple(params) => params - .iter() - .map(|p| fuzz_param_from_state(p, arc_state.clone())) - .collect::>() - .prop_map(Token::Tuple) - .boxed(), - } -} - -#[cfg(test)] -mod tests { - use crate::fuzz::strategies::{build_initial_state, fuzz_calldata, fuzz_calldata_from_state}; - use ethers::abi::HumanReadableParser; - use foundry_config::FuzzDictionaryConfig; - use revm::db::{CacheDB, EmptyDB}; - - #[test] - fn can_fuzz_array() { - let f = "function testArray(uint64[2] calldata values)"; - let func = HumanReadableParser::parse_function(f).unwrap(); - - let db = CacheDB::new(EmptyDB::default()); - let state = build_initial_state(&db, &FuzzDictionaryConfig::default()); - - let strat = proptest::strategy::Union::new_weighted(vec![ - (60, fuzz_calldata(func.clone())), - (40, fuzz_calldata_from_state(func, state)), - ]); - - let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; - let mut runner = proptest::test_runner::TestRunner::new(cfg); - - let _ = runner.run(&strat, |_| Ok(())); - } -} diff --git a/crates/evm/src/fuzz/strategies/state.rs b/crates/evm/src/fuzz/strategies/state.rs deleted file mode 100644 index 67c6c569e4e1c..0000000000000 --- a/crates/evm/src/fuzz/strategies/state.rs +++ /dev/null @@ -1,283 +0,0 @@ -use super::fuzz_param_from_state; -use crate::{ - executor::StateChangeset, - fuzz::invariant::{ArtifactFilters, FuzzRunIdentifiedContracts}, - utils::{self, b160_to_h160, ru256_to_u256}, -}; -use bytes::Bytes; -use ethers::{ - abi::Function, - types::{Address, Log, H256, U256}, -}; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_config::FuzzDictionaryConfig; -use hashbrown::HashSet; -use parking_lot::RwLock; -use proptest::prelude::{BoxedStrategy, Strategy}; -use revm::{ - db::{CacheDB, DatabaseRef}, - interpreter::opcode::{self, spec_opcode_gas}, - primitives::SpecId, -}; -use std::{io::Write, sync::Arc}; - -/// A set of arbitrary 32 byte data from the VM used to generate values for the strategy. -/// -/// Wrapped in a shareable container. -pub type EvmFuzzState = Arc>; - -#[derive(Debug, Default)] -pub struct FuzzDictionary { - /// Collected state values. - state_values: HashSet<[u8; 32]>, - /// Addresses that already had their PUSH bytes collected. - addresses: HashSet
, -} - -impl FuzzDictionary { - #[inline] - pub fn values(&self) -> &HashSet<[u8; 32]> { - &self.state_values - } - - #[inline] - pub fn values_mut(&mut self) -> &mut HashSet<[u8; 32]> { - &mut self.state_values - } - - #[inline] - pub fn addresses(&mut self) -> &HashSet
{ - &self.addresses - } - - #[inline] - pub fn addresses_mut(&mut self) -> &mut HashSet
{ - &mut self.addresses - } -} - -/// Given a function and some state, it returns a strategy which generated valid calldata for the -/// given function's input types, based on state taken from the EVM. -pub fn fuzz_calldata_from_state( - func: Function, - state: EvmFuzzState, -) -> BoxedStrategy { - let strats = func - .inputs - .iter() - .map(|input| fuzz_param_from_state(&input.kind, state.clone())) - .collect::>(); - - strats - .prop_map(move |tokens| { - func.encode_input(&tokens) - .unwrap_or_else(|_| { - panic!( - r#"Fuzzer generated invalid tokens {:?} for function `{}` inputs {:?} -This is a bug, please open an issue: https://github.com/foundry-rs/foundry/issues"#, - tokens, func.name, func.inputs - ) - }) - .into() - }) - .no_shrink() - .boxed() -} - -/// Builds the initial [EvmFuzzState] from a database. -pub fn build_initial_state( - db: &CacheDB, - config: &FuzzDictionaryConfig, -) -> EvmFuzzState { - let mut state = FuzzDictionary::default(); - - for (address, account) in db.accounts.iter() { - let address: Address = (*address).into(); - // Insert basic account information - state.values_mut().insert(H256::from(address).into()); - - // Insert push bytes - if config.include_push_bytes { - if let Some(code) = &account.info.code { - if state.addresses_mut().insert(address) { - for push_byte in collect_push_bytes(code.bytes().clone()) { - state.values_mut().insert(push_byte); - } - } - } - } - - if config.include_storage { - // Insert storage - for (slot, value) in &account.storage { - let slot = (*slot).into(); - let value = (*value).into(); - state.values_mut().insert(utils::u256_to_h256_be(slot).into()); - state.values_mut().insert(utils::u256_to_h256_be(value).into()); - // also add the value below and above the storage value to the dictionary. - if value != U256::zero() { - let below_value = value - U256::one(); - state.values_mut().insert(utils::u256_to_h256_be(below_value).into()); - } - if value != U256::max_value() { - let above_value = value + U256::one(); - state.values_mut().insert(utils::u256_to_h256_be(above_value).into()); - } - } - } - } - - // need at least some state data if db is empty otherwise we can't select random data for state - // fuzzing - if state.values().is_empty() { - // prefill with a random addresses - state.values_mut().insert(H256::from(Address::random()).into()); - } - - Arc::new(RwLock::new(state)) -} - -/// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to the -/// given [FuzzDictionaryConfig]. -pub fn collect_state_from_call( - logs: &[Log], - state_changeset: &StateChangeset, - state: EvmFuzzState, - config: &FuzzDictionaryConfig, -) { - let mut state = state.write(); - - for (address, account) in state_changeset { - // Insert basic account information - state.values_mut().insert(H256::from(b160_to_h160(*address)).into()); - - if config.include_push_bytes && state.addresses.len() < config.max_fuzz_dictionary_addresses - { - // Insert push bytes - if let Some(code) = &account.info.code { - if state.addresses_mut().insert(b160_to_h160(*address)) { - for push_byte in collect_push_bytes(code.bytes().clone()) { - state.values_mut().insert(push_byte); - } - } - } - } - - if config.include_storage && state.state_values.len() < config.max_fuzz_dictionary_values { - // Insert storage - for (slot, value) in &account.storage { - let slot = (*slot).into(); - let value = ru256_to_u256(value.present_value()); - state.values_mut().insert(utils::u256_to_h256_be(slot).into()); - state.values_mut().insert(utils::u256_to_h256_be(value).into()); - // also add the value below and above the storage value to the dictionary. - if value != U256::zero() { - let below_value = value - U256::one(); - state.values_mut().insert(utils::u256_to_h256_be(below_value).into()); - } - if value != U256::max_value() { - let above_value = value + U256::one(); - state.values_mut().insert(utils::u256_to_h256_be(above_value).into()); - } - } - } else { - return - } - - // Insert log topics and data - for log in logs { - log.topics.iter().for_each(|topic| { - state.values_mut().insert(topic.0); - }); - log.data.0.chunks(32).for_each(|chunk| { - let mut buffer: [u8; 32] = [0; 32]; - let _ = (&mut buffer[..]) - .write(chunk) - .expect("log data chunk was larger than 32 bytes"); - state.values_mut().insert(buffer); - }); - } - } -} - -/// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB). -/// -/// This is to limit the performance impact of fuzz tests that might deploy arbitrarily sized -/// bytecode (as is the case with Solmate). -const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024; - -/// Collects all push bytes from the given bytecode. -fn collect_push_bytes(code: Bytes) -> Vec<[u8; 32]> { - let mut bytes: Vec<[u8; 32]> = Vec::new(); - - // We use [SpecId::LATEST] since we do not really care what spec it is - we are not interested - // in gas costs. - let opcode_infos = spec_opcode_gas(SpecId::LATEST); - - let mut i = 0; - while i < code.len().min(PUSH_BYTE_ANALYSIS_LIMIT) { - let op = code[i]; - if opcode_infos[op as usize].is_push() { - let push_size = (op - opcode::PUSH1 + 1) as usize; - let push_start = i + 1; - let push_end = push_start + push_size; - - // As a precaution, if a fuzz test deploys malformed bytecode (such as using `CREATE2`) - // this will terminate the loop early. - if push_start > code.len() || push_end > code.len() { - return bytes - } - - let push_value = U256::from_big_endian(&code[push_start..push_end]); - bytes.push(push_value.into()); - // also add the value below and above the push value to the dictionary. - if push_value != U256::zero() { - bytes.push((push_value - U256::one()).into()); - } - if push_value != U256::max_value() { - bytes.push((push_value + U256::one()).into()); - } - - i += push_size; - } - i += 1; - } - - bytes -} - -/// Collects all created contracts from a StateChangeset which haven't been discovered yet. Stores -/// them at `targeted_contracts` and `created_contracts`. -pub fn collect_created_contracts( - state_changeset: &StateChangeset, - project_contracts: &ContractsByArtifact, - setup_contracts: &ContractsByAddress, - artifact_filters: &ArtifactFilters, - targeted_contracts: FuzzRunIdentifiedContracts, - created_contracts: &mut Vec
, -) -> eyre::Result<()> { - let mut writable_targeted = targeted_contracts.lock(); - - for (address, account) in state_changeset { - if !setup_contracts.contains_key(&b160_to_h160(*address)) { - if let (true, Some(code)) = (&account.is_touched, &account.info.code) { - if !code.is_empty() { - if let Some((artifact, (abi, _))) = project_contracts.find_by_code(code.bytes()) - { - if let Some(functions) = - artifact_filters.get_targeted_functions(artifact, abi)? - { - created_contracts.push(b160_to_h160(*address)); - writable_targeted.insert( - b160_to_h160(*address), - (artifact.name.clone(), abi.clone(), functions), - ); - } - } - } - } - } - } - - Ok(()) -} diff --git a/crates/evm/src/lib.rs b/crates/evm/src/lib.rs deleted file mode 100644 index 1d4d04ce89ad1..0000000000000 --- a/crates/evm/src/lib.rs +++ /dev/null @@ -1,110 +0,0 @@ -#![warn(unused_crate_dependencies)] - -#[macro_use] -extern crate tracing; - -/// Decoding helpers -pub mod decode; - -/// Call tracing -/// Contains a call trace arena, decoding and formatting utilities -pub mod trace; - -/// Debugger data structures -pub mod debug; - -/// Coverage data structures -pub mod coverage; - -/// Forge test execution backends -pub mod executor; - -use ethers::types::{ActionType, CallType, H160}; -pub use executor::abi; - -/// Fuzzing wrapper for executors -pub mod fuzz; - -/// utils for working with revm -pub mod utils; - -// Re-exports -pub use ethers::types::Address; -pub use hashbrown; -use revm::interpreter::{CallScheme, CreateScheme}; -pub use revm::{self, primitives::HashMap}; -use serde::{Deserialize, Serialize}; - -/// Stores the caller address to be used as _sender_ account for: -/// - deploying Test contracts -/// - deploying Script contracts -/// -/// The address was derived from `address(uint160(uint256(keccak256("foundry default caller"))))` -/// and is equal to 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38. -pub const CALLER: Address = H160([ - 0x18, 0x04, 0xc8, 0xAB, 0x1F, 0x12, 0xE6, 0xbb, 0xF3, 0x89, 0x4D, 0x40, 0x83, 0xF3, 0x3E, 0x07, - 0x30, 0x9D, 0x1F, 0x38, -]); - -/// Stores the default test contract address: 0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84 -pub const TEST_CONTRACT_ADDRESS: Address = H160([ - 180, 199, 157, 171, 143, 37, 156, 122, 238, 110, 91, 42, 167, 41, 130, 24, 100, 34, 126, 132, -]); - -#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -#[derive(Default)] -pub enum CallKind { - #[default] - Call, - StaticCall, - CallCode, - DelegateCall, - Create, - Create2, -} - -impl From for CallKind { - fn from(scheme: CallScheme) -> Self { - match scheme { - CallScheme::Call => CallKind::Call, - CallScheme::StaticCall => CallKind::StaticCall, - CallScheme::CallCode => CallKind::CallCode, - CallScheme::DelegateCall => CallKind::DelegateCall, - } - } -} - -impl From for CallKind { - fn from(create: CreateScheme) -> Self { - match create { - CreateScheme::Create => CallKind::Create, - CreateScheme::Create2 { .. } => CallKind::Create2, - } - } -} - -impl From for ActionType { - fn from(kind: CallKind) -> Self { - match kind { - CallKind::Call | CallKind::StaticCall | CallKind::DelegateCall | CallKind::CallCode => { - ActionType::Call - } - CallKind::Create => ActionType::Create, - CallKind::Create2 => ActionType::Create, - } - } -} - -impl From for CallType { - fn from(ty: CallKind) -> Self { - match ty { - CallKind::Call => CallType::Call, - CallKind::StaticCall => CallType::StaticCall, - CallKind::CallCode => CallType::CallCode, - CallKind::DelegateCall => CallType::DelegateCall, - CallKind::Create => CallType::None, - CallKind::Create2 => CallType::None, - } - } -} diff --git a/crates/evm/src/trace/decoder.rs b/crates/evm/src/trace/decoder.rs deleted file mode 100644 index f8e8ae651a1c3..0000000000000 --- a/crates/evm/src/trace/decoder.rs +++ /dev/null @@ -1,353 +0,0 @@ -use super::{ - identifier::{SingleSignaturesIdentifier, TraceIdentifier}, - CallTraceArena, RawOrDecodedCall, RawOrDecodedLog, RawOrDecodedReturnData, -}; -use crate::{ - abi::{CHEATCODE_ADDRESS, CONSOLE_ABI, HARDHAT_CONSOLE_ABI, HARDHAT_CONSOLE_ADDRESS, HEVM_ABI}, - decode, - executor::inspector::DEFAULT_CREATE2_DEPLOYER, - trace::{node::CallTraceNode, utils}, - CALLER, TEST_CONTRACT_ADDRESS, -}; -use ethers::{ - abi::{Abi, Address, Event, Function, Param, ParamType, Token}, - types::{H160, H256}, -}; -use foundry_common::{abi::get_indexed_event, SELECTOR_LEN}; -use hashbrown::HashSet; -use std::collections::{BTreeMap, HashMap}; - -/// Build a new [CallTraceDecoder]. -#[derive(Default)] -pub struct CallTraceDecoderBuilder { - decoder: CallTraceDecoder, -} - -impl CallTraceDecoderBuilder { - pub fn new() -> Self { - Self { decoder: CallTraceDecoder::new() } - } - - /// Add known labels to the decoder. - pub fn with_labels(mut self, labels: impl IntoIterator) -> Self { - self.decoder.labels.extend(labels); - self - } - - /// Add known events to the decoder. - pub fn with_events(mut self, events: impl IntoIterator) -> Self { - for event in events { - self.decoder - .events - .entry((event.signature(), indexed_inputs(&event))) - .or_default() - .push(event); - } - self - } - - /// Sets the verbosity level of the decoder. - pub fn with_verbosity(mut self, level: u8) -> Self { - self.decoder.verbosity = level; - self - } - - /// Build the decoder. - pub fn build(self) -> CallTraceDecoder { - self.decoder - } -} - -/// The call trace decoder. -/// -/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it -/// then uses to decode the call trace. -/// -/// Note that a call trace decoder is required for each new set of traces, since addresses in -/// different sets might overlap. -#[derive(Default, Debug)] -pub struct CallTraceDecoder { - /// Information for decoding precompile calls. - pub precompiles: HashMap, - /// Addresses identified to be a specific contract. - /// - /// The values are in the form `":"`. - pub contracts: HashMap, - /// Address labels - pub labels: HashMap, - /// Information whether the contract address has a receive function - pub receive_contracts: HashMap, - /// A mapping of signatures to their known functions - pub functions: BTreeMap<[u8; 4], Vec>, - /// All known events - pub events: BTreeMap<(H256, usize), Vec>, - /// All known errors - pub errors: Abi, - /// A signature identifier for events and functions. - pub signature_identifier: Option, - /// Verbosity level - pub verbosity: u8, -} - -/// Returns an expression of the type `[(Address, Function); N]` -macro_rules! precompiles { - ($($number:literal : $name:ident($( $name_in:ident : $in:expr ),* $(,)?) -> ($( $name_out:ident : $out:expr ),* $(,)?)),+ $(,)?) => {{ - use std::string::String as RustString; - use ParamType::*; - [$( - ( - H160([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, $number]), - #[allow(deprecated)] - Function { - name: RustString::from(stringify!($name)), - inputs: vec![$(Param { name: RustString::from(stringify!($name_in)), kind: $in, internal_type: None, }),*], - outputs: vec![$(Param { name: RustString::from(stringify!($name_out)), kind: $out, internal_type: None, }),*], - constant: None, - state_mutability: ethers::abi::StateMutability::Pure, - }, - ), - )+] - }}; -} - -impl CallTraceDecoder { - /// Creates a new call trace decoder. - /// - /// The call trace decoder always knows how to decode calls to the cheatcode address, as well - /// as DSTest-style logs. - pub fn new() -> Self { - Self { - // TODO: These are the Ethereum precompiles. We should add a way to support precompiles - // for other networks, too. - precompiles: precompiles!( - 0x01: ecrecover(hash: FixedBytes(32), v: Uint(256), r: Uint(256), s: Uint(256)) -> (publicAddress: Address), - 0x02: sha256(data: Bytes) -> (hash: FixedBytes(32)), - 0x03: ripemd(data: Bytes) -> (hash: FixedBytes(32)), - 0x04: identity(data: Bytes) -> (data: Bytes), - 0x05: modexp(Bsize: Uint(256), Esize: Uint(256), Msize: Uint(256), BEM: Bytes) -> (value: Bytes), - 0x06: ecadd(x1: Uint(256), y1: Uint(256), x2: Uint(256), y2: Uint(256)) -> (x: Uint(256), y: Uint(256)), - 0x07: ecmul(x1: Uint(256), y1: Uint(256), s: Uint(256)) -> (x: Uint(256), y: Uint(256)), - 0x08: ecpairing(x1: Uint(256), y1: Uint(256), x2: Uint(256), y2: Uint(256), x3: Uint(256), y3: Uint(256)) -> (success: Uint(256)), - 0x09: blake2f(rounds: Uint(4), h: FixedBytes(64), m: FixedBytes(128), t: FixedBytes(16), f: FixedBytes(1)) -> (h: FixedBytes(64)), - ).into(), - - contracts: Default::default(), - - labels: [ - (CHEATCODE_ADDRESS, "VM".to_string()), - (HARDHAT_CONSOLE_ADDRESS, "console".to_string()), - (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()), - (CALLER, "DefaultSender".to_string()), - (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()), - ] - .into(), - - functions: HARDHAT_CONSOLE_ABI - .functions() - .chain(HEVM_ABI.functions()) - .map(|func| (func.short_signature(), vec![func.clone()])) - .collect(), - - events: CONSOLE_ABI - .events() - .map(|event| ((event.signature(), indexed_inputs(event)), vec![event.clone()])) - .collect(), - - errors: Default::default(), - signature_identifier: None, - receive_contracts: Default::default(), - verbosity: 0, - } - } - - pub fn add_signature_identifier(&mut self, identifier: SingleSignaturesIdentifier) { - self.signature_identifier = Some(identifier); - } - - /// Identify unknown addresses in the specified call trace using the specified identifier. - /// - /// Unknown contracts are contracts that either lack a label or an ABI. - pub fn identify(&mut self, trace: &CallTraceArena, identifier: &mut impl TraceIdentifier) { - let unidentified_addresses = trace - .addresses() - .into_iter() - .filter(|(address, _)| { - !self.labels.contains_key(address) || !self.contracts.contains_key(address) - }) - .collect(); - - identifier.identify_addresses(unidentified_addresses).iter().for_each(|identity| { - let address = identity.address; - - if let Some(contract) = &identity.contract { - self.contracts.entry(address).or_insert_with(|| contract.to_string()); - } - - if let Some(label) = &identity.label { - self.labels.entry(address).or_insert_with(|| label.to_string()); - } - - if let Some(abi) = &identity.abi { - // Store known functions for the address - abi.functions() - .map(|func| (func.short_signature(), func.clone())) - .for_each(|(sig, func)| self.functions.entry(sig).or_default().push(func)); - - // Flatten events from all ABIs - abi.events() - .map(|event| ((event.signature(), indexed_inputs(event)), event.clone())) - .for_each(|(sig, event)| { - self.events.entry(sig).or_default().push(event); - }); - - // Flatten errors from all ABIs - abi.errors().for_each(|error| { - let entry = self.errors.errors.entry(error.name.clone()).or_default(); - entry.push(error.clone()); - }); - - self.receive_contracts.entry(address).or_insert(abi.receive); - } - }); - } - - pub async fn decode(&self, traces: &mut CallTraceArena) { - for node in traces.arena.iter_mut() { - // Set contract name - if let Some(contract) = self.contracts.get(&node.trace.address).cloned() { - node.trace.contract = Some(contract); - } - - // Set label - if let Some(label) = self.labels.get(&node.trace.address).cloned() { - node.trace.label = Some(label); - } - - // Decode call - if let Some(precompile_fn) = self.precompiles.get(&node.trace.address) { - node.decode_precompile(precompile_fn, &self.labels); - } else if let RawOrDecodedCall::Raw(ref bytes) = node.trace.data { - if bytes.len() >= 4 { - if let Some(funcs) = self.functions.get(&bytes[..SELECTOR_LEN]) { - node.decode_function(funcs, &self.labels, &self.errors, self.verbosity); - } else if node.trace.address == DEFAULT_CREATE2_DEPLOYER { - node.trace.data = - RawOrDecodedCall::Decoded("create2".to_string(), String::new(), vec![]); - } else if let Some(identifier) = &self.signature_identifier { - if let Some(function) = - identifier.write().await.identify_function(&bytes[..SELECTOR_LEN]).await - { - node.decode_function( - &[function], - &self.labels, - &self.errors, - self.verbosity, - ); - } - } - } else { - let has_receive = self - .receive_contracts - .get(&node.trace.address) - .copied() - .unwrap_or_default(); - let func_name = - if bytes.is_empty() && has_receive { "receive" } else { "fallback" }; - - node.trace.data = - RawOrDecodedCall::Decoded(func_name.to_string(), String::new(), Vec::new()); - - if let RawOrDecodedReturnData::Raw(bytes) = &node.trace.output { - if !node.trace.success { - if let Ok(decoded_error) = decode::decode_revert( - &bytes[..], - Some(&self.errors), - Some(node.trace.status), - ) { - node.trace.output = RawOrDecodedReturnData::Decoded(format!( - r#""{decoded_error}""# - )); - } - } - } - } - } - - // Decode events - self.decode_events(node).await; - } - } - - async fn decode_events(&self, node: &mut CallTraceNode) { - for log in node.logs.iter_mut() { - self.decode_event(log).await; - } - } - - async fn decode_event(&self, log: &mut RawOrDecodedLog) { - if let RawOrDecodedLog::Raw(raw_log) = log { - // do not attempt decoding if no topics - if raw_log.topics.is_empty() { - return - } - - let mut events = vec![]; - if let Some(evs) = self.events.get(&(raw_log.topics[0], raw_log.topics.len() - 1)) { - events = evs.clone(); - } else if let Some(identifier) = &self.signature_identifier { - if let Some(event) = - identifier.write().await.identify_event(&raw_log.topics[0].0).await - { - events.push(get_indexed_event(event, raw_log)); - } - } - - for mut event in events { - // ensure all params are named, otherwise this will cause issues with decoding: See also - let empty_params = patch_nameless_params(&mut event); - if let Ok(decoded) = event.parse_log(raw_log.clone()) { - *log = RawOrDecodedLog::Decoded( - event.name, - decoded - .params - .into_iter() - .map(|param| { - // undo patched names - let name = if empty_params.contains(¶m.name) { - "".to_string() - } else { - param.name - }; - (name, self.apply_label(¶m.value)) - }) - .collect(), - ); - break - } - } - } - } - - fn apply_label(&self, token: &Token) -> String { - utils::label(token, &self.labels) - } -} - -/// This is a bit horrible but due to we need to patch nameless (valid) params before decoding a logs, otherwise [`Event::parse_log()`] will result in wrong results since they're identified by name. -/// -/// Returns a set of patched param names, that originally were empty. -fn patch_nameless_params(event: &mut Event) -> HashSet { - let mut patches = HashSet::new(); - if event.inputs.iter().filter(|input| input.name.is_empty()).count() > 1 { - for (idx, param) in event.inputs.iter_mut().enumerate() { - // this is an illegal arg name, which ensures patched identifiers are unique - param.name = format!(""); - patches.insert(param.name.clone()); - } - } - patches -} - -fn indexed_inputs(event: &Event) -> usize { - event.inputs.iter().filter(|param| param.indexed).count() -} diff --git a/crates/evm/src/trace/executor.rs b/crates/evm/src/trace/executor.rs deleted file mode 100644 index 6d941aa4435ef..0000000000000 --- a/crates/evm/src/trace/executor.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::{ - executor::{fork::CreateFork, opts::EvmOpts, Backend, Executor, ExecutorBuilder}, - utils::evm_spec, -}; -use ethers::solc::EvmVersion; -use foundry_config::Config; -use revm::primitives::Env; -use std::ops::{Deref, DerefMut}; - -/// A default executor with tracing enabled -pub struct TracingExecutor { - executor: Executor, -} - -impl TracingExecutor { - pub async fn new( - env: revm::primitives::Env, - fork: Option, - version: Option, - debug: bool, - ) -> Self { - let db = Backend::spawn(fork).await; - - // configures a bare version of the evm executor: no cheatcode inspector is enabled, - // tracing will be enabled only for the targeted transaction - let builder = ExecutorBuilder::default() - .with_config(env) - .with_spec(evm_spec(&version.unwrap_or_default())); - - let mut executor = builder.build(db); - - executor.set_tracing(true).set_debugger(debug); - - Self { executor } - } - - /// uses the fork block number from the config - pub async fn get_fork_material( - config: &Config, - mut evm_opts: EvmOpts, - ) -> eyre::Result<(Env, Option, Option)> { - evm_opts.fork_url = Some(config.get_rpc_url_or_localhost_http()?.into_owned()); - evm_opts.fork_block_number = config.fork_block_number; - - let env = evm_opts.evm_env().await?; - - let fork = evm_opts.get_fork(config, env.clone()); - - Ok((env, fork, evm_opts.get_remote_chain_id())) - } -} - -impl Deref for TracingExecutor { - type Target = Executor; - - fn deref(&self) -> &Self::Target { - &self.executor - } -} - -impl DerefMut for TracingExecutor { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.executor - } -} diff --git a/crates/evm/src/trace/identifier/local.rs b/crates/evm/src/trace/identifier/local.rs deleted file mode 100644 index 9f07720ab1453..0000000000000 --- a/crates/evm/src/trace/identifier/local.rs +++ /dev/null @@ -1,65 +0,0 @@ -use super::{AddressIdentity, TraceIdentifier}; -use ethers::{ - abi::{Abi, Address, Event}, - prelude::ArtifactId, -}; -use foundry_common::contracts::{diff_score, ContractsByArtifact}; -use itertools::Itertools; -use ordered_float::OrderedFloat; -use std::{borrow::Cow, collections::BTreeMap}; - -/// A trace identifier that tries to identify addresses using local contracts. -pub struct LocalTraceIdentifier { - local_contracts: BTreeMap, (ArtifactId, Abi)>, -} - -impl LocalTraceIdentifier { - pub fn new(known_contracts: &ContractsByArtifact) -> Self { - Self { - local_contracts: known_contracts - .iter() - .map(|(id, (abi, runtime_code))| (runtime_code.clone(), (id.clone(), abi.clone()))) - .collect(), - } - } - - /// Get all the events of the local contracts. - pub fn events(&self) -> impl Iterator { - self.local_contracts.iter().flat_map(|(_, (_, abi))| abi.events()) - } -} - -impl TraceIdentifier for LocalTraceIdentifier { - fn identify_addresses( - &mut self, - addresses: Vec<(&Address, Option<&[u8]>)>, - ) -> Vec { - addresses - .into_iter() - .filter_map(|(address, code)| { - let code = code?; - let (_, (_, (id, abi))) = self - .local_contracts - .iter() - .filter_map(|entry| { - let score = diff_score(entry.0, code); - if score < 0.1 { - Some((OrderedFloat(score), entry)) - } else { - None - } - }) - .sorted_by_key(|(score, _)| *score) - .next()?; - - Some(AddressIdentity { - address: *address, - contract: Some(id.identifier()), - label: Some(id.name.clone()), - abi: Some(Cow::Borrowed(abi)), - artifact_id: Some(id.clone()), - }) - }) - .collect() - } -} diff --git a/crates/evm/src/trace/identifier/mod.rs b/crates/evm/src/trace/identifier/mod.rs deleted file mode 100644 index 6fca5d10a274e..0000000000000 --- a/crates/evm/src/trace/identifier/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -mod local; -pub use local::LocalTraceIdentifier; - -mod etherscan; -pub use etherscan::EtherscanIdentifier; - -mod signatures; -pub use signatures::{SignaturesIdentifier, SingleSignaturesIdentifier}; - -use ethers::{ - abi::{Abi, Address}, - prelude::ArtifactId, -}; -use std::borrow::Cow; - -/// An address identity -pub struct AddressIdentity<'a> { - /// The address this identity belongs to - pub address: Address, - /// The label for the address - pub label: Option, - /// The contract this address represents - /// - /// Note: This may be in the format `":"`. - pub contract: Option, - /// The ABI of the contract at this address - pub abi: Option>, - /// The artifact ID of the contract, if any. - pub artifact_id: Option, -} - -/// Trace identifiers figure out what ABIs and labels belong to all the addresses of the trace. -pub trait TraceIdentifier { - // TODO: Update docs - /// Attempts to identify an address in one or more call traces. - #[allow(clippy::type_complexity)] - fn identify_addresses( - &mut self, - addresses: Vec<(&Address, Option<&[u8]>)>, - ) -> Vec; -} diff --git a/crates/evm/src/trace/identifier/signatures.rs b/crates/evm/src/trace/identifier/signatures.rs deleted file mode 100644 index afed2d8278d4c..0000000000000 --- a/crates/evm/src/trace/identifier/signatures.rs +++ /dev/null @@ -1,194 +0,0 @@ -use ethers::abi::{Event, Function}; -use foundry_common::{ - abi::{get_event, get_func}, - fs, - selectors::{SelectorType, SignEthClient}, -}; -use hashbrown::HashSet; -use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; -use tokio::sync::RwLock; - -pub type SingleSignaturesIdentifier = Arc>; - -/// An identifier that tries to identify functions and events using signatures found at -/// `https://openchain.xyz`. -#[derive(Debug)] -pub struct SignaturesIdentifier { - /// Cached selectors for functions and events - cached: 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: SignEthClient, - /// whether traces should be decoded via `sign_eth_api` - offline: bool, -} - -impl SignaturesIdentifier { - #[instrument(target = "forge::signatures")] - pub fn new( - cache_path: Option, - offline: bool, - ) -> eyre::Result { - let sign_eth_api = SignEthClient::new()?; - - let identifier = if let Some(cache_path) = cache_path { - let path = cache_path.join("signatures"); - trace!(?path, "reading signature cache"); - let cached = if path.is_file() { - fs::read_json_file(&path) - .map_err(|err| warn!(?path, ?err, "failed to read cache file")) - .unwrap_or_default() - } else { - if let Err(err) = std::fs::create_dir_all(cache_path) { - warn!("could not create signatures cache dir: {:?}", err); - } - CachedSignatures::default() - }; - Self { - cached, - cached_path: Some(path), - unavailable: HashSet::new(), - sign_eth_api, - offline, - } - } else { - Self { - cached: Default::default(), - cached_path: None, - unavailable: HashSet::new(), - sign_eth_api, - offline, - } - }; - - Ok(Arc::new(RwLock::new(identifier))) - } - - #[instrument(target = "forge::signatures", skip(self))] - pub fn save(&self) { - if let Some(cached_path) = &self.cached_path { - if let Some(parent) = cached_path.parent() { - if let Err(err) = std::fs::create_dir_all(parent) { - warn!(?parent, ?err, "failed to create cache"); - } - } - if let Err(err) = fs::write_json_file(cached_path, &self.cached) { - warn!(?cached_path, ?err, "failed to flush signature cache"); - } else { - trace!(?cached_path, "flushed signature cache") - } - } - } -} - -impl SignaturesIdentifier { - async fn identify( - &mut self, - selector_type: SelectorType, - identifier: &[u8], - get_type: fn(&str) -> eyre::Result, - ) -> Option { - // Exit early if we have unsuccessfully queried it before. - if self.unavailable.contains(identifier) { - return None - } - - let map = match selector_type { - SelectorType::Function => &mut self.cached.functions, - SelectorType::Event => &mut self.cached.events, - }; - - let hex_identifier = format!("0x{}", hex::encode(identifier)); - - if !self.offline && !map.contains_key(&hex_identifier) { - if let Ok(signatures) = - self.sign_eth_api.decode_selector(&hex_identifier, selector_type).await - { - if let Some(signature) = signatures.into_iter().next() { - map.insert(hex_identifier.clone(), signature); - } - } - } - - if let Some(signature) = map.get(&hex_identifier) { - return get_type(signature).ok() - } - - self.unavailable.insert(identifier.to_vec()); - - None - } - - /// Returns `None` if in offline mode - fn ensure_not_offline(&self) -> Option<()> { - if self.offline { - None - } else { - Some(()) - } - } - - /// Identifies `Function` from its cache or `https://api.openchain.xyz` - pub async fn identify_function(&mut self, identifier: &[u8]) -> Option { - self.ensure_not_offline()?; - self.identify(SelectorType::Function, identifier, get_func).await - } - - /// Identifies `Event` from its cache or `https://api.openchain.xyz` - pub async fn identify_event(&mut self, identifier: &[u8]) -> Option { - self.ensure_not_offline()?; - self.identify(SelectorType::Event, identifier, get_event).await - } -} - -impl Drop for SignaturesIdentifier { - fn drop(&mut self) { - self.save(); - } -} - -#[derive(Debug, Deserialize, Serialize, Default)] -pub struct CachedSignatures { - pub events: BTreeMap, - pub functions: BTreeMap, -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test(flavor = "multi_thread")] - async fn can_query_signatures() { - let tmp = tempfile::tempdir().unwrap(); - { - let sigs = SignaturesIdentifier::new(Some(tmp.path().into()), false).unwrap(); - - assert!(sigs.read().await.cached.events.is_empty()); - assert!(sigs.read().await.cached.functions.is_empty()); - - let func = sigs.write().await.identify_function(&[35, 184, 114, 221]).await.unwrap(); - let event = sigs - .write() - .await - .identify_event(&[ - 39, 119, 42, 220, 99, 219, 7, 170, 231, 101, 183, 30, 178, 181, 51, 6, 79, 167, - 129, 189, 87, 69, 126, 27, 19, 133, 146, 216, 25, 141, 9, 89, - ]) - .await - .unwrap(); - - assert_eq!(func, get_func("transferFrom(address,address,uint256)").unwrap()); - assert_eq!(event, get_event("Transfer(address,address,uint128)").unwrap()); - - // dropping saves the cache - } - - let sigs = SignaturesIdentifier::new(Some(tmp.path().into()), false).unwrap(); - assert_eq!(sigs.read().await.cached.events.len(), 1); - assert_eq!(sigs.read().await.cached.functions.len(), 1); - } -} diff --git a/crates/evm/src/trace/mod.rs b/crates/evm/src/trace/mod.rs deleted file mode 100644 index 23b5d1fa47a69..0000000000000 --- a/crates/evm/src/trace/mod.rs +++ /dev/null @@ -1,640 +0,0 @@ -use crate::{ - abi::CHEATCODE_ADDRESS, debug::Instruction, trace::identifier::LocalTraceIdentifier, CallKind, -}; -pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder}; -use ethers::{ - abi::{ethereum_types::BigEndianHash, Address, RawLog}, - core::utils::to_checksum, - types::{Bytes, DefaultFrame, GethDebugTracingOptions, StructLog, H256, U256}, -}; -pub use executor::TracingExecutor; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use hashbrown::HashMap; -use node::CallTraceNode; -use revm::interpreter::{opcode, CallContext, InstructionResult, Memory, Stack}; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashSet}, - fmt::{self, Write}, -}; -use yansi::{Color, Paint}; - -/// Call trace address identifiers. -/// -/// Identifiers figure out what ABIs and labels belong to all the addresses of the trace. -pub mod identifier; - -mod decoder; -mod executor; -pub mod node; -pub mod utils; - -pub type Traces = Vec<(TraceKind, CallTraceArena)>; - -/// An arena of [CallTraceNode]s -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CallTraceArena { - /// The arena of nodes - pub arena: Vec, -} - -impl Default for CallTraceArena { - fn default() -> Self { - CallTraceArena { arena: vec![Default::default()] } - } -} - -impl CallTraceArena { - /// Pushes a new trace into the arena, returning the trace ID - pub fn push_trace(&mut self, entry: usize, new_trace: CallTrace) -> usize { - match new_trace.depth { - // The entry node, just update it - 0 => { - self.arena[0].trace = new_trace; - 0 - } - // We found the parent node, add the new trace as a child - _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { - let id = self.arena.len(); - - let trace_location = self.arena[entry].children.len(); - self.arena[entry].ordering.push(LogCallOrder::Call(trace_location)); - let node = CallTraceNode { - parent: Some(entry), - trace: new_trace, - idx: id, - ..Default::default() - }; - self.arena.push(node); - self.arena[entry].children.push(id); - - id - } - // We haven't found the parent node, go deeper - _ => self.push_trace( - *self.arena[entry].children.last().expect("Disconnected trace"), - new_trace, - ), - } - } - - pub fn addresses(&self) -> HashSet<(&Address, Option<&[u8]>)> { - self.arena - .iter() - .map(|node| { - if node.trace.created() { - if let RawOrDecodedReturnData::Raw(ref bytes) = node.trace.output { - return (&node.trace.address, Some(bytes.as_ref())) - } - } - - (&node.trace.address, None) - }) - .collect() - } - - // Recursively fill in the geth trace by going through the traces - fn add_to_geth_trace( - &self, - storage: &mut HashMap>, - trace_node: &CallTraceNode, - struct_logs: &mut Vec, - opts: &GethDebugTracingOptions, - ) { - let mut child_id = 0; - // Iterate over the steps inside the given trace - for step in trace_node.trace.steps.iter() { - let mut log: StructLog = step.into(); - - // Fill in memory and storage depending on the options - if !opts.disable_storage.unwrap_or_default() { - let contract_storage = storage.entry(step.contract).or_default(); - if let Some((key, value)) = step.state_diff { - contract_storage.insert(H256::from_uint(&key), H256::from_uint(&value)); - log.storage = Some(contract_storage.clone()); - } - } - if opts.disable_stack.unwrap_or_default() { - log.stack = None; - } - if !opts.enable_memory.unwrap_or_default() { - log.memory = None; - } - - // Add step to geth trace - struct_logs.push(log); - - // Check if the step was a call - match step.op { - Instruction::OpCode(opc) => { - match opc { - // If yes, descend into a child trace - opcode::CREATE | - opcode::CREATE2 | - opcode::DELEGATECALL | - opcode::CALL | - opcode::STATICCALL | - opcode::CALLCODE => { - self.add_to_geth_trace( - storage, - &self.arena[trace_node.children[child_id]], - struct_logs, - opts, - ); - child_id += 1; - } - _ => {} - } - } - Instruction::Cheatcode(_) => {} - } - } - } - - /// Generate a geth-style trace e.g. for debug_traceTransaction - pub fn geth_trace( - &self, - receipt_gas_used: U256, - opts: GethDebugTracingOptions, - ) -> DefaultFrame { - if self.arena.is_empty() { - return Default::default() - } - - let mut storage = HashMap::new(); - // Fetch top-level trace - let main_trace_node = &self.arena[0]; - let main_trace = &main_trace_node.trace; - // Start geth trace - let mut acc = DefaultFrame { - // If the top-level trace succeeded, then it was a success - failed: !main_trace.success, - gas: receipt_gas_used, - return_value: main_trace.output.to_bytes(), - ..Default::default() - }; - - self.add_to_geth_trace(&mut storage, main_trace_node, &mut acc.struct_logs, &opts); - - acc - } -} - -const PIPE: &str = " │ "; -const EDGE: &str = " └─ "; -const BRANCH: &str = " ├─ "; -const CALL: &str = "→ "; -const RETURN: &str = "← "; - -impl fmt::Display for CallTraceArena { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fn inner( - arena: &CallTraceArena, - writer: &mut (impl Write + ?Sized), - idx: usize, - left: &str, - child: &str, - verbose: bool, - ) -> fmt::Result { - let node = &arena.arena[idx]; - - // Display trace header - if !verbose { - writeln!(writer, "{left}{}", node.trace)?; - } else { - writeln!(writer, "{left}{:#}", node.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 mut log = String::new(); - write!(log, "{}", node.logs[*index])?; - - // Prepend our tree structure symbols to each line of the displayed log - log.lines().enumerate().try_for_each(|(i, line)| { - writeln!( - writer, - "{}{}", - if i == 0 { &left_prefix } else { &right_prefix }, - line - ) - })?; - } - LogCallOrder::Call(index) => { - inner( - arena, - writer, - node.children[*index], - &left_prefix, - &right_prefix, - verbose, - )?; - } - } - } - - // Display trace return data - let color = trace_color(&node.trace); - write!(writer, "{child}{EDGE}")?; - write!(writer, "{}", color.paint(RETURN))?; - if node.trace.created() { - if let RawOrDecodedReturnData::Raw(bytes) = &node.trace.output { - writeln!(writer, "{} bytes of code", bytes.len())?; - } else { - unreachable!("We should never have decoded calldata for contract creations"); - } - } else { - writeln!(writer, "{}", node.trace.output)?; - } - - Ok(()) - } - - inner(self, f, 0, " ", " ", f.alternate()) - } -} - -/// A raw or decoded log. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum RawOrDecodedLog { - /// A raw log - Raw(RawLog), - /// 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)>), -} - -impl fmt::Display for RawOrDecodedLog { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - RawOrDecodedLog::Raw(log) => { - for (i, topic) in log.topics.iter().enumerate() { - writeln!( - f, - "{:>13}: {}", - if i == 0 { "emit topic 0".to_string() } else { format!("topic {i}") }, - Paint::cyan(format!("0x{}", hex::encode(topic))) - )?; - } - - write!( - f, - " data: {}", - Paint::cyan(format!("0x{}", hex::encode(&log.data))) - ) - } - RawOrDecodedLog::Decoded(name, params) => { - let params = params - .iter() - .map(|(name, value)| format!("{name}: {value}")) - .collect::>() - .join(", "); - - write!(f, "emit {}({params})", Paint::cyan(name.clone())) - } - } - } -} - -/// Ordering enum for calls and logs -/// -/// i.e. if Call 0 occurs before Log 0, it will be pushed into the `CallTraceNode`'s ordering before -/// the log. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum LogCallOrder { - Log(usize), - Call(usize), -} - -/// Raw or decoded calldata. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub enum RawOrDecodedCall { - /// Raw calldata - Raw(Bytes), - /// Decoded calldata. - /// - /// The first element in the tuple is the function name, second is the function signature and - /// the third element is a vector of decoded parameters. - Decoded(String, String, Vec), -} - -impl RawOrDecodedCall { - pub fn to_raw(&self) -> Vec { - match self { - RawOrDecodedCall::Raw(raw) => raw.to_vec(), - RawOrDecodedCall::Decoded(_, _, _) => { - vec![] - } - } - } -} - -impl Default for RawOrDecodedCall { - fn default() -> Self { - RawOrDecodedCall::Raw(Default::default()) - } -} - -/// Raw or decoded return data. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] -pub enum RawOrDecodedReturnData { - /// Raw return data - Raw(Bytes), - /// Decoded return data - Decoded(String), -} - -impl RawOrDecodedReturnData { - /// Returns the data as [`Bytes`] - pub fn to_bytes(&self) -> Bytes { - match self { - RawOrDecodedReturnData::Raw(raw) => raw.clone(), - RawOrDecodedReturnData::Decoded(val) => val.as_bytes().to_vec().into(), - } - } - - pub fn to_raw(&self) -> Vec { - self.to_bytes().to_vec() - } -} - -impl Default for RawOrDecodedReturnData { - fn default() -> Self { - RawOrDecodedReturnData::Raw(Default::default()) - } -} - -impl fmt::Display for RawOrDecodedReturnData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match &self { - RawOrDecodedReturnData::Raw(bytes) => { - if bytes.is_empty() { - write!(f, "()") - } else { - write!(f, "0x{}", hex::encode(bytes)) - } - } - RawOrDecodedReturnData::Decoded(decoded) => write!(f, "{}", decoded.clone()), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct CallTraceStep { - // Fields filled in `step` - /// Call depth - pub depth: u64, - /// Program counter before step execution - pub pc: usize, - /// Opcode to be executed - pub op: Instruction, - /// Current contract address - pub contract: Address, - /// Stack before step execution - pub stack: Stack, - /// Memory before step execution - pub memory: Memory, - /// Remaining gas before step execution - pub gas: u64, - /// Gas refund counter before step execution - pub gas_refund_counter: u64, - - // Fields filled in `step_end` - /// Gas cost of step execution - pub gas_cost: u64, - /// Change of the contract state after step execution (effect of the SLOAD/SSTORE instructions) - pub state_diff: Option<(U256, U256)>, - /// Error (if any) after step execution - pub error: Option, -} - -impl From<&CallTraceStep> for StructLog { - fn from(step: &CallTraceStep) -> Self { - StructLog { - depth: step.depth, - error: step.error.clone(), - gas: step.gas, - gas_cost: step.gas_cost, - memory: Some(convert_memory(step.memory.data())), - op: step.op.to_string(), - pc: step.pc as u64, - refund_counter: if step.gas_refund_counter > 0 { - Some(step.gas_refund_counter) - } else { - None - }, - stack: Some(step.stack.data().iter().copied().map(|data| data.into()).collect()), - // Filled in `CallTraceArena::geth_trace` as a result of compounding all slot changes - storage: None, - } - } -} - -/// A trace of a call. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct CallTrace { - /// The depth of the call - pub depth: usize, - /// Whether the call was successful - pub success: bool, - /// The name of the contract, if any. - /// - /// The format is `":"` for easy lookup in local contracts. - /// - /// This member is not used by the core call tracing functionality (decoding/displaying). The - /// intended use case is for other components that may want to process traces by specific - /// contracts (e.g. gas reports). - pub contract: Option, - /// The label for the destination address, if any - pub label: Option, - /// caller of this call - pub caller: Address, - /// The destination address of the call or the address from the created contract - pub address: Address, - /// The kind of call this is - pub kind: CallKind, - /// The value transferred in the call - pub value: U256, - /// The calldata for the call, or the init code for contract creations - pub data: RawOrDecodedCall, - /// The return data of the call if this was not a contract creation, otherwise it is the - /// runtime bytecode of the created contract - pub output: RawOrDecodedReturnData, - /// The gas cost of the call - pub gas_cost: u64, - /// The status of the trace's call - pub status: InstructionResult, - /// call context of the runtime - pub call_context: Option, - /// Opcode-level execution steps - pub steps: Vec, -} - -// === impl CallTrace === - -impl CallTrace { - /// Whether this is a contract creation or not - pub fn created(&self) -> bool { - matches!(self.kind, CallKind::Create | CallKind::Create2) - } -} - -impl Default for CallTrace { - fn default() -> Self { - Self { - depth: Default::default(), - success: Default::default(), - contract: Default::default(), - label: Default::default(), - caller: Default::default(), - address: Default::default(), - kind: Default::default(), - value: Default::default(), - data: Default::default(), - output: Default::default(), - gas_cost: Default::default(), - status: InstructionResult::Continue, - call_context: Default::default(), - steps: Default::default(), - } - } -} - -impl fmt::Display for CallTrace { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let address = to_checksum(&self.address, None); - if self.created() { - write!( - f, - "[{}] {}{} {}@{}", - self.gas_cost, - Paint::yellow(CALL), - Paint::yellow("new"), - self.label.as_ref().unwrap_or(&"".to_string()), - address - )?; - } else { - let (func, inputs) = match &self.data { - RawOrDecodedCall::Raw(bytes) => { - // We assume that the fallback function (`data.len() < 4`) counts as decoded - // calldata - assert!(bytes.len() >= 4); - (hex::encode(&bytes[0..4]), hex::encode(&bytes[4..])) - } - RawOrDecodedCall::Decoded(func, _, inputs) => (func.clone(), inputs.join(", ")), - }; - - let action = match self.kind { - // do not show anything for CALLs - CallKind::Call => "", - CallKind::StaticCall => "[staticcall]", - CallKind::CallCode => "[callcode]", - CallKind::DelegateCall => "[delegatecall]", - _ => unreachable!(), - }; - - let color = trace_color(self); - write!( - f, - "[{}] {}::{}{}({}) {}", - self.gas_cost, - color.paint(self.label.as_ref().unwrap_or(&address)), - color.paint(func), - if !self.value.is_zero() { - format!("{{value: {}}}", self.value) - } else { - "".to_string() - }, - inputs, - Paint::yellow(action), - )?; - } - - Ok(()) - } -} - -/// Specifies the kind of trace. -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum TraceKind { - Deployment, - Setup, - Execution, -} - -/// 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( - traces: Traces, - known_contracts: Option<&ContractsByArtifact>, -) -> ContractsByAddress { - if let Some(contracts) = known_contracts { - let mut local_identifier = LocalTraceIdentifier::new(contracts); - let mut decoder = CallTraceDecoderBuilder::new().build(); - for (_, trace) in &traces { - decoder.identify(trace, &mut local_identifier); - } - - decoder - .contracts - .iter() - .filter_map(|(addr, name)| { - if let Ok(Some((_, (abi, _)))) = contracts.find_by_name_or_identifier(name) { - return Some((*addr, (name.clone(), abi.clone()))) - } - None - }) - .collect() - } else { - BTreeMap::new() - } -} - -/// creates the memory data in 32byte chunks -/// see -fn convert_memory(data: &[u8]) -> Vec { - let mut memory = Vec::with_capacity((data.len() + 31) / 32); - for idx in (0..data.len()).step_by(32) { - let len = std::cmp::min(idx + 32, data.len()); - memory.push(hex::encode(&data[idx..len])); - } - memory -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_convert_memory() { - let mut data = vec![0u8; 32]; - assert_eq!( - convert_memory(&data), - vec!["0000000000000000000000000000000000000000000000000000000000000000".to_string()] - ); - data.extend(data.clone()); - assert_eq!( - convert_memory(&data), - vec![ - "0000000000000000000000000000000000000000000000000000000000000000".to_string(), - "0000000000000000000000000000000000000000000000000000000000000000".to_string() - ] - ); - } -} diff --git a/crates/evm/src/trace/node.rs b/crates/evm/src/trace/node.rs deleted file mode 100644 index be0c55162a113..0000000000000 --- a/crates/evm/src/trace/node.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::{ - decode, - executor::CHEATCODE_ADDRESS, - trace::{ - utils, utils::decode_cheatcode_outputs, CallTrace, LogCallOrder, RawOrDecodedCall, - RawOrDecodedLog, RawOrDecodedReturnData, - }, - CallKind, -}; -use ethers::{ - abi::{Abi, Function}, - types::{Action, Address, Call, CallResult, Create, CreateResult, Res, Suicide}, -}; -use foundry_common::SELECTOR_LEN; -use revm::interpreter::InstructionResult; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -/// A node in the arena -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct CallTraceNode { - /// Parent node index in the arena - pub parent: Option, - /// Children node indexes in the arena - pub children: Vec, - /// This node's index in the arena - pub idx: usize, - /// The call trace - pub trace: CallTrace, - /// Logs - #[serde(skip)] - pub logs: Vec, - /// Ordering of child calls and logs - pub ordering: Vec, -} - -impl CallTraceNode { - /// Returns the kind of call the trace belongs to - pub fn kind(&self) -> CallKind { - self.trace.kind - } - - /// Returns the status of the call - pub fn status(&self) -> InstructionResult { - self.trace.status - } - - /// Returns the `Res` for a parity trace - pub fn parity_result(&self) -> Res { - match self.kind() { - CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { - Res::Call(CallResult { - gas_used: self.trace.gas_cost.into(), - output: self.trace.output.to_raw().into(), - }) - } - CallKind::Create | CallKind::Create2 => Res::Create(CreateResult { - gas_used: self.trace.gas_cost.into(), - code: self.trace.output.to_raw().into(), - address: self.trace.address, - }), - } - } - - /// Returns the `Action` for a parity trace - pub fn parity_action(&self) -> Action { - if self.status() == InstructionResult::SelfDestruct { - return Action::Suicide(Suicide { - address: self.trace.address, - // TODO deserialize from calldata here? - refund_address: Default::default(), - balance: self.trace.value, - }) - } - match self.kind() { - CallKind::Call | CallKind::StaticCall | CallKind::CallCode | CallKind::DelegateCall => { - Action::Call(Call { - from: self.trace.caller, - to: self.trace.address, - value: self.trace.value, - gas: self.trace.gas_cost.into(), - input: self.trace.data.to_raw().into(), - call_type: self.kind().into(), - }) - } - CallKind::Create | CallKind::Create2 => Action::Create(Create { - from: self.trace.caller, - value: self.trace.value, - gas: self.trace.gas_cost.into(), - init: self.trace.data.to_raw().into(), - }), - } - } - - /// Decode a regular function - pub fn decode_function( - &mut self, - funcs: &[Function], - labels: &HashMap, - errors: &Abi, - verbosity: u8, - ) { - debug_assert!(!funcs.is_empty(), "requires at least 1 func"); - // This is safe because (1) we would not have an entry for the given - // selector if no functions with that selector were added and (2) the - // same selector implies the function has - // the same name and inputs. - let func = &funcs[0]; - - if let RawOrDecodedCall::Raw(ref bytes) = self.trace.data { - let inputs = if bytes.len() >= SELECTOR_LEN { - if self.trace.address == CHEATCODE_ADDRESS { - // Try to decode cheatcode inputs in a more custom way - utils::decode_cheatcode_inputs(func, bytes, errors, verbosity).unwrap_or_else( - || { - func.decode_input(&bytes[SELECTOR_LEN..]) - .expect("bad function input decode") - .iter() - .map(|token| utils::label(token, labels)) - .collect() - }, - ) - } else { - match func.decode_input(&bytes[SELECTOR_LEN..]) { - Ok(v) => v.iter().map(|token| utils::label(token, labels)).collect(), - Err(_) => Vec::new(), - } - } - } else { - Vec::new() - }; - - // add signature to decoded calls for better calls filtering - self.trace.data = - RawOrDecodedCall::Decoded(func.name.clone(), func.signature(), inputs); - - if let RawOrDecodedReturnData::Raw(bytes) = &self.trace.output { - if !bytes.is_empty() && self.trace.success { - if self.trace.address == CHEATCODE_ADDRESS { - if let Some(decoded) = funcs - .iter() - .find_map(|func| decode_cheatcode_outputs(func, bytes, verbosity)) - { - self.trace.output = RawOrDecodedReturnData::Decoded(decoded); - return - } - } - - if let Some(tokens) = - funcs.iter().find_map(|func| func.decode_output(bytes).ok()) - { - // Functions coming from an external database do not have any outputs - // specified, and will lead to returning an empty list of tokens. - if !tokens.is_empty() { - self.trace.output = RawOrDecodedReturnData::Decoded( - tokens - .iter() - .map(|token| utils::label(token, labels)) - .collect::>() - .join(", "), - ); - } - } - } else if let Ok(decoded_error) = - decode::decode_revert(bytes, Some(errors), Some(self.trace.status)) - { - self.trace.output = - RawOrDecodedReturnData::Decoded(format!(r#""{decoded_error}""#)); - } - } - } - } - - /// Decode the node's tracing data for the given precompile function - pub fn decode_precompile( - &mut self, - precompile_fn: &Function, - labels: &HashMap, - ) { - if let RawOrDecodedCall::Raw(ref bytes) = self.trace.data { - self.trace.label = Some("PRECOMPILE".to_string()); - self.trace.data = RawOrDecodedCall::Decoded( - precompile_fn.name.clone(), - precompile_fn.signature(), - precompile_fn.decode_input(bytes).map_or_else( - |_| vec![hex::encode(bytes)], - |tokens| tokens.iter().map(|token| utils::label(token, labels)).collect(), - ), - ); - - if let RawOrDecodedReturnData::Raw(ref bytes) = self.trace.output { - self.trace.output = RawOrDecodedReturnData::Decoded( - precompile_fn.decode_output(bytes).map_or_else( - |_| hex::encode(bytes), - |tokens| { - tokens - .iter() - .map(|token| utils::label(token, labels)) - .collect::>() - .join(", ") - }, - ), - ); - } - } - } -} diff --git a/crates/evm/src/trace/utils.rs b/crates/evm/src/trace/utils.rs deleted file mode 100644 index e65d6c30346ec..0000000000000 --- a/crates/evm/src/trace/utils.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! utilities used within tracing - -use crate::decode; -use ethers::{ - abi::{Abi, Address, Function, ParamType, Token}, - core::utils::to_checksum, -}; -use foundry_common::{abi::format_token, SELECTOR_LEN}; -use std::collections::HashMap; - -/// Returns the label for the given `token` -/// -/// If the `token` is an `Address` then we look abel the label map. -/// by default the token is formatted using standard formatting -pub fn label(token: &Token, labels: &HashMap) -> String { - match token { - Token::Address(addr) => { - if let Some(label) = labels.get(addr) { - format!("{label}: [{}]", to_checksum(addr, None)) - } else { - format_token(token) - } - } - _ => format_token(token), - } -} - -/// Custom decoding of cheatcode calls -pub(crate) fn decode_cheatcode_inputs( - func: &Function, - data: &[u8], - errors: &Abi, - verbosity: u8, -) -> Option> { - match func.name.as_str() { - "expectRevert" => { - decode::decode_revert(data, Some(errors), None).ok().map(|decoded| vec![decoded]) - } - "rememberKey" | "addr" | "startBroadcast" | "broadcast" => { - // these functions accept a private key as uint256, which should not be - // converted to plain text - if !func.inputs.is_empty() && matches!(&func.inputs[0].kind, ParamType::Uint(_)) { - // redact private key input - Some(vec!["".to_string()]) - } else { - None - } - } - "sign" => { - // sign(uint256,bytes32) - let mut decoded = func.decode_input(&data[SELECTOR_LEN..]).ok()?; - if !decoded.is_empty() && matches!(&func.inputs[0].kind, ParamType::Uint(_)) { - decoded[0] = Token::String("".to_string()); - } - Some(decoded.iter().map(format_token).collect()) - } - "deriveKey" => Some(vec!["".to_string()]), - "parseJson" | - "parseJsonUint" | - "parseJsonUintArray" | - "parseJsonInt" | - "parseJsonIntArray" | - "parseJsonString" | - "parseJsonStringArray" | - "parseJsonAddress" | - "parseJsonAddressArray" | - "parseJsonBool" | - "parseJsonBoolArray" | - "parseJsonBytes" | - "parseJsonBytesArray" | - "parseJsonBytes32" | - "parseJsonBytes32Array" | - "writeJson" | - "keyExists" | - "serializeBool" | - "serializeUint" | - "serializeInt" | - "serializeAddress" | - "serializeBytes32" | - "serializeString" | - "serializeBytes" => { - if verbosity == 5 { - None - } else { - let mut decoded = func.decode_input(&data[SELECTOR_LEN..]).ok()?; - let token = - if func.name.as_str() == "parseJson" || func.name.as_str() == "keyExists" { - "" - } else { - "" - }; - decoded[0] = Token::String(token.to_string()); - Some(decoded.iter().map(format_token).collect()) - } - } - _ => None, - } -} - -/// Custom decoding of cheatcode return values -pub(crate) fn decode_cheatcode_outputs( - func: &Function, - _data: &[u8], - verbosity: u8, -) -> Option { - if func.name.starts_with("env") { - // redacts the value stored in the env var - return Some("".to_string()) - } - if func.name == "deriveKey" { - // redacts derived private key - return Some("".to_string()) - } - if func.name == "parseJson" && verbosity != 5 { - return Some("".to_string()) - } - if func.name == "readFile" && verbosity != 5 { - return Some("".to_string()) - } - None -} diff --git a/crates/evm/src/utils.rs b/crates/evm/src/utils.rs deleted file mode 100644 index 166878dcfa25c..0000000000000 --- a/crates/evm/src/utils.rs +++ /dev/null @@ -1,295 +0,0 @@ -use ethers::{ - abi::{Abi, FixedBytes, Function}, - solc::EvmVersion, - types::{Block, Chain, H256, U256}, -}; -use eyre::ContextCompat; -use revm::{ - interpreter::{opcode, opcode::spec_opcode_gas}, - primitives::SpecId, -}; -use std::collections::BTreeMap; - -/// Small helper function to convert [U256] into [H256]. -pub fn u256_to_h256_le(u: U256) -> H256 { - let mut h = H256::default(); - u.to_little_endian(h.as_mut()); - h -} - -/// Small helper function to convert [U256] into [H256]. -pub fn u256_to_h256_be(u: U256) -> H256 { - let mut h = H256::default(); - u.to_big_endian(h.as_mut()); - h -} - -/// Small helper function to convert [H256] into [U256]. -pub fn h256_to_u256_be(storage: H256) -> U256 { - U256::from_big_endian(storage.as_bytes()) -} - -/// Small helper function to convert [H256] into [U256]. -pub fn h256_to_u256_le(storage: H256) -> U256 { - U256::from_little_endian(storage.as_bytes()) -} - -/// Small helper function to convert revm's [B160] into ethers's [H160]. -#[inline] -pub fn b160_to_h160(b: revm::primitives::B160) -> ethers::types::H160 { - ethers::types::H160(b.0) -} - -/// Small helper function to convert ethers's [H160] into revm's [B160]. -#[inline] -pub fn h160_to_b160(h: ethers::types::H160) -> revm::primitives::B160 { - revm::primitives::B160(h.0) -} - -/// Small helper function to convert revm's [B256] into ethers's [H256]. -#[inline] -pub fn b256_to_h256(b: revm::primitives::B256) -> ethers::types::H256 { - ethers::types::H256(b.0) -} - -/// Small helper function to convert ether's [H256] into revm's [B256]. -#[inline] -pub fn h256_to_b256(h: ethers::types::H256) -> revm::primitives::B256 { - revm::primitives::B256(h.0) -} - -/// Small helper function to convert ether's [U256] into revm's [U256]. -#[inline] -pub fn u256_to_ru256(u: ethers::types::U256) -> revm::primitives::U256 { - let mut buffer = [0u8; 32]; - u.to_little_endian(buffer.as_mut_slice()); - revm::primitives::U256::from_le_bytes(buffer) -} - -/// Small helper function to convert revm's [U256] into ethers's [U256]. -#[inline] -pub fn ru256_to_u256(u: revm::primitives::U256) -> ethers::types::U256 { - ethers::types::U256::from_little_endian(&u.as_le_bytes()) -} - -/// Small helper function to convert an Eval into an InstructionResult -pub fn eval_to_instruction_result( - eval: revm::primitives::Eval, -) -> revm::interpreter::InstructionResult { - match eval { - revm::primitives::Eval::Return => revm::interpreter::InstructionResult::Return, - revm::primitives::Eval::Stop => revm::interpreter::InstructionResult::Stop, - revm::primitives::Eval::SelfDestruct => revm::interpreter::InstructionResult::SelfDestruct, - } -} - -/// Small helper function to convert a Halt into an InstructionResult -pub fn halt_to_instruction_result( - halt: revm::primitives::Halt, -) -> revm::interpreter::InstructionResult { - match halt { - revm::primitives::Halt::OutOfGas(_) => revm::interpreter::InstructionResult::OutOfGas, - revm::primitives::Halt::OpcodeNotFound => { - revm::interpreter::InstructionResult::OpcodeNotFound - } - revm::primitives::Halt::InvalidFEOpcode => { - revm::interpreter::InstructionResult::InvalidFEOpcode - } - revm::primitives::Halt::InvalidJump => revm::interpreter::InstructionResult::InvalidJump, - revm::primitives::Halt::NotActivated => revm::interpreter::InstructionResult::NotActivated, - revm::primitives::Halt::StackOverflow => { - revm::interpreter::InstructionResult::StackOverflow - } - revm::primitives::Halt::StackUnderflow => { - revm::interpreter::InstructionResult::StackUnderflow - } - revm::primitives::Halt::OutOfOffset => revm::interpreter::InstructionResult::OutOfOffset, - revm::primitives::Halt::CreateCollision => { - revm::interpreter::InstructionResult::CreateCollision - } - revm::primitives::Halt::PrecompileError => { - revm::interpreter::InstructionResult::PrecompileError - } - revm::primitives::Halt::NonceOverflow => { - revm::interpreter::InstructionResult::NonceOverflow - } - revm::primitives::Halt::CreateContractSizeLimit => { - revm::interpreter::InstructionResult::CreateContractSizeLimit - } - revm::primitives::Halt::CreateContractStartingWithEF => { - revm::interpreter::InstructionResult::CreateContractStartingWithEF - } - revm::primitives::Halt::CreateInitcodeSizeLimit => { - revm::interpreter::InstructionResult::CreateInitcodeSizeLimit - } - revm::primitives::Halt::OverflowPayment => { - revm::interpreter::InstructionResult::OverflowPayment - } - revm::primitives::Halt::StateChangeDuringStaticCall => { - revm::interpreter::InstructionResult::StateChangeDuringStaticCall - } - revm::primitives::Halt::CallNotAllowedInsideStatic => { - revm::interpreter::InstructionResult::CallNotAllowedInsideStatic - } - revm::primitives::Halt::OutOfFund => revm::interpreter::InstructionResult::OutOfFund, - revm::primitives::Halt::CallTooDeep => revm::interpreter::InstructionResult::CallTooDeep, - } -} - -/// Converts an `EvmVersion` into a `SpecId` -pub fn evm_spec(evm: &EvmVersion) -> SpecId { - match evm { - EvmVersion::Homestead => SpecId::HOMESTEAD, - EvmVersion::TangerineWhistle => SpecId::TANGERINE, - EvmVersion::SpuriousDragon => SpecId::SPURIOUS_DRAGON, - EvmVersion::Byzantium => SpecId::BYZANTIUM, - EvmVersion::Constantinople => SpecId::CONSTANTINOPLE, - EvmVersion::Petersburg => SpecId::PETERSBURG, - EvmVersion::Istanbul => SpecId::ISTANBUL, - EvmVersion::Berlin => SpecId::BERLIN, - EvmVersion::London => SpecId::LONDON, - EvmVersion::Paris => SpecId::MERGE, - EvmVersion::Shanghai => SpecId::SHANGHAI, - } -} - -/// Depending on the configured chain id and block number this should apply any specific changes -/// -/// This checks for: -/// - prevrandao mixhash after merge -pub fn apply_chain_and_block_specific_env_changes( - env: &mut revm::primitives::Env, - block: &Block, -) { - if let Ok(chain) = Chain::try_from(ru256_to_u256(env.cfg.chain_id)) { - let block_number = block.number.unwrap_or_default(); - - match chain { - Chain::Mainnet => { - // after merge difficulty is supplanted with prevrandao EIP-4399 - if block_number.as_u64() >= 15_537_351u64 { - env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); - } - - return - } - Chain::Arbitrum | - Chain::ArbitrumGoerli | - Chain::ArbitrumNova | - Chain::ArbitrumTestnet => { - // on arbitrum `block.number` is the L1 block which is included in the - // `l1BlockNumber` field - if let Some(l1_block_number) = block.other.get("l1BlockNumber").cloned() { - if let Ok(l1_block_number) = serde_json::from_value::(l1_block_number) { - env.block.number = l1_block_number.into(); - } - } - } - _ => {} - } - } - - // if difficulty is `0` we assume it's past merge - if block.difficulty.is_zero() { - env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); - } -} - -/// A map of program counters to instruction counters. -pub type PCICMap = BTreeMap; - -/// Builds a mapping from program counters to instruction counters. -pub fn build_pc_ic_map(spec: SpecId, code: &[u8]) -> PCICMap { - let opcode_infos = spec_opcode_gas(spec); - let mut pc_ic_map: PCICMap = BTreeMap::new(); - - let mut i = 0; - let mut cumulative_push_size = 0; - while i < code.len() { - let op = code[i]; - pc_ic_map.insert(i, i - cumulative_push_size); - if opcode_infos[op as usize].is_push() { - // Skip the push bytes. - // - // For more context on the math, see: https://github.com/bluealloy/revm/blob/007b8807b5ad7705d3cacce4d92b89d880a83301/crates/revm/src/interpreter/contract.rs#L114-L115 - i += (op - opcode::PUSH1 + 1) as usize; - cumulative_push_size += (op - opcode::PUSH1 + 1) as usize; - } - i += 1; - } - - pc_ic_map -} - -/// A map of instruction counters to program counters. -pub type ICPCMap = BTreeMap; - -/// Builds a mapping from instruction counters to program counters. -pub fn build_ic_pc_map(spec: SpecId, code: &[u8]) -> ICPCMap { - let opcode_infos = spec_opcode_gas(spec); - let mut ic_pc_map: ICPCMap = ICPCMap::new(); - - let mut i = 0; - let mut cumulative_push_size = 0; - while i < code.len() { - let op = code[i]; - ic_pc_map.insert(i - cumulative_push_size, i); - if opcode_infos[op as usize].is_push() { - // Skip the push bytes. - // - // For more context on the math, see: https://github.com/bluealloy/revm/blob/007b8807b5ad7705d3cacce4d92b89d880a83301/crates/revm/src/interpreter/contract.rs#L114-L115 - i += (op - opcode::PUSH1 + 1) as usize; - cumulative_push_size += (op - opcode::PUSH1 + 1) as usize; - } - i += 1; - } - - ic_pc_map -} - -/// Given an ABI and selector, it tries to find the respective function. -pub fn get_function( - contract_name: &str, - selector: &FixedBytes, - abi: &Abi, -) -> eyre::Result { - abi.functions() - .find(|func| func.short_signature().as_slice() == selector.as_slice()) - .cloned() - .wrap_err(format!("{contract_name} does not have the selector {selector:?}")) -} - -// TODO: Add this once solc is removed from this crate -pub use ethers::solc::utils::RuntimeOrHandle; - -/* -use tokio::runtime::{Handle, Runtime}; - -#[derive(Debug)] -pub enum RuntimeOrHandle { - Runtime(Runtime), - Handle(Handle), -} - -impl Default for RuntimeOrHandle { - fn default() -> Self { - Self::new() - } -} - -impl RuntimeOrHandle { - pub fn new() -> RuntimeOrHandle { - match Handle::try_current() { - Ok(handle) => RuntimeOrHandle::Handle(handle), - Err(_) => RuntimeOrHandle::Runtime(Runtime::new().expect("Failed to start runtime")), - } - } - - pub fn block_on(&self, f: F) -> F::Output { - match &self { - RuntimeOrHandle::Runtime(runtime) => runtime.block_on(f), - RuntimeOrHandle::Handle(handle) => tokio::task::block_in_place(|| handle.block_on(f)), - } - } -} -*/ diff --git a/crates/evm/test-data/storage.json b/crates/evm/test-data/storage.json deleted file mode 100644 index 41114b2cc39be..0000000000000 --- a/crates/evm/test-data/storage.json +++ /dev/null @@ -1 +0,0 @@ -{"meta":{"cfg_env":{"chain_id":"0x1","spec_id":"LATEST","perf_all_precompiles_have_balance":false,"memory_limit":4294967295, "perf_analyse_created_bytecodes":"Analyse", "limit_contract_code_size": 24576},"block_env":{"number":"0xdc42b8","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x1","difficulty":"0x0","basefee":"0x0","gas_limit":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},"hosts":["mainnet.infura.io"]},"accounts":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"balance":"0x0","code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null,"nonce":0}},"storage":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"0x0":"0x0","0x1":"0x0","0x2":"0x0","0x3":"0x0","0x4":"0x0","0x5":"0x0","0x6":"0x0","0x7":"0x0","0x8":"0x0","0x9":"0x0"}},"block_hashes":{}} \ No newline at end of file diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml new file mode 100644 index 0000000000000..f555d619fa228 --- /dev/null +++ b/crates/evm/traces/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "foundry-evm-traces" +description = "EVM trace identifying and decoding" + +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-block-explorers.workspace = true +foundry-common.workspace = true +foundry-compilers.workspace = true +foundry-linking.workspace = true +foundry-config.workspace = true +foundry-evm-core.workspace = true + +alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } +alloy-json-abi.workspace = true +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-sol-types.workspace = true +revm-inspectors.workspace = true + +eyre.workspace = true +futures.workspace = true +itertools.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio = { workspace = true, features = ["time", "macros"] } +tracing.workspace = true +tempfile.workspace = true +rayon.workspace = true +solar-parse.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 0000000000000..1f3fb0b2faedd --- /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 as u32, + 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 preceded 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 preceded by a JUMP marked as [Jump::Out]. + fn jump_out(&mut self) { + let Some((i, _)) = self.stack.iter().enumerate().rfind(|(_, (_, step_idx))| { + self.is_same_loc(*step_idx, self.current_step) || + self.is_same_loc(step_idx + 1, self.current_step - 1) + }) 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 0000000000000..d01c0743946e1 --- /dev/null +++ b/crates/evm/traces/src/debug/sources.rs @@ -0,0 +1,290 @@ +use eyre::{Context, Result}; +use foundry_common::compact_to_contract; +use foundry_compilers::{ + artifacts::{ + sourcemap::{SourceElement, SourceMap}, + Bytecode, Contract, ContractBytecodeSome, Libraries, Source, + }, + multi::MultiCompilerLanguage, + Artifact, Compiler, ProjectCompileOutput, +}; +use foundry_evm_core::utils::PcIcMap; +use foundry_linking::Linker; +use rayon::prelude::*; +use solar_parse::{interface::Session, Parser}; +use std::{ + collections::{BTreeMap, HashMap}, + ops::Range, + 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, Range)>, +} + +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_stem().map(|s| s.to_string_lossy().to_string()) { + contract_definitions.push((name, 0..source.len())); + } + } + MultiCompilerLanguage::Solc(_) => { + let sess = Session::builder().with_silent_emitter(None).build(); + let _ = sess.enter(|| -> solar_parse::interface::Result<()> { + let arena = solar_parse::ast::Arena::new(); + let filename = path.clone().into(); + let mut parser = + Parser::from_source_code(&sess, &arena, filename, source.to_string())?; + let ast = parser.parse_file().map_err(|e| e.emit())?; + for item in ast.items { + if let solar_parse::ast::ItemKind::Contract(contract) = &item.kind { + let range = item.span.lo().to_usize()..item.span.hi().to_usize(); + contract_definitions.push((contract.name.to_string(), range)); + } + } + Ok(()) + }); + } + } + + 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(|(_, r)| start >= r.start && end <= r.end) + .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, name: &str| { + // Only parse source map if it's not empty. + let source_map = if b.source_map.as_ref().is_none_or(|s| s.is_empty()) { + Ok(None) + } else { + b.source_map().transpose().wrap_err_with(|| { + format!("failed to parse {name} source map of file {file_id} in {build_id}") + }) + }; + + // 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, "creation")?; + let (source_map_runtime, pc_ic_map_runtime) = bytecode + .deployed_bytecode + .bytecode + .map(|b| parse(&b, "runtime")) + .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: &Path, + libraries: Option<&Libraries>, + ) -> Result { + let mut sources = Self::default(); + sources.insert(output, root, libraries)?; + Ok(sources) + } + + pub fn insert>( + &mut self, + output: &ProjectCompileOutput, + root: &Path, + libraries: Option<&Libraries>, + ) -> Result<()> + where + C::Language: Into, + { + 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 new_artifact = None; + if let Some(file_id) = artifact.id { + let artifact = if let Some((linker, libraries)) = link_data.as_ref() { + linker.link(id, libraries)? + } else { + artifact.get_contract_bytecode() + }; + let bytecode = compact_to_contract(artifact.into_contract_bytecode())?; + + new_artifact = Some(( + id.name.clone(), + ArtifactData::new(bytecode, id.build_id.clone(), file_id)?, + )); + } else { + warn!(id = id.identifier(), "source not found"); + }; + + Ok(new_artifact) + }) + .collect::>>()?; + + for (name, artifact) in artifacts.into_iter().flatten() { + 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 = match files.entry(path.clone()) { + std::collections::btree_map::Entry::Vacant(entry) => { + 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, + )); + entry.insert(source_data.clone()); + source_data + } + std::collections::btree_map::Entry::Occupied(entry) => entry.get().clone(), + }; + self.sources_by_id + .entry(build_id.clone()) + .or_default() + .insert(*source_id, source_data); + } + } + + Ok(()) + } + + /// Merges given contract sources. + pub fn merge(&mut self, sources: Self) { + self.sources_by_id.extend(sources.sources_by_id); + for (name, artifacts) in sources.artifacts_by_name { + self.artifacts_by_name.entry(name).or_default().extend(artifacts); + } + } + + /// 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: u32, + 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 as usize) + } else { + source_map.get(pc as usize) + }?; + // 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 new file mode 100644 index 0000000000000..d96e8d5dc6c82 --- /dev/null +++ b/crates/evm/traces/src/decoder/mod.rs @@ -0,0 +1,1053 @@ +use crate::{ + debug::DebugTraceIdentifier, + identifier::{ + AddressIdentity, LocalTraceIdentifier, SingleSignaturesIdentifier, TraceIdentifier, + }, + CallTrace, CallTraceArena, CallTraceNode, DecodedCallData, +}; +use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt, FunctionExt, JsonAbiExt}; +use alloy_json_abi::{Error, Event, Function, JsonAbi}; +use alloy_primitives::{ + map::{hash_map::Entry, HashMap}, + Address, LogData, Selector, B256, +}; +use foundry_common::{ + abi::get_indexed_event, fmt::format_token, get_contract_name, ContractsByArtifact, SELECTOR_LEN, +}; +use foundry_evm_core::{ + abi::{console, Vm}, + constants::{ + CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, + TEST_CONTRACT_ADDRESS, + }, + decode::RevertDecoder, + precompiles::{ + BLAKE_2F, EC_ADD, EC_MUL, EC_PAIRING, EC_RECOVER, IDENTITY, MOD_EXP, POINT_EVALUATION, + RIPEMD_160, SHA_256, + }, +}; +use itertools::Itertools; +use revm_inspectors::tracing::types::{DecodedCallLog, DecodedCallTrace}; +use std::{collections::BTreeMap, sync::OnceLock}; + +mod precompiles; + +/// Build a new [CallTraceDecoder]. +#[derive(Default)] +#[must_use = "builders do nothing unless you call `build` on them"] +pub struct CallTraceDecoderBuilder { + decoder: CallTraceDecoder, +} + +impl CallTraceDecoderBuilder { + /// Create a new builder. + #[inline] + pub fn new() -> Self { + Self { decoder: CallTraceDecoder::new().clone() } + } + + /// Add known labels to the decoder. + #[inline] + pub fn with_labels(mut self, labels: impl IntoIterator) -> Self { + self.decoder.labels.extend(labels); + self + } + + /// Add known errors to the decoder. + #[inline] + pub fn with_abi(mut self, abi: &JsonAbi) -> Self { + self.decoder.collect_abi(abi, None); + self + } + + /// Add known contracts to the decoder. + #[inline] + pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self { + trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs"); + for contract in contracts.values() { + self.decoder.collect_abi(&contract.abi, None); + } + self + } + + /// Add known contracts to the decoder from a `LocalTraceIdentifier`. + #[inline] + pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self { + self.with_known_contracts(identifier.contracts()) + } + + /// Sets the verbosity level of the decoder. + #[inline] + pub fn with_verbosity(mut self, level: u8) -> Self { + self.decoder.verbosity = level; + self + } + + /// Sets the signature identifier for events and functions. + #[inline] + pub fn with_signature_identifier(mut self, identifier: SingleSignaturesIdentifier) -> Self { + self.decoder.signature_identifier = Some(identifier); + 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 { + self.decoder + } +} + +/// The call trace decoder. +/// +/// The decoder collects address labels and ABIs from any number of [TraceIdentifier]s, which it +/// then uses to decode the call trace. +/// +/// Note that a call trace decoder is required for each new set of traces, since addresses in +/// different sets might overlap. +#[derive(Clone, Debug, Default)] +pub struct CallTraceDecoder { + /// Addresses identified to be a specific contract. + /// + /// The values are in the form `":"`. + pub contracts: HashMap, + /// Address labels. + pub labels: HashMap, + /// Contract addresses that have a receive function. + pub receive_contracts: Vec
, + /// Contract addresses that have fallback functions, mapped to function sigs. + pub fallback_contracts: HashMap>, + + /// All known functions. + pub functions: HashMap>, + /// All known events. + pub events: BTreeMap<(B256, usize), Vec>, + /// Revert decoder. Contains all known custom errors. + pub revert_decoder: RevertDecoder, + + /// A signature identifier for events and functions. + pub signature_identifier: Option, + /// Verbosity level + pub verbosity: u8, + + /// Optional identifier of individual trace steps. + pub debug_identifier: Option, +} + +impl CallTraceDecoder { + /// Creates a new call trace decoder. + /// + /// The call trace decoder always knows how to decode calls to the cheatcode address, as well + /// as DSTest-style logs. + pub fn new() -> &'static Self { + // If you want to take arguments in this function, assign them to the fields of the cloned + // lazy instead of removing it + static INIT: OnceLock = OnceLock::new(); + INIT.get_or_init(Self::init) + } + + fn init() -> Self { + Self { + contracts: Default::default(), + labels: HashMap::from_iter([ + (CHEATCODE_ADDRESS, "VM".to_string()), + (HARDHAT_CONSOLE_ADDRESS, "console".to_string()), + (DEFAULT_CREATE2_DEPLOYER, "Create2Deployer".to_string()), + (CALLER, "DefaultSender".to_string()), + (TEST_CONTRACT_ADDRESS, "DefaultTestContract".to_string()), + (EC_RECOVER, "ECRecover".to_string()), + (SHA_256, "SHA-256".to_string()), + (RIPEMD_160, "RIPEMD-160".to_string()), + (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()), + ]), + receive_contracts: Default::default(), + fallback_contracts: Default::default(), + + functions: console::hh::abi::functions() + .into_values() + .chain(Vm::abi::functions().into_values()) + .flatten() + .map(|func| (func.selector(), vec![func])) + .collect(), + events: console::ds::abi::events() + .into_values() + .flatten() + .map(|event| ((event.selector(), indexed_inputs(&event)), vec![event])) + .collect(), + revert_decoder: Default::default(), + + signature_identifier: None, + verbosity: 0, + + debug_identifier: None, + } + } + + /// Clears all known addresses. + pub fn clear_addresses(&mut self) { + self.contracts.clear(); + + let default_labels = &Self::new().labels; + if self.labels.len() > default_labels.len() { + self.labels.clone_from(default_labels); + } + + self.receive_contracts.clear(); + self.fallback_contracts.clear(); + } + + /// Identify unknown addresses in the specified call trace using the specified identifier. + /// + /// Unknown contracts are contracts that either lack a label or an ABI. + pub fn identify(&mut self, trace: &CallTraceArena, identifier: &mut impl TraceIdentifier) { + self.collect_identities(identifier.identify_addresses(self.trace_addresses(trace))); + } + + /// Adds a single event to the decoder. + pub fn push_event(&mut self, event: Event) { + self.events.entry((event.selector(), indexed_inputs(&event))).or_default().push(event); + } + + /// Adds a single function to the decoder. + pub fn push_function(&mut self, function: Function) { + match self.functions.entry(function.selector()) { + Entry::Occupied(entry) => { + // This shouldn't happen that often. + if entry.get().contains(&function) { + return; + } + trace!(target: "evm::traces", selector=%entry.key(), new=%function.signature(), "duplicate function selector"); + entry.into_mut().push(function); + } + Entry::Vacant(entry) => { + entry.insert(vec![function]); + } + } + } + + /// Adds a single error to the decoder. + pub fn push_error(&mut self, error: Error) { + self.revert_decoder.push_error(error); + } + + /// Returns an iterator over the trace addresses. + pub fn trace_addresses<'a>( + &'a self, + arena: &'a CallTraceArena, + ) -> impl Iterator, Option<&'a [u8]>)> + Clone + 'a { + arena + .nodes() + .iter() + .map(|node| { + ( + &node.trace.address, + node.trace.kind.is_any_create().then_some(&node.trace.output[..]), + node.trace.kind.is_any_create().then_some(&node.trace.data[..]), + ) + }) + .filter(|&(address, _, _)| { + !self.labels.contains_key(address) || !self.contracts.contains_key(address) + }) + } + + fn collect_identities(&mut self, identities: Vec>) { + // Skip logging if there are no identities. + if identities.is_empty() { + return; + } + + trace!(target: "evm::traces", len=identities.len(), "collecting address identities"); + for AddressIdentity { address, label, contract, abi, artifact_id: _ } in identities { + let _span = trace_span!(target: "evm::traces", "identity", ?contract, ?label).entered(); + + if let Some(contract) = contract { + self.contracts.entry(address).or_insert(contract); + } + + if let Some(label) = label { + self.labels.entry(address).or_insert(label); + } + + if let Some(abi) = abi { + self.collect_abi(&abi, Some(&address)); + } + } + } + + fn collect_abi(&mut self, abi: &JsonAbi, address: Option<&Address>) { + trace!(target: "evm::traces", len=abi.len(), ?address, "collecting ABI"); + for function in abi.functions() { + self.push_function(function.clone()); + } + for event in abi.events() { + self.push_event(event.clone()); + } + for error in abi.errors() { + self.push_error(error.clone()); + } + if let Some(address) = address { + if abi.receive.is_some() { + self.receive_contracts.push(*address); + } + + if abi.fallback.is_some() { + let mut functions_sig = vec![]; + for function in abi.functions() { + functions_sig.push(function.signature()); + } + self.fallback_contracts.insert(*address, functions_sig); + } + } + } + + /// 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 { + if let Some(trace) = precompiles::decode(trace, 1) { + return trace; + } + + let label = self.labels.get(&trace.address).cloned(); + + let cdata = &trace.data; + if trace.address == DEFAULT_CREATE2_DEPLOYER { + return DecodedCallTrace { + label, + call_data: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }), + return_data: self.default_return_data(trace), + }; + } + + if cdata.len() >= SELECTOR_LEN { + let selector = &cdata[..SELECTOR_LEN]; + let mut functions = Vec::new(); + let functions = match self.functions.get(selector) { + Some(fs) => fs, + None => { + if let Some(identifier) = &self.signature_identifier { + if let Some(function) = + identifier.write().await.identify_function(selector).await + { + functions.push(function); + } + } + &functions + } + }; + let [func, ..] = &functions[..] else { + return DecodedCallTrace { + label, + call_data: self.fallback_contracts.get(&trace.address).map(|_| { + DecodedCallData { + signature: "fallback()".to_string(), + args: vec![cdata.to_string()], + } + }), + return_data: self.default_return_data(trace), + }; + }; + + // If traced contract is a fallback contract, check if it has the decoded function. + // If not, then replace call data signature with `fallback`. + let mut call_data = self.decode_function_input(trace, func); + if let Some(fallback_functions) = self.fallback_contracts.get(&trace.address) { + if !fallback_functions.contains(&func.signature()) { + call_data.signature = "fallback()".to_string(); + } + } + + DecodedCallTrace { + label, + call_data: Some(call_data), + return_data: self.decode_function_output(trace, functions), + } + } else { + let has_receive = self.receive_contracts.contains(&trace.address); + let signature = + if cdata.is_empty() && has_receive { "receive()" } else { "fallback()" } + .to_string(); + let args = if cdata.is_empty() { Vec::new() } else { vec![cdata.to_string()] }; + DecodedCallTrace { + label, + call_data: Some(DecodedCallData { signature, args }), + return_data: self.default_return_data(trace), + } + } + } + + /// Decodes a function's input into the given trace. + fn decode_function_input(&self, trace: &CallTrace, func: &Function) -> DecodedCallData { + let mut args = None; + if trace.data.len() >= SELECTOR_LEN { + if trace.address == CHEATCODE_ADDRESS { + // Try to decode cheatcode inputs in a more custom way + if let Some(v) = self.decode_cheatcode_inputs(func, &trace.data) { + args = Some(v); + } + } + + if args.is_none() { + if let Ok(v) = func.abi_decode_input(&trace.data[SELECTOR_LEN..], false) { + args = Some(v.iter().map(|value| self.format_value(value)).collect()); + } + } + } + + DecodedCallData { signature: func.signature(), args: args.unwrap_or_default() } + } + + /// Custom decoding for cheatcode inputs. + fn decode_cheatcode_inputs(&self, func: &Function, data: &[u8]) -> Option> { + match func.name.as_str() { + "expectRevert" => Some(vec![self.revert_decoder.decode(data, None)]), + "addr" | "createWallet" | "deriveKey" | "rememberKey" => { + // Redact private key in all cases + Some(vec!["".to_string()]) + } + "broadcast" | "startBroadcast" => { + // Redact private key if defined + // broadcast(uint256) / startBroadcast(uint256) + if !func.inputs.is_empty() && func.inputs[0].ty == "uint256" { + Some(vec!["".to_string()]) + } else { + None + } + } + "getNonce" => { + // Redact private key if defined + // getNonce(Wallet) + if !func.inputs.is_empty() && func.inputs[0].ty == "tuple" { + Some(vec!["".to_string()]) + } else { + None + } + } + "sign" | "signP256" => { + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + + // Redact private key and replace in trace + // sign(uint256,bytes32) / signP256(uint256,bytes32) / sign(Wallet,bytes32) + if !decoded.is_empty() && + (func.inputs[0].ty == "uint256" || func.inputs[0].ty == "tuple") + { + decoded[0] = DynSolValue::String("".to_string()); + } + + Some(decoded.iter().map(format_token).collect()) + } + "parseJson" | + "parseJsonUint" | + "parseJsonUintArray" | + "parseJsonInt" | + "parseJsonIntArray" | + "parseJsonString" | + "parseJsonStringArray" | + "parseJsonAddress" | + "parseJsonAddressArray" | + "parseJsonBool" | + "parseJsonBoolArray" | + "parseJsonBytes" | + "parseJsonBytesArray" | + "parseJsonBytes32" | + "parseJsonBytes32Array" | + "writeJson" | + // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + "keyExists" | + "keyExistsJson" | + "serializeBool" | + "serializeUint" | + "serializeUintToHex" | + "serializeInt" | + "serializeAddress" | + "serializeBytes32" | + "serializeString" | + "serializeBytes" => { + if self.verbosity >= 5 { + None + } else { + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let token = if func.name.as_str() == "parseJson" || + // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + func.name.as_str() == "keyExists" || + func.name.as_str() == "keyExistsJson" + { + "" + } else { + "" + }; + decoded[0] = DynSolValue::String(token.to_string()); + Some(decoded.iter().map(format_token).collect()) + } + } + s if s.contains("Toml") => { + if self.verbosity >= 5 { + None + } else { + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let token = if func.name.as_str() == "parseToml" || + func.name.as_str() == "keyExistsToml" + { + "" + } else { + "" + }; + decoded[0] = DynSolValue::String(token.to_string()); + Some(decoded.iter().map(format_token).collect()) + } + } + "createFork" | + "createSelectFork" | + "rpc" => { + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + + // Redact RPC URL except if referenced by an alias + if !decoded.is_empty() && func.inputs[0].ty == "string" { + let url_or_alias = decoded[0].as_str().unwrap_or_default(); + + if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") { + decoded[0] = DynSolValue::String("".to_string()); + } + } else { + return None; + } + + Some(decoded.iter().map(format_token).collect()) + } + _ => None, + } + } + + /// Decodes a function's output into the given trace. + fn decode_function_output(&self, trace: &CallTrace, funcs: &[Function]) -> Option { + if !trace.success { + return self.default_return_data(trace); + } + + if trace.address == CHEATCODE_ADDRESS { + if let Some(decoded) = funcs.iter().find_map(|func| self.decode_cheatcode_outputs(func)) + { + return Some(decoded); + } + } + + if 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; + } + + return Some( + values.iter().map(|value| self.format_value(value)).format(", ").to_string(), + ); + } + + None + } + + /// Custom decoding for cheatcode outputs. + fn decode_cheatcode_outputs(&self, func: &Function) -> Option { + match func.name.as_str() { + s if s.starts_with("env") => Some(""), + "createWallet" | "deriveKey" => Some(""), + "promptSecret" | "promptSecretUint" => Some(""), + "parseJson" if self.verbosity < 5 => Some(""), + "readFile" if self.verbosity < 5 => Some(""), + "rpcUrl" | "rpcUrls" | "rpcUrlStructs" => Some(""), + _ => None, + } + .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(&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)) { + Some(es) => es, + None => { + if let Some(identifier) = &self.signature_identifier { + if let Some(event) = identifier.write().await.identify_event(&t0[..]).await { + events.push(get_indexed_event(event, log)); + } + } + &events + } + }; + for event in events { + if let Ok(decoded) = event.decode_log(log, false) { + let params = reconstruct_params(event, &decoded); + 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 { name: None, params: None } + } + + /// Prefetches function and event signatures into the identifier cache + pub async fn prefetch_signatures(&self, nodes: &[CallTraceNode]) { + let Some(identifier) = &self.signature_identifier else { return }; + + let events_it = nodes + .iter() + .flat_map(|node| node.logs.iter().filter_map(|log| log.raw_log.topics().first())) + .unique(); + identifier.write().await.identify_events(events_it).await; + + const DEFAULT_CREATE2_DEPLOYER_BYTES: [u8; 20] = DEFAULT_CREATE2_DEPLOYER.0 .0; + let funcs_it = nodes + .iter() + .filter_map(|n| match n.trace.address.0 .0 { + DEFAULT_CREATE2_DEPLOYER_BYTES => None, + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01..=0x0a] => None, + _ => n.trace.data.get(..SELECTOR_LEN), + }) + .filter(|v| !self.functions.contains_key(*v)) + .unique(); + identifier.write().await.identify_functions(funcs_it).await; + } + + /// 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}]"); + } + } + format_token(value) + } +} + +/// Restore the order of the params of a decoded event, +/// as Alloy returns the indexed and unindexed params separately. +fn reconstruct_params(event: &Event, decoded: &DecodedEvent) -> Vec { + let mut indexed = 0; + let mut unindexed = 0; + let mut inputs = vec![]; + for input in event.inputs.iter() { + // Prevent panic of event `Transfer(from, to)` decoded with a signature + // `Transfer(address indexed from, address indexed to, uint256 indexed tokenId)` by making + // sure the event inputs is not higher than decoded indexed / un-indexed values. + if input.indexed && indexed < decoded.indexed.len() { + inputs.push(decoded.indexed[indexed].clone()); + indexed += 1; + } else if unindexed < decoded.body.len() { + inputs.push(decoded.body[unindexed].clone()); + unindexed += 1; + } + } + + inputs +} + +fn indexed_inputs(event: &Event) -> usize { + event.inputs.iter().filter(|param| param.indexed).count() +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + + #[test] + fn test_should_redact() { + let decoder = CallTraceDecoder::new(); + + // [function_signature, data, expected] + let cheatcode_input_test_cases = vec![ + // Should redact private key from traces in all cases: + ("addr(uint256)", vec![], Some(vec!["".to_string()])), + ("createWallet(string)", vec![], Some(vec!["".to_string()])), + ("createWallet(uint256)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,uint32)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,string,uint32)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,uint32,string)", vec![], Some(vec!["".to_string()])), + ("deriveKey(string,string,uint32,string)", vec![], Some(vec!["".to_string()])), + ("rememberKey(uint256)", vec![], Some(vec!["".to_string()])), + // + // Should redact private key from traces in specific cases with exceptions: + ("broadcast(uint256)", vec![], Some(vec!["".to_string()])), + ("broadcast()", vec![], None), // Ignore: `private key` is not passed. + ("startBroadcast(uint256)", vec![], Some(vec!["".to_string()])), + ("startBroadcast()", vec![], None), // Ignore: `private key` is not passed. + ("getNonce((address,uint256,uint256,uint256))", vec![], Some(vec!["".to_string()])), + ("getNonce(address)", vec![], None), // Ignore: `address` is public. + // + // Should redact private key and replace in trace in cases: + ( + "sign(uint256,bytes32)", + hex!( + " + e341eaa4 + 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "0x0000000000000000000000000000000000000000000000000000000000000000" + .to_string(), + ]), + ), + ( + "signP256(uint256,bytes32)", + hex!( + " + 83211b40 + 7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6 + 0000000000000000000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "0x0000000000000000000000000000000000000000000000000000000000000000" + .to_string(), + ]), + ), + ( + // cast calldata "createFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key" + "createFork(string)", + hex!( + " + 31ba3498 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"\"".to_string()]), + ), + ( + // cast calldata "createFork(string)" "wss://eth-mainnet.g.alchemy.com/v2/api_key" + "createFork(string)", + hex!( + " + 31ba3498 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000002a + 7773733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f6d2f + 76322f6170695f6b657900000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"\"".to_string()]), + ), + ( + // cast calldata "createFork(string)" "mainnet" + "createFork(string)", + hex!( + " + 31ba3498 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"mainnet\"".to_string()]), + ), + ( + // cast calldata "createFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1 + "createFork(string,uint256)", + hex!( + " + 6ba3ba2b + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000001 + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"\"".to_string(), "1".to_string()]), + ), + ( + // cast calldata "createFork(string,uint256)" "mainnet" 1 + "createFork(string,uint256)", + hex!( + " + 6ba3ba2b + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"mainnet\"".to_string(), "1".to_string()]), + ), + ( + // cast calldata "createFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + "createFork(string,bytes32)", + hex!( + " + 7ca29682 + 0000000000000000000000000000000000000000000000000000000000000040 + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + .to_string(), + ]), + ), + ( + // cast calldata "createFork(string,bytes32)" "mainnet" + // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + "createFork(string,bytes32)", + hex!( + " + 7ca29682 + 0000000000000000000000000000000000000000000000000000000000000040 + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"mainnet\"".to_string(), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + .to_string(), + ]), + ), + ( + // cast calldata "createSelectFork(string)" "https://eth-mainnet.g.alchemy.com/v2/api_key" + "createSelectFork(string)", + hex!( + " + 98680034 + 0000000000000000000000000000000000000000000000000000000000000020 + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"\"".to_string()]), + ), + ( + // cast calldata "createSelectFork(string)" "mainnet" + "createSelectFork(string)", + hex!( + " + 98680034 + 0000000000000000000000000000000000000000000000000000000000000020 + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"mainnet\"".to_string()]), + ), + ( + // cast calldata "createSelectFork(string,uint256)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 1 + "createSelectFork(string,uint256)", + hex!( + " + 71ee464d + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000001 + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"\"".to_string(), "1".to_string()]), + ), + ( + // cast calldata "createSelectFork(string,uint256)" "mainnet" 1 + "createSelectFork(string,uint256)", + hex!( + " + 71ee464d + 0000000000000000000000000000000000000000000000000000000000000040 + 0000000000000000000000000000000000000000000000000000000000000001 + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec!["\"mainnet\"".to_string(), "1".to_string()]), + ), + ( + // cast calldata "createSelectFork(string,bytes32)" "https://eth-mainnet.g.alchemy.com/v2/api_key" 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + "createSelectFork(string,bytes32)", + hex!( + " + 84d52b7a + 0000000000000000000000000000000000000000000000000000000000000040 + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + .to_string(), + ]), + ), + ( + // cast calldata "createSelectFork(string,bytes32)" "mainnet" + // 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + "createSelectFork(string,bytes32)", + hex!( + " + 84d52b7a + 0000000000000000000000000000000000000000000000000000000000000040 + ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"mainnet\"".to_string(), + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + .to_string(), + ]), + ), + ( + // cast calldata "rpc(string,string,string)" "https://eth-mainnet.g.alchemy.com/v2/api_key" "eth_getBalance" "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]" + "rpc(string,string,string)", + hex!( + " + 0199a220 + 0000000000000000000000000000000000000000000000000000000000000060 + 00000000000000000000000000000000000000000000000000000000000000c0 + 0000000000000000000000000000000000000000000000000000000000000100 + 000000000000000000000000000000000000000000000000000000000000002c + 68747470733a2f2f6574682d6d61696e6e65742e672e616c6368656d792e636f + 6d2f76322f6170695f6b65790000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000000e + 6574685f67657442616c616e6365000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000034 + 5b22307835353165373738343737386566386530343865343935646634396632 + 363134663834613466316463222c22307830225d000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"\"".to_string(), + "\"eth_getBalance\"".to_string(), + "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\"" + .to_string(), + ]), + ), + ( + // cast calldata "rpc(string,string,string)" "mainnet" "eth_getBalance" + // "[\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\",\"0x0\"]" + "rpc(string,string,string)", + hex!( + " + 0199a220 + 0000000000000000000000000000000000000000000000000000000000000060 + 00000000000000000000000000000000000000000000000000000000000000a0 + 00000000000000000000000000000000000000000000000000000000000000e0 + 0000000000000000000000000000000000000000000000000000000000000007 + 6d61696e6e657400000000000000000000000000000000000000000000000000 + 000000000000000000000000000000000000000000000000000000000000000e + 6574685f67657442616c616e6365000000000000000000000000000000000000 + 0000000000000000000000000000000000000000000000000000000000000034 + 5b22307835353165373738343737386566386530343865343935646634396632 + 363134663834613466316463222c22307830225d000000000000000000000000 + " + ) + .to_vec(), + Some(vec![ + "\"mainnet\"".to_string(), + "\"eth_getBalance\"".to_string(), + "\"[\\\"0x551e7784778ef8e048e495df49f2614f84a4f1dc\\\",\\\"0x0\\\"]\"" + .to_string(), + ]), + ), + ]; + + // [function_signature, expected] + let cheatcode_output_test_cases = vec![ + // Should redact private key on output in all cases: + ("createWallet(string)", Some("".to_string())), + ("deriveKey(string,uint32)", Some("".to_string())), + // Should redact RPC URL if defined, except if referenced by an alias: + ("rpcUrl(string)", Some("".to_string())), + ("rpcUrls()", Some("".to_string())), + ("rpcUrlStructs()", Some("".to_string())), + ]; + + for (function_signature, data, expected) in cheatcode_input_test_cases { + let function = Function::parse(function_signature).unwrap(); + let result = decoder.decode_cheatcode_inputs(&function, &data); + assert_eq!(result, expected, "Input case failed for: {function_signature}"); + } + + for (function_signature, expected) in cheatcode_output_test_cases { + let function = Function::parse(function_signature).unwrap(); + let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default()); + assert_eq!(result, expected, "Output case failed for: {function_signature}"); + } + } +} diff --git a/crates/evm/traces/src/decoder/precompiles.rs b/crates/evm/traces/src/decoder/precompiles.rs new file mode 100644 index 0000000000000..0719f29b77aef --- /dev/null +++ b/crates/evm/traces/src/decoder/precompiles.rs @@ -0,0 +1,206 @@ +use crate::{CallTrace, DecodedCallData}; +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 +/// Solidity ABI codec. +/// +/// Parameter names and types are taken from [evm.codes](https://www.evm.codes/precompiled). +interface Precompiles { + struct EcPairingInput { + uint256 x1; + uint256 y1; + uint256 x2; + uint256 y2; + uint256 x3; + uint256 y3; + } + + /* 0x01 */ function ecrecover(bytes32 hash, uint8 v, uint256 r, uint256 s) returns (address publicAddress); + /* 0x02 */ function sha256(bytes data) returns (bytes32 hash); + /* 0x03 */ function ripemd(bytes data) returns (bytes20 hash); + /* 0x04 */ function identity(bytes data) returns (bytes data); + /* 0x05 */ function modexp(uint256 Bsize, uint256 Esize, uint256 Msize, bytes B, bytes E, bytes M) returns (bytes value); + /* 0x06 */ function ecadd(uint256 x1, uint256 y1, uint256 x2, uint256 y2) returns (uint256 x, uint256 y); + /* 0x07 */ function ecmul(uint256 x1, uint256 y1, uint256 s) returns (uint256 x, uint256 y); + /* 0x08 */ function ecpairing(EcPairingInput[] input) returns (bool success); + /* 0x09 */ function blake2f(uint32 rounds, uint64[8] h, uint64[16] m, uint64[2] t, bool f) returns (uint64[8] h); + /* 0x0a */ function pointEvaluation(bytes32 versionedHash, bytes32 z, bytes32 y, bytes1[48] commitment, bytes1[48] proof) returns (bytes value); +} +} +use Precompiles::*; + +macro_rules! tri { + ($e:expr) => { + match $e { + Ok(x) => x, + Err(_) => return None, + } + }; +} + +/// Tries to decode a precompile call. Returns `Some` if successful. +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 trace.address { + EC_RECOVER => { + let (sig, ecrecoverCall { hash, v, r, s }) = tri!(abi_decode_call(data)); + (sig, vec![hash.to_string(), v.to_string(), r.to_string(), s.to_string()]) + } + SHA_256 => (sha256Call::SIGNATURE, vec![data.to_string()]), + RIPEMD_160 => (ripemdCall::SIGNATURE, vec![data.to_string()]), + IDENTITY => (identityCall::SIGNATURE, vec![data.to_string()]), + MOD_EXP => (modexpCall::SIGNATURE, tri!(decode_modexp(data))), + EC_ADD => { + let (sig, ecaddCall { x1, y1, x2, y2 }) = tri!(abi_decode_call(data)); + (sig, vec![x1.to_string(), y1.to_string(), x2.to_string(), y2.to_string()]) + } + EC_MUL => { + let (sig, ecmulCall { x1, y1, s }) = tri!(abi_decode_call(data)); + (sig, vec![x1.to_string(), y1.to_string(), s.to_string()]) + } + 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(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 +// convenient way to decode the data. + +fn decode_modexp(data: &[u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data, false); + let b_size = decoder.take_offset()?; + let e_size = decoder.take_offset()?; + let m_size = decoder.take_offset()?; + let b = decoder.take_slice_unchecked(b_size)?; + let e = decoder.take_slice_unchecked(e_size)?; + let m = decoder.take_slice_unchecked(m_size)?; + Ok(vec![ + b_size.to_string(), + e_size.to_string(), + m_size.to_string(), + hex::encode_prefixed(b), + hex::encode_prefixed(e), + hex::encode_prefixed(m), + ]) +} + +fn decode_ecpairing(data: &[u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data, false); + let mut values = Vec::new(); + // input must be either empty or a multiple of 6 32-byte values + let mut tmp = <[&B256; 6]>::default(); + while !decoder.is_empty() { + for tmp in &mut tmp { + *tmp = decoder.take_word()?; + } + values.push(iter_to_string(tmp.iter().map(|x| U256::from_be_bytes(x.0)))); + } + Ok(values) +} + +fn decode_blake2f<'a>(data: &'a [u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data, false); + let rounds = u32::from_be_bytes(decoder.take_slice_unchecked(4)?.try_into().unwrap()); + let u64_le_list = + |x: &'a [u8]| x.chunks_exact(8).map(|x| u64::from_le_bytes(x.try_into().unwrap())); + let h = u64_le_list(decoder.take_slice_unchecked(64)?); + let m = u64_le_list(decoder.take_slice_unchecked(128)?); + let t = u64_le_list(decoder.take_slice_unchecked(16)?); + let f = decoder.take_slice_unchecked(1)?[0]; + Ok(vec![ + rounds.to_string(), + iter_to_string(h), + iter_to_string(m), + iter_to_string(t), + f.to_string(), + ]) +} + +fn decode_kzg(data: &[u8]) -> alloy_sol_types::Result> { + let mut decoder = abi::Decoder::new(data, false); + let versioned_hash = decoder.take_word()?; + let z = decoder.take_word()?; + let y = decoder.take_word()?; + let commitment = decoder.take_slice_unchecked(48)?; + let proof = decoder.take_slice_unchecked(48)?; + Ok(vec![ + versioned_hash.to_string(), + z.to_string(), + y.to_string(), + hex::encode_prefixed(commitment), + hex::encode_prefixed(proof), + ]) +} + +fn abi_decode_call(data: &[u8]) -> alloy_sol_types::Result<(&'static str, T)> { + // raw because there are no selectors here + Ok((T::SIGNATURE, T::abi_decode_raw(data, false)?)) +} + +fn iter_to_string, T: std::fmt::Display>(iter: I) -> String { + format!("[{}]", iter.format(", ")) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + + #[test] + fn ecpairing() { + // https://github.com/foundry-rs/foundry/issues/5337#issuecomment-1627384480 + let data = hex!( + " + 26bbb723f965460ca7282cd75f0e3e7c67b15817f7cee60856b394936ed02917 + 0fbe873ac672168143a91535450bab6c412dce8dc8b66a88f2da6e245f9282df + 13cd4f0451538ece5014fe6688b197aefcc611a5c6a7c319f834f2188ba04b08 + 126ff07e81490a1b6ae92b2d9e700c8e23e9d5c7f6ab857027213819a6c9ae7d + 04183624c9858a56c54deb237c26cb4355bc2551312004e65fc5b299440b15a3 + 2e4b11aa549ad6c667057b18be4f4437fda92f018a59430ebb992fa3462c9ca1 + 2d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e2 + 14bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d1926 + 0967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c + 0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab + 304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a7 + 1739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec8 + 001d6fedb032f70e377635238e0563f131670001f6abf439adb3a9d5d52073c6 + 1889afe91e4e367f898a7fcd6464e5ca4e822fe169bccb624f6aeb87e4d060bc + 198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2 + 1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed + 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b + 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa + 2dde6d7baf0bfa09329ec8d44c38282f5bf7f9ead1914edd7dcaebb498c84519 + 0c359f868a85c6e6c1ea819cfab4a867501a3688324d74df1fe76556558b1937 + 29f41c6e0e30802e2749bfb0729810876f3423e6f24829ad3e30adb1934f1c8a + 030e7a5f70bb5daa6e18d80d6d447e772efb0bb7fb9d0ffcd54fc5a48af1286d + 0ea726b117e48cda8bce2349405f006a84cdd3dcfba12efc990df25970a27b6d + 30364cd4f8a293b1a04f0153548d3e01baad091c69097ca4e9f26be63e4095b5 + " + ); + let decoded = decode_ecpairing(&data).unwrap(); + // 4 arrays of 6 32-byte values + assert_eq!(decoded.len(), 4); + } +} diff --git a/crates/evm/traces/src/folded_stack_trace.rs b/crates/evm/traces/src/folded_stack_trace.rs new file mode 100644 index 0000000000000..ee3a05afad29d --- /dev/null +++ b/crates/evm/traces/src/folded_stack_trace.rs @@ -0,0 +1,302 @@ +use alloy_primitives::hex::ToHexExt; +use revm_inspectors::tracing::{ + types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder}, + CallTraceArena, +}; + +/// Builds a folded stack trace from a call trace arena. +pub fn build(arena: &CallTraceArena) -> Vec { + let mut fst = EvmFoldedStackTraceBuilder::default(); + fst.process_call_node(arena.nodes(), 0); + fst.build() +} + +/// Wrapper for building a folded stack trace using EVM call trace node. +#[derive(Default)] +pub struct EvmFoldedStackTraceBuilder { + /// Raw folded stack trace builder. + fst: FoldedStackTraceBuilder, +} + +impl EvmFoldedStackTraceBuilder { + /// Returns the folded stack trace. + pub fn build(self) -> Vec { + self.fst.build() + } + + /// Creates an entry for a EVM CALL in the folded stack trace. This method recursively processes + /// all the children nodes of the call node and at the end it exits. + pub fn process_call_node(&mut self, nodes: &[CallTraceNode], idx: usize) { + let node = &nodes[idx]; + + let func_name = if node.trace.kind.is_any_create() { + let contract_name = node.trace.decoded.label.as_deref().unwrap_or("Contract"); + format!("new {contract_name}") + } else { + let selector = node + .selector() + .map(|selector| selector.encode_hex_with_prefix()) + .unwrap_or_else(|| "fallback".to_string()); + let signature = + node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector); + + if let Some(label) = &node.trace.decoded.label { + format!("{label}.{signature}") + } else { + signature.clone() + } + }; + + self.fst.enter(func_name, node.trace.gas_used as i64); + + // Track internal function step exits to do in this call context. + let mut step_exits = vec![]; + + // Process children nodes. + for order in &node.ordering { + match order { + TraceMemberOrder::Call(child_idx) => { + let child_node_idx = node.children[*child_idx]; + self.process_call_node(nodes, child_node_idx); + } + TraceMemberOrder::Step(step_idx) => { + self.exit_previous_steps(&mut step_exits, *step_idx); + self.process_step(&node.trace.steps, *step_idx, &mut step_exits) + } + TraceMemberOrder::Log(_) => {} + } + } + + // Exit pending internal function calls if any. + for _ in 0..step_exits.len() { + self.fst.exit(); + } + + // Exit from this call context in the folded stack trace. + self.fst.exit(); + } + + /// Creates an entry for an internal function call in the folded stack trace. This method only + /// enters the function in the folded stack trace, we cannot exit since we need to exit at a + /// future step. Hence, we keep track of the step end index in the `step_exits`. + fn process_step( + &mut self, + steps: &[CallTraceStep], + step_idx: usize, + step_exits: &mut Vec, + ) { + let step = &steps[step_idx]; + if let Some(decoded_step) = &step.decoded { + match decoded_step { + DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => { + let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used); + self.fst.enter(decoded_internal_call.func_name.clone(), gas_used as i64); + step_exits.push(*step_end_idx); + } + DecodedTraceStep::Line(_) => {} + } + } + } + + /// Exits all the previous internal calls that should end before starting step_idx. + fn exit_previous_steps(&mut self, step_exits: &mut Vec, step_idx: usize) { + let initial_length = step_exits.len(); + step_exits.retain(|&number| number > step_idx); + + let num_exits = initial_length - step_exits.len(); + for _ in 0..num_exits { + self.fst.exit(); + } + } +} + +/// Helps to translate a function enter-exit flow into a folded stack trace. +/// +/// Example: +/// ```solidity +/// function top() { child_a(); child_b() } // consumes 500 gas +/// function child_a() {} // consumes 100 gas +/// function child_b() {} // consumes 200 gas +/// ``` +/// +/// For execution of the `top` function looks like: +/// 1. enter `top` +/// 2. enter `child_a` +/// 3. exit `child_a` +/// 4. enter `child_b` +/// 5. exit `child_b` +/// 6. exit `top` +/// +/// The translated folded stack trace lines look like: +/// 1. top +/// 2. top;child_a +/// 3. top;child_b +/// +/// Including the gas consumed by the function by itself. +/// 1. top 200 // 500 - 100 - 200 +/// 2. top;child_a 100 +/// 3. top;child_b 200 +#[derive(Debug, Default)] +pub struct FoldedStackTraceBuilder { + /// Trace entries. + traces: Vec, + /// Number of exits to be done before entering a new function. + exits: usize, +} + +#[derive(Debug, Default)] +struct TraceEntry { + /// Names of all functions in the call stack of this trace. + names: Vec, + /// Gas consumed by this function, allowed to be negative due to refunds. + gas: i64, +} + +impl FoldedStackTraceBuilder { + /// Enter execution of a function call that consumes `gas`. + pub fn enter(&mut self, label: String, gas: i64) { + let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default(); + + while self.exits > 0 { + names.pop(); + self.exits -= 1; + } + + names.push(label); + self.traces.push(TraceEntry { names, gas }); + } + + /// Exit execution of a function call. + pub fn exit(&mut self) { + self.exits += 1; + } + + /// Returns folded stack trace. + pub fn build(mut self) -> Vec { + self.subtract_children(); + self.build_without_subtraction() + } + + /// Internal method to build the folded stack trace without subtracting gas consumed by + /// the children function calls. + fn build_without_subtraction(&mut self) -> Vec { + let mut lines = Vec::new(); + for TraceEntry { names, gas } in self.traces.iter() { + lines.push(format!("{} {}", names.join(";"), gas)); + } + lines + } + + /// Subtracts gas consumed by the children function calls from the parent function calls. + fn subtract_children(&mut self) { + // Iterate over each trace to find the children and subtract their values from the parents. + for i in 0..self.traces.len() { + let (left, right) = self.traces.split_at_mut(i); + let TraceEntry { names, gas } = &right[0]; + if names.len() > 1 { + let parent_trace_to_match = &names[..names.len() - 1]; + for parent in left.iter_mut().rev() { + if parent.names == parent_trace_to_match { + parent.gas -= gas; + break; + } + } + } + } + } +} + +mod tests { + #[test] + fn test_fst_1() { + let mut trace = super::FoldedStackTraceBuilder::default(); + trace.enter("top".to_string(), 500); + trace.enter("child_a".to_string(), 100); + trace.exit(); + trace.enter("child_b".to_string(), 200); + + assert_eq!( + trace.build_without_subtraction(), + vec![ + "top 500", // + "top;child_a 100", + "top;child_b 200", + ] + ); + assert_eq!( + trace.build(), + vec![ + "top 200", // 500 - 100 - 200 + "top;child_a 100", + "top;child_b 200", + ] + ); + } + + #[test] + fn test_fst_2() { + let mut trace = super::FoldedStackTraceBuilder::default(); + trace.enter("top".to_string(), 500); + trace.enter("child_a".to_string(), 300); + trace.enter("child_b".to_string(), 100); + trace.exit(); + trace.exit(); + trace.enter("child_c".to_string(), 100); + + assert_eq!( + trace.build_without_subtraction(), + vec![ + "top 500", // + "top;child_a 300", + "top;child_a;child_b 100", + "top;child_c 100", + ] + ); + + assert_eq!( + trace.build(), + vec![ + "top 100", // 500 - 300 - 100 + "top;child_a 200", // 300 - 100 + "top;child_a;child_b 100", + "top;child_c 100", + ] + ); + } + + #[test] + fn test_fst_3() { + let mut trace = super::FoldedStackTraceBuilder::default(); + trace.enter("top".to_string(), 1700); + trace.enter("child_a".to_string(), 500); + trace.exit(); + trace.enter("child_b".to_string(), 500); + trace.enter("child_c".to_string(), 500); + trace.exit(); + trace.exit(); + trace.exit(); + trace.enter("top2".to_string(), 1700); + + assert_eq!( + trace.build_without_subtraction(), + vec![ + "top 1700", // + "top;child_a 500", + "top;child_b 500", + "top;child_b;child_c 500", + "top2 1700", + ] + ); + + assert_eq!( + trace.build(), + vec![ + "top 700", // + "top;child_a 500", + "top;child_b 0", + "top;child_b;child_c 500", + "top2 1700", + ] + ); + } +} diff --git a/crates/evm/src/trace/identifier/etherscan.rs b/crates/evm/traces/src/identifier/etherscan.rs similarity index 57% rename from crates/evm/src/trace/identifier/etherscan.rs rename to crates/evm/traces/src/identifier/etherscan.rs index e13570aa2448a..96e4b69667b32 100644 --- a/crates/evm/src/trace/identifier/etherscan.rs +++ b/crates/evm/traces/src/identifier/etherscan.rs @@ -1,19 +1,16 @@ use super::{AddressIdentity, TraceIdentifier}; -use crate::utils::RuntimeOrHandle; -use ethers::{ - abi::Address, - etherscan, - etherscan::contract::{ContractMetadata, Metadata}, - prelude::{artifacts::ContractBytecodeSome, errors::EtherscanError, ArtifactId}, - types::H160, +use crate::debug::ContractSources; +use alloy_primitives::Address; +use foundry_block_explorers::{ + contract::{ContractMetadata, Metadata}, + errors::EtherscanError, }; -use foundry_common::compile; +use foundry_common::compile::etherscan_project; use foundry_config::{Chain, Config}; use futures::{ future::{join_all, Future}, stream::{FuturesUnordered, Stream, StreamExt}, task::{Context, Poll}, - TryFutureExt, }; use std::{ borrow::Cow, @@ -27,125 +24,133 @@ use std::{ use tokio::time::{Duration, Interval}; /// A trace identifier that tries to identify addresses using Etherscan. -#[derive(Default)] pub struct EtherscanIdentifier { /// The Etherscan client - client: Option>, + client: Arc, /// Tracks whether the API key provides was marked as invalid /// /// After the first [EtherscanError::InvalidApiKey] this will get set to true, so we can /// prevent any further attempts invalid_api_key: Arc, - pub contracts: BTreeMap, + pub contracts: BTreeMap, pub sources: BTreeMap, } impl EtherscanIdentifier { /// Creates a new Etherscan identifier with the given client - pub fn new(config: &Config, chain: Option>) -> eyre::Result { - if let Some(config) = config.get_etherscan_config_with_chain(chain)? { - trace!(target: "etherscanidentifier", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); - Ok(Self { - client: Some(Arc::new(config.into_client()?)), - invalid_api_key: Arc::new(Default::default()), - contracts: BTreeMap::new(), - sources: BTreeMap::new(), - }) - } else { - Ok(Default::default()) + pub fn new(config: &Config, chain: Option) -> eyre::Result> { + // In offline mode, don't use Etherscan. + if config.offline { + return Ok(None); } + let Some(config) = config.get_etherscan_config_with_chain(chain)? else { + return Ok(None); + }; + trace!(target: "traces::etherscan", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); + Ok(Some(Self { + client: Arc::new(config.into_client()?), + invalid_api_key: Arc::new(AtomicBool::new(false)), + contracts: BTreeMap::new(), + sources: BTreeMap::new(), + })) } /// Goes over the list of contracts we have pulled from the traces, clones their source from /// Etherscan and compiles them locally, for usage in the debugger. - pub async fn get_compiled_contracts( - &self, - ) -> eyre::Result<(BTreeMap, BTreeMap)> - { - let mut compiled_contracts = BTreeMap::new(); - let mut sources = BTreeMap::new(); - + pub async fn get_compiled_contracts(&self) -> eyre::Result { // TODO: Add caching so we dont double-fetch contracts. - let contracts_iter = self + let outputs_fut = self .contracts .iter() // filter out vyper files - .filter(|(_, metadata)| !metadata.is_vyper()); + .filter(|(_, metadata)| !metadata.is_vyper()) + .map(|(address, metadata)| async move { + sh_println!("Compiling: {} {address}", metadata.contract_name)?; + let root = tempfile::tempdir()?; + let root_path = root.path(); + let project = etherscan_project(metadata, root_path)?; + let output = project.compile()?; - let outputs_fut = contracts_iter - .clone() - .map(|(address, metadata)| { - println!("Compiling: {} {address:?}", metadata.contract_name); - let err_msg = format!( - "Failed to compile contract {} from {address:?}", - metadata.contract_name - ); - compile::compile_from_source(metadata).map_err(move |err| err.wrap_err(err_msg)) + if output.has_compiler_errors() { + eyre::bail!("{output}") + } + + Ok((project, output, root)) }) .collect::>(); // poll all the futures concurrently - let artifacts = join_all(outputs_fut).await; + let outputs = join_all(outputs_fut).await; + + let mut sources: ContractSources = Default::default(); // construct the map - for (results, (_, metadata)) in artifacts.into_iter().zip(contracts_iter) { - // get the inner type - let (artifact_id, bytecode) = results?; - compiled_contracts.insert(artifact_id.clone(), bytecode); - sources.insert(artifact_id, metadata.source_code()); + for res in outputs { + let (project, output, _root) = res?; + sources.insert(&output, project.root(), None)?; } - Ok((sources, compiled_contracts)) + Ok(sources) } } impl TraceIdentifier for EtherscanIdentifier { - fn identify_addresses( - &mut self, - addresses: Vec<(&Address, Option<&[u8]>)>, - ) -> Vec { - trace!(target: "etherscanidentifier", "identify {} addresses", addresses.len()); - - let Some(client) = self.client.clone() else { - // no client was configured - return Vec::new() - }; + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> + where + A: Iterator, Option<&'a [u8]>)>, + { + trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1); if self.invalid_api_key.load(Ordering::Relaxed) { // api key was marked as invalid return Vec::new() } + let mut identities = Vec::new(); let mut fetcher = EtherscanFetcher::new( - client, + self.client.clone(), Duration::from_secs(1), 5, Arc::clone(&self.invalid_api_key), ); - for (addr, _) in addresses { - if !self.contracts.contains_key(addr) { - fetcher.push(*addr); - } - } - - let fut = fetcher - .map(|(address, metadata)| { + for (addr, _, _) in addresses { + if let Some(metadata) = self.contracts.get(addr) { let label = metadata.contract_name.clone(); let abi = metadata.abi().ok().map(Cow::Owned); - self.contracts.insert(address, metadata); - AddressIdentity { - address, + identities.push(AddressIdentity { + address: *addr, label: Some(label.clone()), contract: Some(label), abi, artifact_id: None, - } - }) - .collect(); + }); + } else { + fetcher.push(*addr); + } + } + + let fetched_identities = foundry_common::block_on( + fetcher + .map(|(address, metadata)| { + let label = metadata.contract_name.clone(); + let abi = metadata.abi().ok().map(Cow::Owned); + self.contracts.insert(address, metadata); - RuntimeOrHandle::new().block_on(fut) + AddressIdentity { + address, + label: Some(label.clone()), + contract: Some(label), + abi, + artifact_id: None, + } + }) + .collect::>>(), + ); + + identities.extend(fetched_identities); + identities } } @@ -155,9 +160,9 @@ type EtherscanFuture = /// A rate limit aware Etherscan client. /// /// Fetches information about multiple addresses concurrently, while respecting rate limits. -pub struct EtherscanFetcher { +struct EtherscanFetcher { /// The Etherscan client - client: Arc, + client: Arc, /// The time we wait if we hit the rate limit timeout: Duration, /// The interval we are currently waiting for before making a new request @@ -173,8 +178,8 @@ pub struct EtherscanFetcher { } impl EtherscanFetcher { - pub fn new( - client: Arc, + fn new( + client: Arc, timeout: Duration, concurrency: usize, invalid_api_key: Arc, @@ -190,22 +195,19 @@ impl EtherscanFetcher { } } - pub fn push(&mut self, address: Address) { + fn push(&mut self, address: Address) { self.queue.push(address); } fn queue_next_reqs(&mut self) { while self.in_progress.len() < self.concurrency { - if let Some(addr) = self.queue.pop() { - let client = Arc::clone(&self.client); - trace!(target: "etherscanidentifier", "fetching info for {:?}", addr); - self.in_progress.push(Box::pin(async move { - let res = client.contract_source_code(addr).await; - (addr, res) - })); - } else { - break - } + let Some(addr) = self.queue.pop() else { break }; + let client = Arc::clone(&self.client); + self.in_progress.push(Box::pin(async move { + trace!(target: "traces::etherscan", ?addr, "fetching info"); + let res = client.contract_source_code(addr).await; + (addr, res) + })); } } } @@ -239,24 +241,24 @@ impl Stream for EtherscanFetcher { } } Err(EtherscanError::RateLimitExceeded) => { - warn!(target: "etherscanidentifier", "rate limit exceeded on attempt"); + warn!(target: "traces::etherscan", "rate limit exceeded on attempt"); pin.backoff = Some(tokio::time::interval(pin.timeout)); pin.queue.push(addr); } Err(EtherscanError::InvalidApiKey) => { - warn!(target: "etherscanidentifier", "invalid api key"); + warn!(target: "traces::etherscan", "invalid api key"); // mark key as invalid pin.invalid_api_key.store(true, Ordering::Relaxed); return Poll::Ready(None) } Err(EtherscanError::BlockedByCloudflare) => { - warn!(target: "etherscanidentifier", "blocked by cloudflare"); + warn!(target: "traces::etherscan", "blocked by cloudflare"); // mark key as invalid pin.invalid_api_key.store(true, Ordering::Relaxed); return Poll::Ready(None) } Err(err) => { - warn!(target: "etherscanidentifier", "could not get etherscan info: {:?}", err); + warn!(target: "traces::etherscan", "could not get etherscan info: {:?}", err); } } } diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs new file mode 100644 index 0000000000000..90c28e016e1ff --- /dev/null +++ b/crates/evm/traces/src/identifier/local.rs @@ -0,0 +1,154 @@ +use super::{AddressIdentity, TraceIdentifier}; +use alloy_json_abi::JsonAbi; +use alloy_primitives::Address; +use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact}; +use foundry_compilers::ArtifactId; +use std::borrow::Cow; + +/// A trace identifier that tries to identify addresses using local contracts. +pub struct LocalTraceIdentifier<'a> { + /// Known contracts to search through. + known_contracts: &'a ContractsByArtifact, + /// Vector of pairs of artifact ID and the runtime code length of the given artifact. + ordered_ids: Vec<(&'a ArtifactId, usize)>, +} + +impl<'a> LocalTraceIdentifier<'a> { + /// Creates a new local trace identifier. + #[inline] + pub fn new(known_contracts: &'a ContractsByArtifact) -> Self { + let mut ordered_ids = known_contracts + .iter() + .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode()?))) + .map(|(id, bytecode)| (id, bytecode.len())) + .collect::>(); + ordered_ids.sort_by_key(|(_, len)| *len); + Self { known_contracts, ordered_ids } + } + + /// Returns the known contracts. + #[inline] + pub fn contracts(&self) -> &'a ContractsByArtifact { + self.known_contracts + } + + /// Identifies the artifact based on score computed for both creation and deployed bytecodes. + pub fn identify_code( + &self, + runtime_code: &[u8], + creation_code: &[u8], + ) -> Option<(&'a ArtifactId, &'a JsonAbi)> { + let len = runtime_code.len(); + + let mut min_score = f64::MAX; + let mut min_score_id = None; + + let mut check = |id, is_creation, min_score: &mut f64| { + let contract = self.known_contracts.get(id)?; + // Select bytecodes to compare based on `is_creation` flag. + let (contract_bytecode, current_bytecode) = if is_creation { + (contract.bytecode(), creation_code) + } else { + (contract.deployed_bytecode(), runtime_code) + }; + + if let Some(bytecode) = contract_bytecode { + let score = bytecode_diff_score(bytecode, current_bytecode); + if score == 0.0 { + trace!(target: "evm::traces", "found exact match"); + return Some((id, &contract.abi)); + } + if score < *min_score { + *min_score = score; + min_score_id = Some((id, &contract.abi)); + } + } + None + }; + + // Check `[len * 0.9, ..., len * 1.1]`. + let max_len = (len * 11) / 10; + + // Start at artifacts with the same code length: `len..len*1.1`. + let same_length_idx = self.find_index(len); + for idx in same_length_idx..self.ordered_ids.len() { + let (id, len) = self.ordered_ids[idx]; + if len > max_len { + break; + } + if let found @ Some(_) = check(id, true, &mut min_score) { + return found; + } + } + + // Iterate over the remaining artifacts with less code length: `len*0.9..len`. + let min_len = (len * 9) / 10; + let idx = self.find_index(min_len); + for i in idx..same_length_idx { + let (id, _) = self.ordered_ids[i]; + if let found @ Some(_) = check(id, true, &mut min_score) { + return found; + } + } + + // Fallback to comparing deployed code if min score greater than threshold. + if min_score >= 0.85 { + for (artifact, _) in &self.ordered_ids { + if let found @ Some(_) = check(artifact, false, &mut min_score) { + return found; + } + } + } + + trace!(target: "evm::traces", %min_score, "no exact match found"); + + // Note: the diff score can be inaccurate for small contracts so we're using a relatively + // high threshold here to avoid filtering out too many contracts. + if min_score < 0.85 { + min_score_id + } else { + None + } + } + + /// Returns the index of the artifact with the given code length, or the index of the first + /// artifact with a greater code length if the exact code length is not found. + fn find_index(&self, len: usize) -> usize { + let (Ok(mut idx) | Err(mut idx)) = + self.ordered_ids.binary_search_by_key(&len, |(_, probe)| *probe); + + // In case of multiple artifacts with the same code length, we need to find the first one. + while idx > 0 && self.ordered_ids[idx - 1].1 == len { + idx -= 1; + } + + idx + } +} + +impl TraceIdentifier for LocalTraceIdentifier<'_> { + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> + where + A: Iterator, Option<&'a [u8]>)>, + { + trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1); + + addresses + .filter_map(|(address, runtime_code, creation_code)| { + let _span = trace_span!(target: "evm::traces", "identify", %address).entered(); + + trace!(target: "evm::traces", "identifying"); + let (id, abi) = self.identify_code(runtime_code?, creation_code?)?; + trace!(target: "evm::traces", id=%id.identifier(), "identified"); + + Some(AddressIdentity { + address: *address, + contract: Some(id.identifier()), + label: Some(id.name.clone()), + abi: Some(Cow::Borrowed(abi)), + artifact_id: Some(id.clone()), + }) + }) + .collect() + } +} diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs new file mode 100644 index 0000000000000..51f949832659f --- /dev/null +++ b/crates/evm/traces/src/identifier/mod.rs @@ -0,0 +1,93 @@ +use alloy_json_abi::JsonAbi; +use alloy_primitives::Address; +use foundry_common::ContractsByArtifact; +use foundry_compilers::ArtifactId; +use foundry_config::{Chain, Config}; +use std::borrow::Cow; + +mod local; +pub use local::LocalTraceIdentifier; + +mod etherscan; +pub use etherscan::EtherscanIdentifier; + +mod signatures; +pub use signatures::{CachedSignatures, SignaturesIdentifier, SingleSignaturesIdentifier}; + +/// An address identity +pub struct AddressIdentity<'a> { + /// The address this identity belongs to + pub address: Address, + /// The label for the address + pub label: Option, + /// The contract this address represents + /// + /// Note: This may be in the format `":"`. + pub contract: Option, + /// The ABI of the contract at this address + pub abi: Option>, + /// The artifact ID of the contract, if any. + pub artifact_id: Option, +} + +/// Trace identifiers figure out what ABIs and labels belong to all the addresses of the trace. +pub trait TraceIdentifier { + /// Attempts to identify an address in one or more call traces. + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> + where + A: Iterator, Option<&'a [u8]>)> + Clone; +} + +/// A collection of trace identifiers. +pub struct TraceIdentifiers<'a> { + /// The local trace identifier. + pub local: Option>, + /// The optional Etherscan trace identifier. + pub etherscan: Option, +} + +impl Default for TraceIdentifiers<'_> { + fn default() -> Self { + Self::new() + } +} + +impl TraceIdentifier for TraceIdentifiers<'_> { + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> + where + A: Iterator, Option<&'a [u8]>)> + Clone, + { + let mut identities = Vec::new(); + if let Some(local) = &mut self.local { + identities.extend(local.identify_addresses(addresses.clone())); + } + if let Some(etherscan) = &mut self.etherscan { + identities.extend(etherscan.identify_addresses(addresses)); + } + identities + } +} + +impl<'a> TraceIdentifiers<'a> { + /// Creates a new, empty instance. + pub const fn new() -> Self { + Self { local: None, etherscan: None } + } + + /// Sets the local identifier. + pub fn with_local(mut self, known_contracts: &'a ContractsByArtifact) -> Self { + self.local = Some(LocalTraceIdentifier::new(known_contracts)); + self + } + + /// Sets the etherscan identifier. + pub fn with_etherscan(mut self, config: &Config, chain: Option) -> eyre::Result { + self.etherscan = EtherscanIdentifier::new(config, chain)?; + Ok(self) + } + + /// Returns `true` if there are no set identifiers. + pub fn is_empty(&self) -> bool { + self.local.is_none() && self.etherscan.is_none() + } +} diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs new file mode 100644 index 0000000000000..d1c6f61aa3571 --- /dev/null +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -0,0 +1,181 @@ +use alloy_json_abi::{Error, Event, Function}; +use alloy_primitives::{hex, map::HashSet}; +use foundry_common::{ + abi::{get_error, get_event, get_func}, + fs, + selectors::{OpenChainClient, SelectorType}, +}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; +use tokio::sync::RwLock; + +pub type SingleSignaturesIdentifier = Arc>; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct CachedSignatures { + pub errors: BTreeMap, + pub events: BTreeMap, + pub functions: BTreeMap, +} + +impl CachedSignatures { + #[instrument(target = "evm::traces")] + pub fn load(cache_path: PathBuf) -> Self { + let path = cache_path.join("signatures"); + if path.is_file() { + fs::read_json_file(&path) + .map_err( + |err| warn!(target: "evm::traces", ?path, ?err, "failed to read cache file"), + ) + .unwrap_or_default() + } else { + if let Err(err) = std::fs::create_dir_all(cache_path) { + warn!(target: "evm::traces", "could not create signatures cache dir: {:?}", err); + } + Self::default() + } + } +} +/// An identifier that tries to identify functions and events using signatures found at +/// `https://openchain.xyz` or a local cache. +#[derive(Debug)] +pub struct SignaturesIdentifier { + /// Cached selectors for functions, events and custom errors. + cached: CachedSignatures, + /// Location where to save `CachedSignatures`. + cached_path: Option, + /// Selectors that were unavailable during the session. + unavailable: HashSet, + /// The OpenChain client to fetch signatures from. + client: Option, +} + +impl SignaturesIdentifier { + #[instrument(target = "evm::traces")] + pub fn new( + cache_path: Option, + offline: bool, + ) -> eyre::Result { + let client = if !offline { Some(OpenChainClient::new()?) } else { None }; + + let identifier = if let Some(cache_path) = cache_path { + let path = cache_path.join("signatures"); + trace!(target: "evm::traces", ?path, "reading signature cache"); + let cached = CachedSignatures::load(cache_path); + Self { cached, cached_path: Some(path), unavailable: HashSet::default(), client } + } else { + Self { + cached: Default::default(), + cached_path: None, + unavailable: HashSet::default(), + client, + } + }; + + Ok(Arc::new(RwLock::new(identifier))) + } + + #[instrument(target = "evm::traces", skip(self))] + pub fn save(&self) { + if let Some(cached_path) = &self.cached_path { + if let Some(parent) = cached_path.parent() { + if let Err(err) = std::fs::create_dir_all(parent) { + warn!(target: "evm::traces", ?parent, ?err, "failed to create cache"); + } + } + if let Err(err) = fs::write_json_file(cached_path, &self.cached) { + warn!(target: "evm::traces", ?cached_path, ?err, "failed to flush signature cache"); + } else { + trace!(target: "evm::traces", ?cached_path, "flushed signature cache") + } + } + } +} + +impl SignaturesIdentifier { + async fn identify( + &mut self, + selector_type: SelectorType, + identifiers: impl IntoIterator>, + get_type: impl Fn(&str) -> eyre::Result, + ) -> Vec> { + let cache = match selector_type { + SelectorType::Function => &mut self.cached.functions, + SelectorType::Event => &mut self.cached.events, + SelectorType::Error => &mut self.cached.errors, + }; + + let hex_identifiers: Vec = + identifiers.into_iter().map(hex::encode_prefixed).collect(); + + 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) = 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 { + if let Some(decoded_result) = decoded_results.into_iter().next() { + cache.insert(hex_id.clone(), decoded_result); + found = true; + } + } + if !found { + self.unavailable.insert(hex_id.clone()); + } + } + } + } + + hex_identifiers.iter().map(|v| cache.get(v).and_then(|v| get_type(v).ok())).collect() + } + + /// Identifies `Function`s from its cache or `https://api.openchain.xyz` + pub async fn identify_functions( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Function, identifiers, get_func).await + } + + /// Identifies `Function` from its cache or `https://api.openchain.xyz` + pub async fn identify_function(&mut self, identifier: &[u8]) -> Option { + self.identify_functions(&[identifier]).await.pop().unwrap() + } + + /// Identifies `Event`s from its cache or `https://api.openchain.xyz` + pub async fn identify_events( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Event, identifiers, get_event).await + } + + /// Identifies `Event` from its cache or `https://api.openchain.xyz` + pub async fn identify_event(&mut self, identifier: &[u8]) -> Option { + self.identify_events(&[identifier]).await.pop().unwrap() + } + + /// Identifies `Error`s from its cache or `https://api.openchain.xyz`. + pub async fn identify_errors( + &mut self, + identifiers: impl IntoIterator>, + ) -> Vec> { + self.identify(SelectorType::Error, identifiers, get_error).await + } + + /// Identifies `Error` from its cache or `https://api.openchain.xyz`. + pub async fn identify_error(&mut self, identifier: &[u8]) -> Option { + self.identify_errors(&[identifier]).await.pop().unwrap() + } +} + +impl Drop for SignaturesIdentifier { + fn drop(&mut self) { + self.save(); + } +} diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs new file mode 100644 index 0000000000000..0d22352ca9e3b --- /dev/null +++ b/crates/evm/traces/src/lib.rs @@ -0,0 +1,393 @@ +//! # foundry-evm-traces +//! +//! EVM trace identifying and decoding. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; + +use foundry_common::{ + contracts::{ContractsByAddress, ContractsByArtifact}, + shell, +}; +use revm::interpreter::OpCode; +use revm_inspectors::tracing::{ + types::{DecodedTraceStep, TraceMemberOrder}, + OpcodeFilter, +}; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + collections::BTreeSet, + ops::{Deref, DerefMut}, +}; + +use alloy_primitives::map::HashMap; + +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. +/// +/// Identifiers figure out what ABIs and labels belong to all the addresses of the trace. +pub mod identifier; +use identifier::{LocalTraceIdentifier, TraceIdentifier}; + +mod decoder; +pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder}; + +pub mod debug; +pub use debug::DebugTraceIdentifier; + +pub mod folded_stack_trace; + +pub type Traces = Vec<(TraceKind, SparsedTraceArena)>; + +/// Trace arena keeping track of ignored trace items. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SparsedTraceArena { + /// Full trace arena. + #[serde(flatten)] + pub arena: CallTraceArena, + /// Ranges of trace steps to ignore in format (start_node, start_step) -> (end_node, end_step). + /// See `foundry_cheatcodes::utils::IgnoredTraces` for more information. + #[serde(default, skip_serializing_if = "HashMap::is_empty")] + pub ignored: HashMap<(usize, usize), (usize, usize)>, +} + +impl SparsedTraceArena { + /// Goes over entire trace arena and removes ignored trace items. + fn resolve_arena(&self) -> Cow<'_, CallTraceArena> { + if self.ignored.is_empty() { + Cow::Borrowed(&self.arena) + } else { + let mut arena = self.arena.clone(); + + fn clear_node( + nodes: &mut [CallTraceNode], + node_idx: usize, + ignored: &HashMap<(usize, usize), (usize, usize)>, + cur_ignore_end: &mut Option<(usize, usize)>, + ) { + // Prepend an additional None item to the ordering to handle the beginning of the + // trace. + let items = std::iter::once(None) + .chain(nodes[node_idx].ordering.clone().into_iter().map(Some)) + .enumerate(); + + let mut iternal_calls = Vec::new(); + let mut items_to_remove = BTreeSet::new(); + for (item_idx, item) in items { + if let Some(end_node) = ignored.get(&(node_idx, item_idx)) { + *cur_ignore_end = Some(*end_node); + } + + let mut remove = cur_ignore_end.is_some() & item.is_some(); + + match item { + // we only remove calls if they did not start/pause tracing + Some(TraceMemberOrder::Call(child_idx)) => { + clear_node( + nodes, + nodes[node_idx].children[child_idx], + ignored, + cur_ignore_end, + ); + remove &= cur_ignore_end.is_some(); + } + // we only remove decoded internal calls if they did not start/pause tracing + Some(TraceMemberOrder::Step(step_idx)) => { + // If this is an internal call beginning, track it in `iternal_calls` + if let Some(DecodedTraceStep::InternalCall(_, end_step_idx)) = + &nodes[node_idx].trace.steps[step_idx].decoded + { + iternal_calls.push((item_idx, remove, *end_step_idx)); + // we decide if we should remove it later + remove = false; + } + // Handle ends of internal calls + iternal_calls.retain(|(start_item_idx, remove_start, end_step_idx)| { + if *end_step_idx != step_idx { + return true; + } + // only remove start if end should be removed as well + if *remove_start && remove { + items_to_remove.insert(*start_item_idx); + } else { + remove = false; + } + + false + }); + } + _ => {} + } + + if remove { + items_to_remove.insert(item_idx); + } + + if let Some((end_node, end_step_idx)) = cur_ignore_end { + if node_idx == *end_node && item_idx == *end_step_idx { + *cur_ignore_end = None; + } + } + } + + for (offset, item_idx) in items_to_remove.into_iter().enumerate() { + nodes[node_idx].ordering.remove(item_idx - offset - 1); + } + } + + clear_node(arena.nodes_mut(), 0, &self.ignored, &mut None); + + Cow::Owned(arena) + } + } +} + +impl Deref for SparsedTraceArena { + type Target = CallTraceArena; + + fn deref(&self) -> &Self::Target { + &self.arena + } +} + +impl DerefMut for SparsedTraceArena { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.arena + } +} + +/// Decode a collection of call traces. +/// +/// The traces will be decoded using the given decoder, if possible. +pub async fn decode_trace_arena( + arena: &mut CallTraceArena, + decoder: &CallTraceDecoder, +) -> Result<(), std::fmt::Error> { + decoder.prefetch_signatures(arena.nodes()).await; + decoder.populate_traces(arena.nodes_mut()).await; + + Ok(()) +} + +/// Render a collection of call traces to a string. +pub fn render_trace_arena(arena: &SparsedTraceArena) -> String { + render_trace_arena_inner(arena, false, false) +} + +/// Render a collection of call traces to a string optionally including contract creation bytecodes +/// and in JSON format. +pub fn render_trace_arena_inner( + arena: &SparsedTraceArena, + with_bytecodes: bool, + with_storage_changes: bool, +) -> String { + if shell::is_json() { + return serde_json::to_string(&arena.resolve_arena()).expect("Failed to write traces"); + } + + let mut w = TraceWriter::new(Vec::::new()) + .color_cheatcodes(true) + .use_colors(convert_color_choice(shell::color_choice())) + .write_bytecodes(with_bytecodes) + .with_storage_changes(with_storage_changes); + w.write_arena(&arena.resolve_arena()).expect("Failed to write traces"); + String::from_utf8(w.into_writer()).expect("trace writer wrote invalid UTF-8") +} + +fn convert_color_choice(choice: shell::ColorChoice) -> revm_inspectors::ColorChoice { + match choice { + shell::ColorChoice::Auto => revm_inspectors::ColorChoice::Auto, + shell::ColorChoice::Always => revm_inspectors::ColorChoice::Always, + shell::ColorChoice::Never => revm_inspectors::ColorChoice::Never, + } +} + +/// Specifies the kind of trace. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum TraceKind { + Deployment, + Setup, + Execution, +} + +impl TraceKind { + /// Returns `true` if the trace kind is [`Deployment`]. + /// + /// [`Deployment`]: TraceKind::Deployment + #[must_use] + pub fn is_deployment(self) -> bool { + matches!(self, Self::Deployment) + } + + /// Returns `true` if the trace kind is [`Setup`]. + /// + /// [`Setup`]: TraceKind::Setup + #[must_use] + pub fn is_setup(self) -> bool { + matches!(self, Self::Setup) + } + + /// Returns `true` if the trace kind is [`Execution`]. + /// + /// [`Execution`]: TraceKind::Execution + #[must_use] + pub fn is_execution(self) -> bool { + matches!(self, Self::Execution) + } +} + +/// Given a list of traces and artifacts, it returns a map connecting address to abi +pub fn load_contracts<'a>( + traces: impl IntoIterator, + known_contracts: &ContractsByArtifact, +) -> ContractsByAddress { + let mut local_identifier = LocalTraceIdentifier::new(known_contracts); + let decoder = CallTraceDecoder::new(); + let mut contracts = ContractsByAddress::new(); + for trace in traces { + 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, + /// Debug trace with storage changes. + RecordStateDiff, +} + +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 record_state_diff(self) -> bool { + matches!(self, Self::RecordStateDiff) + } + + 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_state_changes(self, yes: bool) -> Self { + if yes { + std::cmp::max(self, Self::RecordStateDiff) + } else { + self + } + } + + pub fn with_verbosity(self, verbosity: u8) -> Self { + if verbosity >= 3 { + std::cmp::max(self, Self::Call) + } else { + self + } + } + + 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: self.record_state_diff(), + record_returndata_snapshots: self.is_debug(), + record_opcodes_filter: (self.is_jump() || self.is_jump_simple()) + .then(|| OpcodeFilter::new().enabled(OpCode::JUMP).enabled(OpCode::JUMPDEST)), + exclude_precompile_calls: false, + record_immediate_bytes: self.is_debug(), + } + .into() + } + } +} diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index fd57b6ec80bc5..bc1f44fdc5e03 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -9,24 +9,22 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -# foundry dep foundry-config.workspace = true -# ethers -ethers-core.workspace = true - -# parser -solang-parser.workspace = true +alloy-primitives.workspace = true -# misc +ariadne = "0.5" itertools.workspace = true -thiserror = "1" -ariadne = "0.2" -tracing = "0.1" +solang-parser.workspace = true +thiserror.workspace = true +tracing.workspace = true [dev-dependencies] -pretty_assertions = "1" itertools.workspace = true -toml = "0.7" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } +similar-asserts.workspace = true +toml.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/fmt/README.md b/crates/fmt/README.md index 7e51bf7f8a111..1fc2712ad6e0e 100644 --- a/crates/fmt/README.md +++ b/crates/fmt/README.md @@ -1,6 +1,7 @@ # Formatter (`fmt`) -Solidity formatter that respects (some parts of) the [Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html) and +Solidity formatter that respects (some parts of) +the [Style Guide](https://docs.soliditylang.org/en/latest/style-guide.html) and is tested on the [Prettier Solidity Plugin](https://github.com/prettier-solidity/prettier-plugin-solidity) cases. ## Architecture @@ -17,13 +18,19 @@ and works as following: 1. Implement `Formatter` callback functions for each PT node type. Every callback function should write formatted output for the current node and call `Visitable::visit` function for child nodes delegating the output writing. -1. Implement `Visitable` trait and its `visit` function for each PT node type. Every `visit` function should call corresponding `Formatter`'s callback function. +1. Implement `Visitable` trait and its `visit` function for each PT node type. Every `visit` function should call + corresponding `Formatter`'s callback function. ### Output -The formatted output is written into the output buffer in _chunks_. The `Chunk` struct holds the content to be written & metadata for it. This includes the comments surrounding the content as well as the `needs_space` flag specifying whether this _chunk_ needs a space. The flag overrides the default behavior of `Formatter::next_char_needs_space` method. +The formatted output is written into the output buffer in _chunks_. The `Chunk` struct holds the content to be written & +metadata for it. This includes the comments surrounding the content as well as the `needs_space` flag specifying whether +this _chunk_ needs a space. The flag overrides the default behavior of `Formatter::next_char_needs_space` method. -The content gets written into the `FormatBuffer` which contains the information about the current indentation level, indentation length, current state as well as the other data determining the rules for writing the content. `FormatBuffer` implements the `std::fmt::Write` trait where it evaluates the current information and decides how the content should be written to the destination. +The content gets written into the `FormatBuffer` which contains the information about the current indentation level, +indentation length, current state as well as the other data determining the rules for writing the content. +`FormatBuffer` implements the `std::fmt::Write` trait where it evaluates the current information and decides how the +content should be written to the destination. ### Comments @@ -107,17 +114,22 @@ event Greet(string indexed name); The formatter supports multiple configuration options defined in `FormatterConfig`. -| Option | Default | Description | -| -------------------------------- | -------- | ---------------------------------------------------------------------------------------------- | -| line_length | 120 | Maximum line length where formatter will try to wrap the line | -| tab_width | 4 | Number of spaces per indentation level | -| bracket_spacing | false | Print spaces between brackets | -| int_types | long | Style of uint/int256 types. Available options: `long`, `short`, `preserve` | -| func_attrs_with_params_multiline | true | If function parameters are multiline then always put the function attributes on separate lines | -| quote_style | double | Style of quotation marks. Available options: `double`, `single`, `preserve` | -| number_underscore | preserve | Style of underscores in number literals. Available options: `remove`, `thousands`, `preserve` | - -TODO: update ^ +| Option | Default | Description | +|------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| +| line_length | 120 | Maximum line length where formatter will try to wrap the line | +| tab_width | 4 | Number of spaces per indentation level | +| bracket_spacing | false | Print spaces between brackets | +| int_types | long | Style of uint/int256 types. Available options: `long`, `short`, `preserve` | +| multiline_func_header | attributes_first | Style of multiline function header in case it doesn't fit. Available options: `params_first`, `params_first_multi`, `attributes_first`, `all`, `all_params` | +| quote_style | double | Style of quotation marks. Available options: `double`, `single`, `preserve` | +| number_underscore | preserve | Style of underscores in number literals. Available options: `preserve`, `remove`, `thousands` | +| hex_underscore | remove | Style of underscores in hex literals. Available options: `preserve`, `remove`, `bytes` | +| single_line_statement_blocks | preserve | Style of single line blocks in statements. Available options: `single`, `multi`, `preserve` | +| override_spacing | false | Print space in state variable, function and modifier `override` attribute | +| wrap_comments | false | Wrap comments on `line_length` reached | +| ignore | [] | Globs to ignore | +| contract_new_lines | false | Add new line at start and end of contract declarations | +| sort_imports | false | Sort import statements alphabetically in groups | ### Disable Line @@ -128,23 +140,40 @@ The formatter can be disabled on specific lines by adding a comment `// forgefmt uint x = 100; ``` -Alternatively, the comment can also be placed at the end of the line. In this case, you'd have to use `disable-line` instead: +Alternatively, the comment can also be placed at the end of the line. In this case, you'd have to use `disable-line` +instead: ```solidity uint x = 100; // forgefmt: disable-line ``` +### Disable Block + +The formatter can be disabled for a section of code by adding a comment `// forgefmt: disable-start` before and a +comment `// forgefmt: disable-end` after, like this: + +```solidity +// forgefmt: disable-start +uint x = 100; +uint y = 101; +// forgefmt: disable-end +``` + ### Testing -Tests reside under the `fmt/testdata` folder and specify the malformatted & expected Solidity code. The source code file is named `original.sol` and expected file(s) are named in a format `({prefix}.)?fmt.sol`. Multiple expected files are needed for tests covering available configuration options. +Tests reside under the `fmt/testdata` folder and specify the malformatted & expected Solidity code. The source code file +is named `original.sol` and expected file(s) are named in a format `({prefix}.)?fmt.sol`. Multiple expected files are +needed for tests covering available configuration options. -The default configuration values can be overridden from within the expected file by adding a comment in the format `// config: {config_entry} = {config_value}`. For example: +The default configuration values can be overridden from within the expected file by adding a comment in the format +`// config: {config_entry} = {config_value}`. For example: ```solidity // config: line_length = 160 ``` -The `test_directory` macro is used to specify a new folder with source files for the test suite. Each test suite has the following process: +The `test_directory` macro is used to specify a new folder with source files for the test suite. Each test suite has the +following process: 1. Preparse comments with config values 2. Parse and compare the AST for source & expected files. @@ -190,4 +219,4 @@ Guidelines for contributing to `forge fmt`: 3. Provide the test coverage for the new feature. These should include: - Adding malformatted & expected solidity code under `fmt/testdata/$dir/` - Testing the behavior of pre and postfix comments - - If it's a new config value, tests covering **all** available options + - If it's a new config value, tests covering **all** available options \ No newline at end of file diff --git a/crates/fmt/src/buffer.rs b/crates/fmt/src/buffer.rs index 6d4c113cb6bc6..cd92809a49650 100644 --- a/crates/fmt/src/buffer.rs +++ b/crates/fmt/src/buffer.rs @@ -1,14 +1,13 @@ -//! Format buffer - -use std::fmt::Write; +//! Format buffer. use crate::{ comments::{CommentState, CommentStringExt}, string::{QuoteState, QuotedStringExt}, }; +use std::fmt::Write; /// An indent group. The group may optionally skip the first line -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug, Default)] struct IndentGroup { skip_line: bool, } @@ -23,16 +22,16 @@ enum WriteState { impl WriteState { fn comment_state(&self) -> CommentState { match self { - WriteState::LineStart(state) => *state, - WriteState::WriteTokens(state) => *state, - WriteState::WriteString(_) => CommentState::None, + Self::LineStart(state) => *state, + Self::WriteTokens(state) => *state, + Self::WriteString(_) => CommentState::None, } } } impl Default for WriteState { fn default() -> Self { - WriteState::LineStart(CommentState::default()) + Self::LineStart(CommentState::default()) } } @@ -89,7 +88,7 @@ impl FormatBuffer { /// Indent the buffer by delta pub fn indent(&mut self, delta: usize) { - self.indents.extend(std::iter::repeat(IndentGroup::default()).take(delta)); + self.indents.extend(std::iter::repeat_n(IndentGroup::default(), delta)); } /// Dedent the buffer by delta @@ -166,11 +165,15 @@ impl FormatBuffer { /// Write a raw string to the buffer. This will ignore indents and remove the indents of the /// written string to match the current base indent of this buffer if it is a temp buffer pub fn write_raw(&mut self, s: impl AsRef) -> std::fmt::Result { - let mut lines = s.as_ref().lines().peekable(); + self._write_raw(s.as_ref()) + } + + fn _write_raw(&mut self, s: &str) -> std::fmt::Result { + let mut lines = s.lines().peekable(); let mut comment_state = self.state.comment_state(); while let Some(line) = lines.next() { // remove the whitespace that covered by the base indent length (this is normally the - // case with temporary buffers as this will be readded by the underlying IndentWriter + // case with temporary buffers as this will be re-added by the underlying IndentWriter // later on let (new_comment_state, line_start) = line .comment_state_char_indices() @@ -188,7 +191,7 @@ impl FormatBuffer { self.last_char = trimmed_line.chars().next_back(); self.state = WriteState::WriteTokens(comment_state); } - if lines.peek().is_some() || s.as_ref().ends_with('\n') { + if lines.peek().is_some() || s.ends_with('\n') { if self.restrict_to_single_line { return Err(std::fmt::Error) } @@ -415,17 +418,17 @@ mod tests { fn test_preserves_original_content_with_default_settings() -> std::fmt::Result { let contents = [ "simple line", - r#" + r" some multiline - content"#, + content", "// comment", "/* comment */", - r#"mutliline + r"mutliline content // comment1 with comments - /* comment2 */ "#, + /* comment2 */ ", ]; for content in contents.iter() { diff --git a/crates/fmt/src/chunk.rs b/crates/fmt/src/chunk.rs index badce88db5675..7d9ce25c7fbd0 100644 --- a/crates/fmt/src/chunk.rs +++ b/crates/fmt/src/chunk.rs @@ -12,13 +12,13 @@ pub struct Chunk { impl From for Chunk { fn from(string: String) -> Self { - Chunk { content: string, ..Default::default() } + Self { content: string, ..Default::default() } } } impl From<&str> for Chunk { fn from(string: &str) -> Self { - Chunk { content: string.to_owned(), ..Default::default() } + Self { content: string.to_owned(), ..Default::default() } } } @@ -37,7 +37,7 @@ impl SurroundingChunk { before: Option, next: Option, ) -> Self { - SurroundingChunk { before, next, content: format!("{content}"), spaced: None } + Self { before, next, content: format!("{content}"), spaced: None } } pub fn spaced(mut self) -> Self { diff --git a/crates/fmt/src/comments.rs b/crates/fmt/src/comments.rs index a76c64ffc53a2..eafdb998910d9 100644 --- a/crates/fmt/src/comments.rs +++ b/crates/fmt/src/comments.rs @@ -4,7 +4,7 @@ use solang_parser::pt::*; use std::collections::VecDeque; /// The type of a Comment -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CommentType { /// A Line comment (e.g. `// ...`) Line, @@ -17,7 +17,7 @@ pub enum CommentType { } /// The comment position -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum CommentPosition { /// Comes before the code it describes Prefix, @@ -26,7 +26,7 @@ pub enum CommentPosition { } /// Comment with additional metadata -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct CommentWithMetadata { pub ty: CommentType, pub loc: Loc, @@ -72,11 +72,7 @@ impl CommentWithMetadata { } /// Construct a comment with metadata by analyzing its surrounding source code - fn from_comment_and_src( - comment: Comment, - src: &str, - last_comment: Option<&CommentWithMetadata>, - ) -> Self { + fn from_comment_and_src(comment: Comment, src: &str, last_comment: Option<&Self>) -> Self { let src_before = &src[..comment.loc().start()]; if src_before.is_empty() { return Self::new(comment, CommentPosition::Prefix, false, 0) @@ -92,7 +88,7 @@ impl CommentWithMetadata { return Self::new( comment, CommentPosition::Prefix, - last_line.map_or(true, str::is_empty), + last_line.is_none_or(str::is_empty), indent_len, ) } @@ -116,7 +112,7 @@ impl CommentWithMetadata { // line has something // check if the last comment after code was a postfix comment if last_comment - .map_or(false, |last| last.loc.end() > code_end && !last.is_prefix()) + .is_some_and(|last| last.loc.end() > code_end && !last.is_prefix()) { // get the indent size of the next item of code let next_indent_len = src[comment.loc().end()..] @@ -152,6 +148,10 @@ impl CommentWithMetadata { matches!(self.ty, CommentType::Line | CommentType::DocLine) } + pub fn is_doc_block(&self) -> bool { + matches!(self.ty, CommentType::DocBlock) + } + pub fn is_prefix(&self) -> bool { matches!(self.position, CommentPosition::Prefix) } @@ -208,7 +208,7 @@ impl CommentWithMetadata { } /// A list of comments -#[derive(Default, Debug, Clone)] +#[derive(Clone, Debug, Default)] pub struct Comments { prefixes: VecDeque, postfixes: VecDeque, @@ -230,7 +230,7 @@ impl Comments { Self { prefixes, postfixes } } - /// Heloer for removing comments before a byte offset + /// Helper for removing comments before a byte offset fn remove_comments_before( comments: &mut VecDeque, byte: usize, @@ -295,7 +295,7 @@ impl Comments { } /// The state of a character in a string with possible comments -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum CommentState { /// character not in a comment #[default] @@ -394,13 +394,13 @@ impl Iterator for CommentStateCharIndices<'_> { } #[inline] - fn count(self) -> usize { - self.iter.count() + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() } #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() + fn count(self) -> usize { + self.iter.count() } } @@ -409,7 +409,7 @@ impl std::iter::FusedIterator for CommentStateCharIndices<'_> {} /// An Iterator over characters in a string slice which are not a apart of comments pub struct NonCommentChars<'a>(CommentStateCharIndices<'a>); -impl<'a> Iterator for NonCommentChars<'a> { +impl Iterator for NonCommentChars<'_> { type Item = char; #[inline] @@ -425,10 +425,10 @@ impl<'a> Iterator for NonCommentChars<'a> { /// Helpers for iterating over comment containing strings pub trait CommentStringExt { - fn comment_state_char_indices(&self) -> CommentStateCharIndices; + fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_>; #[inline] - fn non_comment_chars(&self) -> NonCommentChars { + fn non_comment_chars(&self) -> NonCommentChars<'_> { NonCommentChars(self.comment_state_char_indices()) } @@ -443,14 +443,14 @@ where T: AsRef, { #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices { + fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_> { CommentStateCharIndices::new(self.as_ref()) } } impl CommentStringExt for str { #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices { + fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_> { CommentStateCharIndices::new(self) } } diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 60bb6d4e256ee..8f52d9e259b89 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -6,39 +6,46 @@ use crate::{ comments::{ CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, }, + format_diagnostics_report, + helpers::import_path_string, macros::*, solang_ext::{pt::*, *}, string::{QuoteState, QuotedStringExt}, visit::{Visitable, Visitor}, - FormatterConfig, InlineConfig, IntTypes, NumberUnderscore, + FormatterConfig, InlineConfig, IntTypes, }; -use ethers_core::{types::H160, utils::to_checksum}; -use foundry_config::fmt::{MultilineFuncHeaderStyle, SingleLineBlockStyle}; +use alloy_primitives::Address; +use foundry_config::fmt::{HexUnderscore, MultilineFuncHeaderStyle, SingleLineBlockStyle}; use itertools::{Either, Itertools}; -use std::{fmt::Write, str::FromStr}; +use solang_parser::diagnostics::Diagnostic; +use std::{fmt::Write, path::PathBuf, str::FromStr}; use thiserror::Error; type Result = std::result::Result; /// A custom Error thrown by the Formatter -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum FormatterError { /// Error thrown by `std::fmt::Write` interfaces #[error(transparent)] Fmt(#[from] std::fmt::Error), /// Encountered invalid parse tree item. - #[error("Encountered invalid parse tree item at {0:?}")] + #[error("encountered invalid parse tree item at {0:?}")] InvalidParsedItem(Loc), + /// Failed to parse the source code + #[error("failed to parse file:\n{}", format_diagnostics_report(_0, _1.as_deref(), _2))] + Parse(String, Option, Vec), /// All other errors #[error(transparent)] - Custom(Box), + Custom(Box), } impl FormatterError { fn fmt() -> Self { Self::Fmt(std::fmt::Error) } - fn custom(err: impl std::error::Error + 'static) -> Self { + + fn custom(err: impl std::error::Error + Send + Sync + 'static) -> Self { Self::Custom(Box::new(err)) } } @@ -71,13 +78,20 @@ macro_rules! bail { // TODO: store context entities as references without copying /// Current context of the Formatter (e.g. inside Contract or Function definition) -#[derive(Default, Debug)] +#[derive(Debug, Default)] struct Context { contract: Option, function: Option, if_stmt_single_line: Option, } +impl Context { + /// Returns true if the current function context is the constructor + pub(crate) fn is_constructor_function(&self) -> bool { + self.function.as_ref().is_some_and(|f| matches!(f.ty, FunctionTy::Constructor)) + } +} + /// A Solidity formatter #[derive(Debug)] pub struct Formatter<'a, W> { @@ -90,46 +104,6 @@ pub struct Formatter<'a, W> { inline_config: InlineConfig, } -/// An action which may be committed to a Formatter -struct Transaction<'f, 'a, W> { - fmt: &'f mut Formatter<'a, W>, - buffer: String, - comments: Comments, -} - -impl<'f, 'a, W> std::ops::Deref for Transaction<'f, 'a, W> { - type Target = Formatter<'a, W>; - fn deref(&self) -> &Self::Target { - self.fmt - } -} - -impl<'f, 'a, W> std::ops::DerefMut for Transaction<'f, 'a, W> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.fmt - } -} - -impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { - /// Create a new transaction from a callback - fn new( - fmt: &'f mut Formatter<'a, W>, - fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, - ) -> Result { - let mut comments = fmt.comments.clone(); - let buffer = fmt.with_temp_buf(fun)?.w; - comments = std::mem::replace(&mut fmt.comments, comments); - Ok(Self { fmt, buffer, comments }) - } - - /// Commit the transaction to the Formatter - fn commit(self) -> Result { - self.fmt.comments = self.comments; - write_chunk!(self.fmt, "{}", self.buffer)?; - Ok(self.buffer) - } -} - impl<'a, W: Write> Formatter<'a, W> { pub fn new( w: W, @@ -272,8 +246,23 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(()) } + /// Write unformatted src and comments for given location. + fn write_raw_src(&mut self, loc: Loc) -> Result<()> { + let disabled_stmts_src = String::from_utf8(self.source.as_bytes()[loc.range()].to_vec()) + .map_err(FormatterError::custom)?; + self.write_raw(disabled_stmts_src.trim_end())?; + self.write_whitespace_separator(true)?; + // Remove comments as they're already included in disabled src. + let _ = self.comments.remove_all_comments_before(loc.end()); + Ok(()) + } + /// Returns number of blank lines in source between two byte indexes fn blank_lines(&self, start: usize, end: usize) -> usize { + // because of sorting import statements, start can be greater than end + if start > end { + return 0 + } self.source[start..end].trim_comments().matches('\n').count() } @@ -313,7 +302,7 @@ impl<'a, W: Write> Formatter<'a, W> { first_char == ch && state == CommentState::None && idx + needle.len() <= subset.len() && - subset[idx..idx + needle.len()].eq(needle) + subset[idx..idx + needle.len()] == *needle }) .map(|p| byte_offset + p) }) @@ -352,7 +341,7 @@ impl<'a, W: Write> Formatter<'a, W> { _ => stmt.loc().start(), }; - self.find_next_line(start_from).map_or(false, |loc| loc >= end_at) + self.find_next_line(start_from).is_some_and(|loc| loc >= end_at) } } } @@ -381,7 +370,16 @@ impl<'a, W: Write> Formatter<'a, W> { &mut self, byte_offset: usize, next_byte_offset: Option, - fun: impl FnMut(&mut Self) -> Result<()>, + mut fun: impl FnMut(&mut Self) -> Result<()>, + ) -> Result { + self.chunked_mono(byte_offset, next_byte_offset, &mut fun) + } + + fn chunked_mono( + &mut self, + byte_offset: usize, + next_byte_offset: Option, + fun: &mut dyn FnMut(&mut Self) -> Result<()>, ) -> Result { let postfixes_before = self.comments.remove_postfixes_before(byte_offset); let prefixes = self.comments.remove_prefixes_before(byte_offset); @@ -416,12 +414,31 @@ impl<'a, W: Write> Formatter<'a, W> { while let Some((loc, item)) = items.next() { let chunk_next_byte_offset = items.peek().map(|(loc, _)| loc.start()).or(next_byte_offset); - out.push(self.visit_to_chunk(loc.start(), chunk_next_byte_offset, item)?); + + let chunk = if self.inline_config.is_disabled(loc) { + // If item format is disabled, we determine last disabled line from item and create + // chunk with raw src. + let mut disabled_loc = loc; + self.chunked(disabled_loc.start(), chunk_next_byte_offset, |fmt| { + while fmt.inline_config.is_disabled(disabled_loc) { + if let Some(next_line) = fmt.find_next_line(disabled_loc.end()) { + disabled_loc = disabled_loc.with_end(next_line); + } else { + break; + } + } + fmt.write_raw_src(disabled_loc)?; + Ok(()) + })? + } else { + self.visit_to_chunk(loc.start(), chunk_next_byte_offset, item)? + }; + out.push(chunk); } Ok(out) } - /// Transform [Visitable] items to a list of chunks and then sort those chunks by [AttrSortKey] + /// Transform [Visitable] items to a list of chunks and then sort those chunks. fn items_to_chunks_sorted<'b>( &mut self, next_byte_offset: Option, @@ -543,7 +560,7 @@ impl<'a, W: Write> Formatter<'a, W> { fn write_doc_block_line(&mut self, comment: &CommentWithMetadata, line: &str) -> Result<()> { if line.trim().starts_with('*') { let line = line.trim().trim_start_matches('*'); - let needs_space = line.chars().next().map_or(false, |ch| !ch.is_whitespace()); + let needs_space = line.chars().next().is_some_and(|ch| !ch.is_whitespace()); write!(self.buf(), " *{}", if needs_space { " " } else { "" })?; self.write_comment_line(comment, line)?; self.write_whitespace_separator(true)?; @@ -555,8 +572,12 @@ impl<'a, W: Write> Formatter<'a, W> { .take_while(|(idx, ch)| ch.is_whitespace() && *idx <= self.buf.current_indent_len()) .count(); let to_skip = indent_whitespace_count - indent_whitespace_count % self.config.tab_width; - write!(self.buf(), " * ")?; - self.write_comment_line(comment, &line[to_skip..])?; + write!(self.buf(), " *")?; + let content = &line[to_skip..]; + if !content.trim().is_empty() { + write!(self.buf(), " ")?; + self.write_comment_line(comment, &line[to_skip..])?; + } self.write_whitespace_separator(true)?; Ok(()) } @@ -609,8 +630,8 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(false) } - /// Write a raw comment. This is like [`write_comment`] but won't do any formatting or worry - /// about whitespace behind the comment + /// Write a raw comment. This is like [`write_comment`](Self::write_comment) but won't do any + /// formatting or worry about whitespace behind the comment. fn write_raw_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { self.write_raw(&comment.comment)?; if comment.is_line() { @@ -844,7 +865,7 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(self.transact(fun)?.buffer) } - /// Turn a chunk and its surrounding comments into a a string + /// Turn a chunk and its surrounding comments into a string fn chunk_to_string(&mut self, chunk: &Chunk) -> Result { self.simulate_to_string(|fmt| fmt.write_chunk(chunk)) } @@ -919,7 +940,7 @@ impl<'a, W: Write> Formatter<'a, W> { write_chunk!(fmt, "{}", stringified.trim_start()) })?; if !last.content.trim_start().is_empty() { - self.write_whitespace_separator(true)?; + self.indented(1, |fmt| fmt.write_whitespace_separator(true))?; } let last_chunk = self.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content); @@ -987,8 +1008,14 @@ impl<'a, W: Write> Formatter<'a, W> { (None, None) => return Ok(()), } .start(); + let mut last_loc: Option = None; + let mut visited_locs: Vec = Vec::new(); + + // marker for whether the next item needs additional space let mut needs_space = false; + let mut last_comment = None; + while let Some(mut line_item) = pop_next(self, &mut items) { let loc = line_item.as_ref().either(|c| c.loc, |i| i.loc()); let (unwritten_whitespace_loc, unwritten_whitespace) = @@ -1017,8 +1044,32 @@ impl<'a, W: Write> Formatter<'a, W> { Either::Right(item) => { if !ignore_whitespace { self.write_whitespace_separator(true)?; - if let Some(last_loc) = last_loc { - if needs_space || self.blank_lines(last_loc.end(), loc.start()) > 1 { + if let Some(mut last_loc) = last_loc { + // here's an edge case when we reordered items so the last_loc isn't + // necessarily the item that directly precedes the current item because + // the order might have changed, so we need to find the last item that + // is before the current item by checking the recorded locations + if let Some(last_item) = visited_locs + .iter() + .rev() + .find(|prev_item| prev_item.start() > last_loc.end()) + { + last_loc = *last_item; + } + + // The blank lines check is susceptible additional trailing new lines + // because the block docs can contain + // multiple lines, but the function def should follow directly after the + // block comment + let is_last_doc_comment = matches!( + last_comment, + Some(CommentWithMetadata { ty: CommentType::DocBlock, .. }) + ); + + if needs_space || + (!is_last_doc_comment && + self.blank_lines(last_loc.end(), loc.start()) > 1) + { writeln!(self.buf())?; } } @@ -1035,12 +1086,17 @@ impl<'a, W: Write> Formatter<'a, W> { } last_loc = Some(loc); + visited_locs.push(loc); + + last_comment = None; + last_byte_written = loc.end(); - if let Some(comment) = line_item.as_ref().left() { + if let Some(comment) = line_item.left() { if comment.is_line() { last_byte_written = self.find_next_line(last_byte_written).unwrap_or(last_byte_written); } + last_comment = Some(comment); } } @@ -1069,7 +1125,7 @@ impl<'a, W: Write> Formatter<'a, W> { /// Visit the right side of an assignment. The function will try to write the assignment on a /// single line or indented on the next line. If it can't do this it resorts to letting the - /// expression decide how to split iself on multiple lines + /// expression decide how to split itself on multiple lines fn visit_assignment(&mut self, expr: &mut Expression) -> Result<()> { if self.try_on_single_line(|fmt| expr.visit(fmt))? { return Ok(()) @@ -1108,7 +1164,7 @@ impl<'a, W: Write> Formatter<'a, W> { fn visit_list( &mut self, prefix: &str, - items: &mut Vec, + items: &mut [T], start_offset: Option, end_offset: Option, paren_required: bool, @@ -1151,7 +1207,7 @@ impl<'a, W: Write> Formatter<'a, W> { fn visit_block( &mut self, loc: Loc, - statements: &mut Vec, + statements: &mut [T], attempt_single_line: bool, attempt_omit_braces: bool, ) -> Result @@ -1175,22 +1231,114 @@ impl<'a, W: Write> Formatter<'a, W> { } } - write_chunk!(self, "{{")?; + // Determine if any of start / end of the block is disabled and block lines boundaries. + let is_start_disabled = self.inline_config.is_disabled(loc.with_end(loc.start())); + let is_end_disabled = self.inline_config.is_disabled(loc.with_start(loc.end())); + let end_of_first_line = self.find_next_line(loc.start()).unwrap_or_default(); + let end_of_last_line = self.find_next_line(loc.end()).unwrap_or_default(); - if let Some(statement) = statements.first() { - self.write_whitespace_separator(true)?; - self.write_postfix_comments_before(CodeLocation::loc(statement).start())?; + // Write first line of the block: + // - as it is until the end of line, if format disabled + // - start block if line formatted + if is_start_disabled { + self.write_raw_src(loc.with_end(end_of_first_line))?; + } else { + write_chunk!(self, "{{")?; } - self.indented(1, |fmt| { - fmt.write_lined_visitable(loc, statements.iter_mut(), |_, _| false)?; - Ok(()) - })?; + // Write comments and close block if no statement. + if statements.is_empty() { + self.indented(1, |fmt| { + fmt.write_prefix_comments_before(loc.end())?; + fmt.write_postfix_comments_before(loc.end())?; + Ok(()) + })?; + + write_chunk!(self, "}}")?; + return Ok(false) + } + + // Determine writable statements by excluding statements from disabled start / end lines. + // We check the position of last statement from first line (if disabled) and position of + // first statement from last line (if disabled) and slice accordingly. + let writable_statments = match ( + statements.iter().rposition(|stmt| { + is_start_disabled && + self.find_next_line(stmt.loc().end()).unwrap_or_default() == + end_of_first_line + }), + statements.iter().position(|stmt| { + is_end_disabled && + self.find_next_line(stmt.loc().end()).unwrap_or_default() == end_of_last_line + }), + ) { + // We have statements on both disabled start / end lines. + (Some(start), Some(end)) => { + if start == end || start + 1 == end { + None + } else { + Some(&mut statements[start + 1..end]) + } + } + // We have statements only on disabled start line. + (Some(start), None) => { + if start + 1 == statements.len() { + None + } else { + Some(&mut statements[start + 1..]) + } + } + // We have statements only on disabled end line. + (None, Some(end)) => { + if end == 0 { + None + } else { + Some(&mut statements[..end]) + } + } + // No statements on disabled start / end line. + (None, None) => Some(statements), + }; - if !statements.is_empty() { + // Write statements that are not on any disabled first / last block line. + let mut statements_loc = loc; + if let Some(writable_statements) = writable_statments { + if let Some(first_statement) = writable_statements.first() { + statements_loc = statements_loc.with_start(first_statement.loc().start()); + self.write_whitespace_separator(true)?; + self.write_postfix_comments_before(statements_loc.start())?; + } + // If last line is disabled then statements location ends where last block line starts. + if is_end_disabled { + if let Some(last_statement) = writable_statements.last() { + statements_loc = statements_loc.with_end( + self.find_next_line(last_statement.loc().end()).unwrap_or_default(), + ); + } + } + self.indented(1, |fmt| { + fmt.write_lined_visitable( + statements_loc, + writable_statements.iter_mut(), + |_, _| false, + )?; + Ok(()) + })?; self.write_whitespace_separator(true)?; } - write_chunk!(self, loc.end(), "}}")?; + + // Write last line of the block: + // - as it is from where statements location ends until the end of last line, if format + // disabled + // - close block if line formatted + if is_end_disabled { + self.write_raw_src(loc.with_start(statements_loc.end()).with_end(end_of_last_line))?; + } else { + if end_of_first_line != end_of_last_line { + self.write_whitespace_separator(true)?; + } + write_chunk!(self, loc.end(), "}}")?; + } Ok(false) } @@ -1205,7 +1353,7 @@ impl<'a, W: Write> Formatter<'a, W> { Statement::Block { loc, statements, .. } => { self.visit_block(*loc, statements, attempt_single_line, true) } - _ => self.visit_block(stmt.loc(), &mut vec![stmt], attempt_single_line, true), + _ => self.visit_block(stmt.loc(), &mut [stmt], attempt_single_line, true), } } @@ -1244,7 +1392,8 @@ impl<'a, W: Write> Formatter<'a, W> { /// Visit the yul string with an optional identifier. /// If the identifier is present, write the value in the format `:`. - /// Ref: https://docs.soliditylang.org/en/v0.8.15/yul.html#variable-declarations + /// + /// Ref: fn visit_yul_string_with_ident( &mut self, loc: Loc, @@ -1252,7 +1401,7 @@ impl<'a, W: Write> Formatter<'a, W> { ident: &mut Option, ) -> Result<()> { let ident = - if let Some(ident) = ident { format!(":{}", ident.name) } else { "".to_owned() }; + if let Some(ident) = ident { format!(":{}", ident.name) } else { String::new() }; write_chunk!(self, loc.start(), loc.end(), "{val}{ident}")?; Ok(()) } @@ -1302,7 +1451,7 @@ impl<'a, W: Write> Formatter<'a, W> { let config = self.config.number_underscore; // get source if we preserve underscores - let (value, fractional, exponent) = if matches!(config, NumberUnderscore::Preserve) { + let (value, fractional, exponent) = if config.is_preserve() { let source = &self.source[loc.start()..loc.end()]; // Strip unit let (source, _) = source.split_once(' ').unwrap_or((source, "")); @@ -1334,7 +1483,7 @@ impl<'a, W: Write> Formatter<'a, W> { exp = exp.trim().trim_start_matches('0'); let add_underscores = |string: &str, reversed: bool| -> String { - if !matches!(config, NumberUnderscore::Thousands) || string.len() < 5 { + if !config.is_thousands() || string.len() < 5 { return string.to_string() } if reversed { @@ -1375,6 +1524,32 @@ impl<'a, W: Write> Formatter<'a, W> { self.write_unit(unit) } + /// Write and hex literals according to the configuration. + fn write_hex_literal(&mut self, lit: &HexLiteral) -> Result<()> { + let HexLiteral { loc, hex } = lit; + match self.config.hex_underscore { + HexUnderscore::Remove => self.write_quoted_str(*loc, Some("hex"), hex), + HexUnderscore::Preserve => { + let quote = &self.source[loc.start()..loc.end()].trim_start_matches("hex"); + // source is always quoted so we remove the quotes first so we can adhere to the + // configured quoting style + let hex = "e[1..quote.len() - 1]; + self.write_quoted_str(*loc, Some("hex"), hex) + } + HexUnderscore::Bytes => { + // split all bytes + let hex = hex + .chars() + .chunks(2) + .into_iter() + .map(|chunk| chunk.collect::()) + .collect::>() + .join("_"); + self.write_quoted_str(*loc, Some("hex"), &hex) + } + } + } + /// Write built-in unit. fn write_unit(&mut self, unit: &mut Option) -> Result<()> { if let Some(unit) = unit { @@ -1415,7 +1590,8 @@ impl<'a, W: Write> Formatter<'a, W> { self.extend_loc_until(&mut loc, ')'); loc }; - if self.inline_config.is_disabled(params_loc) { + let params_disabled = self.inline_config.is_disabled(params_loc); + if params_disabled { let chunk = self.chunked(func.loc.start(), None, |fmt| fmt.visit_source(params_loc))?; params_multiline = chunk.content.contains('\n'); self.write_chunk(&chunk)?; @@ -1450,7 +1626,10 @@ impl<'a, W: Write> Formatter<'a, W> { let should_multiline = header_multiline && matches!( fmt.config.multiline_func_header, - MultilineFuncHeaderStyle::ParamsFirst | MultilineFuncHeaderStyle::All + MultilineFuncHeaderStyle::ParamsFirst | + MultilineFuncHeaderStyle::ParamsFirstMulti | + MultilineFuncHeaderStyle::All | + MultilineFuncHeaderStyle::AllParams ); params_multiline = should_multiline || multiline || @@ -1459,6 +1638,19 @@ impl<'a, W: Write> Formatter<'a, W> { ¶ms, ",", )?; + // Write new line if we have only one parameter and params first set, + // or if the function definition is multiline and all params set. + let single_param_multiline = matches!( + fmt.config.multiline_func_header, + MultilineFuncHeaderStyle::ParamsFirst + ) || params_multiline && + matches!( + fmt.config.multiline_func_header, + MultilineFuncHeaderStyle::AllParams + ); + if params.len() == 1 && single_param_multiline { + writeln!(fmt.buf())?; + } fmt.write_chunks_separated(¶ms, ",", params_multiline)?; Ok(()) }, @@ -1475,7 +1667,16 @@ impl<'a, W: Write> Formatter<'a, W> { .loc() .with_end_from(&func.attributes.last().unwrap().loc()); if fmt.inline_config.is_disabled(attrs_loc) { - fmt.indented(1, |fmt| fmt.visit_source(attrs_loc))?; + // If params are also disabled then write functions attributes on the same line. + if params_disabled { + fmt.write_whitespace_separator(false)?; + let attrs_src = + String::from_utf8(self.source.as_bytes()[attrs_loc.range()].to_vec()) + .map_err(FormatterError::custom)?; + fmt.write_raw(attrs_src)?; + } else { + fmt.indented(1, |fmt| fmt.visit_source(attrs_loc))?; + } } else { fmt.write_postfix_comments_before(attrs_loc.start())?; fmt.write_whitespace_separator(multiline)?; @@ -1493,14 +1694,33 @@ impl<'a, W: Write> Formatter<'a, W> { let returns_start_loc = func.returns.first().unwrap().0; let returns_loc = returns_start_loc.with_end_from(&func.returns.last().unwrap().0); if fmt.inline_config.is_disabled(returns_loc) { - fmt.indented(1, |fmt| fmt.visit_source(returns_loc))?; + fmt.write_whitespace_separator(false)?; + let returns_src = + String::from_utf8(self.source.as_bytes()[returns_loc.range()].to_vec()) + .map_err(FormatterError::custom)?; + fmt.write_raw(format!("returns ({returns_src})"))?; } else { - let returns = fmt.items_to_chunks( + let mut returns = fmt.items_to_chunks( returns_end, func.returns .iter_mut() .filter_map(|(loc, param)| param.as_mut().map(|param| (*loc, param))), )?; + + // there's an issue with function return value that would lead to indent issues because those can be formatted with line breaks + for function_chunk in + returns.iter_mut().filter(|chunk| chunk.content.starts_with("function(")) + { + // this will bypass the recursive indent that was applied when the function + // content was formatted in the chunk + function_chunk.content = function_chunk + .content + .split('\n') + .map(|s| s.trim_start()) + .collect::>() + .join("\n"); + } + fmt.write_postfix_comments_before(returns_loc.start())?; fmt.write_whitespace_separator(multiline)?; fmt.indented(1, |fmt| { @@ -1521,7 +1741,10 @@ impl<'a, W: Write> Formatter<'a, W> { let should_multiline = header_multiline && if params_multiline { - matches!(self.config.multiline_func_header, MultilineFuncHeaderStyle::All) + matches!( + self.config.multiline_func_header, + MultilineFuncHeaderStyle::All | MultilineFuncHeaderStyle::AllParams + ) } else { matches!( self.config.multiline_func_header, @@ -1557,6 +1780,7 @@ impl<'a, W: Write> Formatter<'a, W> { SurroundingChunk::new("if (", Some(loc.start()), Some(cond.loc().start())), SurroundingChunk::new(")", None, Some(if_branch.loc().start())), |fmt, _| { + fmt.write_prefix_comments_before(cond.loc().end())?; cond.visit(fmt)?; fmt.write_postfix_comments_before(if_branch.loc().start()) }, @@ -1582,7 +1806,7 @@ impl<'a, W: Write> Formatter<'a, W> { self.visit_if(*loc, cond, if_branch, else_branch, false)?; } else { let else_branch_is_single_line = - self.visit_stmt_as_block(else_branch, if_branch_is_single_line)?; + self.visit_stmt_as_block(else_branch, attempt_single_line)?; if single_line_stmt_wide && !else_branch_is_single_line { bail!(FormatterError::fmt()) } @@ -1590,10 +1814,73 @@ impl<'a, W: Write> Formatter<'a, W> { } Ok(()) } + + /// Sorts grouped import statement alphabetically. + fn sort_imports(&self, source_unit: &mut SourceUnit) { + // first we need to find the grouped import statements + // A group is defined as a set of import statements that are separated by a blank line + let mut import_groups = Vec::new(); + let mut current_group = Vec::new(); + let mut source_unit_parts = source_unit.0.iter().enumerate().peekable(); + while let Some((i, part)) = source_unit_parts.next() { + if let SourceUnitPart::ImportDirective(_) = part { + current_group.push(i); + let current_loc = part.loc(); + if let Some((_, next_part)) = source_unit_parts.peek() { + let next_loc = next_part.loc(); + // import statements are followed by a new line, so if there are more than one + // we have a group + if self.blank_lines(current_loc.end(), next_loc.start()) > 1 { + import_groups.push(std::mem::take(&mut current_group)); + } + } + } else if !current_group.is_empty() { + import_groups.push(std::mem::take(&mut current_group)); + } + } + + if !current_group.is_empty() { + import_groups.push(current_group); + } + + if import_groups.is_empty() { + // nothing to sort + return + } + + // order all groups alphabetically + for group in import_groups.iter() { + // SAFETY: group is not empty + let first = group[0]; + let last = group.last().copied().expect("group is not empty"); + let import_directives = &mut source_unit.0[first..=last]; + + // sort rename style imports alphabetically based on the actual import and not the + // rename + for source_unit_part in import_directives.iter_mut() { + if let SourceUnitPart::ImportDirective(Import::Rename(_, renames, _)) = + source_unit_part + { + renames.sort_by_cached_key(|(og_ident, _)| og_ident.name.clone()); + } + } + + import_directives.sort_by_cached_key(|item| match item { + SourceUnitPart::ImportDirective(import) => match import { + Import::Plain(path, _) => path.to_string(), + Import::GlobalSymbol(path, _, _) => path.to_string(), + Import::Rename(path, _, _) => path.to_string(), + }, + _ => { + unreachable!("import group contains non-import statement") + } + }); + } + } } // Traverse the Solidity Parse Tree and write to the code formatter -impl<'a, W: Write> Visitor for Formatter<'a, W> { +impl Visitor for Formatter<'_, W> { type Error = FormatterError; #[instrument(name = "source", skip(self))] @@ -1616,6 +1903,9 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { #[instrument(name = "SU", skip_all)] fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<()> { + if self.config.sort_imports { + self.sort_imports(source_unit); + } // TODO: do we need to put pragma and import directives at the top of the file? // source_unit.0.sort_by_key(|item| match item { // SourceUnitPart::PragmaDirective(_, _, _) => 0, @@ -1655,7 +1945,7 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { )?; // EOF newline - if self.last_char().map_or(true, |char| char != '\n') { + if self.last_char() != Some('\n') { writeln!(self.buf())?; } @@ -1783,6 +2073,18 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } + // Support extension for Solana/Substrate + #[instrument(name = "annotation", skip_all)] + fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { + return_source_if_disabled!(self, annotation.loc); + let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; + write!(self.buf(), "@{id}")?; + write!(self.buf(), "(")?; + annotation.value.visit(self)?; + write!(self.buf(), ")")?; + Ok(()) + } + #[instrument(name = "pragma", skip_all)] fn visit_pragma( &mut self, @@ -1810,12 +2112,12 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } #[instrument(name = "import_plain", skip_all)] - fn visit_import_plain(&mut self, loc: Loc, import: &mut StringLiteral) -> Result<()> { + fn visit_import_plain(&mut self, loc: Loc, import: &mut ImportPath) -> Result<()> { return_source_if_disabled!(self, loc, ';'); self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), import.loc.start(), "import")?; - fmt.write_quoted_str(import.loc, None, &import.string)?; + write_chunk!(fmt, loc.start(), import.loc().start(), "import")?; + fmt.write_quoted_str(import.loc(), None, &import_path_string(import))?; fmt.write_semicolon()?; Ok(()) })?; @@ -1826,14 +2128,14 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { fn visit_import_global( &mut self, loc: Loc, - global: &mut StringLiteral, + global: &mut ImportPath, alias: &mut Identifier, ) -> Result<()> { return_source_if_disabled!(self, loc, ';'); self.grouped(|fmt| { - write_chunk!(fmt, loc.start(), global.loc.start(), "import")?; - fmt.write_quoted_str(global.loc, None, &global.string)?; + write_chunk!(fmt, loc.start(), global.loc().start(), "import")?; + fmt.write_quoted_str(global.loc(), None, &import_path_string(global))?; write_chunk!(fmt, loc.start(), alias.loc.start(), "as")?; alias.visit(fmt)?; fmt.write_semicolon()?; @@ -1847,7 +2149,7 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { &mut self, loc: Loc, imports: &mut [(Identifier, Option)], - from: &mut StringLiteral, + from: &mut ImportPath, ) -> Result<()> { return_source_if_disabled!(self, loc, ';'); @@ -1855,8 +2157,8 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { self.grouped(|fmt| { write_chunk!(fmt, loc.start(), "import")?; fmt.write_empty_brackets()?; - write_chunk!(fmt, loc.start(), from.loc.start(), "from")?; - fmt.write_quoted_str(from.loc, None, &from.string)?; + write_chunk!(fmt, loc.start(), from.loc().start(), "from")?; + fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; fmt.write_semicolon()?; Ok(()) })?; @@ -1869,7 +2171,7 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { self.surrounded( SurroundingChunk::new("{", Some(imports_start), None), - SurroundingChunk::new("}", None, Some(from.loc.start())), + SurroundingChunk::new("}", None, Some(from.loc().start())), |fmt, _multiline| { let mut imports = imports.iter_mut().peekable(); let mut import_chunks = Vec::new(); @@ -1892,7 +2194,7 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } let multiline = fmt.are_chunks_separated_multiline( - &format!("{{}} }} from \"{}\";", from.string), + &format!("{{}} }} from \"{}\";", import_path_string(from)), &import_chunks, ",", )?; @@ -1902,8 +2204,8 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { )?; self.grouped(|fmt| { - write_chunk!(fmt, imports_start, from.loc.start(), "from")?; - fmt.write_quoted_str(from.loc, None, &from.string)?; + write_chunk!(fmt, imports_start, from.loc().start(), "from")?; + fmt.write_quoted_str(from.loc(), None, &import_path_string(from))?; fmt.write_semicolon()?; Ok(()) })?; @@ -1918,105 +2220,212 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { let enum_name = enumeration.name.safe_unwrap_mut(); let mut name = self.visit_to_chunk(enum_name.loc.start(), Some(enum_name.loc.end()), enum_name)?; - name.content = format!("enum {}", name.content); - self.write_chunk(&name)?; - + name.content = format!("enum {} ", name.content); if enumeration.values.is_empty() { + self.write_chunk(&name)?; self.write_empty_brackets()?; } else { - self.surrounded( - SurroundingChunk::new( - "{", - Some(enumeration.values.first_mut().unwrap().safe_unwrap().loc.start()), - None, - ), - SurroundingChunk::new("}", None, Some(enumeration.loc.end())), - |fmt, _multiline| { - let values = fmt.items_to_chunks( - Some(enumeration.loc.end()), - enumeration.values.iter_mut().map(|ident| { - let ident = ident.safe_unwrap_mut(); - (ident.loc, ident) - }), - )?; - fmt.write_chunks_separated(&values, ",", true)?; - Ok(()) - }, - )?; + name.content.push('{'); + self.write_chunk(&name)?; + + self.indented(1, |fmt| { + let values = fmt.items_to_chunks( + Some(enumeration.loc.end()), + enumeration.values.iter_mut().map(|ident| { + let ident = ident.safe_unwrap_mut(); + (ident.loc, ident) + }), + )?; + fmt.write_chunks_separated(&values, ",", true)?; + writeln!(fmt.buf())?; + Ok(()) + })?; + write_chunk!(self, "}}")?; } Ok(()) } - #[instrument(name = "expr", skip_all)] - fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> { + #[instrument(name = "assembly", skip_all)] + fn visit_assembly( + &mut self, + loc: Loc, + dialect: &mut Option, + block: &mut YulBlock, + flags: &mut Option>, + ) -> Result<(), Self::Error> { return_source_if_disabled!(self, loc); - match expr { - Expression::Type(loc, typ) => match typ { - Type::Address => write_chunk!(self, loc.start(), "address")?, - Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?, - Type::Payable => write_chunk!(self, loc.start(), "payable")?, - Type::Bool => write_chunk!(self, loc.start(), "bool")?, - Type::String => write_chunk!(self, loc.start(), "string")?, - Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?, - Type::Rational => write_chunk!(self, loc.start(), "rational")?, - Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?, - Type::Int(ref n) | Type::Uint(ref n) => { - let int = if matches!(typ, Type::Int(_)) { "int" } else { "uint" }; - match n { - 256 => match self.config.int_types { - IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?, - IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?, - IntTypes::Preserve => self.visit_source(*loc)?, - }, - _ => write_chunk!(self, loc.start(), "{int}{n}")?, - } - } - Type::Mapping { loc, key, key_name, value, value_name } => { - let arrow_loc = self.find_next_str_in_src(loc.start(), "=>"); - let close_paren_loc = - self.find_next_in_src(value.loc().end(), ')').unwrap_or(loc.end()); - let first = SurroundingChunk::new( - "mapping(", - Some(loc.start()), - Some(key.loc().start()), - ); - let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end())) - .non_spaced(); - self.surrounded(first, last, |fmt, multiline| { - fmt.grouped(|fmt| { - key.visit(fmt)?; - - if let Some(name) = key_name { - let end_loc = arrow_loc.unwrap_or(value.loc().start()); - write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?; - } else if let Some(arrow_loc) = arrow_loc { - fmt.write_postfix_comments_before(arrow_loc)?; - } - - let mut write_arrow_and_value = |fmt: &mut Self| { - write!(fmt.buf(), "=> ")?; - value.visit(fmt)?; - if let Some(name) = value_name { - write_chunk!(fmt, name.loc.start(), " {}", name)?; - } - Ok(()) - }; - - let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?; - let multiline = multiline && !fmt.will_it_fit(rest_str); - fmt.write_whitespace_separator(multiline)?; - - write_arrow_and_value(fmt)?; - - fmt.write_postfix_comments_before(close_paren_loc)?; - fmt.write_prefix_comments_before(close_paren_loc) - })?; - Ok(()) - })?; - } - Type::Function { .. } => self.visit_source(*loc)?, + write_chunk!(self, loc.start(), "assembly")?; + if let Some(StringLiteral { loc, string, .. }) = dialect { + write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; + } + if let Some(flags) = flags { + if !flags.is_empty() { + let loc_start = flags.first().unwrap().loc.start(); + self.surrounded( + SurroundingChunk::new("(", Some(loc_start), None), + SurroundingChunk::new(")", None, Some(block.loc.start())), + |fmt, _| { + let mut flags = flags.iter_mut().peekable(); + let mut chunks = vec![]; + while let Some(flag) = flags.next() { + let next_byte_offset = + flags.peek().map(|next_flag| next_flag.loc.start()); + chunks.push(fmt.chunked( + flag.loc.start(), + next_byte_offset, + |fmt| { + write!(fmt.buf(), "\"{}\"", flag.string)?; + Ok(()) + }, + )?); + } + fmt.write_chunks_separated(&chunks, ",", false)?; + Ok(()) + }, + )?; + } + } + + block.visit(self) + } + + #[instrument(name = "block", skip_all)] + fn visit_block( + &mut self, + loc: Loc, + unchecked: bool, + statements: &mut Vec, + ) -> Result<()> { + return_source_if_disabled!(self, loc); + if unchecked { + write_chunk!(self, loc.start(), "unchecked ")?; + } + + self.visit_block(loc, statements, false, false)?; + Ok(()) + } + + #[instrument(name = "args", skip_all)] + fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + write!(self.buf(), "{{")?; + + let mut args_iter = args.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { + let next_byte_offset = args_iter + .peek() + .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) + .unwrap_or_else(|| loc.end()); + chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { + fmt.grouped(|fmt| { + write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; + expr.visit(fmt) + })?; + Ok(()) + })?); + } + + if let Some(first) = chunks.first_mut() { + if first.prefixes.is_empty() && + first.postfixes_before.is_empty() && + !self.config.bracket_spacing + { + first.needs_space = Some(false); + } + } + let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; + self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; + + let prefix = if multiline && !self.is_beginning_of_line() { + "\n" + } else if self.config.bracket_spacing { + " " + } else { + "" + }; + let closing_bracket = format!("{prefix}{}", "}"); + if let Some(arg) = args.last() { + write_chunk!(self, arg.loc.end(), "{closing_bracket}")?; + } else { + write_chunk!(self, "{closing_bracket}")?; + } + + Ok(()) + } + + #[instrument(name = "expr", skip_all)] + fn visit_expr(&mut self, loc: Loc, expr: &mut Expression) -> Result<()> { + return_source_if_disabled!(self, loc); + + match expr { + Expression::Type(loc, typ) => match typ { + Type::Address => write_chunk!(self, loc.start(), "address")?, + Type::AddressPayable => write_chunk!(self, loc.start(), "address payable")?, + Type::Payable => write_chunk!(self, loc.start(), "payable")?, + Type::Bool => write_chunk!(self, loc.start(), "bool")?, + Type::String => write_chunk!(self, loc.start(), "string")?, + Type::Bytes(n) => write_chunk!(self, loc.start(), "bytes{}", n)?, + Type::Rational => write_chunk!(self, loc.start(), "rational")?, + Type::DynamicBytes => write_chunk!(self, loc.start(), "bytes")?, + Type::Int(ref n) | Type::Uint(ref n) => { + let int = if matches!(typ, Type::Int(_)) { "int" } else { "uint" }; + match n { + 256 => match self.config.int_types { + IntTypes::Long => write_chunk!(self, loc.start(), "{int}{n}")?, + IntTypes::Short => write_chunk!(self, loc.start(), "{int}")?, + IntTypes::Preserve => self.visit_source(*loc)?, + }, + _ => write_chunk!(self, loc.start(), "{int}{n}")?, + } + } + Type::Mapping { loc, key, key_name, value, value_name } => { + let arrow_loc = self.find_next_str_in_src(loc.start(), "=>"); + let close_paren_loc = + self.find_next_in_src(value.loc().end(), ')').unwrap_or(loc.end()); + let first = SurroundingChunk::new( + "mapping(", + Some(loc.start()), + Some(key.loc().start()), + ); + let last = SurroundingChunk::new(")", Some(close_paren_loc), Some(loc.end())) + .non_spaced(); + self.surrounded(first, last, |fmt, multiline| { + fmt.grouped(|fmt| { + key.visit(fmt)?; + + if let Some(name) = key_name { + let end_loc = arrow_loc.unwrap_or(value.loc().start()); + write_chunk!(fmt, name.loc.start(), end_loc, " {}", name)?; + } else if let Some(arrow_loc) = arrow_loc { + fmt.write_postfix_comments_before(arrow_loc)?; + } + + let mut write_arrow_and_value = |fmt: &mut Self| { + write!(fmt.buf(), "=> ")?; + value.visit(fmt)?; + if let Some(name) = value_name { + write_chunk!(fmt, name.loc.start(), " {}", name)?; + } + Ok(()) + }; + + let rest_str = fmt.simulate_to_string(&mut write_arrow_and_value)?; + let multiline = multiline && !fmt.will_it_fit(rest_str); + fmt.write_whitespace_separator(multiline)?; + + write_arrow_and_value(fmt)?; + + fmt.write_postfix_comments_before(close_paren_loc)?; + fmt.write_prefix_comments_before(close_paren_loc) + })?; + Ok(()) + })?; + } + Type::Function { .. } => self.visit_source(*loc)?, }, Expression::BoolLiteral(loc, val) => { write_chunk!(self, loc.start(), loc.end(), "{val}")?; @@ -2027,7 +2436,7 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Expression::HexNumberLiteral(loc, val, unit) => { // ref: https://docs.soliditylang.org/en/latest/types.html?highlight=address%20literal#address-literals let val = if val.len() == 42 { - to_checksum(&H160::from_str(val).expect(""), None) + Address::from_str(val).expect("").to_string() } else { val.to_owned() }; @@ -2044,8 +2453,8 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } } Expression::HexLiteral(vals) => { - for HexLiteral { loc, hex } in vals { - self.write_quoted_str(*loc, Some("hex"), hex)?; + for val in vals { + self.write_hex_literal(val)?; } } Expression::AddressLiteral(loc, val) => { @@ -2269,6 +2678,10 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { expr.visit(self)?; stmt.visit(self)?; } + Expression::New(_, expr) => { + write_chunk!(self, "new ")?; + self.visit_expr(expr.loc(), expr)?; + } _ => self.visit_source(loc)?, }; @@ -2314,6 +2727,62 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } + #[instrument(name = "var_definition", skip_all)] + fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { + return_source_if_disabled!(self, var.loc, ';'); + + var.ty.visit(self)?; + + let multiline = self.grouped(|fmt| { + let var_name = var.name.safe_unwrap_mut(); + let name_start = var_name.loc.start(); + + let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; + if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { + fmt.write_chunks_separated(&attrs, "", true)?; + } + + let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; + if var.initializer.is_some() { + name.content.push_str(" ="); + } + fmt.write_chunk(&name)?; + + Ok(()) + })?; + + var.initializer + .as_mut() + .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) + .transpose()?; + + self.write_semicolon()?; + + Ok(()) + } + + #[instrument(name = "var_definition_stmt", skip_all)] + fn visit_var_definition_stmt( + &mut self, + loc: Loc, + declaration: &mut VariableDeclaration, + expr: &mut Option, + ) -> Result<()> { + return_source_if_disabled!(self, loc, ';'); + + let declaration = self + .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; + let multiline = declaration.content.contains('\n'); + self.write_chunk(&declaration)?; + + if let Some(expr) = expr { + write!(self.buf(), " =")?; + self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; + } + + self.write_semicolon() + } + #[instrument(name = "var_declaration", skip_all)] fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<()> { return_source_if_disabled!(self, var.loc); @@ -2328,528 +2797,323 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "break", skip_all)] - fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); - } - write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) - } + #[instrument(name = "return", skip_all)] + fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); - #[instrument(name = "continue", skip_all)] - fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { - if semicolon { - return_source_if_disabled!(self, loc, ';'); - } else { - return_source_if_disabled!(self, loc); + self.write_postfix_comments_before(loc.start())?; + self.write_prefix_comments_before(loc.start())?; + + if expr.is_none() { + write_chunk!(self, loc.end(), "return;")?; + return Ok(()) } - write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) - } - #[instrument(name = "function", skip_all)] - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { - if func.body.is_some() { - return_source_if_disabled!(self, func.loc()); - } else { - return_source_if_disabled!(self, func.loc(), ';'); - } - - self.with_function_context(func.clone(), |fmt| { - fmt.write_postfix_comments_before(func.loc.start())?; - fmt.write_prefix_comments_before(func.loc.start())?; + let expr = expr.as_mut().unwrap(); + let expr_loc_start = expr.loc().start(); + let write_return = |fmt: &mut Self| -> Result<()> { + write_chunk!(fmt, loc.start(), "return")?; + fmt.write_postfix_comments_before(expr_loc_start)?; + Ok(()) + }; - let body_loc = func.body.as_ref().map(CodeLocation::loc); - let mut attrs_multiline = false; + let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { let fits_on_single = fmt.try_on_single_line(|fmt| { - fmt.write_function_header(func, body_loc, false)?; - Ok(()) + write_return(fmt)?; + expr.visit(fmt) })?; - if !fits_on_single { - attrs_multiline = fmt.write_function_header(func, body_loc, true)?; + if fits_on_single { + return Ok(()) } - // write function body - match &mut func.body { - Some(body) => { - let body_loc = body.loc(); - let byte_offset = body_loc.start(); - let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; - fmt.write_whitespace_separator( - attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), - )?; - fmt.write_chunk(&body)?; - } - None => fmt.write_semicolon()?, + let mut fit_on_next_line = false; + let tx = fmt.transact(|fmt| { + fmt.grouped(|fmt| { + write_return(fmt)?; + if !fmt.is_beginning_of_line() { + fmt.write_whitespace_separator(true)?; + } + fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; + Ok(()) + })?; + Ok(()) + })?; + if fit_on_next_line { + tx.commit()?; + return Ok(()) } + write_return(fmt)?; + expr.visit(fmt)?; Ok(()) - })?; + }; + write_return_with_expr(self)?; + write_chunk!(self, loc.end(), ";")?; Ok(()) } - #[instrument(name = "function_attribute", skip_all)] - fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - match attribute { - FunctionAttribute::Mutability(mutability) => { - write_chunk!(self, mutability.loc().end(), "{mutability}")? - } - FunctionAttribute::Visibility(visibility) => { - // Visibility will always have a location in a Function attribute - write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? - } - FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, - FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, - FunctionAttribute::Override(loc, args) => { - write_chunk!(self, loc.start(), "override")?; - if !args.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", args, None, Some(loc.end()), false)? - } - FunctionAttribute::BaseOrModifier(loc, base) => { - let is_contract_base = self.context.contract.as_ref().map_or(false, |contract| { - contract.base.iter().any(|contract_base| { - contract_base - .name - .identifiers - .iter() - .zip(&base.name.identifiers) - .all(|(l, r)| l.name == r.name) - }) - }); - - if is_contract_base { - base.visit(self)?; - } else { - let mut base_or_modifier = - self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; - if base_or_modifier.content.ends_with("()") { - base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); - } - self.write_chunk(&base_or_modifier)?; - } - } - FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, - }; + #[instrument(name = "revert", skip_all)] + fn visit_revert( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "revert")?; + if let Some(error) = error { + error.visit(self)?; + } + self.visit_list("", args, None, Some(loc.end()), true)?; + self.write_semicolon()?; Ok(()) } - #[instrument(name = "base", skip_all)] - fn visit_base(&mut self, base: &mut Base) -> Result<()> { - return_source_if_disabled!(self, base.loc); - - let name_loc = &base.name.loc; - let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { - fmt.visit_ident_path(&mut base.name)?; - Ok(()) - })?; + #[instrument(name = "revert_named_args", skip_all)] + fn visit_revert_named_args( + &mut self, + loc: Loc, + error: &mut Option, + args: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); - if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { - if self.context.function.is_some() { - name.content.push_str("()"); + write_chunk!(self, loc.start(), "revert")?; + let mut error_indented = false; + if let Some(error) = error { + if !self.try_on_single_line(|fmt| error.visit(fmt))? { + error.visit(self)?; + error_indented = true; } - self.write_chunk(&name)?; - return Ok(()) } - let args = base.args.as_mut().unwrap(); - let args_start = CodeLocation::loc(args.first().unwrap()).start(); + if args.is_empty() { + write!(self.buf(), "({{}});")?; + return Ok(()) + } - name.content.push('('); - let formatted_name = self.chunk_to_string(&name)?; + write!(self.buf(), "(")?; + self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; + write!(self.buf(), ")")?; + self.write_semicolon()?; - let multiline = !self.will_it_fit(&formatted_name); + Ok(()) + } - self.surrounded( - SurroundingChunk::new(&formatted_name, Some(args_start), None), - SurroundingChunk::new(")", None, Some(base.loc.end())), - |fmt, multiline_hint| { - let args = fmt.items_to_chunks( - Some(base.loc.end()), - args.iter_mut().map(|arg| (arg.loc(), arg)), - )?; - let multiline = multiline || - multiline_hint || - fmt.are_chunks_separated_multiline("{}", &args, ",")?; - fmt.write_chunks_separated(&args, ",", multiline)?; - Ok(()) - }, - )?; + #[instrument(name = "break", skip_all)] + fn visit_break(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!(self, loc.start(), loc.end(), "break{}", if semicolon { ";" } else { "" }) + } - Ok(()) + #[instrument(name = "continue", skip_all)] + fn visit_continue(&mut self, loc: Loc, semicolon: bool) -> Result<()> { + if semicolon { + return_source_if_disabled!(self, loc, ';'); + } else { + return_source_if_disabled!(self, loc); + } + write_chunk!(self, loc.start(), loc.end(), "continue{}", if semicolon { ";" } else { "" }) } - #[instrument(name = "parameter", skip_all)] - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { - return_source_if_disabled!(self, parameter.loc); - self.grouped(|fmt| { - parameter.ty.visit(fmt)?; - if let Some(storage) = ¶meter.storage { - write_chunk!(fmt, storage.loc().end(), "{storage}")?; - } - if let Some(name) = ¶meter.name { - write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; + #[instrument(name = "try", skip_all)] + fn visit_try( + &mut self, + loc: Loc, + expr: &mut Expression, + returns: &mut Option<(Vec<(Loc, Option)>, Box)>, + clauses: &mut Vec, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + + let try_next_byte = clauses.first().map(|c| match c { + CatchClause::Simple(loc, ..) => loc.start(), + CatchClause::Named(loc, ..) => loc.start(), + }); + let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { + write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; + expr.visit(fmt)?; + if let Some((params, stmt)) = returns { + let mut params = + params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); + let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); + fmt.surrounded( + SurroundingChunk::new("returns (", Some(byte_offset), None), + SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), + |fmt, _| { + let chunks = fmt.items_to_chunks( + Some(stmt.loc().start()), + params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), + )?; + let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + Ok(()) + }, + )?; + stmt.visit(fmt)?; } Ok(()) })?; - Ok(()) - } - - #[instrument(name = "struct", skip_all)] - fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { - return_source_if_disabled!(self, structure.loc); - self.grouped(|fmt| { - let struct_name = structure.name.safe_unwrap_mut(); - write_chunk!(fmt, struct_name.loc.start(), "struct")?; - struct_name.visit(fmt)?; - if structure.fields.is_empty() { - return fmt.write_empty_brackets() - } - write!(fmt.buf(), " {{")?; - fmt.surrounded( - SurroundingChunk::new("", Some(struct_name.loc.end()), None), - SurroundingChunk::new("}", None, Some(structure.loc.end())), - |fmt, _multiline| { - let chunks = fmt.items_to_chunks( - Some(structure.loc.end()), - structure.fields.iter_mut().map(|ident| (ident.loc, ident)), - )?; - for mut chunk in chunks { - chunk.content.push(';'); - fmt.write_chunk(&chunk)?; - fmt.write_whitespace_separator(true)?; - } - Ok(()) - }, - ) - })?; + let mut chunks = vec![try_chunk]; + for clause in clauses { + let (loc, ident, mut param, stmt) = match clause { + CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), + CatchClause::Named(loc, ident, param, stmt) => { + (loc, Some(ident), Some(param), stmt) + } + }; - Ok(()) - } + let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { + write_chunk!(fmt, "catch")?; + if let Some(ident) = ident.as_ref() { + fmt.write_postfix_comments_before( + param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), + )?; + write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; + } + if let Some(param) = param.as_mut() { + write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; + fmt.surrounded( + SurroundingChunk::new("", Some(param.loc.start()), None), + SurroundingChunk::new(")", None, Some(stmt.loc().start())), + |fmt, _| param.visit(fmt), + )?; + } - #[instrument(name = "type_definition", skip_all)] - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { - return_source_if_disabled!(self, def.loc, ';'); - self.grouped(|fmt| { - write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; - def.name.visit(fmt)?; - write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; - def.ty.visit(fmt)?; - fmt.write_semicolon()?; - Ok(()) - })?; - Ok(()) - } + stmt.visit(fmt)?; + Ok(()) + })?; - #[instrument(name = "stray_semicolon", skip_all)] - fn visit_stray_semicolon(&mut self) -> Result<()> { - self.write_semicolon() - } + chunks.push(chunk); + } - #[instrument(name = "block", skip_all)] - fn visit_block( - &mut self, - loc: Loc, - unchecked: bool, - statements: &mut Vec, - ) -> Result<()> { - return_source_if_disabled!(self, loc); - if unchecked { - write_chunk!(self, loc.start(), "unchecked ")?; + let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; + if !multiline { + self.write_chunks_separated(&chunks, "", false)?; + return Ok(()) } - self.visit_block(loc, statements, false, false)?; - Ok(()) - } + let mut chunks = chunks.iter_mut().peekable(); + let mut prev_multiline = false; - #[instrument(name = "opening_paren", skip_all)] - fn visit_opening_paren(&mut self) -> Result<()> { - write_chunk!(self, "(")?; - Ok(()) - } + // write try chunk first + if let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + write!(self.buf(), "{chunk_str}")?; + prev_multiline = chunk_str.contains('\n'); + } - #[instrument(name = "closing_paren", skip_all)] - fn visit_closing_paren(&mut self) -> Result<()> { - write_chunk!(self, ")")?; + while let Some(chunk) = chunks.next() { + let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; + let multiline = chunk_str.contains('\n'); + self.indented_if(!multiline, 1, |fmt| { + chunk.needs_space = Some(false); + let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); + let prefix = if fmt.is_beginning_of_line() { + "" + } else if on_same_line { + " " + } else { + "\n" + }; + let chunk_str = format!("{prefix}{chunk_str}"); + write!(fmt.buf(), "{chunk_str}")?; + Ok(()) + })?; + prev_multiline = multiline; + } Ok(()) } - #[instrument(name = "newline", skip_all)] - fn visit_newline(&mut self) -> Result<()> { - writeln_chunk!(self)?; - Ok(()) - } + #[instrument(name = "if", skip_all)] + fn visit_if( + &mut self, + loc: Loc, + cond: &mut Expression, + if_branch: &mut Box, + else_branch: &mut Option>, + is_first_stmt: bool, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); - #[instrument(name = "event", skip_all)] - fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { - return_source_if_disabled!(self, event.loc, ';'); + if !is_first_stmt { + self.write_if_stmt(loc, cond, if_branch, else_branch)?; + return Ok(()) + } - let event_name = event.name.safe_unwrap_mut(); - let mut name = - self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; - name.content = format!("event {}(", name.content); + self.context.if_stmt_single_line = Some(true); + let mut stmt_fits_on_single = false; + let tx = self.transact(|fmt| { + stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { + Ok(()) => true, + Err(FormatterError::Fmt(_)) => false, + Err(err) => bail!(err), + }; + Ok(()) + })?; - let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; - if event.fields.is_empty() { - name.content.push_str(last_chunk); - self.write_chunk(&name)?; + if stmt_fits_on_single { + tx.commit()?; } else { - let byte_offset = event.fields.first().unwrap().loc.start(); - let first_chunk = self.chunk_to_string(&name)?; - self.surrounded( - SurroundingChunk::new(first_chunk, Some(byte_offset), None), - SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), - |fmt, multiline| { - let params = fmt - .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; - - let multiline = - multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; - fmt.write_chunks_separated(¶ms, ",", multiline) - }, - )?; + self.context.if_stmt_single_line = Some(false); + self.write_if_stmt(loc, cond, if_branch, else_branch)?; } + self.context.if_stmt_single_line = None; Ok(()) } - #[instrument(name = "event_parameter", skip_all)] - fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if param.indexed { - write_chunk!(fmt, param.loc.start(), "indexed")?; - } - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; + #[instrument(name = "do_while", skip_all)] + fn visit_do_while( + &mut self, + loc: Loc, + body: &mut Statement, + cond: &mut Expression, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc, ';'); + write_chunk!(self, loc.start(), "do ")?; + self.visit_stmt_as_block(body, false)?; + visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { + self.surrounded( + SurroundingChunk::new("while (", Some(cond.loc().start()), None), + SurroundingChunk::new(");", None, Some(loc.end())), + |fmt, _| cond.visit(fmt), + )?; + }); Ok(()) } - #[instrument(name = "error", skip_all)] - fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { - return_source_if_disabled!(self, error.loc, ';'); - - let error_name = error.name.safe_unwrap_mut(); - let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; - name.content = format!("error {}", name.content); - - let formatted_name = self.chunk_to_string(&name)?; - write!(self.buf(), "{formatted_name}")?; - let start_offset = error.fields.first().map(|f| f.loc.start()); - self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; - self.write_semicolon()?; - - Ok(()) - } + #[instrument(name = "while", skip_all)] + fn visit_while( + &mut self, + loc: Loc, + cond: &mut Expression, + body: &mut Statement, + ) -> Result<(), Self::Error> { + return_source_if_disabled!(self, loc); + self.surrounded( + SurroundingChunk::new("while (", Some(loc.start()), None), + SurroundingChunk::new(")", None, Some(cond.loc().end())), + |fmt, _| { + cond.visit(fmt)?; + fmt.write_postfix_comments_before(body.loc().start()) + }, + )?; - #[instrument(name = "error_parameter", skip_all)] - fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { - return_source_if_disabled!(self, param.loc); - self.grouped(|fmt| { - param.ty.visit(fmt)?; - if let Some(name) = ¶m.name { - write_chunk!(fmt, name.loc.end(), "{}", name.name)?; - } - Ok(()) - })?; + let cond_close_paren_loc = + self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); + let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); + self.visit_stmt_as_block(body, attempt_single_line)?; Ok(()) } - #[instrument(name = "using", skip_all)] - fn visit_using(&mut self, using: &mut Using) -> Result<()> { - return_source_if_disabled!(self, using.loc, ';'); - - write_chunk!(self, using.loc.start(), "using")?; - - let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); - let global_start = using.global.as_mut().map(|global| global.loc.start()); - let loc_end = using.loc.end(); - - let (is_library, mut list_chunks) = match &mut using.list { - UsingList::Library(library) => { - (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) - } - UsingList::Functions(funcs) => { - let mut funcs = funcs.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(func) = funcs.next() { - let next_byte_end = funcs.peek().map(|func| func.loc.start()); - chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { - fmt.visit_ident_path(&mut func.path)?; - if let Some(op) = func.oper { - write!(fmt.buf(), " as {op}")?; - } - Ok(()) - })?); - } - (false, chunks) - } - UsingList::Error => return self.visit_parser_error(using.loc), - }; - - let for_chunk = self.chunk_at( - using.loc.start(), - Some(ty_start.or(global_start).unwrap_or(loc_end)), - None, - "for", - ); - let ty_chunk = if let Some(ty) = &mut using.ty { - self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? - } else { - self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") - }; - let global_chunk = using - .global - .as_mut() - .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) - .transpose()?; - - let write_for_def = |fmt: &mut Self| { - fmt.grouped(|fmt| { - fmt.write_chunk(&for_chunk)?; - fmt.write_chunk(&ty_chunk)?; - if let Some(global_chunk) = global_chunk.as_ref() { - fmt.write_chunk(global_chunk)?; - } - Ok(()) - })?; - Ok(()) - }; - - let simulated_for_def = self.simulate_to_string(write_for_def)?; - - if is_library { - let chunk = list_chunks.pop().unwrap(); - if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { - self.write_chunk(&chunk)?; - write_for_def(self)?; - } else { - self.write_whitespace_separator(true)?; - self.grouped(|fmt| { - fmt.write_chunk(&chunk)?; - Ok(()) - })?; - self.write_whitespace_separator(true)?; - write_for_def(self)?; - } - } else { - self.surrounded( - SurroundingChunk::new("{", Some(using.loc.start()), None), - SurroundingChunk::new( - "}", - None, - Some(ty_start.or(global_start).unwrap_or(loc_end)), - ), - |fmt, _multiline| { - let multiline = fmt.are_chunks_separated_multiline( - &format!("{{ {{}} }} {simulated_for_def};"), - &list_chunks, - ",", - )?; - fmt.write_chunks_separated(&list_chunks, ",", multiline)?; - Ok(()) - }, - )?; - write_for_def(self)?; - } - - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "var_attribute", skip_all)] - fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { - return_source_if_disabled!(self, attribute.loc()); - - let token = match attribute { - VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), - VariableAttribute::Constant(_) => Some("constant".to_string()), - VariableAttribute::Immutable(_) => Some("immutable".to_string()), - VariableAttribute::Override(loc, idents) => { - write_chunk!(self, loc.start(), "override")?; - if !idents.is_empty() && self.config.override_spacing { - self.write_whitespace_separator(false)?; - } - self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; - None - } - }; - if let Some(token) = token { - let loc = attribute.loc(); - write_chunk!(self, loc.start(), loc.end(), "{}", token)?; - } - Ok(()) - } - - #[instrument(name = "var_definition", skip_all)] - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<()> { - return_source_if_disabled!(self, var.loc, ';'); - - var.ty.visit(self)?; - - let multiline = self.grouped(|fmt| { - let var_name = var.name.safe_unwrap_mut(); - let name_start = var_name.loc.start(); - - let attrs = fmt.items_to_chunks_sorted(Some(name_start), var.attrs.iter_mut())?; - if !fmt.try_on_single_line(|fmt| fmt.write_chunks_separated(&attrs, "", false))? { - fmt.write_chunks_separated(&attrs, "", true)?; - } - - let mut name = fmt.visit_to_chunk(name_start, Some(var_name.loc.end()), var_name)?; - if var.initializer.is_some() { - name.content.push_str(" ="); - } - fmt.write_chunk(&name)?; - - Ok(()) - })?; - - var.initializer - .as_mut() - .map(|init| self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(init))) - .transpose()?; - - self.write_semicolon()?; - - Ok(()) - } - - #[instrument(name = "var_definition_stmt", skip_all)] - fn visit_var_definition_stmt( - &mut self, - loc: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<()> { - return_source_if_disabled!(self, loc, ';'); - - let declaration = self - .chunked(declaration.loc.start(), None, |fmt| fmt.visit_var_declaration(declaration))?; - let multiline = declaration.content.contains('\n'); - self.write_chunk(&declaration)?; - - if let Some(expr) = expr { - write!(self.buf(), " =")?; - self.indented_if(multiline, 1, |fmt| fmt.visit_assignment(expr))?; - } - - self.write_semicolon() - } - #[instrument(name = "for", skip_all)] fn visit_for( &mut self, @@ -2904,393 +3168,477 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "while", skip_all)] - fn visit_while( - &mut self, - loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - self.surrounded( - SurroundingChunk::new("while (", Some(loc.start()), None), - SurroundingChunk::new(")", None, Some(cond.loc().end())), - |fmt, _| { - cond.visit(fmt)?; - fmt.write_postfix_comments_before(body.loc().start()) - }, - )?; + #[instrument(name = "function", skip_all)] + fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<()> { + if func.body.is_some() { + return_source_if_disabled!(self, func.loc()); + } else { + return_source_if_disabled!(self, func.loc(), ';'); + } - let cond_close_paren_loc = - self.find_next_in_src(cond.loc().end(), ')').unwrap_or_else(|| cond.loc().end()); - let attempt_single_line = self.should_attempt_block_single_line(body, cond_close_paren_loc); - self.visit_stmt_as_block(body, attempt_single_line)?; - Ok(()) - } + self.with_function_context(func.clone(), |fmt| { + fmt.write_postfix_comments_before(func.loc.start())?; + fmt.write_prefix_comments_before(func.loc.start())?; + + let body_loc = func.body.as_ref().map(CodeLocation::loc); + let mut attrs_multiline = false; + let fits_on_single = fmt.try_on_single_line(|fmt| { + fmt.write_function_header(func, body_loc, false)?; + Ok(()) + })?; + if !fits_on_single { + attrs_multiline = fmt.write_function_header(func, body_loc, true)?; + } + + // write function body + match &mut func.body { + Some(body) => { + let body_loc = body.loc(); + // Handle case where block / statements starts on disabled line. + if fmt.inline_config.is_disabled(body_loc.with_end(body_loc.start())) { + match body { + Statement::Block { statements, .. } if !statements.is_empty() => { + fmt.write_whitespace_separator(false)?; + fmt.visit_block(body_loc, statements, false, false)?; + return Ok(()) + } + _ => { + // Attrs should be written on same line if first line is disabled + // and there's no statement. + attrs_multiline = false + } + } + } + + let byte_offset = body_loc.start(); + let body = fmt.visit_to_chunk(byte_offset, Some(body_loc.end()), body)?; + fmt.write_whitespace_separator( + attrs_multiline && !(func.attributes.is_empty() && func.returns.is_empty()), + )?; + fmt.write_chunk(&body)?; + } + None => fmt.write_semicolon()?, + } + Ok(()) + })?; - #[instrument(name = "do_while", skip_all)] - fn visit_do_while( - &mut self, - loc: Loc, - body: &mut Statement, - cond: &mut Expression, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "do ")?; - self.visit_stmt_as_block(body, false)?; - visit_source_if_disabled_else!(self, loc.with_start(body.loc().end()), { - self.surrounded( - SurroundingChunk::new("while (", Some(cond.loc().start()), None), - SurroundingChunk::new(");", None, Some(loc.end())), - |fmt, _| cond.visit(fmt), - )?; - }); Ok(()) } - #[instrument(name = "if", skip_all)] - fn visit_if( - &mut self, - loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - is_first_stmt: bool, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + #[instrument(name = "function_attribute", skip_all)] + fn visit_function_attribute(&mut self, attribute: &mut FunctionAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); - if !is_first_stmt { - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - return Ok(()) - } + match attribute { + FunctionAttribute::Mutability(mutability) => { + write_chunk!(self, mutability.loc().end(), "{mutability}")? + } + FunctionAttribute::Visibility(visibility) => { + // Visibility will always have a location in a Function attribute + write_chunk!(self, visibility.loc_opt().unwrap().end(), "{visibility}")? + } + FunctionAttribute::Virtual(loc) => write_chunk!(self, loc.end(), "virtual")?, + FunctionAttribute::Immutable(loc) => write_chunk!(self, loc.end(), "immutable")?, + FunctionAttribute::Override(loc, args) => { + write_chunk!(self, loc.start(), "override")?; + if !args.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", args, None, Some(loc.end()), false)? + } + FunctionAttribute::BaseOrModifier(loc, base) => { + // here we need to find out if this attribute belongs to the constructor because the + // modifier need to include the trailing parenthesis + // This is very ambiguous because the modifier can either by an inherited contract + // or a modifier here: e.g.: This is valid constructor: + // `constructor() public Ownable() OnlyOwner {}` + let is_constructor = self.context.is_constructor_function(); + // we can't make any decisions here regarding trailing `()` because we'd need to + // find out if the `base` is a solidity modifier or an + // interface/contract therefore we we its raw content. + + // we can however check if the contract `is` the `base`, this however also does + // not cover all cases + let is_contract_base = self.context.contract.as_ref().is_some_and(|contract| { + contract.base.iter().any(|contract_base| { + contract_base + .name + .identifiers + .iter() + .zip(&base.name.identifiers) + .all(|(l, r)| l.name == r.name) + }) + }); - self.context.if_stmt_single_line = Some(true); - let mut stmt_fits_on_single = false; - let tx = self.transact(|fmt| { - stmt_fits_on_single = match fmt.write_if_stmt(loc, cond, if_branch, else_branch) { - Ok(()) => true, - Err(FormatterError::Fmt(_)) => false, - Err(err) => bail!(err), - }; - Ok(()) - })?; + if is_contract_base { + base.visit(self)?; + } else if is_constructor { + // This is ambiguous because the modifier can either by an inherited + // contract modifiers with empty parenthesis are + // valid, but not required so we make the assumption + // here that modifiers are lowercase + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + let is_lowercase = + base_or_modifier.content.chars().next().is_some_and(|c| c.is_lowercase()); + if is_lowercase && base_or_modifier.content.ends_with("()") { + base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); + } - if stmt_fits_on_single { - tx.commit()?; - } else { - self.context.if_stmt_single_line = Some(false); - self.write_if_stmt(loc, cond, if_branch, else_branch)?; - } - self.context.if_stmt_single_line = None; + self.write_chunk(&base_or_modifier)?; + } else { + let mut base_or_modifier = + self.visit_to_chunk(loc.start(), Some(loc.end()), base)?; + if base_or_modifier.content.ends_with("()") { + base_or_modifier.content.truncate(base_or_modifier.content.len() - 2); + } + self.write_chunk(&base_or_modifier)?; + } + } + FunctionAttribute::Error(loc) => self.visit_parser_error(*loc)?, + }; Ok(()) } - #[instrument(name = "args", skip_all)] - fn visit_args(&mut self, loc: Loc, args: &mut Vec) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); - - write!(self.buf(), "{{")?; - - let mut args_iter = args.iter_mut().peekable(); - let mut chunks = Vec::new(); - while let Some(NamedArgument { loc: arg_loc, name, expr }) = args_iter.next() { - let next_byte_offset = args_iter - .peek() - .map(|NamedArgument { loc: arg_loc, .. }| arg_loc.start()) - .unwrap_or_else(|| loc.end()); - chunks.push(self.chunked(arg_loc.start(), Some(next_byte_offset), |fmt| { - fmt.grouped(|fmt| { - write_chunk!(fmt, name.loc.start(), "{}: ", name.name)?; - expr.visit(fmt) - })?; - Ok(()) - })?); - } + #[instrument(name = "var_attribute", skip_all)] + fn visit_var_attribute(&mut self, attribute: &mut VariableAttribute) -> Result<()> { + return_source_if_disabled!(self, attribute.loc()); - if let Some(first) = chunks.first_mut() { - if first.prefixes.is_empty() && - first.postfixes_before.is_empty() && - !self.config.bracket_spacing - { - first.needs_space = Some(false); + let token = match attribute { + VariableAttribute::Visibility(visibility) => Some(visibility.to_string()), + VariableAttribute::Constant(_) => Some("constant".to_string()), + VariableAttribute::Immutable(_) => Some("immutable".to_string()), + VariableAttribute::Override(loc, idents) => { + write_chunk!(self, loc.start(), "override")?; + if !idents.is_empty() && self.config.override_spacing { + self.write_whitespace_separator(false)?; + } + self.visit_list("", idents, Some(loc.start()), Some(loc.end()), false)?; + None } - } - let multiline = self.are_chunks_separated_multiline("{}}", &chunks, ",")?; - self.indented_if(multiline, 1, |fmt| fmt.write_chunks_separated(&chunks, ",", multiline))?; - - let prefix = if multiline && !self.is_beginning_of_line() { - "\n" - } else if self.config.bracket_spacing { - " " - } else { - "" }; - let closing_bracket = format!("{prefix}{}", "}"); - let closing_bracket_loc = args.last().unwrap().loc.end(); - write_chunk!(self, closing_bracket_loc, "{closing_bracket}")?; - - Ok(()) - } - - #[instrument(name = "revert", skip_all)] - fn visit_revert( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); - write_chunk!(self, loc.start(), "revert")?; - if let Some(error) = error { - error.visit(self)?; + if let Some(token) = token { + let loc = attribute.loc(); + write_chunk!(self, loc.start(), loc.end(), "{}", token)?; } - self.visit_list("", args, None, Some(loc.end()), true)?; - self.write_semicolon()?; - Ok(()) } - #[instrument(name = "revert_named_args", skip_all)] - fn visit_revert_named_args( - &mut self, - loc: Loc, - error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); + #[instrument(name = "base", skip_all)] + fn visit_base(&mut self, base: &mut Base) -> Result<()> { + return_source_if_disabled!(self, base.loc); - write_chunk!(self, loc.start(), "revert")?; - let mut error_indented = false; - if let Some(error) = error { - if !self.try_on_single_line(|fmt| error.visit(fmt))? { - error.visit(self)?; - error_indented = true; - } - } + let name_loc = &base.name.loc; + let mut name = self.chunked(name_loc.start(), Some(name_loc.end()), |fmt| { + fmt.visit_ident_path(&mut base.name)?; + Ok(()) + })?; - if args.is_empty() { - write!(self.buf(), "({{}});")?; + if base.args.is_none() || base.args.as_ref().unwrap().is_empty() { + // This is ambiguous because the modifier can either by an inherited contract or a + // modifier + if self.context.function.is_some() { + name.content.push_str("()"); + } + self.write_chunk(&name)?; return Ok(()) } - write!(self.buf(), "(")?; - self.indented_if(error_indented, 1, |fmt| fmt.visit_args(loc, args))?; - write!(self.buf(), ")")?; - self.write_semicolon()?; + let args = base.args.as_mut().unwrap(); + let args_start = CodeLocation::loc(args.first().unwrap()).start(); - Ok(()) - } + name.content.push('('); + let formatted_name = self.chunk_to_string(&name)?; - #[instrument(name = "return", skip_all)] - fn visit_return(&mut self, loc: Loc, expr: &mut Option) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc, ';'); + let multiline = !self.will_it_fit(&formatted_name); - self.write_postfix_comments_before(loc.start())?; - self.write_prefix_comments_before(loc.start())?; + self.surrounded( + SurroundingChunk::new(&formatted_name, Some(args_start), None), + SurroundingChunk::new(")", None, Some(base.loc.end())), + |fmt, multiline_hint| { + let args = fmt.items_to_chunks( + Some(base.loc.end()), + args.iter_mut().map(|arg| (arg.loc(), arg)), + )?; + let multiline = multiline || + multiline_hint || + fmt.are_chunks_separated_multiline("{}", &args, ",")?; + fmt.write_chunks_separated(&args, ",", multiline)?; + Ok(()) + }, + )?; - if expr.is_none() { - write_chunk!(self, loc.end(), "return;")?; - return Ok(()) - } + Ok(()) + } - let expr = expr.as_mut().unwrap(); - let expr_loc_start = expr.loc().start(); - let write_return = |fmt: &mut Self| -> Result<()> { - write_chunk!(fmt, loc.start(), "return")?; - fmt.write_postfix_comments_before(expr_loc_start)?; + #[instrument(name = "parameter", skip_all)] + fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<()> { + return_source_if_disabled!(self, parameter.loc); + self.grouped(|fmt| { + parameter.ty.visit(fmt)?; + if let Some(storage) = ¶meter.storage { + write_chunk!(fmt, storage.loc().end(), "{storage}")?; + } + if let Some(name) = ¶meter.name { + write_chunk!(fmt, parameter.loc.end(), "{}", name.name)?; + } Ok(()) - }; + })?; + Ok(()) + } - let mut write_return_with_expr = |fmt: &mut Self| -> Result<()> { - let fits_on_single = fmt.try_on_single_line(|fmt| { - write_return(fmt)?; - expr.visit(fmt) - })?; - if fits_on_single { - return Ok(()) + #[instrument(name = "struct", skip_all)] + fn visit_struct(&mut self, structure: &mut StructDefinition) -> Result<()> { + return_source_if_disabled!(self, structure.loc); + self.grouped(|fmt| { + let struct_name = structure.name.safe_unwrap_mut(); + write_chunk!(fmt, struct_name.loc.start(), "struct")?; + struct_name.visit(fmt)?; + if structure.fields.is_empty() { + return fmt.write_empty_brackets() } - let mut fit_on_next_line = false; - let tx = fmt.transact(|fmt| { - fmt.grouped(|fmt| { - write_return(fmt)?; - if !fmt.is_beginning_of_line() { + write!(fmt.buf(), " {{")?; + fmt.surrounded( + SurroundingChunk::new("", Some(struct_name.loc.end()), None), + SurroundingChunk::new("}", None, Some(structure.loc.end())), + |fmt, _multiline| { + let chunks = fmt.items_to_chunks( + Some(structure.loc.end()), + structure.fields.iter_mut().map(|ident| (ident.loc, ident)), + )?; + for mut chunk in chunks { + chunk.content.push(';'); + fmt.write_chunk(&chunk)?; fmt.write_whitespace_separator(true)?; } - fit_on_next_line = fmt.try_on_single_line(|fmt| expr.visit(fmt))?; Ok(()) - })?; - Ok(()) - })?; - if fit_on_next_line { - tx.commit()?; - return Ok(()) - } + }, + ) + })?; - write_return(fmt)?; - expr.visit(fmt)?; - Ok(()) - }; + Ok(()) + } + + #[instrument(name = "event", skip_all)] + fn visit_event(&mut self, event: &mut EventDefinition) -> Result<()> { + return_source_if_disabled!(self, event.loc, ';'); + + let event_name = event.name.safe_unwrap_mut(); + let mut name = + self.visit_to_chunk(event_name.loc.start(), Some(event.loc.end()), event_name)?; + name.content = format!("event {}(", name.content); + + let last_chunk = if event.anonymous { ") anonymous;" } else { ");" }; + if event.fields.is_empty() { + name.content.push_str(last_chunk); + self.write_chunk(&name)?; + } else { + let byte_offset = event.fields.first().unwrap().loc.start(); + let first_chunk = self.chunk_to_string(&name)?; + self.surrounded( + SurroundingChunk::new(first_chunk, Some(byte_offset), None), + SurroundingChunk::new(last_chunk, None, Some(event.loc.end())), + |fmt, multiline| { + let params = fmt + .items_to_chunks(None, event.fields.iter_mut().map(|arg| (arg.loc, arg)))?; + + let multiline = + multiline && fmt.are_chunks_separated_multiline("{}", ¶ms, ",")?; + fmt.write_chunks_separated(¶ms, ",", multiline) + }, + )?; + } - write_return_with_expr(self)?; - write_chunk!(self, loc.end(), ";")?; Ok(()) } - #[instrument(name = "try", skip_all)] - fn visit_try( - &mut self, - loc: Loc, - expr: &mut Expression, - returns: &mut Option<(Vec<(Loc, Option)>, Box)>, - clauses: &mut Vec, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + #[instrument(name = "event_parameter", skip_all)] + fn visit_event_parameter(&mut self, param: &mut EventParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); - let try_next_byte = clauses.first().map(|c| match c { - CatchClause::Simple(loc, ..) => loc.start(), - CatchClause::Named(loc, ..) => loc.start(), - }); - let try_chunk = self.chunked(loc.start(), try_next_byte, |fmt| { - write_chunk!(fmt, loc.start(), expr.loc().start(), "try")?; - expr.visit(fmt)?; - if let Some((params, stmt)) = returns { - let mut params = - params.iter_mut().filter(|(_, param)| param.is_some()).collect::>(); - let byte_offset = params.first().map_or(stmt.loc().start(), |p| p.0.start()); - fmt.surrounded( - SurroundingChunk::new("returns (", Some(byte_offset), None), - SurroundingChunk::new(")", None, params.last().map(|p| p.0.end())), - |fmt, _| { - let chunks = fmt.items_to_chunks( - Some(stmt.loc().start()), - params.iter_mut().map(|(loc, ref mut ident)| (*loc, ident)), - )?; - let multiline = fmt.are_chunks_separated_multiline("{})", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - Ok(()) - }, - )?; - stmt.visit(fmt)?; + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if param.indexed { + write_chunk!(fmt, param.loc.start(), "indexed")?; + } + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; } Ok(()) })?; + Ok(()) + } - let mut chunks = vec![try_chunk]; - for clause in clauses { - let (loc, ident, mut param, stmt) = match clause { - CatchClause::Simple(loc, param, stmt) => (loc, None, param.as_mut(), stmt), - CatchClause::Named(loc, ident, param, stmt) => { - (loc, Some(ident), Some(param), stmt) - } - }; + #[instrument(name = "error", skip_all)] + fn visit_error(&mut self, error: &mut ErrorDefinition) -> Result<()> { + return_source_if_disabled!(self, error.loc, ';'); - let chunk = self.chunked(loc.start(), Some(stmt.loc().start()), |fmt| { - write_chunk!(fmt, "catch")?; - if let Some(ident) = ident.as_ref() { - fmt.write_postfix_comments_before( - param.as_ref().map(|p| p.loc.start()).unwrap_or_else(|| ident.loc.end()), - )?; - write_chunk!(fmt, ident.loc.start(), "{}", ident.name)?; - } - if let Some(param) = param.as_mut() { - write_chunk_spaced!(fmt, param.loc.start(), Some(ident.is_none()), "(")?; - fmt.surrounded( - SurroundingChunk::new("", Some(param.loc.start()), None), - SurroundingChunk::new(")", None, Some(stmt.loc().start())), - |fmt, _| param.visit(fmt), - )?; - } + let error_name = error.name.safe_unwrap_mut(); + let mut name = self.visit_to_chunk(error_name.loc.start(), None, error_name)?; + name.content = format!("error {}", name.content); - stmt.visit(fmt)?; - Ok(()) - })?; + let formatted_name = self.chunk_to_string(&name)?; + write!(self.buf(), "{formatted_name}")?; + let start_offset = error.fields.first().map(|f| f.loc.start()); + self.visit_list("", &mut error.fields, start_offset, Some(error.loc.end()), true)?; + self.write_semicolon()?; - chunks.push(chunk); - } + Ok(()) + } - let multiline = self.are_chunks_separated_multiline("{}", &chunks, "")?; - if !multiline { - self.write_chunks_separated(&chunks, "", false)?; - return Ok(()) - } + #[instrument(name = "error_parameter", skip_all)] + fn visit_error_parameter(&mut self, param: &mut ErrorParameter) -> Result<()> { + return_source_if_disabled!(self, param.loc); + self.grouped(|fmt| { + param.ty.visit(fmt)?; + if let Some(name) = ¶m.name { + write_chunk!(fmt, name.loc.end(), "{}", name.name)?; + } + Ok(()) + })?; + Ok(()) + } - let mut chunks = chunks.iter_mut().peekable(); - let mut prev_multiline = false; + #[instrument(name = "type_definition", skip_all)] + fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<()> { + return_source_if_disabled!(self, def.loc, ';'); + self.grouped(|fmt| { + write_chunk!(fmt, def.loc.start(), def.name.loc.start(), "type")?; + def.name.visit(fmt)?; + write_chunk!(fmt, def.name.loc.end(), CodeLocation::loc(&def.ty).start(), "is")?; + def.ty.visit(fmt)?; + fmt.write_semicolon()?; + Ok(()) + })?; + Ok(()) + } - // write try chunk first - if let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - write!(self.buf(), "{chunk_str}")?; - prev_multiline = chunk_str.contains('\n'); - } + #[instrument(name = "stray_semicolon", skip_all)] + fn visit_stray_semicolon(&mut self) -> Result<()> { + self.write_semicolon() + } - while let Some(chunk) = chunks.next() { - let chunk_str = self.simulate_to_string(|fmt| fmt.write_chunk(chunk))?; - let multiline = chunk_str.contains('\n'); - self.indented_if(!multiline, 1, |fmt| { - chunk.needs_space = Some(false); - let on_same_line = prev_multiline && (multiline || chunks.peek().is_none()); - let prefix = if fmt.is_beginning_of_line() { - "" - } else if on_same_line { - " " - } else { - "\n" - }; - let chunk_str = format!("{prefix}{chunk_str}"); - write!(fmt.buf(), "{chunk_str}")?; - Ok(()) - })?; - prev_multiline = multiline; - } + #[instrument(name = "opening_paren", skip_all)] + fn visit_opening_paren(&mut self) -> Result<()> { + write_chunk!(self, "(")?; + Ok(()) + } + + #[instrument(name = "closing_paren", skip_all)] + fn visit_closing_paren(&mut self) -> Result<()> { + write_chunk!(self, ")")?; + Ok(()) + } + + #[instrument(name = "newline", skip_all)] + fn visit_newline(&mut self) -> Result<()> { + writeln_chunk!(self)?; Ok(()) } - #[instrument(name = "assembly", skip_all)] - fn visit_assembly( - &mut self, - loc: Loc, - dialect: &mut Option, - block: &mut YulBlock, - flags: &mut Option>, - ) -> Result<(), Self::Error> { - return_source_if_disabled!(self, loc); + #[instrument(name = "using", skip_all)] + fn visit_using(&mut self, using: &mut Using) -> Result<()> { + return_source_if_disabled!(self, using.loc, ';'); - write_chunk!(self, loc.start(), "assembly")?; - if let Some(StringLiteral { loc, string, .. }) = dialect { - write_chunk!(self, loc.start(), loc.end(), "\"{string}\"")?; - } - if let Some(flags) = flags { - if !flags.is_empty() { - let loc_start = flags.first().unwrap().loc.start(); - self.surrounded( - SurroundingChunk::new("(", Some(loc_start), None), - SurroundingChunk::new(")", None, Some(block.loc.start())), - |fmt, _| { - let mut flags = flags.iter_mut().peekable(); - let mut chunks = vec![]; - while let Some(flag) = flags.next() { - let next_byte_offset = - flags.peek().map(|next_flag| next_flag.loc.start()); - chunks.push(fmt.chunked( - flag.loc.start(), - next_byte_offset, - |fmt| { - write!(fmt.buf(), "\"{}\"", flag.string)?; - Ok(()) - }, - )?); + write_chunk!(self, using.loc.start(), "using")?; + + let ty_start = using.ty.as_mut().map(|ty| CodeLocation::loc(&ty).start()); + let global_start = using.global.as_mut().map(|global| global.loc.start()); + let loc_end = using.loc.end(); + + let (is_library, mut list_chunks) = match &mut using.list { + UsingList::Library(library) => { + (true, vec![self.visit_to_chunk(library.loc.start(), None, library)?]) + } + UsingList::Functions(funcs) => { + let mut funcs = funcs.iter_mut().peekable(); + let mut chunks = Vec::new(); + while let Some(func) = funcs.next() { + let next_byte_end = funcs.peek().map(|func| func.loc.start()); + chunks.push(self.chunked(func.loc.start(), next_byte_end, |fmt| { + fmt.visit_ident_path(&mut func.path)?; + if let Some(op) = func.oper { + write!(fmt.buf(), " as {op}")?; } - fmt.write_chunks_separated(&chunks, ",", false)?; Ok(()) - }, - )?; + })?); + } + (false, chunks) + } + UsingList::Error => return self.visit_parser_error(using.loc), + }; + + let for_chunk = self.chunk_at( + using.loc.start(), + Some(ty_start.or(global_start).unwrap_or(loc_end)), + None, + "for", + ); + let ty_chunk = if let Some(ty) = &mut using.ty { + self.visit_to_chunk(ty.loc().start(), Some(global_start.unwrap_or(loc_end)), ty)? + } else { + self.chunk_at(using.loc.start(), Some(global_start.unwrap_or(loc_end)), None, "*") + }; + let global_chunk = using + .global + .as_mut() + .map(|global| self.visit_to_chunk(global.loc.start(), Some(using.loc.end()), global)) + .transpose()?; + + let write_for_def = |fmt: &mut Self| { + fmt.grouped(|fmt| { + fmt.write_chunk(&for_chunk)?; + fmt.write_chunk(&ty_chunk)?; + if let Some(global_chunk) = global_chunk.as_ref() { + fmt.write_chunk(global_chunk)?; + } + Ok(()) + })?; + Ok(()) + }; + + let simulated_for_def = self.simulate_to_string(write_for_def)?; + + if is_library { + let chunk = list_chunks.pop().unwrap(); + if self.will_chunk_fit(&format!("{{}} {simulated_for_def};"), &chunk)? { + self.write_chunk(&chunk)?; + write_for_def(self)?; + } else { + self.write_whitespace_separator(true)?; + self.grouped(|fmt| { + fmt.write_chunk(&chunk)?; + Ok(()) + })?; + self.write_whitespace_separator(true)?; + write_for_def(self)?; } + } else { + self.surrounded( + SurroundingChunk::new("{", Some(using.loc.start()), None), + SurroundingChunk::new( + "}", + None, + Some(ty_start.or(global_start).unwrap_or(loc_end)), + ), + |fmt, _multiline| { + let multiline = fmt.are_chunks_separated_multiline( + &format!("{{ {{}} }} {simulated_for_def};"), + &list_chunks, + ",", + )?; + fmt.write_chunks_separated(&list_chunks, ",", multiline)?; + Ok(()) + }, + )?; + write_for_def(self)?; } - block.visit(self) + self.write_semicolon()?; + + Ok(()) } #[instrument(name = "yul_block", skip_all)] @@ -3305,38 +3653,6 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - #[instrument(name = "yul_assignment", skip_all)] - fn visit_yul_assignment( - &mut self, - loc: Loc, - exprs: &mut Vec, - expr: &mut Option<&mut YulExpression>, - ) -> Result<(), Self::Error> - where - T: Visitable + CodeLocation, - { - return_source_if_disabled!(self, loc); - - self.grouped(|fmt| { - let chunks = - fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; - - let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; - fmt.write_chunks_separated(&chunks, ",", multiline)?; - - if let Some(expr) = expr { - write_chunk!(fmt, expr.loc().start(), ":=")?; - let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; - if !fmt.will_chunk_fit("{}", &chunk)? { - fmt.write_whitespace_separator(true)?; - } - fmt.write_chunk(&chunk)?; - } - Ok(()) - })?; - Ok(()) - } - #[instrument(name = "yul_expr", skip_all)] fn visit_yul_expr(&mut self, expr: &mut YulExpression) -> Result<(), Self::Error> { return_source_if_disabled!(self, expr.loc()); @@ -3381,6 +3697,38 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { } } + #[instrument(name = "yul_assignment", skip_all)] + fn visit_yul_assignment( + &mut self, + loc: Loc, + exprs: &mut Vec, + expr: &mut Option<&mut YulExpression>, + ) -> Result<(), Self::Error> + where + T: Visitable + CodeLocation, + { + return_source_if_disabled!(self, loc); + + self.grouped(|fmt| { + let chunks = + fmt.items_to_chunks(None, exprs.iter_mut().map(|expr| (expr.loc(), expr)))?; + + let multiline = fmt.are_chunks_separated_multiline("{} := ", &chunks, ",")?; + fmt.write_chunks_separated(&chunks, ",", multiline)?; + + if let Some(expr) = expr { + write_chunk!(fmt, expr.loc().start(), ":=")?; + let chunk = fmt.visit_to_chunk(expr.loc().start(), Some(loc.end()), expr)?; + if !fmt.will_chunk_fit("{}", &chunk)? { + fmt.write_whitespace_separator(true)?; + } + fmt.write_chunk(&chunk)?; + } + Ok(()) + })?; + Ok(()) + } + #[instrument(name = "yul_for", skip_all)] fn visit_yul_for(&mut self, stmt: &mut YulFor) -> Result<(), Self::Error> { return_source_if_disabled!(self, stmt.loc); @@ -3399,12 +3747,6 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { self.visit_list("", &mut stmt.arguments, None, Some(stmt.loc.end()), true) } - #[instrument(name = "yul_typed_ident", skip_all)] - fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { - return_source_if_disabled!(self, ident.loc); - self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) - } - #[instrument(name = "yul_fun_def", skip_all)] fn visit_yul_fun_def(&mut self, stmt: &mut YulFunctionDefinition) -> Result<(), Self::Error> { return_source_if_disabled!(self, stmt.loc); @@ -3493,16 +3835,10 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Ok(()) } - // Support extension for Solana/Substrate - #[instrument(name = "annotation", skip_all)] - fn visit_annotation(&mut self, annotation: &mut Annotation) -> Result<()> { - return_source_if_disabled!(self, annotation.loc); - let id = self.simulate_to_string(|fmt| annotation.id.visit(fmt))?; - write!(self.buf(), "@{id}")?; - write!(self.buf(), "(")?; - annotation.value.visit(self)?; - write!(self.buf(), ")")?; - Ok(()) + #[instrument(name = "yul_typed_ident", skip_all)] + fn visit_yul_typed_ident(&mut self, ident: &mut YulTypedIdentifier) -> Result<(), Self::Error> { + return_source_if_disabled!(self, ident.loc); + self.visit_yul_string_with_ident(ident.loc, &ident.id.name, &mut ident.ty) } #[instrument(name = "parser_error", skip_all)] @@ -3510,3 +3846,43 @@ impl<'a, W: Write> Visitor for Formatter<'a, W> { Err(FormatterError::InvalidParsedItem(loc)) } } + +/// An action which may be committed to a Formatter +struct Transaction<'f, 'a, W> { + fmt: &'f mut Formatter<'a, W>, + buffer: String, + comments: Comments, +} + +impl<'a, W> std::ops::Deref for Transaction<'_, 'a, W> { + type Target = Formatter<'a, W>; + fn deref(&self) -> &Self::Target { + self.fmt + } +} + +impl std::ops::DerefMut for Transaction<'_, '_, W> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.fmt + } +} + +impl<'f, 'a, W: Write> Transaction<'f, 'a, W> { + /// Create a new transaction from a callback + fn new( + fmt: &'f mut Formatter<'a, W>, + fun: impl FnMut(&mut Formatter<'a, W>) -> Result<()>, + ) -> Result { + let mut comments = fmt.comments.clone(); + let buffer = fmt.with_temp_buf(fun)?.w; + comments = std::mem::replace(&mut fmt.comments, comments); + Ok(Self { fmt, buffer, comments }) + } + + /// Commit the transaction to the Formatter + fn commit(self) -> Result { + self.fmt.comments = self.comments; + write_chunk!(self.fmt, "{}", self.buffer)?; + Ok(self.buffer) + } +} diff --git a/crates/fmt/src/helpers.rs b/crates/fmt/src/helpers.rs index d419b88a9d4a3..1d036ba6b66d0 100644 --- a/crates/fmt/src/helpers.rs +++ b/crates/fmt/src/helpers.rs @@ -10,20 +10,31 @@ use std::{fmt::Write, path::Path}; /// Result of parsing the source code #[derive(Debug)] pub struct Parsed<'a> { - /// The original source code + /// The original source code. pub src: &'a str, - /// The Parse Tree via [`solang`] + /// The parse tree. pub pt: SourceUnit, - /// Parsed comments + /// Parsed comments. pub comments: Comments, - /// Parsed inline config + /// Parsed inline config. pub inline_config: InlineConfig, - /// Invalid inline config items parsed + /// Invalid inline config items parsed. pub invalid_inline_config_items: Vec<(Loc, InvalidInlineConfigItem)>, } -/// Parse source code -pub fn parse(src: &str) -> Result> { +/// Parse source code. +pub fn parse(src: &str) -> Result, FormatterError> { + parse_raw(src).map_err(|diag| FormatterError::Parse(src.to_string(), None, diag)) +} + +/// Parse source code with a path for diagnostics. +pub fn parse2<'s>(src: &'s str, path: Option<&Path>) -> Result, FormatterError> { + parse_raw(src) + .map_err(|diag| FormatterError::Parse(src.to_string(), path.map(ToOwned::to_owned), diag)) +} + +/// Parse source code, returning a list of diagnostics on failure. +pub fn parse_raw(src: &str) -> Result, Vec> { let (pt, comments) = solang_parser::parse(src, 0)?; let comments = Comments::new(comments, src); let (inline_config_items, invalid_inline_config_items): (Vec<_>, Vec<_>) = @@ -33,9 +44,9 @@ pub fn parse(src: &str) -> Result> { } /// Format parsed code -pub fn format( +pub fn format_to( writer: W, - mut parsed: Parsed, + mut parsed: Parsed<'_>, config: FormatterConfig, ) -> Result<(), FormatterError> { trace!(?parsed, ?config, "Formatting"); @@ -45,11 +56,11 @@ pub fn format( } /// Parse and format a string with default settings -pub fn fmt(src: &str) -> Result { - let parsed = parse(src).map_err(|_| FormatterError::Fmt(std::fmt::Error))?; +pub fn format(src: &str) -> Result { + let parsed = parse(src)?; let mut output = String::new(); - format(&mut output, parsed, FormatterConfig::default())?; + format_to(&mut output, parsed, FormatterConfig::default())?; Ok(output) } @@ -72,29 +83,53 @@ pub fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) { unreachable!("content.len() > start") } -/// Print the report of parser's diagnostics -pub fn print_diagnostics_report( +/// Formats parser diagnostics +pub fn format_diagnostics_report( content: &str, path: Option<&Path>, - diagnostics: Vec, -) -> std::io::Result<()> { + diagnostics: &[Diagnostic], +) -> String { + if diagnostics.is_empty() { + return String::new(); + } + let filename = path.map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_default(); + let mut s = Vec::new(); for diag in diagnostics { - let (start, end) = (diag.loc.start(), diag.loc.end()); - let mut report = Report::build(ReportKind::Error, &filename, start) + let span = (filename.as_str(), diag.loc.start()..diag.loc.end()); + let mut report = Report::build(ReportKind::Error, span.clone()) .with_message(format!("{:?}", diag.ty)) .with_label( - Label::new((&filename, start..end)) + Label::new(span) .with_color(Color::Red) - .with_message(format!("{}", diag.message.fg(Color::Red))), + .with_message(diag.message.as_str().fg(Color::Red)), ); - for note in diag.notes { - report = report.with_note(note.message); + for note in &diag.notes { + report = report.with_note(¬e.message); } - report.finish().print((&filename, Source::from(content)))?; + report.finish().write((filename.as_str(), Source::from(content)), &mut s).unwrap(); + } + String::from_utf8(s).unwrap() +} + +pub fn import_path_string(path: &ImportPath) -> String { + match path { + ImportPath::Filename(s) => s.string.clone(), + ImportPath::Path(p) => p.to_string(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // + #[test] + fn test_interface_format() { + let s = "interface I {\n function increment() external;\n function number() external view returns (uint256);\n function setNumber(uint256 newNumber) external;\n}"; + let _formatted = format(s).unwrap(); } - Ok(()) } diff --git a/crates/fmt/src/inline_config.rs b/crates/fmt/src/inline_config.rs index b0e3893d50da4..9f0e548061106 100644 --- a/crates/fmt/src/inline_config.rs +++ b/crates/fmt/src/inline_config.rs @@ -5,7 +5,7 @@ use std::{fmt, str::FromStr}; /// An inline config item #[allow(clippy::enum_variant_names)] -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum InlineConfigItem { /// Disables the next code item regardless of newlines DisableNextItem, @@ -23,11 +23,11 @@ impl FromStr for InlineConfigItem { type Err = InvalidInlineConfigItem; fn from_str(s: &str) -> Result { Ok(match s { - "disable-next-item" => InlineConfigItem::DisableNextItem, - "disable-line" => InlineConfigItem::DisableLine, - "disable-next-line" => InlineConfigItem::DisableNextLine, - "disable-start" => InlineConfigItem::DisableStart, - "disable-end" => InlineConfigItem::DisableEnd, + "disable-next-item" => Self::DisableNextItem, + "disable-line" => Self::DisableLine, + "disable-next-line" => Self::DisableNextLine, + "disable-start" => Self::DisableStart, + "disable-end" => Self::DisableEnd, s => return Err(InvalidInlineConfigItem(s.into())), }) } @@ -61,9 +61,11 @@ impl DisabledRange { /// An inline config. Keeps track of disabled ranges. /// /// This is a list of Inline Config items for locations in a source file. This is -/// usually acquired by parsing the comments for an `forgefmt:` items. See -/// [`Comments::parse_inline_config_items`] for details. -#[derive(Default, Debug)] +/// usually acquired by parsing the comments for an `forgefmt:` items. +/// +/// See [`Comments::parse_inline_config_items`](crate::Comments::parse_inline_config_items) for +/// details. +#[derive(Debug, Default)] pub struct InlineConfig { disabled_ranges: Vec, } diff --git a/crates/fmt/src/lib.rs b/crates/fmt/src/lib.rs index 66f8bc4a5d35c..006b4db02abe8 100644 --- a/crates/fmt/src/lib.rs +++ b/crates/fmt/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -19,6 +20,8 @@ pub use foundry_config::fmt::*; pub use comments::Comments; pub use formatter::{Formatter, FormatterError}; -pub use helpers::{fmt, format, offset_to_line_column, parse, print_diagnostics_report, Parsed}; +pub use helpers::{ + format, format_diagnostics_report, format_to, offset_to_line_column, parse, parse2, Parsed, +}; pub use inline_config::InlineConfig; pub use visit::{Visitable, Visitor}; diff --git a/crates/fmt/src/solang_ext/ast_eq.rs b/crates/fmt/src/solang_ext/ast_eq.rs index 7beef80a711e2..2640008e2eaa0 100644 --- a/crates/fmt/src/solang_ext/ast_eq.rs +++ b/crates/fmt/src/solang_ext/ast_eq.rs @@ -1,11 +1,11 @@ -use ethers_core::types::{H160, I256, U256}; +use alloy_primitives::{Address, I256, U256}; use solang_parser::pt::*; use std::str::FromStr; /// Helper to convert a string number into a comparable one fn to_num(string: &str) -> I256 { if string.is_empty() { - return I256::from(0) + return I256::ZERO } string.replace('_', "").trim().parse().unwrap() } @@ -22,7 +22,7 @@ fn to_num_reversed(string: &str) -> U256 { /// Helper to filter [ParameterList] to omit empty /// parameters fn filter_params(list: &ParameterList) -> ParameterList { - list.iter().cloned().filter(|(_, param)| param.is_some()).collect::>() + list.iter().filter(|(_, param)| param.is_some()).cloned().collect::>() } /// Check if two ParseTrees are equal ignoring location information or ordering if ordering does @@ -147,8 +147,8 @@ where impl AstEq for String { fn ast_eq(&self, other: &Self) -> bool { - match (H160::from_str(self), H160::from_str(other)) { - (Ok(left), Ok(right)) => left.eq(&right), + match (Address::from_str(self), Address::from_str(other)) { + (Ok(left), Ok(right)) => left == right, _ => self == other, } } @@ -225,13 +225,13 @@ macro_rules! wrap_in_box { impl AstEq for Statement { fn ast_eq(&self, other: &Self) -> bool { match self { - Statement::If(loc, expr, stmt1, stmt2) => { + Self::If(loc, expr, stmt1, stmt2) => { #[allow(clippy::borrowed_box)] - let wrap_if = |stmt1: &Box, stmt2: &Option>| { + let wrap_if = |stmt1: &Box, stmt2: &Option>| { ( wrap_in_box!(stmt1, *loc), stmt2.as_ref().map(|stmt2| { - if matches!(**stmt2, Statement::If(..)) { + if matches!(**stmt2, Self::If(..)) { stmt2.clone() } else { wrap_in_box!(stmt2, *loc) @@ -241,7 +241,7 @@ impl AstEq for Statement { }; let (stmt1, stmt2) = wrap_if(stmt1, stmt2); let left = (loc, expr, &stmt1, &stmt2); - if let Statement::If(loc, expr, stmt1, stmt2) = other { + if let Self::If(loc, expr, stmt1, stmt2) = other { let (stmt1, stmt2) = wrap_if(stmt1, stmt2); let right = (loc, expr, &stmt1, &stmt2); left.ast_eq(&right) @@ -249,10 +249,10 @@ impl AstEq for Statement { false } } - Statement::While(loc, expr, stmt1) => { + Self::While(loc, expr, stmt1) => { let stmt1 = wrap_in_box!(stmt1, *loc); let left = (loc, expr, &stmt1); - if let Statement::While(loc, expr, stmt1) = other { + if let Self::While(loc, expr, stmt1) = other { let stmt1 = wrap_in_box!(stmt1, *loc); let right = (loc, expr, &stmt1); left.ast_eq(&right) @@ -260,10 +260,10 @@ impl AstEq for Statement { false } } - Statement::DoWhile(loc, stmt1, expr) => { + Self::DoWhile(loc, stmt1, expr) => { let stmt1 = wrap_in_box!(stmt1, *loc); let left = (loc, &stmt1, expr); - if let Statement::DoWhile(loc, stmt1, expr) = other { + if let Self::DoWhile(loc, stmt1, expr) = other { let stmt1 = wrap_in_box!(stmt1, *loc); let right = (loc, &stmt1, expr); left.ast_eq(&right) @@ -271,10 +271,10 @@ impl AstEq for Statement { false } } - Statement::For(loc, stmt1, expr, stmt2, stmt3) => { + Self::For(loc, stmt1, expr, stmt2, stmt3) => { let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); let left = (loc, stmt1, expr, stmt2, &stmt3); - if let Statement::For(loc, stmt1, expr, stmt2, stmt3) = other { + if let Self::For(loc, stmt1, expr, stmt2, stmt3) = other { let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); let right = (loc, stmt1, expr, stmt2, &stmt3); left.ast_eq(&right) @@ -282,11 +282,11 @@ impl AstEq for Statement { false } } - Statement::Try(loc, expr, returns, catch) => { + Self::Try(loc, expr, returns, catch) => { let left_returns = returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); let left = (loc, expr, left_returns, catch); - if let Statement::Try(loc, expr, returns, catch) = other { + if let Self::Try(loc, expr, returns, catch) = other { let right_returns = returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); let right = (loc, expr, right_returns, catch); @@ -311,7 +311,7 @@ impl AstEq for Statement { While(loc, expr, stmt1), DoWhile(loc, stmt1, expr), For(loc, stmt1, expr, stmt2, stmt3), - Try(loc, expr, params, claus), + Try(loc, expr, params, clause), Error(loc) _ Block { @@ -629,6 +629,12 @@ derive_ast_eq! { enum SourceUnitPart { Annotation(annotation), _ }} +derive_ast_eq! { enum ImportPath { + _ + Filename(lit), + Path(path), + _ +}} derive_ast_eq! { enum Import { _ Plain(string, loc), diff --git a/crates/fmt/src/solang_ext/loc.rs b/crates/fmt/src/solang_ext/loc.rs index e8b3184e8fa82..54bf771c6df90 100644 --- a/crates/fmt/src/solang_ext/loc.rs +++ b/crates/fmt/src/solang_ext/loc.rs @@ -10,19 +10,19 @@ pub trait CodeLocationExt { fn loc(&self) -> pt::Loc; } -impl<'a, T: ?Sized + CodeLocationExt> CodeLocationExt for &'a T { +impl CodeLocationExt for &T { fn loc(&self) -> pt::Loc { (**self).loc() } } -impl<'a, T: ?Sized + CodeLocationExt> CodeLocationExt for &'a mut T { +impl CodeLocationExt for &mut T { fn loc(&self) -> pt::Loc { (**self).loc() } } -impl<'a, T: ?Sized + ToOwned + CodeLocationExt> CodeLocationExt for Cow<'a, T> { +impl CodeLocationExt for Cow<'_, T> { fn loc(&self) -> pt::Loc { (**self).loc() } @@ -81,6 +81,15 @@ impl CodeLocationExt for pt::SourceUnitPart { } } +impl CodeLocationExt for pt::ImportPath { + fn loc(&self) -> pt::Loc { + match self { + Self::Filename(s) => s.loc(), + Self::Path(i) => i.loc(), + } + } +} + macro_rules! impl_delegate { ($($t:ty),+ $(,)?) => {$( impl CodeLocationExt for $t { diff --git a/crates/fmt/src/solang_ext/mod.rs b/crates/fmt/src/solang_ext/mod.rs index 2a34c1a349b93..aa4fe734ee64f 100644 --- a/crates/fmt/src/solang_ext/mod.rs +++ b/crates/fmt/src/solang_ext/mod.rs @@ -10,12 +10,12 @@ pub mod pt { Annotation, Base, CatchClause, Comment, ContractDefinition, ContractPart, ContractTy, EnumDefinition, ErrorDefinition, ErrorParameter, EventDefinition, EventParameter, Expression, FunctionAttribute, FunctionDefinition, FunctionTy, HexLiteral, Identifier, - IdentifierPath, Import, Loc, Mutability, NamedArgument, OptionalCodeLocation, Parameter, - ParameterList, SourceUnit, SourceUnitPart, Statement, StorageLocation, StringLiteral, - StructDefinition, Type, TypeDefinition, UserDefinedOperator, Using, UsingFunction, - UsingList, VariableAttribute, VariableDeclaration, VariableDefinition, Visibility, - YulBlock, YulExpression, YulFor, YulFunctionCall, YulFunctionDefinition, YulStatement, - YulSwitch, YulSwitchOptions, YulTypedIdentifier, + IdentifierPath, Import, ImportPath, Loc, Mutability, NamedArgument, OptionalCodeLocation, + Parameter, ParameterList, SourceUnit, SourceUnitPart, Statement, StorageLocation, + StringLiteral, StructDefinition, Type, TypeDefinition, UserDefinedOperator, Using, + UsingFunction, UsingList, VariableAttribute, VariableDeclaration, VariableDefinition, + Visibility, YulBlock, YulExpression, YulFor, YulFunctionCall, YulFunctionDefinition, + YulStatement, YulSwitch, YulSwitchOptions, YulTypedIdentifier, }; } diff --git a/crates/fmt/src/string.rs b/crates/fmt/src/string.rs index 5abfc7d3a1e92..ae570a39b827a 100644 --- a/crates/fmt/src/string.rs +++ b/crates/fmt/src/string.rs @@ -1,10 +1,10 @@ -//! Helpfers for dealing with quoted strings +//! Helpers for dealing with quoted strings /// The state of a character in a string with quotable components /// This is a simplified version of the /// [actual parser](https://docs.soliditylang.org/en/v0.8.15/grammar.html#a4.SolidityLexer.EscapeSequence) /// as we don't care about hex or other character meanings -#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum QuoteState { /// Not currently in quoted string #[default] @@ -38,7 +38,7 @@ impl<'a> QuoteStateCharIndices<'a> { } } -impl<'a> Iterator for QuoteStateCharIndices<'a> { +impl Iterator for QuoteStateCharIndices<'_> { type Item = (QuoteState, usize, char); fn next(&mut self) -> Option { let (idx, ch) = self.iter.next()?; @@ -65,17 +65,17 @@ impl<'a> Iterator for QuoteStateCharIndices<'a> { } } -/// An iterator over the the indices of quoted string locations +/// An iterator over the indices of quoted string locations pub struct QuotedRanges<'a>(QuoteStateCharIndices<'a>); -impl<'a> QuotedRanges<'a> { +impl QuotedRanges<'_> { pub fn with_state(mut self, state: QuoteState) -> Self { self.0 = self.0.with_state(state); self } } -impl<'a> Iterator for QuotedRanges<'a> { +impl Iterator for QuotedRanges<'_> { type Item = (char, usize, usize); fn next(&mut self) -> Option { let (quote, start) = loop { @@ -100,12 +100,14 @@ impl<'a> Iterator for QuotedRanges<'a> { /// Helpers for iterating over quoted strings pub trait QuotedStringExt { - /// Get an iterator of characters, indices and their quoted string state - fn quote_state_char_indices(&self) -> QuoteStateCharIndices; - /// Get an iterator of quoted string ranges - fn quoted_ranges(&self) -> QuotedRanges { + /// Returns an iterator of characters, indices and their quoted string state. + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_>; + + /// Returns an iterator of quoted string ranges. + fn quoted_ranges(&self) -> QuotedRanges<'_> { QuotedRanges(self.quote_state_char_indices()) } + /// Check to see if a string is quoted. This will return true if the first character /// is a quote and the last character is a quote with no non-quoted sections in between. fn is_quoted(&self) -> bool { @@ -126,13 +128,13 @@ impl QuotedStringExt for T where T: AsRef, { - fn quote_state_char_indices(&self) -> QuoteStateCharIndices { + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_> { QuoteStateCharIndices::new(self.as_ref()) } } impl QuotedStringExt for str { - fn quote_state_char_indices(&self) -> QuoteStateCharIndices { + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_> { QuoteStateCharIndices::new(self) } } @@ -140,7 +142,7 @@ impl QuotedStringExt for str { #[cfg(test)] mod tests { use super::*; - use pretty_assertions::assert_eq; + use similar_asserts::assert_eq; #[test] fn quote_state_char_indices() { diff --git a/crates/fmt/src/visit.rs b/crates/fmt/src/visit.rs index 6a80c8d12ea4e..3fff8893d232c 100644 --- a/crates/fmt/src/visit.rs +++ b/crates/fmt/src/visit.rs @@ -5,7 +5,7 @@ use crate::solang_ext::pt::*; /// A trait that is invoked while traversing the Solidity Parse Tree. /// Each method of the [Visitor] trait is a hook that can be potentially overridden. /// -/// Currently the main implementor of this trait is the [`Formatter`](crate::Formatter) struct. +/// Currently the main implementer of this trait is the [`Formatter`](crate::Formatter<'_>) struct. pub trait Visitor { type Error: std::error::Error; @@ -37,7 +37,7 @@ pub trait Visitor { fn visit_import_plain( &mut self, _loc: Loc, - _import: &mut StringLiteral, + _import: &mut ImportPath, ) -> Result<(), Self::Error> { Ok(()) } @@ -45,7 +45,7 @@ pub trait Visitor { fn visit_import_global( &mut self, _loc: Loc, - _global: &mut StringLiteral, + _global: &mut ImportPath, _alias: &mut Identifier, ) -> Result<(), Self::Error> { Ok(()) @@ -55,7 +55,7 @@ pub trait Visitor { &mut self, _loc: Loc, _imports: &mut [(Identifier, Option)], - _from: &mut StringLiteral, + _from: &mut ImportPath, ) -> Result<(), Self::Error> { Ok(()) } @@ -383,6 +383,8 @@ pub trait Visitor { } } +/// Visitable trait for [`solang_parser::pt`] types. +/// /// All [`solang_parser::pt`] types, such as [Statement], should implement the [Visitable] trait /// that accepts a trait [Visitor] implementation, which has various callback handles for Solidity /// Parse Tree nodes. @@ -456,19 +458,19 @@ impl Visitable for SourceUnitPart { V: Visitor, { match self { - SourceUnitPart::ContractDefinition(contract) => v.visit_contract(contract), - SourceUnitPart::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), - SourceUnitPart::ImportDirective(import) => import.visit(v), - SourceUnitPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), - SourceUnitPart::StructDefinition(structure) => v.visit_struct(structure), - SourceUnitPart::EventDefinition(event) => v.visit_event(event), - SourceUnitPart::ErrorDefinition(error) => v.visit_error(error), - SourceUnitPart::FunctionDefinition(function) => v.visit_function(function), - SourceUnitPart::VariableDefinition(variable) => v.visit_var_definition(variable), - SourceUnitPart::TypeDefinition(def) => v.visit_type_definition(def), - SourceUnitPart::StraySemicolon(_) => v.visit_stray_semicolon(), - SourceUnitPart::Using(using) => v.visit_using(using), - SourceUnitPart::Annotation(annotation) => v.visit_annotation(annotation), + Self::ContractDefinition(contract) => v.visit_contract(contract), + Self::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), + Self::ImportDirective(import) => import.visit(v), + Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), + Self::StructDefinition(structure) => v.visit_struct(structure), + Self::EventDefinition(event) => v.visit_event(event), + Self::ErrorDefinition(error) => v.visit_error(error), + Self::FunctionDefinition(function) => v.visit_function(function), + Self::VariableDefinition(variable) => v.visit_var_definition(variable), + Self::TypeDefinition(def) => v.visit_type_definition(def), + Self::StraySemicolon(_) => v.visit_stray_semicolon(), + Self::Using(using) => v.visit_using(using), + Self::Annotation(annotation) => v.visit_annotation(annotation), } } } @@ -479,11 +481,11 @@ impl Visitable for Import { V: Visitor, { match self { - Import::Plain(import, loc) => v.visit_import_plain(*loc, import), - Import::GlobalSymbol(global, import_as, loc) => { + Self::Plain(import, loc) => v.visit_import_plain(*loc, import), + Self::GlobalSymbol(global, import_as, loc) => { v.visit_import_global(*loc, global, import_as) } - Import::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), + Self::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), } } } @@ -494,16 +496,16 @@ impl Visitable for ContractPart { V: Visitor, { match self { - ContractPart::StructDefinition(structure) => v.visit_struct(structure), - ContractPart::EventDefinition(event) => v.visit_event(event), - ContractPart::ErrorDefinition(error) => v.visit_error(error), - ContractPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), - ContractPart::VariableDefinition(variable) => v.visit_var_definition(variable), - ContractPart::FunctionDefinition(function) => v.visit_function(function), - ContractPart::TypeDefinition(def) => v.visit_type_definition(def), - ContractPart::StraySemicolon(_) => v.visit_stray_semicolon(), - ContractPart::Using(using) => v.visit_using(using), - ContractPart::Annotation(annotation) => v.visit_annotation(annotation), + Self::StructDefinition(structure) => v.visit_struct(structure), + Self::EventDefinition(event) => v.visit_event(event), + Self::ErrorDefinition(error) => v.visit_error(error), + Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), + Self::VariableDefinition(variable) => v.visit_var_definition(variable), + Self::FunctionDefinition(function) => v.visit_function(function), + Self::TypeDefinition(def) => v.visit_type_definition(def), + Self::StraySemicolon(_) => v.visit_stray_semicolon(), + Self::Using(using) => v.visit_using(using), + Self::Annotation(annotation) => v.visit_annotation(annotation), } } } @@ -514,40 +516,34 @@ impl Visitable for Statement { V: Visitor, { match self { - Statement::Block { loc, unchecked, statements } => { + Self::Block { loc, unchecked, statements } => { v.visit_block(*loc, *unchecked, statements) } - Statement::Assembly { loc, dialect, block, flags } => { + Self::Assembly { loc, dialect, block, flags } => { v.visit_assembly(*loc, dialect, block, flags) } - Statement::Args(loc, args) => v.visit_args(*loc, args), - Statement::If(loc, cond, if_branch, else_branch) => { + Self::Args(loc, args) => v.visit_args(*loc, args), + Self::If(loc, cond, if_branch, else_branch) => { v.visit_if(*loc, cond, if_branch, else_branch, true) } - Statement::While(loc, cond, body) => v.visit_while(*loc, cond, body), - Statement::Expression(loc, expr) => { + Self::While(loc, cond, body) => v.visit_while(*loc, cond, body), + Self::Expression(loc, expr) => { v.visit_expr(*loc, expr)?; v.visit_stray_semicolon() } - Statement::VariableDefinition(loc, declaration, expr) => { + Self::VariableDefinition(loc, declaration, expr) => { v.visit_var_definition_stmt(*loc, declaration, expr) } - Statement::For(loc, init, cond, update, body) => { - v.visit_for(*loc, init, cond, update, body) - } - Statement::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), - Statement::Continue(loc) => v.visit_continue(*loc, true), - Statement::Break(loc) => v.visit_break(*loc, true), - Statement::Return(loc, expr) => v.visit_return(*loc, expr), - Statement::Revert(loc, error, args) => v.visit_revert(*loc, error, args), - Statement::RevertNamedArgs(loc, error, args) => { - v.visit_revert_named_args(*loc, error, args) - } - Statement::Emit(loc, event) => v.visit_emit(*loc, event), - Statement::Try(loc, expr, returns, clauses) => { - v.visit_try(*loc, expr, returns, clauses) - } - Statement::Error(loc) => v.visit_parser_error(*loc), + Self::For(loc, init, cond, update, body) => v.visit_for(*loc, init, cond, update, body), + Self::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), + Self::Continue(loc) => v.visit_continue(*loc, true), + Self::Break(loc) => v.visit_break(*loc, true), + Self::Return(loc, expr) => v.visit_return(*loc, expr), + Self::Revert(loc, error, args) => v.visit_revert(*loc, error, args), + Self::RevertNamedArgs(loc, error, args) => v.visit_revert_named_args(*loc, error, args), + Self::Emit(loc, event) => v.visit_emit(*loc, event), + Self::Try(loc, expr, returns, clauses) => v.visit_try(*loc, expr, returns, clauses), + Self::Error(loc) => v.visit_parser_error(*loc), } } } @@ -603,24 +599,20 @@ impl Visitable for YulStatement { V: Visitor, { match self { - YulStatement::Assign(loc, exprs, expr) => { - v.visit_yul_assignment(*loc, exprs, &mut Some(expr)) - } - YulStatement::Block(block) => { - v.visit_yul_block(block.loc, block.statements.as_mut(), false) - } - YulStatement::Break(loc) => v.visit_break(*loc, false), - YulStatement::Continue(loc) => v.visit_continue(*loc, false), - YulStatement::For(stmt) => v.visit_yul_for(stmt), - YulStatement::FunctionCall(stmt) => v.visit_yul_function_call(stmt), - YulStatement::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), - YulStatement::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), - YulStatement::Leave(loc) => v.visit_yul_leave(*loc), - YulStatement::Switch(stmt) => v.visit_yul_switch(stmt), - YulStatement::VariableDeclaration(loc, idents, expr) => { + Self::Assign(loc, exprs, expr) => v.visit_yul_assignment(*loc, exprs, &mut Some(expr)), + Self::Block(block) => v.visit_yul_block(block.loc, block.statements.as_mut(), false), + Self::Break(loc) => v.visit_break(*loc, false), + Self::Continue(loc) => v.visit_continue(*loc, false), + Self::For(stmt) => v.visit_yul_for(stmt), + Self::FunctionCall(stmt) => v.visit_yul_function_call(stmt), + Self::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), + Self::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), + Self::Leave(loc) => v.visit_yul_leave(*loc), + Self::Switch(stmt) => v.visit_yul_switch(stmt), + Self::VariableDeclaration(loc, idents, expr) => { v.visit_yul_var_declaration(*loc, idents, expr) } - YulStatement::Error(loc) => v.visit_parser_error(*loc), + Self::Error(loc) => v.visit_parser_error(*loc), } } } diff --git a/crates/fmt/testdata/ArrayExpressions/fmt.sol b/crates/fmt/testdata/ArrayExpressions/fmt.sol index 0476da9e1f020..adda7a30e098d 100644 --- a/crates/fmt/testdata/ArrayExpressions/fmt.sol +++ b/crates/fmt/testdata/ArrayExpressions/fmt.sol @@ -4,7 +4,7 @@ contract ArrayExpressions { uint256[10] memory sample; uint256 length = 10; - uint256[] memory sample2 = new uint[](length); + uint256[] memory sample2 = new uint256[](length); uint256[] /* comment1 */ memory /* comment2 */ sample3; // comment3 diff --git a/crates/fmt/testdata/ArrayExpressions/original.sol b/crates/fmt/testdata/ArrayExpressions/original.sol index 7f26bd5f7aaa3..919cd241fdaed 100644 --- a/crates/fmt/testdata/ArrayExpressions/original.sol +++ b/crates/fmt/testdata/ArrayExpressions/original.sol @@ -4,7 +4,8 @@ contract ArrayExpressions { uint[10] memory sample; uint256 length = 10; - uint[] memory sample2 = new uint[](length); + uint[] memory sample2 = new uint[]( + length); uint /* comment1 */ [] memory /* comment2 */ sample3 // comment3 ; diff --git a/crates/fmt/testdata/BlockComments/fmt.sol b/crates/fmt/testdata/BlockComments/fmt.sol new file mode 100644 index 0000000000000..1d7025f2ad591 --- /dev/null +++ b/crates/fmt/testdata/BlockComments/fmt.sol @@ -0,0 +1,25 @@ +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } +} diff --git a/crates/fmt/testdata/BlockComments/original.sol b/crates/fmt/testdata/BlockComments/original.sol new file mode 100644 index 0000000000000..b91934bf7d12b --- /dev/null +++ b/crates/fmt/testdata/BlockComments/original.sol @@ -0,0 +1,26 @@ +contract CounterTest is Test { + /** + * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. + */ + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev See {IERC721-balanceOf}. + */ + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + /** + * @dev See {IERC165-supportsInterface}. + */ function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); +} + +} \ No newline at end of file diff --git a/crates/fmt/testdata/BlockCommentsFunction/fmt.sol b/crates/fmt/testdata/BlockCommentsFunction/fmt.sol new file mode 100644 index 0000000000000..368749bf4fdaf --- /dev/null +++ b/crates/fmt/testdata/BlockCommentsFunction/fmt.sol @@ -0,0 +1,20 @@ +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + * function testGetFuzz(bytes[2][] memory kvs) public { + * for (uint256 i = 0; i < kvs.length; i++) { + * bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + * console.logBytes32(root); + * } + * + * for (uint256 i = 0; i < kvs.length; i++) { + * (bool exist, bytes memory value) = trie.get(kvs[i][0]); + * console.logBool(exist); + * console.logBytes(value); + * require(exist); + * require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + * } + * } + */ +} diff --git a/crates/fmt/testdata/BlockCommentsFunction/original.sol b/crates/fmt/testdata/BlockCommentsFunction/original.sol new file mode 100644 index 0000000000000..089f1bac430cd --- /dev/null +++ b/crates/fmt/testdata/BlockCommentsFunction/original.sol @@ -0,0 +1,20 @@ +contract A { + Counter public counter; + /** + * TODO: this fuzz use too much time to execute + function testGetFuzz(bytes[2][] memory kvs) public { + for (uint256 i = 0; i < kvs.length; i++) { + bytes32 root = trie.update(kvs[i][0], kvs[i][1]); + console.logBytes32(root); + } + + for (uint256 i = 0; i < kvs.length; i++) { + (bool exist, bytes memory value) = trie.get(kvs[i][0]); + console.logBool(exist); + console.logBytes(value); + require(exist); + require(BytesSlice.equal(value, trie.getRaw(kvs[i][0]))); + } + } + */ +} \ No newline at end of file diff --git a/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol b/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol new file mode 100644 index 0000000000000..88694860aded2 --- /dev/null +++ b/crates/fmt/testdata/ConstructorModifierStyle/fmt.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC1155} from "solmate/tokens/ERC1155.sol"; + +import {IAchievements} from "./interfaces/IAchievements.sol"; +import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; + +contract Achievements is IAchievements, SoulBound1155, Ownable { + constructor(address owner) Ownable() ERC1155() {} +} diff --git a/crates/fmt/testdata/ConstructorModifierStyle/original.sol b/crates/fmt/testdata/ConstructorModifierStyle/original.sol new file mode 100644 index 0000000000000..88694860aded2 --- /dev/null +++ b/crates/fmt/testdata/ConstructorModifierStyle/original.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.5.2; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC1155} from "solmate/tokens/ERC1155.sol"; + +import {IAchievements} from "./interfaces/IAchievements.sol"; +import {SoulBound1155} from "./abstracts/SoulBound1155.sol"; + +contract Achievements is IAchievements, SoulBound1155, Ownable { + constructor(address owner) Ownable() ERC1155() {} +} diff --git a/crates/fmt/testdata/DocComments/fmt.sol b/crates/fmt/testdata/DocComments/fmt.sol index a002e6c2364f4..4248f0fe587da 100644 --- a/crates/fmt/testdata/DocComments/fmt.sol +++ b/crates/fmt/testdata/DocComments/fmt.sol @@ -49,7 +49,7 @@ contract HelloWorld { /// A long doc line comment that will be wrapped function docLineOverflow() external {} - function docLinePostfixOveflow() external {} + function docLinePostfixOverflow() external {} /// A long doc line comment that will be wrapped @@ -87,7 +87,7 @@ contract HelloWorld { * } * } */ - function malformedIndentOveflow() external {} + function malformedIndentOverflow() external {} } /** diff --git a/crates/fmt/testdata/DocComments/original.sol b/crates/fmt/testdata/DocComments/original.sol index dff602236c248..28f654b57903d 100644 --- a/crates/fmt/testdata/DocComments/original.sol +++ b/crates/fmt/testdata/DocComments/original.sol @@ -46,7 +46,7 @@ contract HelloWorld { /// A long doc line comment that will be wrapped function docLineOverflow() external {} - function docLinePostfixOveflow() external {} /// A long doc line comment that will be wrapped + function docLinePostfixOverflow() external {} /// A long doc line comment that will be wrapped /** * @notice Here is my comment @@ -82,7 +82,7 @@ function withALongNameThatWillCauseCommentWrap() public { } } */ - function malformedIndentOveflow() external {} + function malformedIndentOverflow() external {} } /** diff --git a/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol b/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol index 6d7f3c584ce44..c3c7fe00c9180 100644 --- a/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol +++ b/crates/fmt/testdata/DocComments/wrap-comments.fmt.sol @@ -64,7 +64,7 @@ contract HelloWorld { external {} - function docLinePostfixOveflow() + function docLinePostfixOverflow() external {} @@ -113,7 +113,7 @@ contract HelloWorld { * } * } */ - function malformedIndentOveflow() + function malformedIndentOverflow() external {} } diff --git a/crates/fmt/testdata/EnumVariants/fmt.sol b/crates/fmt/testdata/EnumVariants/fmt.sol new file mode 100644 index 0000000000000..b33b8846984d2 --- /dev/null +++ b/crates/fmt/testdata/EnumVariants/fmt.sol @@ -0,0 +1,19 @@ +interface I { + enum Empty {} + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode { + /// No caller modification is currently active. + None + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode2 { + /// No caller modification is currently active. + None, + /// No caller modification is currently active2. + Some + } + + function bar() public {} +} diff --git a/crates/fmt/testdata/EnumVariants/original.sol b/crates/fmt/testdata/EnumVariants/original.sol new file mode 100644 index 0000000000000..8e146ae0fb574 --- /dev/null +++ b/crates/fmt/testdata/EnumVariants/original.sol @@ -0,0 +1,23 @@ +interface I { + enum Empty { + + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode + {/// No caller modification is currently active. + None + } + + /// A modification applied to either `msg.sender` or `tx.origin`. Returned by `readCallers`. + enum CallerMode2 + {/// No caller modification is currently active. + None,/// No caller modification is currently active2. + + Some + } + + function bar() public { + + } +} \ No newline at end of file diff --git a/crates/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol b/crates/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol index 84a4ce8b1e376..2bfe9798c5294 100644 --- a/crates/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol +++ b/crates/fmt/testdata/FunctionCall/bracket-spacing.fmt.sol @@ -30,8 +30,13 @@ contract FunctionCall { function a(uint256 foo) { foo; + MyContract c = new MyContract(address(0), hex"beef"); } function b() { a({ foo: 5 }); } + +contract MyContract { + constructor(address arg, bytes memory data) { } +} diff --git a/crates/fmt/testdata/FunctionCall/fmt.sol b/crates/fmt/testdata/FunctionCall/fmt.sol index c57c5fda030aa..ff22f63aaa5ce 100644 --- a/crates/fmt/testdata/FunctionCall/fmt.sol +++ b/crates/fmt/testdata/FunctionCall/fmt.sol @@ -29,8 +29,13 @@ contract FunctionCall { function a(uint256 foo) { foo; + MyContract c = new MyContract(address(0), hex"beef"); } function b() { a({foo: 5}); } + +contract MyContract { + constructor(address arg, bytes memory data) {} +} diff --git a/crates/fmt/testdata/FunctionCall/original.sol b/crates/fmt/testdata/FunctionCall/original.sol index 8bdc92e464169..ea03850554e25 100644 --- a/crates/fmt/testdata/FunctionCall/original.sol +++ b/crates/fmt/testdata/FunctionCall/original.sol @@ -22,8 +22,13 @@ contract FunctionCall { function a(uint256 foo) { foo; + MyContract c = new MyContract(address( 0),hex"beef"); } function b() { a( {foo: 5} ); } + +contract MyContract { + constructor(address arg, bytes memory data) {} +} diff --git a/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol b/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol new file mode 100644 index 0000000000000..db7164d284a54 --- /dev/null +++ b/crates/fmt/testdata/FunctionDefinition/all-params.fmt.sol @@ -0,0 +1,732 @@ +// config: line_length = 60 +// config: multiline_func_header = "all_params" +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint256 x); + + function oneModifier() modifier1; + + function oneReturn() returns (uint256 y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + ) + // public prefix + public // public postfix + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 + ); // y3 postfix + // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ); + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3; + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns (uint256 y1, uint256 y2, uint256 y3); + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3; + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns (uint256 y1, uint256 y2, uint256 y3); + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string); + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address); + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256); +} + +contract FunctionDefinitions { + function() external {} + fallback() external {} + + function() external payable {} + fallback() external payable {} + receive() external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint256 x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns (uint256 y1) { + a = 1; + } + + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) { + a = 1; + } + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + { + a = 1; + } + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + { + a = 1; + } + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + public + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string) + { + a = 1; + } + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address) + { + a = 1; + } + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is + FunctionInterfaces, + FunctionDefinitions +{ + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam( + uint256 x + ) + override( + FunctionInterfaces, + FunctionDefinitions, + SomeOtherFunctionContract, + SomeImport.AndAnotherFunctionContract + ) + { + a = 1; + } +} diff --git a/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol b/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol index 50f539c329cae..3e7ebfff6b3aa 100644 --- a/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol +++ b/crates/fmt/testdata/FunctionDefinition/params-first.fmt.sol @@ -3,7 +3,9 @@ interface FunctionInterfaces { function noParamsNoModifiersNoReturns(); - function oneParam(uint256 x); + function oneParam( + uint256 x + ); function oneModifier() modifier1; @@ -335,7 +337,9 @@ contract FunctionDefinitions { a = 1; } - function oneParam(uint256 x) { + function oneParam( + uint256 x + ) { a = 1; } @@ -697,7 +701,9 @@ contract FunctionOverrides is a = 1; } - function oneParam(uint256 x) + function oneParam( + uint256 x + ) override( FunctionInterfaces, FunctionDefinitions, diff --git a/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol b/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol new file mode 100644 index 0000000000000..cd2015c9e050e --- /dev/null +++ b/crates/fmt/testdata/FunctionDefinition/params-multi.fmt.sol @@ -0,0 +1,710 @@ +// config: line_length = 60 +// config: multiline_func_header = "params_first_multi" +interface FunctionInterfaces { + function noParamsNoModifiersNoReturns(); + + function oneParam(uint256 x); + + function oneModifier() modifier1; + + function oneReturn() returns (uint256 y1); + + // function prefix + function withComments( // function name postfix + // x1 prefix + uint256 x1, // x1 postfix + // x2 prefix + uint256 x2, // x2 postfix + // x2 postfix2 + /* + multi-line x3 prefix + */ + uint256 x3 // x3 postfix + ) + // public prefix + public // public postfix + // pure prefix + pure // pure postfix + // modifier1 prefix + modifier1 // modifier1 postfix + // modifier2 prefix + modifier2 /* + mutliline modifier2 postfix + */ + // modifier3 prefix + modifier3 // modifier3 postfix + returns ( + // y1 prefix + uint256 y1, // y1 postfix + // y2 prefix + uint256 y2, // y2 postfix + // y3 prefix + uint256 y3 + ); // y3 postfix + // function postfix + + /*////////////////////////////////////////////////////////////////////////// + TEST + //////////////////////////////////////////////////////////////////////////*/ + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ); + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3; + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3); + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3; + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3); + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10; + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ); + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string); + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address); + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256); + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256); +} + +contract FunctionDefinitions { + function() external {} + fallback() external {} + + function() external payable {} + fallback() external payable {} + receive() external payable {} + + function noParamsNoModifiersNoReturns() { + a = 1; + } + + function oneParam(uint256 x) { + a = 1; + } + + function oneModifier() modifier1 { + a = 1; + } + + function oneReturn() returns (uint256 y1) { + a = 1; + } + + function manyParams( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) { + a = 1; + } + + function manyModifiers() + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyReturns() + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function someParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function someParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function someModifiersSomeReturns() + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamSomeModifiersSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + returns (uint256 y1, uint256 y2, uint256 y3) + { + a = 1; + } + + function someParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function someParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsSomeModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) modifier1 modifier2 modifier3 { + a = 1; + } + + function manyParamsSomeReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) returns (uint256 y1, uint256 y2, uint256 y3) { + a = 1; + } + + function manyParamsManyModifiers( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + public + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + { + a = 1; + } + + function manyParamsManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function manyParamsManyModifiersManyReturns( + uint256 x1, + uint256 x2, + uint256 x3, + uint256 x4, + uint256 x5, + uint256 x6, + uint256 x7, + uint256 x8, + uint256 x9, + uint256 x10 + ) + modifier1 + modifier2 + modifier3 + modifier4 + modifier5 + modifier6 + modifier7 + modifier8 + modifier9 + modifier10 + returns ( + uint256 y1, + uint256 y2, + uint256 y3, + uint256 y4, + uint256 y5, + uint256 y6, + uint256 y7, + uint256 y8, + uint256 y9, + uint256 y10 + ) + { + a = 1; + } + + function modifierOrderCorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderCorrect02() + private + pure + virtual + modifier1 + modifier2 + returns (string) + { + a = 1; + } + + function modifierOrderCorrect03() + external + payable + override + modifier1 + modifier2 + returns (address) + { + a = 1; + } + + function modifierOrderCorrect04() + internal + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect01() + public + view + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect02() + external + virtual + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect03() + internal + pure + virtual + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + function modifierOrderIncorrect04() + external + payable + override + modifier1 + modifier2 + returns (uint256) + { + a = 1; + } + + fallback() external payable virtual {} + receive() external payable virtual {} +} + +contract FunctionOverrides is + FunctionInterfaces, + FunctionDefinitions +{ + function noParamsNoModifiersNoReturns() override { + a = 1; + } + + function oneParam(uint256 x) + override( + FunctionInterfaces, + FunctionDefinitions, + SomeOtherFunctionContract, + SomeImport.AndAnotherFunctionContract + ) + { + a = 1; + } +} diff --git a/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol b/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol new file mode 100644 index 0000000000000..7b751e22ec26a --- /dev/null +++ b/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/fmt.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract ReturnFnFormat { + function returnsFunction() + internal + pure + returns ( + function() + internal pure returns (uint256) + ) + {} +} + +// https://github.com/foundry-rs/foundry/issues/7920 +contract ReturnFnDisableFormat { + // forgefmt: disable-next-line + function disableFnFormat() external returns (uint256) { + return 0; + } +} diff --git a/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/original.sol b/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/original.sol new file mode 100644 index 0000000000000..0c785cde81b14 --- /dev/null +++ b/crates/fmt/testdata/FunctionDefinitionWithFunctionReturns/original.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +contract ReturnFnFormat { + function returnsFunction() + internal + pure + returns ( + function() + internal pure returns (uint256) + ) + {} +} + +// https://github.com/foundry-rs/foundry/issues/7920 +contract ReturnFnDisableFormat { + // forgefmt: disable-next-line + function disableFnFormat() external returns (uint256) { + return 0; + } +} \ No newline at end of file diff --git a/crates/fmt/testdata/FunctionType/fmt.sol b/crates/fmt/testdata/FunctionType/fmt.sol index 67c784cac68df..39053d816058f 100644 --- a/crates/fmt/testdata/FunctionType/fmt.sol +++ b/crates/fmt/testdata/FunctionType/fmt.sol @@ -5,7 +5,7 @@ library ArrayUtils { pure returns (uint256[] memory r) { - r = new uint[](self.length); + r = new uint256[](self.length); for (uint256 i = 0; i < self.length; i++) { r[i] = f(self[i]); } @@ -23,7 +23,7 @@ library ArrayUtils { } function range(uint256 length) internal pure returns (uint256[] memory r) { - r = new uint[](length); + r = new uint256[](length); for (uint256 i = 0; i < r.length; i++) { r[i] = i; } diff --git a/crates/fmt/testdata/FunctionType/original.sol b/crates/fmt/testdata/FunctionType/original.sol index 7247a4f933cc3..27b402d85ef5f 100644 --- a/crates/fmt/testdata/FunctionType/original.sol +++ b/crates/fmt/testdata/FunctionType/original.sol @@ -6,7 +6,7 @@ library ArrayUtils { uint[] memory r ) { - r = new uint[](self.length); + r = new uint[]( self.length); for (uint i = 0; i < self.length; i++) { r[i] = f(self[i]); } @@ -23,7 +23,7 @@ library ArrayUtils { } function range(uint256 length) internal pure returns (uint[] memory r) { - r = new uint[](length); + r = new uint256[](length ); for (uint i = 0; i < r.length; i++) { r[i] = i; } diff --git a/crates/fmt/testdata/HexUnderscore/bytes.fmt.sol b/crates/fmt/testdata/HexUnderscore/bytes.fmt.sol new file mode 100644 index 0000000000000..b3be2a8657c18 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/bytes.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "bytes" +contract HexLiteral { + function test() external { + hex"01_23_00_00"; + hex"01_23_00_00"; + hex"01_23_00_00"; + hex""; + hex"60_01_60_02_53"; + } +} diff --git a/crates/fmt/testdata/HexUnderscore/fmt.sol b/crates/fmt/testdata/HexUnderscore/fmt.sol new file mode 100644 index 0000000000000..0c8710a924758 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/fmt.sol @@ -0,0 +1,9 @@ +contract HexLiteral { + function test() external { + hex"01230000"; + hex"01230000"; + hex"01230000"; + hex""; + hex"6001600253"; + } +} diff --git a/crates/fmt/testdata/HexUnderscore/original.sol b/crates/fmt/testdata/HexUnderscore/original.sol new file mode 100644 index 0000000000000..5c29187475489 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/original.sol @@ -0,0 +1,9 @@ +contract HexLiteral { + function test() external { + hex"0123_0000"; + hex"01230000"; + hex"0123_00_00"; + hex""; + hex"6001_6002_53"; + } +} \ No newline at end of file diff --git a/crates/fmt/testdata/HexUnderscore/preserve.fmt.sol b/crates/fmt/testdata/HexUnderscore/preserve.fmt.sol new file mode 100644 index 0000000000000..0f5db52e3c3f3 --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/preserve.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "preserve" +contract HexLiteral { + function test() external { + hex"0123_0000"; + hex"01230000"; + hex"0123_00_00"; + hex""; + hex"6001_6002_53"; + } +} diff --git a/crates/fmt/testdata/HexUnderscore/remove.fmt.sol b/crates/fmt/testdata/HexUnderscore/remove.fmt.sol new file mode 100644 index 0000000000000..39aae1465cf7d --- /dev/null +++ b/crates/fmt/testdata/HexUnderscore/remove.fmt.sol @@ -0,0 +1,10 @@ +// config: hex_underscore = "remove" +contract HexLiteral { + function test() external { + hex"01230000"; + hex"01230000"; + hex"01230000"; + hex""; + hex"6001600253"; + } +} diff --git a/crates/fmt/testdata/InlineDisable/fmt.sol b/crates/fmt/testdata/InlineDisable/fmt.sol index 03979bc188935..d7adea60b32da 100644 --- a/crates/fmt/testdata/InlineDisable/fmt.sol +++ b/crates/fmt/testdata/InlineDisable/fmt.sol @@ -90,8 +90,7 @@ function testFunc(uint256 num, bytes32 data , address receiver) function testAttrs(uint256 num, bytes32 data, address receiver) // forgefmt: disable-next-line - public payable attr1 Cool( "hello" ) -{} + public payable attr1 Cool( "hello" ) {} // forgefmt: disable-next-line function testParams(uint256 num, bytes32 data , address receiver) @@ -406,9 +405,9 @@ function testUnit() { value = 1 gwei; value = 1 ether; - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + uint256 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 value = 1 // comment3 // comment4 @@ -489,3 +488,20 @@ error TopLevelCustomErrorArgWithoutName (string); event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); // forgefmt: disable-stop + +function setNumber(uint256 newNumber /* param1 */, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf /* param2 */) public view returns (bool,bool) { /* inline*/ number1 = newNumber1; // forgefmt: disable-line + number = newNumber; + return (true, true); +} + +function setNumber1(uint256 newNumber /* param1 */, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf /* param2 */) public view returns (bool,bool) { /* inline*/ number1 = newNumber1; // forgefmt: disable-line +} + +// forgefmt: disable-next-line +function setNumber1(uint256 newNumber , uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf) public view returns (bool,bool) { number1 = newNumber1; +} + +function setNumber(uint256 newNumber, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf) public { // forgefmt: disable-line + number = newNumber; + number1 = newNumber1; // forgefmt: disable-line +} diff --git a/crates/fmt/testdata/InlineDisable/original.sol b/crates/fmt/testdata/InlineDisable/original.sol index 617da7719c2c6..7731678940cbf 100644 --- a/crates/fmt/testdata/InlineDisable/original.sol +++ b/crates/fmt/testdata/InlineDisable/original.sol @@ -387,9 +387,9 @@ function testUnit() { value = 1 gwei; value = 1 ether; - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + uint256 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 value = 1 // comment3 // comment4 @@ -467,3 +467,20 @@ error TopLevelCustomErrorArgWithoutName (string); event Event1(uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a, uint256 indexed a); // forgefmt: disable-stop + +function setNumber(uint256 newNumber /* param1 */, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf /* param2 */) public view returns (bool,bool) { /* inline*/ number1 = newNumber1; // forgefmt: disable-line + number = newNumber; + return (true, true); +} + +function setNumber1(uint256 newNumber /* param1 */, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf /* param2 */) public view returns (bool,bool) { /* inline*/ number1 = newNumber1; // forgefmt: disable-line +} + +// forgefmt: disable-next-line +function setNumber1(uint256 newNumber , uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf) public view returns (bool,bool) { number1 = newNumber1; +} + +function setNumber(uint256 newNumber, uint256 sjdfasdfasdfasdfasfsdfsadfasdfasdfasdfsadjfkhasdfljkahsdfkjasdkfhsaf) public { // forgefmt: disable-line + number = newNumber; + number1 = newNumber1; // forgefmt: disable-line +} diff --git a/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol b/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol new file mode 100644 index 0000000000000..d87dc99d9653d --- /dev/null +++ b/crates/fmt/testdata/NumberLiteralUnderscore/preserve.fmt.sol @@ -0,0 +1,26 @@ +// config: number_underscore = "preserve" +contract NumberLiteral { + function test() external { + 1; + 123_000; + 1_2e345_678; + -1; + 2e-10; + 0.1; + 1.3; + 2.5e1; + 1.23454; + 1.2e34_5_678; + 134411.2e34_5_678; + 13431.134112e34_135_678; + 13431.0134112; + 13431.134112e-139_3141340; + 134411.2e34_5_6780; + 13431.134112e34_135_6780; + 0.134112; + 1.0; + 13431.134112e-139_3141340; + 123e456; + 1_000; + } +} diff --git a/crates/fmt/testdata/Repros/fmt.sol b/crates/fmt/testdata/Repros/fmt.sol index 8439563ab4e76..0a480c0b02bdf 100644 --- a/crates/fmt/testdata/Repros/fmt.sol +++ b/crates/fmt/testdata/Repros/fmt.sol @@ -5,3 +5,157 @@ function errorIdentifier() { bytes memory error = bytes(""); if (error.length > 0) {} } + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} + +// https://github.com/foundry-rs/foundry/issues/3979 +contract Format { + bool public test; + + function testing(uint256 amount) public payable { + if ( + // This is a comment + msg.value == amount + ) { + test = true; + } else { + test = false; + } + + if ( + // Another one + block.timestamp >= amount + ) {} + } +} + +// https://github.com/foundry-rs/foundry/issues/3830 +contract TestContract { + function test(uint256 a) public { + if (a > 1) { + a = 2; + } // forgefmt: disable-line + } + + function test1() public { + assembly{ sstore( 1, 1) /* inline comment*/ // forgefmt: disable-line + sstore(2, 2) + } + } + + function test2() public { + assembly{ sstore( 1, 1) // forgefmt: disable-line + sstore(2, 2) + sstore(3, 3)// forgefmt: disable-line + sstore(4, 4) + } + } + + function test3() public { + // forgefmt: disable-next-line + assembly{ sstore( 1, 1) + sstore(2, 2) + sstore(3, 3)// forgefmt: disable-line + sstore(4, 4) + }// forgefmt: disable-line + } + + function test4() public { + // forgefmt: disable-next-line + assembly{ + sstore(1, 1) + sstore(2, 2) + sstore(3, 3)// forgefmt: disable-line + sstore(4, 4) + }// forgefmt: disable-line + if (condition) execute(); // comment7 + } + + function test5() public { + assembly { sstore(0, 0) }// forgefmt: disable-line + } + + function test6() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + } + return true ; } // forgefmt: disable-line + + function test7() returns (bool) { // forgefmt: disable-line + if (true) { // forgefmt: disable-line + uint256 a = 1; // forgefmt: disable-line + } + return true; + } + + function test8() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + uint256 a = 1; + } else { + uint256 b = 1; // forgefmt: disable-line + } + return true; + } +} + +// https://github.com/foundry-rs/foundry/issues/5825 +library MyLib { + bytes32 private constant TYPE_HASH = keccak256( + // forgefmt: disable-start + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + // forgefmt: disable-end + ); + + bytes32 private constant TYPE_HASH_1 = keccak256( + "MyStruct(" "uint8 myEnum," "address myAddress" ")" // forgefmt: disable-line + ); + + // forgefmt: disable-start + bytes32 private constant TYPE_HASH_2 = keccak256( + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + ); + // forgefmt: disable-end +} + +contract IfElseTest { + function setNumber(uint256 newNumber) public { + number = newNumber; + if (newNumber = 1) { + number = 1; + } else if (newNumber = 2) { + // number = 2; + } else { + newNumber = 3; + } + } +} + +contract DbgFmtTest is Test { + function test_argsList() public { + uint256 result1 = internalNoArgs({}); + result2 = add({a: 1, b: 2}); + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function internalNoArgs() internal pure returns (uint256) { + return 0; + } +} diff --git a/crates/fmt/testdata/Repros/original.sol b/crates/fmt/testdata/Repros/original.sol index 8439563ab4e76..6f18784d39dea 100644 --- a/crates/fmt/testdata/Repros/original.sol +++ b/crates/fmt/testdata/Repros/original.sol @@ -5,3 +5,156 @@ function errorIdentifier() { bytes memory error = bytes(""); if (error.length > 0) {} } + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} + +// https://github.com/foundry-rs/foundry/issues/3979 +contract Format { + bool public test; + + function testing(uint256 amount) public payable { + if ( + // This is a comment + msg.value == amount + ) { + test = true; + } else { + test = false; + } + + if ( + // Another one + block.timestamp >= amount + ) {} + } +} + +// https://github.com/foundry-rs/foundry/issues/3830 +contract TestContract { + function test(uint256 a) public { + if (a > 1) { + a = 2; + } // forgefmt: disable-line + } + + function test1() public { + assembly { sstore( 1, 1) /* inline comment*/ // forgefmt: disable-line + sstore(2, 2) + } + } + + function test2() public { + assembly { sstore( 1, 1) // forgefmt: disable-line + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + } + } + + function test3() public { + // forgefmt: disable-next-line + assembly{ sstore( 1, 1) + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + }// forgefmt: disable-line + } + + function test4() public { + // forgefmt: disable-next-line + assembly { + sstore( 1, 1) + sstore(2, 2) + sstore(3, 3) // forgefmt: disable-line + sstore(4, 4) + }// forgefmt: disable-line + if (condition) execute(); // comment7 + } + + function test5() public { + assembly { sstore(0, 0) }// forgefmt: disable-line + } + + function test6() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + } + return true ; } // forgefmt: disable-line + + function test7() returns (bool) { // forgefmt: disable-line + if (true) { // forgefmt: disable-line + uint256 a = 1; // forgefmt: disable-line + } + return true ; } + + function test8() returns (bool) { // forgefmt: disable-line + if ( true ) { // forgefmt: disable-line + uint256 a = 1; + } else { + uint256 b = 1; // forgefmt: disable-line + } + return true ; } +} + +// https://github.com/foundry-rs/foundry/issues/5825 +library MyLib { + bytes32 private constant TYPE_HASH = keccak256( + // forgefmt: disable-start + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + // forgefmt: disable-end + ); + + bytes32 private constant TYPE_HASH_1 = keccak256( + "MyStruct(" "uint8 myEnum," "address myAddress" ")" // forgefmt: disable-line + ); + + // forgefmt: disable-start + bytes32 private constant TYPE_HASH_2 = keccak256( + "MyStruct(" + "uint8 myEnum," + "address myAddress" + ")" + ); + // forgefmt: disable-end +} + +contract IfElseTest { + function setNumber(uint256 newNumber) public { + number = newNumber; + if (newNumber = 1) { + number = 1; + } else if (newNumber = 2) { + // number = 2; + } + else { + newNumber = 3; + } + } +} + +contract DbgFmtTest is Test { + function test_argsList() public { + uint256 result1 = internalNoArgs({}); + result2 = add({a: 1, b: 2}); + } + + function add(uint256 a, uint256 b) internal pure returns (uint256) { + return a + b; + } + + function internalNoArgs() internal pure returns (uint256) { + return 0; + } +} diff --git a/crates/fmt/testdata/SortedImports/fmt.sol b/crates/fmt/testdata/SortedImports/fmt.sol new file mode 100644 index 0000000000000..f9b2c0ee2a9c3 --- /dev/null +++ b/crates/fmt/testdata/SortedImports/fmt.sol @@ -0,0 +1,34 @@ +// config: sort_imports = true +import "SomeFile0.sol" as SomeOtherFile; +import "SomeFile1.sol" as SomeOtherFile; +import "SomeFile2.sol"; +import "SomeFile3.sol"; + +import "AnotherFile1.sol" as SomeSymbol; +import "AnotherFile2.sol" as SomeSymbol; + +import { + symbol1 as alias3, + symbol2 as alias2, + symbol3 as alias1, + symbol4 +} from "File0.sol"; +import {symbol1 as alias, symbol2} from "File2.sol"; +import {symbol1 as alias, symbol2} from "File3.sol"; +import { + symbol1 as alias1, + symbol2 as alias2, + symbol3 as alias3, + symbol4 +} from "File6.sol"; + +uint256 constant someConstant = 10; + +import {Something2, Something3} from "someFile.sol"; + +// This is a comment +import {Something2, Something3} from "someFile.sol"; + +import {symbol1 as alias, symbol2} from "File3.sol"; +// comment inside group is treated as a separator for now +import {symbol1 as alias, symbol2} from "File2.sol"; diff --git a/crates/fmt/testdata/SortedImports/original.sol b/crates/fmt/testdata/SortedImports/original.sol new file mode 100644 index 0000000000000..54b3ca3b59cfb --- /dev/null +++ b/crates/fmt/testdata/SortedImports/original.sol @@ -0,0 +1,23 @@ +import "SomeFile3.sol"; +import "SomeFile2.sol"; +import "SomeFile1.sol" as SomeOtherFile; +import "SomeFile0.sol" as SomeOtherFile; + +import "AnotherFile2.sol" as SomeSymbol; +import "AnotherFile1.sol" as SomeSymbol; + +import {symbol2, symbol1 as alias} from "File3.sol"; +import {symbol2, symbol1 as alias} from "File2.sol"; +import {symbol2 as alias2, symbol1 as alias1, symbol3 as alias3, symbol4} from "File6.sol"; +import {symbol3 as alias1, symbol2 as alias2, symbol1 as alias3, symbol4} from "File0.sol"; + +uint256 constant someConstant = 10; + +import {Something3, Something2} from "someFile.sol"; + +// This is a comment +import {Something3, Something2} from "someFile.sol"; + +import {symbol2, symbol1 as alias} from "File3.sol"; +// comment inside group is treated as a separator for now +import {symbol2, symbol1 as alias} from "File2.sol"; \ No newline at end of file diff --git a/crates/fmt/testdata/UnitExpression/fmt.sol b/crates/fmt/testdata/UnitExpression/fmt.sol index 2d616ee6e07a0..ceb16c86c7813 100644 --- a/crates/fmt/testdata/UnitExpression/fmt.sol +++ b/crates/fmt/testdata/UnitExpression/fmt.sol @@ -12,9 +12,9 @@ contract UnitExpression { value = 1 gwei; value = 1 ether; - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + uint256 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 value = 1 // comment3 diff --git a/crates/fmt/testdata/UnitExpression/original.sol b/crates/fmt/testdata/UnitExpression/original.sol index b48a1d49fc6a5..f85af34fe7770 100644 --- a/crates/fmt/testdata/UnitExpression/original.sol +++ b/crates/fmt/testdata/UnitExpression/original.sol @@ -12,9 +12,9 @@ contract UnitExpression { value = 1 gwei; value = 1 ether; - uint256 someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue; + uint256 someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue; - value = someVeryVeryVeryLongVaribleNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 + value = someVeryVeryVeryLongVariableNameForTheMultiplierForEtherValue * 1 /* comment1 */ ether; // comment2 value = 1 // comment3 // comment4 diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index e736edebab87b..ba1d9216d92da 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -1,4 +1,4 @@ -use forge_fmt::{format, parse, solang_ext::AstEq, FormatterConfig}; +use forge_fmt::{format_to, parse, solang_ext::AstEq, FormatterConfig}; use itertools::Itertools; use std::{fs, path::PathBuf}; use tracing_subscriber::{EnvFilter, FmtSubscriber}; @@ -11,7 +11,7 @@ fn tracing() { let _ = tracing::subscriber::set_global_default(subscriber); } -fn test_directory(base_name: &str) { +fn test_directory(base_name: &str, test_config: TestConfig) { tracing(); let mut original = None; @@ -36,7 +36,7 @@ fn test_directory(base_name: &str) { let default_config = FormatterConfig { line_length: 80, ..Default::default() }; - let mut config = toml::Value::try_from(&default_config).unwrap(); + let mut config = toml::Value::try_from(default_config).unwrap(); let config_table = config.as_table_mut().unwrap(); let mut lines = source.split('\n').peekable(); let mut line_num = 1; @@ -74,6 +74,7 @@ fn test_directory(base_name: &str) { config, original.as_ref().expect("original.sol not found"), &formatted, + test_config, ); } } @@ -82,18 +83,24 @@ fn assert_eof(content: &str) { assert!(content.ends_with('\n') && !content.ends_with("\n\n")); } -fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expected_source: &str) { +fn test_formatter( + filename: &str, + config: FormatterConfig, + source: &str, + expected_source: &str, + test_config: TestConfig, +) { #[derive(Eq)] struct PrettyString(String); impl PartialEq for PrettyString { - fn eq(&self, other: &PrettyString) -> bool { + fn eq(&self, other: &Self) -> bool { self.0.lines().eq(other.0.lines()) } } impl std::fmt::Debug for PrettyString { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } @@ -103,8 +110,8 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte let source_parsed = parse(source).unwrap(); let expected_parsed = parse(expected_source).unwrap(); - if !source_parsed.pt.ast_eq(&expected_parsed.pt) { - pretty_assertions::assert_eq!( + if !test_config.skip_compare_ast_eq && !source_parsed.pt.ast_eq(&expected_parsed.pt) { + similar_asserts::assert_eq!( source_parsed.pt, expected_parsed.pt, "(formatted Parse Tree == expected Parse Tree) in {}", @@ -115,13 +122,12 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte let expected = PrettyString(expected_source.to_string()); let mut source_formatted = String::new(); - format(&mut source_formatted, source_parsed, config.clone()).unwrap(); + format_to(&mut source_formatted, source_parsed, config.clone()).unwrap(); assert_eof(&source_formatted); - // println!("{}", source_formatted); let source_formatted = PrettyString(source_formatted); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( source_formatted, expected, "(formatted == expected) in {}", @@ -129,12 +135,12 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte ); let mut expected_formatted = String::new(); - format(&mut expected_formatted, expected_parsed, config).unwrap(); + format_to(&mut expected_formatted, expected_parsed, config).unwrap(); assert_eof(&expected_formatted); let expected_formatted = PrettyString(expected_formatted); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( expected_formatted, expected, "(formatted == expected) in {}", @@ -142,24 +148,47 @@ fn test_formatter(filename: &str, config: FormatterConfig, source: &str, expecte ); } -macro_rules! test_directories { - ($($dir:ident),+ $(,)?) => {$( +#[derive(Clone, Copy, Default)] +struct TestConfig { + /// Whether to compare the formatted source code AST with the original AST + skip_compare_ast_eq: bool, +} + +impl TestConfig { + fn skip_compare_ast_eq() -> Self { + Self { skip_compare_ast_eq: true } + } +} + +macro_rules! test_dir { + ($dir:ident $(,)?) => { + test_dir!($dir, Default::default()); + }; + ($dir:ident, $config:expr $(,)?) => { #[allow(non_snake_case)] #[test] fn $dir() { - test_directory(stringify!($dir)); + test_directory(stringify!($dir), $config); } + }; +} + +macro_rules! test_directories { + ($($dir:ident),+ $(,)?) => {$( + test_dir!($dir); )+}; } test_directories! { ConstructorDefinition, + ConstructorModifierStyle, ContractDefinition, DocComments, EnumDefinition, ErrorDefinition, EventDefinition, FunctionDefinition, + FunctionDefinitionWithFunctionReturns, FunctionType, ImportDirective, ModifierDefinition, @@ -192,6 +221,7 @@ test_directories! { IntTypes, InlineDisable, NumberLiteralUnderscore, + HexUnderscore, FunctionCall, TrailingComma, PragmaDirective, @@ -199,4 +229,9 @@ test_directories! { MappingType, EmitStatement, Repros, + BlockComments, + BlockCommentsFunction, + EnumVariants, } + +test_dir!(SortedImports, TestConfig::skip_compare_ast_eq()); diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index c7e48e46bd8b3..d743c9cd4f7d2 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -10,81 +10,121 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "forge" path = "bin/main.rs" -[build-dependencies] -vergen = { version = "8", default-features = false, features = ["build", "git", "git2"] } - [dependencies] # lib -foundry-utils.workspace = true +foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } foundry-common.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } foundry-config.workspace = true foundry-evm.workspace = true +foundry-wallets.workspace = true +foundry-linking.workspace = true +forge-script-sequence.workspace = true -comfy-table = "6" -ethers = { workspace = true, features = ["solc-full"] } -eyre = "0.6" -proptest = "1" -rayon = "1" -serde = "1" -tracing = "0.1" -yansi = "0.5" +revm-inspectors.workspace = true + +comfy-table.workspace = true +eyre.workspace = true +proptest.workspace = true +rayon.workspace = true +serde.workspace = true +tracing.workspace = true +yansi.workspace = true +humantime-serde = "1.1.1" +chrono.workspace = true # bin -forge-fmt.workspace = true forge-doc.workspace = true +forge-fmt.workspace = true +forge-verify.workspace = true +forge-script.workspace = true +forge-sol-macro-gen.workspace = true foundry-cli.workspace = true -ui.workspace = true +foundry-debugger.workspace = true + +alloy-chains.workspace = true +alloy-consensus.workspace = true +alloy-dyn-abi.workspace = true +alloy-json-abi.workspace = true +alloy-network.workspace = true +alloy-primitives = { workspace = true, features = ["serde"] } +alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } +alloy-rpc-types.workspace = true +alloy-serde.workspace = true +alloy-signer.workspace = true +alloy-sol-macro-expander = { workspace = true, features = ["json"] } +alloy-sol-macro-input.workspace = true +alloy-transport.workspace = true -async-trait = "0.1" -bytes = "1.4" +async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" -dialoguer = { version = "0.10", default-features = false } -dunce = "1" -futures = "0.3" -hex.workspace = true +dialoguer = { version = "0.11", default-features = false } +dunce.workspace = true +futures.workspace = true indicatif = "0.17" +inferno = { version = "0.12", default-features = false } itertools.workspace = true -once_cell = "1" -parking_lot = "0.12" -regex = { version = "1", default-features = false } -reqwest = { version = "0.11", default-features = false, features = ["json"] } -semver = "1" -serde_json = "1" +parking_lot.workspace = true +regex = { workspace = true, default-features = false } +reqwest = { workspace = true, features = ["json"] } +semver.workspace = true +serde_json.workspace = true similar = { version = "2", features = ["inline"] } solang-parser.workspace = true -strum = { version = "0.25", features = ["derive"] } -thiserror = "1" -tokio = { version = "1", features = ["time"] } -watchexec = "2" +solar-parse.workspace = true +strum = { workspace = true, features = ["derive"] } +thiserror.workspace = true +tokio = { workspace = true, features = ["time"] } +toml = { workspace = true, features = ["preserve_order"] } +toml_edit = "0.22" +watchexec = "5.0" +watchexec-events = "4.0" +watchexec-signals = "4.0" +clearscreen = "4.0" +evm-disassembler.workspace = true + +# doc server +axum = { workspace = true, features = ["ws"] } +hyper.workspace = true +tower-http = { workspace = true, features = ["fs"] } +opener = "0.7" + +# soldeer +soldeer-commands.workspace = true +quick-junit = "0.5.0" + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] anvil.workspace = true foundry-test-utils.workspace = true -criterion = "0.5" +mockall = "0.13" globset = "0.4" +paste = "1.0" path-slash = "0.2" -pretty_assertions = "1" -serial_test = "2" -svm = { package = "svm-rs", version = "0.3", default-features = false, features = ["rustls"] } - -[features] -default = ["rustls"] -rustls = ["foundry-cli/rustls", "reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] -openssl = ["foundry-cli/openssl", "reqwest/default-tls"] - -# feature for integration tests that test external projects -external-integration-tests = [] +similar-asserts.workspace = true +svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ + "rustls", +] } +tempfile.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter"] } -# feature for heavy (long-running) integration tests -heavy-integration-tests = [] +alloy-signer-local.workspace = true -[[bench]] -name = "test" -harness = false +[features] +default = ["jemalloc"] +asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] +aws-kms = ["foundry-wallets/aws-kms"] +isolate-by-default = ["foundry-config/isolate-by-default"] diff --git a/crates/forge/README.md b/crates/forge/README.md deleted file mode 100644 index 35cb196ced432..0000000000000 --- a/crates/forge/README.md +++ /dev/null @@ -1,446 +0,0 @@ -# `forge` - -Forge is a fast and flexible Ethereum testing framework, inspired by -[Dapp](https://github.com/dapphub/dapptools/tree/master/src/dapp). - -If you are looking into how to consume the software as an end user, check the -[CLI README](../cli/README.md). - -For more context on how the package works under the hood, look in the -[code docs](./src/lib.rs). - -**Need help with Forge? Read the [📖 Foundry Book (Forge Guide)][foundry-book-forge-guide] (WIP)!** - -[foundry-book-forge-guide]: https://book.getfoundry.sh/forge/ - -## Why? - -### Write your tests in Solidity to minimize context switching - -Writing tests in Javascript/Typescript while writing your smart contracts in -Solidity can be confusing. Forge lets you write your tests in Solidity, so you -can focus on what matters. - -```solidity -contract Foo { - uint256 public x = 1; - function set(uint256 _x) external { - x = _x; - } - - function double() external { - x = 2 * x; - } -} - -contract FooTest { - Foo foo; - - // The state of the contract gets reset before each - // test is run, with the `setUp()` function being called - // each time after deployment. - function setUp() public { - foo = new Foo(); - } - - // A simple unit test - function testDouble() public { - require(foo.x() == 1); - foo.double(); - require(foo.x() == 2); - } -} -``` - -### Fuzzing: Go beyond unit testing - -When testing smart contracts, fuzzing can uncover edge cases which would be hard -to manually detect with manual unit testing. We support fuzzing natively, where -any test function that takes >0 arguments will be fuzzed, using the -[proptest](https://docs.rs/proptest/1.0.0/proptest/) crate. - -An example of how a fuzzed test would look like can be seen below: - -```solidity -function testDoubleWithFuzzing(uint256 x) public { - foo.set(x); - require(foo.x() == x); - foo.double(); - require(foo.x() == 2 * x); -} -``` - -## Features - -- [ ] test - - [x] Simple unit tests - - [x] Gas costs - - [x] DappTools style test output - - [x] JSON test output - - [x] Matching on regex - - [x] DSTest-style assertions support - - [x] Fuzzing - - [ ] Symbolic execution - - [ ] Coverage - - [x] HEVM-style Solidity cheatcodes - - [ ] Structured tracing with abi decoding - - [ ] Per-line gas profiling - - [x] Forking mode - - [x] Automatic solc selection -- [x] build - - [x] Can read DappTools-style .sol.json artifacts - - [x] Manual remappings - - [x] Automatic remappings - - [x] Multiple compiler versions - - [x] Incremental compilation - - [ ] Can read Hardhat-style artifacts - - [ ] Can read Truffle-style artifacts -- [x] install -- [x] update -- [ ] debug -- [x] CLI Tracing with `RUST_LOG=forge=trace` - -### Gas Report - -Foundry will show you a comprehensive gas report about your contracts. It returns the `min`, `average`, `median` and, `max` gas cost for every function. - -It looks at **all** the tests that make a call to a given function and records the associated gas costs. For example, if something calls a function and it reverts, that's probably the `min` value. Another example is the `max` value that is generated usually during the first call of the function (as it has to initialise storage, variables, etc.) - -Usually, the `median` value is what your users will probably end up paying. `max` and `min` concern edge cases that you might want to explicitly test against, but users will probably never encounter. - -image - -### Cheat codes - -_The below is modified from -[Dapp's README](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md#cheat-codes)_ - -We allow modifying blockchain state with "cheat codes". These can be accessed by -calling into a contract at address `0x7109709ECfa91a80626fF3989D68f67F5b1DD12D`, -which implements the following methods: - -- `function warp(uint x) public` Sets the block timestamp to `x`. - -- `function difficulty(uint x) public` Sets the block difficulty to `x`. - -- `function roll(uint x) public` Sets the block number to `x`. - -- `function coinbase(address c) public` Sets the block coinbase to `c`. - -- `function store(address c, bytes32 loc, bytes32 val) public` Sets the slot - `loc` of contract `c` to `val`. - -- `function load(address c, bytes32 loc) public returns (bytes32 val)` Reads the - slot `loc` of contract `c`. - -- `function sign(uint sk, bytes32 digest) public returns (uint8 v, bytes32 r, bytes32 s)` - Signs the `digest` using the private key `sk`. Note that signatures produced - via `hevm.sign` will leak the private key. - -- `function addr(uint sk) public returns (address addr)` Derives an ethereum - address from the private key `sk`. Note that `hevm.addr(0)` will fail with - `BadCheatCode` as `0` is an invalid ECDSA private key. `sk` values above the - secp256k1 curve order, near the max uint256 value will also fail. - -- `function ffi(string[] calldata) external returns (bytes memory)` Executes the - arguments as a command in the system shell and returns stdout. Note that this - cheatcode means test authors can execute arbitrary code on user machines as - part of a call to `forge test`, for this reason all calls to `ffi` will fail - unless the `--ffi` flag is passed. - -- `function deal(address who, uint256 amount)`: Sets an account's balance - -- `function etch(address where, bytes memory what)`: Sets the contract code at - some address contract code - -- `function prank(address sender)`: Performs the next smart contract call as another address (prank just changes msg.sender. Tx still occurs as normal) - -- `function prank(address sender, address origin)`: Performs the next smart contract call setting both `msg.sender` and `tx.origin`. - -- `function startPrank(address sender)`: Performs smart contract calls as another address. The account impersonation lasts until the end of the transaction, or until `stopPrank` is called. - -- `function startPrank(address sender, address origin)`: Performs smart contract calls as another address, while also setting `tx.origin`. The account impersonation lasts until the end of the transaction, or until `stopPrank` is called. - -- `function stopPrank()`: Stop calling smart contracts with the address set at `startPrank` - -- `function expectRevert( expectedError)`: - Tells the evm to expect that the next call reverts with specified error bytes. Valid input types: `bytes`, and `bytes4`. Implicitly, strings get converted to bytes except when shorter than 4, in which case you will need to cast explicitly to `bytes`. -- `function expectEmit(bool,bool,bool,bool) external`: Expects the next emitted event. Params check topic 1, topic 2, topic 3 and data are the same. - -- `function expectEmit(bool,bool,bool,bool,address) external`: Expects the next emitted event. Params check topic 1, topic 2, topic 3 and data are the same. Also checks supplied address against address of originating contract. - -- `function getCode(string calldata) external returns (bytes memory)`: Fetches bytecode from a contract artifact. The parameter can either be in the form `ContractFile.sol` (if the filename and contract name are the same), `ContractFile.sol:ContractName`, or `./path/to/artifact.json`. - -- `function label(address addr, string calldata label) external`: Label an address in test traces. - -- `function assume(bool) external`: When fuzzing, generate new inputs if conditional not met - -- `function setNonce(address account, uint64 nonce) external`: Set nonce for an account, increment only. - -- `function getNonce(address account)`: Get nonce for an account. - -- `function chainId(uint x) public` Sets the block chainid to `x`. - -The below example uses the `warp` cheatcode to override the timestamp & `expectRevert` to expect a specific revert string: - -```solidity -interface Vm { - function warp(uint256 x) external; - function expectRevert(bytes calldata) external; -} - -contract Foo { - function bar(uint256 a) public returns (uint256) { - require(a < 100, "My expected revert string"); - return a; - } -} - -contract MyTest { - Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - function testWarp() public { - vm.warp(100); - require(block.timestamp == 100); - } - - function testBarExpectedRevert() public { - vm.expectRevert("My expected revert string"); - // This would fail *if* we didnt expect revert. Since we expect the revert, - // it doesn't, unless the revert string is wrong. - foo.bar(101); - } - - function testFailBar() public { - // this call would revert, causing this test to pass - foo.bar(101); - } -} -``` - -Below is another example using the `expectEmit` cheatcode to check events: - -```solidity -interface Vm { - function expectEmit(bool,bool,bool,bool) external; - function expectEmit(bool,bool,bool,bool,address) external; -} - -contract T is DSTest { - Vm vm = Vm(HEVM_ADDRESS); - event Transfer(address indexed from,address indexed to, uint256 amount); - function testExpectEmit() public { - ExpectEmit emitter = new ExpectEmit(); - // check topic 1, topic 2, and data are the same as the following emitted event - vm.expectEmit(true,true,false,true); - emit Transfer(address(this), address(1337), 1337); - emitter.t(); - } - - function testExpectEmitWithAddress() public { - ExpectEmit emitter = new ExpectEmit(); - // do the same as above and check emitting address - vm.expectEmit(true,true,false,true,address(emitter)); - emit Transfer(address(this), address(1337), 1337); - emitter.t(); - } -} - -contract ExpectEmit { - event Transfer(address indexed from,address indexed to, uint256 amount); - function t() public { - emit Transfer(msg.sender, address(1337), 1337); - } -} -``` - -A full interface for all cheatcodes is here: - -```solidity -interface Hevm { - // Set block.timestamp (newTimestamp) - function warp(uint256) external; - // Set block.height (newHeight) - function roll(uint256) external; - // Set block.basefee (newBasefee) - function fee(uint256) external; - // Set block.coinbase (who) - function coinbase(address) external; - // Loads a storage slot from an address (who, slot) - function load(address,bytes32) external returns (bytes32); - // Stores a value to an address' storage slot, (who, slot, value) - function store(address,bytes32,bytes32) external; - // Signs data, (privateKey, digest) => (v, r, s) - function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32); - // Gets address for a given private key, (privateKey) => (address) - function addr(uint256) external returns (address); - // Performs a foreign function call via terminal, (stringInputs) => (result) - function ffi(string[] calldata) external returns (bytes memory); - // Sets the *next* call's msg.sender to be the input address - function prank(address) external; - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called - function startPrank(address) external; - // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input - function prank(address,address) external; - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input - function startPrank(address,address) external; - // Resets subsequent calls' msg.sender to be `address(this)` - function stopPrank() external; - // Sets an address' balance, (who, newBalance) - function deal(address, uint256) external; - // Sets an address' code, (who, newCode) - function etch(address, bytes calldata) external; - // Expects an error on next call - function expectRevert() external; - function expectRevert(bytes calldata) external; - function expectRevert(bytes4) external; - // Record all storage reads and writes - function record() external; - // Gets all accessed reads and write slot from a recording session, for a given address - function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); - // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). - // Call this function, then emit an event, then call a function. Internally after the call, we check if - // logs were emitted in the expected order with the expected topics and data (as specified by the booleans) - function expectEmit(bool,bool,bool,bool) external; - // Mocks a call to an address, returning specified data. - // Calldata can either be strict or a partial match, e.g. if you only - // pass a Solidity selector to the expected calldata, then the entire Solidity - // function will be mocked. - function mockCall(address,bytes calldata,bytes calldata) external; - // Mocks a call to an address with a specific msg.value, returning specified data. - // Calldata match takes precedence over msg.value in case of ambiguity. - function mockCall(address,uint256,bytes calldata,bytes calldata) external; - // Reverts a call to an address with specified revert data. - function mockCallRevert(address, bytes calldata, bytes calldata) external; - // Reverts a call to an address with a specific msg.value, with specified revert data. - function mockCallRevert(address, uint256 msgValue, bytes calldata, bytes calldata) external; - // Clears all mocked and reverted mocked calls - function clearMockedCalls() external; - // Expect a call to an address with the specified calldata. - // Calldata can either be strict or a partial match - function expectCall(address, bytes calldata) external; - // Expect given number of calls to an address with the specified calldata. - // Calldata can either be strict or a partial match - function expectCall(address, bytes calldata, uint64) external; - // Expect a call to an address with the specified msg.value and calldata - function expectCall(address, uint256, bytes calldata) external; - // Expect a given number of calls to an address with the specified msg.value and calldata - function expectCall(address, uint256, bytes calldata, uint64) external; - // Expect a call to an address with the specified msg.value, gas, and calldata. - function expectCall(address, uint256, uint64, bytes calldata) external; - // Expect a given number of calls to an address with the specified msg.value, gas, and calldata. - function expectCall(address, uint256, uint64, bytes calldata, uint64) external; - // Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address, uint256, uint64, bytes calldata) external; - // Expect a given number of calls to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address, uint256, uint64, bytes calldata, uint64) external; - - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other - // memory is written to, the test will fail. - function expectSafeMemory(uint64, uint64) external; - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. - // If any other memory is written to, the test will fail. - function expectSafeMemoryCall(uint64, uint64) external; - // Fetches the contract bytecode from its artifact file - function getCode(string calldata) external returns (bytes memory); - // Label an address in test traces - function label(address addr, string calldata label) external; - // When fuzzing, generate new inputs if conditional not met - function assume(bool) external; - // Set nonce for an account, increment only - function setNonce(address,uint64) external; - // Get nonce for an account - function getNonce(address) external returns(uint64); -} -``` - -### `console.log` - -We support the logging functionality from Hardhat's `console.log`. - -If you are on a hardhat project, `import hardhat/console.sol` should just work if you use `forge test --hh`. - -If no, there is an implementation contract [here](https://raw.githubusercontent.com/NomicFoundation/hardhat/master/packages/hardhat-core/console.sol). We currently recommend that you copy this contract, place it in your `test` folder, and import it into the contract where you wish to use `console.log`, though there should be more streamlined functionality soon. - -Usage follows the same format as [Hardhat](https://hardhat.org/hardhat-network/reference/#console-log): - -```solidity -import "./console.sol"; -... -console.log(someValue); - -``` - -Note: to make logs visible in `stdout`, you must use at least level 2 verbosity. - -```bash -$> forge test -vv -[PASS] test1() (gas: 7683) -... -Logs: - - ... -``` - -## Remappings - -If you are working in a repo with NPM-style imports, like - -``` -import "@openzeppelin/contracts/access/Ownable.sol"; -``` - -then you will need to create a `remappings.txt` file at the top level of your project directory, so that Forge knows where to find these dependencies. - -For example, if you have `@openzeppelin` imports, you would - -1. `forge install openzeppelin/openzeppelin-contracts` (this will add the repo to `lib/openzepplin-contracts`) -2. Create a remappings file: `touch remappings.txt` -3. Add this line to `remappings.txt` - -``` -@openzeppelin/=lib/openzeppelin-contracts/ -``` - -## Github Actions CI - -We recommend using the [Github Actions CI setup](https://book.getfoundry.sh/config/continous-integration.html) from the [📖 Foundry Book](https://book.getfoundry.sh/index.html). - -## Future Features - -### Dapptools feature parity - -Over the next months, we intend to add the following features which are -available in upstream dapptools: - -1. Stack Traces: Currently we do not provide any debug information when a call - fails. We intend to add a structured printer (something like - [this](https://twitter.com/gakonst/status/1434337110111182848) which will - show all the calls, logs and arguments passed across intermediate smart - contract calls, which should help with debugging. -1. [Invariant Tests](https://github.com/dapphub/dapptools/blob/master/src/dapp/README.md#invariant-testing) -1. [Interactive Debugger](https://github.com/dapphub/dapptools/blob/master/src/hevm/README.md#interactive-debugger-key-bindings) -1. [Code coverage](https://twitter.com/dapptools/status/1435973810545729536) -1. [Gas snapshots](https://github.com/dapphub/dapptools/pull/850/files) -1. [Symbolic EVM](https://fv.ethereum.org/2020/07/28/symbolic-hevm-release/) - -### Unique features? - -We also intend to add features which are not available in dapptools: - -1. Even faster tests with parallel EVM execution that produces state diffs - instead of modifying the state -1. Improved UX for assertions: - 1. Check revert error or reason on a Solidity call - 1. Check that an event was emitted with expected arguments -1. Support more EVM backends ([revm](https://github.com/bluealloy/revm/), geth's - evm, hevm etc.) & benchmark performance across them -1. Declarative deployment system based on a config file -1. Formatting & Linting (maybe powered by - [Solang](https://github.com/hyperledger-labs/solang)) - 1. `forge fmt`, an automatic code formatter according to standard rules (like - [`prettier-plugin-solidity`](https://github.com/prettier-solidity/prettier-plugin-solidity)) - 1. `forge lint`, a linter + static analyzer, like a combination of - [`solhint`](https://github.com/protofire/solhint) and - [slither](https://github.com/crytic/slither/) -1. Flamegraphs for gas profiling diff --git a/crates/forge/assets/CounterTemplate.s.sol b/crates/forge/assets/CounterTemplate.s.sol index 1a47b40b82aab..cdc1fe9a1ba25 100644 --- a/crates/forge/assets/CounterTemplate.s.sol +++ b/crates/forge/assets/CounterTemplate.s.sol @@ -1,12 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Script, console2} from "forge-std/Script.sol"; +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/CounterTemplate.t.sol b/crates/forge/assets/CounterTemplate.t.sol index e9b9e6acf2b43..54b724f7ae766 100644 --- a/crates/forge/assets/CounterTemplate.t.sol +++ b/crates/forge/assets/CounterTemplate.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; contract CounterTest is Test { diff --git a/crates/forge/assets/generated/TestTemplate.t.sol b/crates/forge/assets/generated/TestTemplate.t.sol index 468acba01069a..816a8c68fd0a9 100644 --- a/crates/forge/assets/generated/TestTemplate.t.sol +++ b/crates/forge/assets/generated/TestTemplate.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console} from "forge-std/Test.sol"; import {{contract_name}} from "../src/{contract_name}.sol"; contract {contract_name}Test is Test { @@ -10,4 +10,4 @@ contract {contract_name}Test is Test { function setUp() public { {instance_name} = new {contract_name}(); } -} \ No newline at end of file +} diff --git a/crates/forge/assets/workflowTemplate.yml b/crates/forge/assets/workflowTemplate.yml index 09880b1d79a75..34a4a527be6f9 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 @@ -13,18 +16,24 @@ jobs: name: Foundry project runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 - 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/benches/test.rs b/crates/forge/benches/test.rs deleted file mode 100644 index 7650077b116ac..0000000000000 --- a/crates/forge/benches/test.rs +++ /dev/null @@ -1,24 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use foundry_test_utils::{util::setup_forge_remote, TestCommand, TestProject}; - -/// Returns a cloned and `forge built` `solmate` project -fn built_solmate() -> (TestProject, TestCommand) { - setup_forge_remote("transmissions11/solmate") -} - -fn forge_test_benchmark(c: &mut Criterion) { - let (prj, _) = built_solmate(); - - let mut group = c.benchmark_group("forge test"); - group.sample_size(10); - group.bench_function("solmate", |b| { - let mut cmd = prj.forge_command(); - cmd.arg("test"); - b.iter(|| { - cmd.ensure_execute_success().unwrap(); - }); - }); -} - -criterion_group!(benches, forge_test_benchmark); -criterion_main!(benches); diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index 237af98b87534..1198a89af5199 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -1,24 +1,26 @@ +use alloy_primitives::map::HashSet; use clap::{Parser, ValueHint}; -use ethers::contract::{Abigen, ContractFilter, ExcludeContracts, MultiAbigen, SelectContracts}; use eyre::Result; -use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; -use foundry_common::{compile, fs::json_files}; +use forge_sol_macro_gen::{MultiSolMacroGen, SolMacroGen}; +use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; +use foundry_common::{compile::ProjectCompiler, fs::json_files}; use foundry_config::impl_figment_convert; +use regex::Regex; use std::{ fs, path::{Path, PathBuf}, }; -impl_figment_convert!(BindArgs, build_args); +impl_figment_convert!(BindArgs, build); const DEFAULT_CRATE_NAME: &str = "foundry-contracts"; const DEFAULT_CRATE_VERSION: &str = "0.1.0"; /// CLI arguments for `forge bind`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct BindArgs { /// Path to where the contract artifacts are stored. - #[clap( + #[arg( long = "bindings-path", short, value_hint = ValueHint::DirPath, @@ -27,183 +29,253 @@ pub struct BindArgs { pub bindings: Option, /// Create bindings only for contracts whose names match the specified filter(s) - #[clap(long)] + #[arg(long)] pub select: Vec, - /// Create bindings only for contracts whose names do not match the specified filter(s) - #[clap(long, conflicts_with = "select")] - pub skip: Vec, - /// Explicitly generate bindings for all contracts /// /// By default all contracts ending with `Test` or `Script` are excluded. - #[clap(long, conflicts_with_all = &["select", "skip"])] + #[arg(long, conflicts_with_all = &["select", "skip"])] pub select_all: bool, /// The name of the Rust crate to generate. /// /// This should be a valid crates.io crate name, /// however, this is not currently validated by this command. - #[clap(long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME")] + #[arg(long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME")] crate_name: String, /// The version of the Rust crate to generate. /// /// This should be a standard semver version string, /// however, this is not currently validated by this command. - #[clap(long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION")] + #[arg(long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION")] crate_version: String, /// Generate the bindings as a module instead of a crate. - #[clap(long)] + #[arg(long)] module: bool, /// Overwrite existing generated bindings. /// /// By default, the command will check that the bindings are correct, and then exit. If /// --overwrite is passed, it will instead delete and overwrite the bindings. - #[clap(long)] + #[arg(long)] overwrite: bool, /// Generate bindings as a single file. - #[clap(long)] + #[arg(long)] single_file: bool, /// Skip Cargo.toml consistency checks. - #[clap(long)] + #[arg(long)] skip_cargo_toml: bool, /// Skips running forge build before generating binding - #[clap(long)] + #[arg(long)] skip_build: bool, - #[clap(flatten)] - build_args: CoreBuildArgs, + /// Don't add any additional derives to generated bindings + #[arg(long)] + skip_extra_derives: bool, + + /// Generate bindings for the `alloy` library, instead of `ethers`. + #[arg(long, hide = true)] + alloy: bool, + + /// Specify the `alloy` version on Crates. + #[arg(long)] + alloy_version: Option, + + /// Specify the `alloy` revision on GitHub. + #[arg(long, conflicts_with = "alloy_version")] + alloy_rev: Option, + + /// Generate bindings for the `ethers` library (removed), instead of `alloy`. + #[arg(long, hide = true)] + ethers: bool, + + #[command(flatten)] + build: BuildOpts, } impl BindArgs { - /// Get the path to the root of the autogenerated crate - fn bindings_root(&self, artifacts: impl AsRef) -> PathBuf { - self.bindings.clone().unwrap_or_else(|| artifacts.as_ref().join("bindings")) - } + pub fn run(self) -> Result<()> { + if self.ethers { + eyre::bail!("`--ethers` bindings have been removed. Use `--alloy` (default) instead."); + } + + if !self.skip_build { + let project = self.build.project()?; + let _ = ProjectCompiler::new().compile(&project)?; + } + + let config = self.load_config()?; + let artifacts = config.out; + let bindings_root = self.bindings.clone().unwrap_or_else(|| artifacts.join("bindings")); + + if bindings_root.exists() { + if !self.overwrite { + sh_println!("Bindings found. Checking for consistency.")?; + return self.check_existing_bindings(&artifacts, &bindings_root); + } - /// `true` if the bindings root already exists - fn bindings_exist(&self, artifacts: impl AsRef) -> bool { - self.bindings_root(artifacts).is_dir() + trace!(?artifacts, "Removing existing bindings"); + fs::remove_dir_all(&bindings_root)?; + } + + self.generate_bindings(&artifacts, &bindings_root)?; + + sh_println!("Bindings have been generated to {}", bindings_root.display())?; + Ok(()) } - /// Returns the filter to use for `MultiAbigen` - fn get_filter(&self) -> ContractFilter { + fn get_filter(&self) -> Result { if self.select_all { - return ContractFilter::All + // Select all json files + return Ok(Filter::All); } if !self.select.is_empty() { - return SelectContracts::default().extend_regex(self.select.clone()).into() + // Return json files that match the select regex + return Ok(Filter::Select(self.select.clone())); } - if !self.skip.is_empty() { - return ExcludeContracts::default().extend_regex(self.skip.clone()).into() + + if let Some(skip) = self.build.skip.as_ref().filter(|s| !s.is_empty()) { + return Ok(Filter::Skip( + skip.clone() + .into_iter() + .map(|s| Regex::new(s.file_pattern())) + .collect::, _>>()?, + )); } - // This excludes all Test/Script and forge-std contracts - ExcludeContracts::default() - .extend_pattern([ - ".*Test.*", - ".*Script", - "console[2]?", - "CommonBase", - "Components", - "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Assertions|Storage(Safe)?)", - "[Vv]m.*", - ]) - .extend_names(["IMulticall3"]) - .into() + + // Exclude defaults + Ok(Filter::skip_default()) } - /// Instantiate the multi-abigen - fn get_multi(&self, artifacts: impl AsRef) -> Result { - let abigens = json_files(artifacts.as_ref()) - .into_iter() + /// Returns an iterator over the JSON files and the contract name in the `artifacts` directory. + fn get_json_files(&self, artifacts: &Path) -> Result> { + let filter = self.get_filter()?; + Ok(json_files(artifacts) .filter_map(|path| { - // we don't want `.metadata.json files - let stem = path.file_stem()?; - if stem.to_str()?.ends_with(".metadata") { - None + // Ignore the build info JSON. + if path.to_str()?.contains("build-info") { + return None; + } + + // Ignore the `target` directory in case the user has built the project. + if path.iter().any(|comp| comp == "target") { + return None; + } + + // We don't want `.metadata.json` files. + let stem = path.file_stem()?.to_str()?; + if stem.ends_with(".metadata") { + return None; + } + + let name = stem.split('.').next().unwrap(); + + // Best effort identifier cleanup. + let name = name.replace(char::is_whitespace, "").replace('-', "_"); + + Some((name, path)) + }) + .filter(move |(name, _path)| filter.is_match(name))) + } + + fn get_solmacrogen(&self, artifacts: &Path) -> Result { + let mut dup = HashSet::::default(); + let instances = self + .get_json_files(artifacts)? + .filter_map(|(name, path)| { + trace!(?path, "parsing SolMacroGen from file"); + if dup.insert(name.clone()) { + Some(SolMacroGen::new(path, name)) } else { - Some(path) + None } }) - .map(Abigen::from_file) - .collect::, _>>()?; - let multi = MultiAbigen::from_abigens(abigens).with_filter(self.get_filter()); - - eyre::ensure!( - !multi.is_empty(), - r#" -No contract artifacts found. Hint: Have you built your contracts yet? `forge bind` does not currently invoke `forge build`, although this is planned for future versions. - "# - ); + .collect::>(); + + let multi = MultiSolMacroGen::new(artifacts, instances); + eyre::ensure!(!multi.instances.is_empty(), "No contract artifacts found"); Ok(multi) } /// Check that the existing bindings match the expected abigen output - fn check_existing_bindings(&self, artifacts: impl AsRef) -> Result<()> { - let bindings = self.get_multi(&artifacts)?.build()?; - println!("Checking bindings for {} contracts.", bindings.len()); - if !self.module { - bindings.ensure_consistent_crate( - &self.crate_name, - &self.crate_version, - self.bindings_root(&artifacts), - self.single_file, - !self.skip_cargo_toml, - )?; - } else { - bindings.ensure_consistent_module(self.bindings_root(&artifacts), self.single_file)?; - } - println!("OK."); + fn check_existing_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let mut bindings = self.get_solmacrogen(artifacts)?; + bindings.generate_bindings()?; + sh_println!("Checking bindings for {} contracts", bindings.instances.len())?; + bindings.check_consistency( + &self.crate_name, + &self.crate_version, + bindings_root, + self.single_file, + !self.skip_cargo_toml, + self.module, + self.alloy_version.clone(), + self.alloy_rev.clone(), + )?; + sh_println!("OK.")?; Ok(()) } /// Generate the bindings - fn generate_bindings(&self, artifacts: impl AsRef) -> Result<()> { - let bindings = self.get_multi(&artifacts)?.build()?; - println!("Generating bindings for {} contracts", bindings.len()); + fn generate_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let mut solmacrogen = self.get_solmacrogen(artifacts)?; + sh_println!("Generating bindings for {} contracts", solmacrogen.instances.len())?; + if !self.module { - bindings.write_to_crate( + trace!(single_file = self.single_file, "generating crate"); + solmacrogen.write_to_crate( &self.crate_name, &self.crate_version, - self.bindings_root(&artifacts), + bindings_root, self.single_file, + self.alloy_version.clone(), + self.alloy_rev.clone(), )?; } else { - bindings.write_to_module(self.bindings_root(&artifacts), self.single_file)?; + trace!(single_file = self.single_file, "generating module"); + solmacrogen.write_to_module(bindings_root, self.single_file)?; } + Ok(()) } +} - pub fn run(self) -> Result<()> { - if !self.skip_build { - // run `forge build` - let project = self.build_args.project()?; - compile::compile(&project, false, false)?; - } - - let artifacts = self.try_load_config_emit_warnings()?.out; - - if !self.overwrite && self.bindings_exist(&artifacts) { - println!("Bindings found. Checking for consistency."); - return self.check_existing_bindings(&artifacts) - } +pub enum Filter { + All, + Select(Vec), + Skip(Vec), +} - if self.overwrite && self.bindings_exist(&artifacts) { - fs::remove_dir_all(self.bindings_root(&artifacts))?; +impl Filter { + pub fn is_match(&self, name: &str) -> bool { + match self { + Self::All => true, + Self::Select(regexes) => regexes.iter().any(|regex| regex.is_match(name)), + Self::Skip(regexes) => !regexes.iter().any(|regex| regex.is_match(name)), } + } - self.generate_bindings(&artifacts)?; + pub fn skip_default() -> Self { + let skip = [ + ".*Test.*", + ".*Script", + "console[2]?", + "CommonBase", + "Components", + "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Toml|Storage(Safe)?)", + "[Vv]m.*", + "IMulticall3", + ] + .iter() + .map(|pattern| regex::Regex::new(pattern).unwrap()) + .collect::>(); - println!( - "Bindings have been output to {}", - self.bindings_root(&artifacts).to_str().unwrap() - ); - Ok(()) + Self::Skip(skip) } } diff --git a/crates/forge/bin/cmd/bind_json.rs b/crates/forge/bin/cmd/bind_json.rs new file mode 100644 index 0000000000000..918882aaa50f0 --- /dev/null +++ b/crates/forge/bin/cmd/bind_json.rs @@ -0,0 +1,556 @@ +use super::eip712::Resolver; +use clap::{Parser, ValueHint}; +use eyre::Result; +use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; +use foundry_common::{compile::with_compilation_reporter, fs}; +use foundry_compilers::{ + artifacts::{ + output_selection::OutputSelection, ContractDefinitionPart, Source, SourceUnit, + SourceUnitPart, Sources, + }, + multi::{MultiCompilerLanguage, MultiCompilerParsedSource}, + project::ProjectCompiler, + solc::SolcLanguage, + Graph, Project, +}; +use foundry_config::Config; +use itertools::Itertools; +use rayon::prelude::*; +use solar_parse::{ + ast::{self, interface::source_map::FileName, visit::Visit, Arena, FunctionKind, Span, VarMut}, + interface::Session, + Parser as SolarParser, +}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::{self, Write}, + ops::ControlFlow, + path::PathBuf, + sync::Arc, +}; + +foundry_config::impl_figment_convert!(BindJsonArgs, build); + +/// 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)] + build: BuildOpts, +} + +impl BindJsonArgs { + pub fn run(self) -> Result<()> { + self.preprocess()?.compile()?.find_structs()?.resolve_imports_and_aliases().write()?; + + Ok(()) + } + + /// In cases when user moves/renames/deletes structs, compiler will start failing because + /// generated bindings will be referencing non-existing structs or importing non-existing + /// files. + /// + /// Because of that, we need a little bit of preprocessing to make sure that bindings will still + /// be valid. + /// + /// The strategy is: + /// 1. Replace bindings file with an empty one to get rid of potentially invalid imports. + /// 2. Remove all function bodies to get rid of `serialize`/`deserialize` invocations. + /// 3. Remove all `immutable` attributes to avoid errors because of erased constructors + /// initializing them. + /// + /// After that we'll still have enough information for bindings but compilation should succeed + /// in most of the cases. + fn preprocess(self) -> Result { + let config = self.load_config()?; + let project = config.ephemeral_project()?; + + let target_path = config.root.join(self.out.as_ref().unwrap_or(&config.bind_json.out)); + + 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)? + .sources + .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; + + let sess = Session::builder().with_stderr_emitter().build(); + let result = sess.enter_parallel(|| -> solar_parse::interface::Result<()> { + sources.0.par_iter_mut().try_for_each(|(path, source)| { + let mut content = Arc::try_unwrap(std::mem::take(&mut source.content)).unwrap(); + + let arena = Arena::new(); + let mut parser = SolarParser::from_source_code( + &sess, + &arena, + FileName::Real(path.clone()), + content.to_string(), + )?; + let ast = parser.parse_file().map_err(|e| e.emit())?; + + let mut visitor = PreprocessorVisitor::new(); + visitor.visit_source_unit(&ast); + visitor.update(&sess, &mut content); + + source.content = Arc::new(content); + Ok(()) + }) + }); + eyre::ensure!(result.is_ok(), "failed parsing"); + + // Insert empty bindings file. + sources.insert(target_path.clone(), Source::new("library JsonBindings {}")); + + Ok(PreprocessedState { sources, target_path, project, config }) + } +} + +struct PreprocessorVisitor { + updates: Vec<(Span, &'static str)>, +} + +impl PreprocessorVisitor { + fn new() -> Self { + Self { updates: Vec::new() } + } + + fn update(mut self, sess: &Session, content: &mut String) { + if self.updates.is_empty() { + return; + } + + let sf = sess.source_map().lookup_source_file(self.updates[0].0.lo()); + let base = sf.start_pos.0; + + self.updates.sort_by_key(|(span, _)| span.lo()); + let mut shift = 0_i64; + for (span, new) in self.updates { + let lo = span.lo() - base; + let hi = span.hi() - base; + let start = ((lo.0 as i64) - shift) as usize; + let end = ((hi.0 as i64) - shift) as usize; + + content.replace_range(start..end, new); + shift += (end - start) as i64; + shift -= new.len() as i64; + } + } +} + +impl<'ast> Visit<'ast> for PreprocessorVisitor { + type BreakValue = solar_parse::interface::data_structures::Never; + + fn visit_item_function( + &mut self, + func: &'ast ast::ItemFunction<'ast>, + ) -> ControlFlow { + // Replace function bodies with a noop statement. + if let Some(block) = &func.body { + if !block.is_empty() { + let span = block.first().unwrap().span.to(block.last().unwrap().span); + let new_body = match func.kind { + FunctionKind::Modifier => "_;", + _ => "revert();", + }; + self.updates.push((span, new_body)); + } + } + + self.walk_item_function(func) + } + + fn visit_variable_definition( + &mut self, + var: &'ast ast::VariableDefinition<'ast>, + ) -> ControlFlow { + // Remove `immutable` attributes. + if let Some(VarMut::Immutable) = var.mutability { + self.updates.push((var.span, "")); + } + + self.walk_variable_definition(var) + } +} + +/// A single struct definition for which we need to generate bindings. +#[derive(Debug, Clone)] +struct StructToWrite { + /// Name of the struct definition. + name: String, + /// Name of the contract containing the struct definition. None if the struct is defined at the + /// file level. + contract_name: Option, + /// Import alias for the contract or struct, depending on whether the struct is imported + /// directly, or via a contract. + import_alias: Option, + /// Path to the file containing the struct definition. + path: PathBuf, + /// EIP712 schema for the struct. + schema: String, + /// Name of the struct definition used in function names and schema_* variables. + name_in_fns: String, +} + +impl StructToWrite { + /// Returns the name of the imported item. If struct is defined at the file level, returns the + /// struct name, otherwise returns the parent contract name. + fn struct_or_contract_name(&self) -> &str { + self.contract_name.as_deref().unwrap_or(&self.name) + } + + /// Same as [StructToWrite::struct_or_contract_name] but with alias applied. + fn struct_or_contract_name_with_alias(&self) -> &str { + self.import_alias.as_deref().unwrap_or(self.struct_or_contract_name()) + } + + /// Path which can be used to reference this struct in input/output parameters. Either + /// StructName or ParantName.StructName + fn full_path(&self) -> String { + if self.contract_name.is_some() { + format!("{}.{}", self.struct_or_contract_name_with_alias(), self.name) + } else { + self.struct_or_contract_name_with_alias().to_string() + } + } + + fn import_item(&self) -> String { + if let Some(alias) = &self.import_alias { + format!("{} as {}", self.struct_or_contract_name(), alias) + } else { + self.struct_or_contract_name().to_string() + } + } +} + +#[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.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)? 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)?; + + sh_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 7c4e8fce2ca33..133e2f9ad70ca 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -1,11 +1,12 @@ use super::{install, watch::WatchArgs}; use clap::Parser; -use ethers::solc::{Project, ProjectCompileOutput}; use eyre::Result; -use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; -use foundry_common::{ - compile, - compile::{ProjectCompiler, SkipBuildFilter}, +use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; +use foundry_common::{compile::ProjectCompiler, shell}; +use foundry_compilers::{ + compilers::{multi::MultiCompilerLanguage, Language}, + utils::source_files_iter, + Project, ProjectCompileOutput, }; use foundry_config::{ figment::{ @@ -17,9 +18,9 @@ use foundry_config::{ Config, }; use serde::Serialize; -use watchexec::config::{InitConfig, RuntimeConfig}; +use std::path::PathBuf; -foundry_config::merge_impl_figment_convert!(BuildArgs, args); +foundry_config::merge_impl_figment_convert!(BuildArgs, build); /// CLI arguments for `forge build`. /// @@ -27,80 +28,91 @@ foundry_config::merge_impl_figment_convert!(BuildArgs, args); /// In order to override them in the foundry `Config` they need to be merged into an existing /// `figment::Provider`, like `foundry_config::Config` is. /// -/// # Example -/// -/// ``` -/// use foundry_cli::cmd::forge::build::BuildArgs; -/// use foundry_config::Config; -/// # fn t(args: BuildArgs) { -/// let config = Config::from(&args); -/// # } -/// ``` -/// /// `BuildArgs` implements `figment::Provider` in which all config related fields are serialized and /// then merged into an existing `Config`, effectively overwriting them. /// /// Some arguments are marked as `#[serde(skip)]` and require manual processing in /// `figment::Provider` implementation -#[derive(Debug, Clone, Parser, Serialize, Default)] -#[clap(next_help_heading = "Build options", about = None, long_about = None)] // override doc +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Build options", about = None, long_about = None)] // override doc pub struct BuildArgs { + /// Build source files from specified paths. + #[serde(skip)] + pub paths: Option>, + /// Print compiled contract names. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub names: bool, /// Print compiled contract sizes. - #[clap(long)] + /// Constructor argument length is not included in the calculation of initcode size. + #[arg(long)] #[serde(skip)] pub sizes: bool, - /// Skip building files whose names contain the given filter. - /// - /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. - #[clap(long, num_args(1..))] + /// Ignore initcode contract bytecode size limit introduced by EIP-3860. + #[arg(long, alias = "ignore-initcode-size")] #[serde(skip)] - pub skip: Option>, + pub ignore_eip_3860: bool, - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] - pub args: CoreBuildArgs, + pub build: BuildOpts, - #[clap(flatten)] + #[command(flatten)] #[serde(skip)] pub watch: WatchArgs, } impl BuildArgs { pub fn run(self) -> Result { - let mut config = self.try_load_config_emit_warnings()?; - let mut project = config.project()?; + let mut config = self.load_config()?; - if install::install_missing_dependencies(&mut config, self.args.silent) && - config.auto_detect_remappings - { + if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings - config = self.load_config(); - project = config.project()?; + config = self.load_config()?; } - let filters = self.skip.unwrap_or_default(); + let project = config.project()?; + + // Collect sources to compile if build subdirectories specified. + let mut files = vec![]; + if let Some(paths) = &self.paths { + for path in paths { + let joined = project.root().join(path); + let path = if joined.exists() { &joined } else { path }; + files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS)); + } + if files.is_empty() { + eyre::bail!("No source files found in specified build paths.") + } + } + + let format_json = shell::is_json(); + let compiler = ProjectCompiler::new() + .files(files) + .print_names(self.names) + .print_sizes(self.sizes) + .ignore_eip_3860(self.ignore_eip_3860) + .bail(!format_json); - if self.args.silent { - compile::suppress_compile_with_filter(&project, filters) - } else { - let compiler = ProjectCompiler::with_filter(self.names, self.sizes, filters); - compiler.compile(&project) + let output = compiler.compile(&project)?; + + if format_json && !self.names && !self.sizes { + sh_println!("{}", serde_json::to_string_pretty(&output.output())?)?; } + + Ok(output) } /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see - /// [`utils::find_project_root_path`] and merges the cli `BuildArgs` into it before returning + /// [`utils::find_project_root`] and merges the cli `BuildArgs` into it before returning /// [`foundry_config::Config::project()`] pub fn project(&self) -> Result { - self.args.project() + self.build.project() } /// Returns whether `BuildArgs` was configured with `--watch` @@ -110,11 +122,13 @@ impl BuildArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { - // use the path arguments or if none where provided the `src` dir + pub(crate) fn watchexec_config(&self) -> Result { + // Use the path arguments or if none where provided the `src`, `test` and `script` + // directories as well as the `foundry.toml` configuration file. self.watch.watchexec_config(|| { - let config = Config::from(self); - vec![config.src, config.test, config.script] + let config = self.load_config()?; + let foundry_toml: PathBuf = config.root.join(Config::FILE_NAME); + Ok([config.src, config.test, config.script, foundry_toml]) }) } } @@ -138,27 +152,10 @@ impl Provider for BuildArgs { dict.insert("sizes".to_string(), true.into()); } - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_parse_build_filters() { - let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests])); - - let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "scripts"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Scripts])); - - let args: BuildArgs = - BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "--skip", "scripts"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts])); + if self.ignore_eip_3860 { + dict.insert("ignore_eip_3860".to_string(), true.into()); + } - let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "scripts"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts])); + Ok(Map::from([(Config::selected_profile(), dict)])) } } diff --git a/crates/forge/bin/cmd/cache.rs b/crates/forge/bin/cmd/cache.rs index e70a2c66f2c83..efbdde5cb6981 100644 --- a/crates/forge/bin/cmd/cache.rs +++ b/crates/forge/bin/cmd/cache.rs @@ -3,16 +3,15 @@ use clap::{ builder::{PossibleValuesParser, TypedValueParser}, Arg, Command, Parser, Subcommand, }; -use ethers::prelude::Chain; use eyre::Result; -use foundry_config::{cache, Chain as FoundryConfigChain, Config}; +use foundry_config::{cache, Chain, Config, NamedChain}; use std::{ffi::OsStr, str::FromStr}; use strum::VariantNames; /// CLI arguments for `forge cache`. #[derive(Debug, Parser)] pub struct CacheArgs { - #[clap(subcommand)] + #[command(subcommand)] pub sub: CacheSubcommands, } @@ -27,12 +26,12 @@ pub enum CacheSubcommands { /// CLI arguments for `forge clean`. #[derive(Debug, Parser)] -#[clap(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))] +#[command(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))] pub struct CleanArgs { /// The chains to clean the cache for. /// /// Can also be "all" to clean all chains. - #[clap( + #[arg( env = "CHAIN", default_value = "all", value_parser = ChainOrAllValueParser::default(), @@ -40,28 +39,29 @@ pub struct CleanArgs { chains: Vec, /// The blocks to clean the cache for. - #[clap( + #[arg( short, long, num_args(1..), - use_value_delimiter(true), value_delimiter(','), group = "etherscan-blocks" )] blocks: Vec, /// Whether to clean the Etherscan cache. - #[clap(long, group = "etherscan-blocks")] + #[arg(long, group = "etherscan-blocks")] etherscan: bool, } impl CleanArgs { pub fn run(self) -> Result<()> { - let CleanArgs { chains, blocks, etherscan } = self; + let Self { chains, blocks, etherscan } = self; for chain_or_all in chains { match chain_or_all { - ChainOrAll::Chain(chain) => clean_chain_cache(chain, blocks.to_vec(), etherscan)?, + ChainOrAll::NamedChain(chain) => { + clean_chain_cache(chain, blocks.to_vec(), etherscan)? + } ChainOrAll::All => { if etherscan { Config::clean_foundry_etherscan_cache()?; @@ -81,7 +81,7 @@ pub struct LsArgs { /// The chains to list the cache for. /// /// Can also be "all" to list all chains. - #[clap( + #[arg( env = "CHAIN", default_value = "all", value_parser = ChainOrAllValueParser::default(), @@ -91,24 +91,24 @@ pub struct LsArgs { impl LsArgs { pub fn run(self) -> Result<()> { - let LsArgs { chains } = self; + let Self { chains } = self; let mut cache = Cache::default(); for chain_or_all in chains { match chain_or_all { - ChainOrAll::Chain(chain) => { + ChainOrAll::NamedChain(chain) => { cache.chains.push(Config::list_foundry_chain_cache(chain.into())?) } ChainOrAll::All => cache = Config::list_foundry_cache()?, } } - print!("{cache}"); + sh_print!("{cache}")?; Ok(()) } } -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum ChainOrAll { - Chain(Chain), + NamedChain(NamedChain), All, } @@ -116,21 +116,17 @@ impl FromStr for ChainOrAll { type Err = String; fn from_str(s: &str) -> Result { - if let Ok(chain) = ethers::prelude::Chain::from_str(s) { - Ok(ChainOrAll::Chain(chain)) + if let Ok(chain) = NamedChain::from_str(s) { + Ok(Self::NamedChain(chain)) } else if s == "all" { - Ok(ChainOrAll::All) + Ok(Self::All) } else { Err(format!("Expected known chain or all, found: {s}")) } } } -fn clean_chain_cache( - chain: impl Into, - blocks: Vec, - etherscan: bool, -) -> Result<()> { +fn clean_chain_cache(chain: impl Into, blocks: Vec, etherscan: bool) -> Result<()> { let chain = chain.into(); if blocks.is_empty() { Config::clean_foundry_etherscan_chain_cache(chain)?; @@ -154,7 +150,7 @@ pub struct ChainOrAllValueParser { impl Default for ChainOrAllValueParser { fn default() -> Self { - ChainOrAllValueParser { inner: possible_chains() } + Self { inner: possible_chains() } } } @@ -177,7 +173,7 @@ impl TypedValueParser for ChainOrAllValueParser { } fn possible_chains() -> PossibleValuesParser { - Some(&"all").into_iter().chain(Chain::VARIANTS).into() + Some(&"all").into_iter().chain(NamedChain::VARIANTS).into() } #[cfg(test)] diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs new file mode 100644 index 0000000000000..481b68632e840 --- /dev/null +++ b/crates/forge/bin/cmd/clone.rs @@ -0,0 +1,803 @@ +use super::{init::InitArgs, install::DependencyInstallOpts}; +use alloy_primitives::{Address, Bytes, ChainId, TxHash}; +use clap::{Parser, ValueHint}; +use eyre::Result; +use foundry_block_explorers::{ + contract::{ContractCreationData, ContractMetadata, Metadata}, + errors::EtherscanError, + Client, +}; +use foundry_cli::{ + opts::EtherscanOpts, + utils::{Git, LoadConfig}, +}; +use foundry_common::{compile::ProjectCompiler, fs}; +use foundry_compilers::{ + artifacts::{ + output_selection::ContractOutputSelection, + remappings::{RelativeRemapping, Remapping}, + ConfigurableContractArtifact, Settings, StorageLayout, + }, + compilers::solc::Solc, + ProjectCompileOutput, ProjectPathsConfig, +}; +use foundry_config::{Chain, Config}; +use std::{ + fs::read_dir, + path::{Path, PathBuf}, + time::Duration, +}; + +/// CloneMetadata stores the metadata that are not included by `foundry.toml` but necessary for a +/// cloned contract. The metadata can be serialized to a metadata file in the cloned project root. +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloneMetadata { + /// The path to the source file that contains the contract declaration. + /// The path is relative to the root directory of the project. + pub path: PathBuf, + /// The name of the contract in the file. + pub target_contract: String, + /// The address of the contract on the blockchain. + pub address: Address, + /// The chain id. + pub chain_id: ChainId, + /// The transaction hash of the creation transaction. + pub creation_transaction: TxHash, + /// The address of the deployer, i.e., sender of the creation transaction. + pub deployer: Address, + /// The constructor arguments of the contract on chain. + pub constructor_arguments: Bytes, + /// The storage layout of the contract on chain. + pub storage_layout: StorageLayout, +} + +/// CLI arguments for `forge clone`. +/// +/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan) in the +/// following steps: +/// 1. Fetch the contract source code from the block explorer. +/// 2. Initialize a empty foundry project at the `root` directory specified in `CloneArgs`. +/// 3. Dump the contract sources to the source directory. +/// 4. Update the `foundry.toml` configuration file with the compiler settings from Etherscan. +/// 5. Try compile the cloned contract, so that we can get the original storage layout. This +/// original storage layout is preserved in the `CloneMetadata` so that if the user later +/// modifies the contract, it is possible to quickly check the storage layout compatibility with +/// the original on-chain contract. +/// 6. Dump the `CloneMetadata` to the root directory of the cloned project as `.clone.meta` file. +#[derive(Clone, Debug, Parser)] +pub struct CloneArgs { + /// The contract address to clone. + pub address: Address, + + /// The root directory of the cloned project. + #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] + pub root: PathBuf, + + /// Do not generate the remappings.txt file. Instead, keep the remappings in the configuration. + #[arg(long)] + pub no_remappings_txt: bool, + + /// Keep the original directory structure collected from Etherscan. + /// + /// If this flag is set, the directory structure of the cloned project will be kept as is. + /// By default, the directory structure is re-orgnized to increase the readability, but may + /// risk some compilation failures. + #[arg(long)] + pub keep_directory_structure: bool, + + #[command(flatten)] + pub etherscan: EtherscanOpts, + + #[command(flatten)] + pub install: DependencyInstallOpts, +} + +impl CloneArgs { + pub async fn run(self) -> Result<()> { + let Self { address, root, install, etherscan, no_remappings_txt, keep_directory_structure } = + self; + + // step 0. get the chain and api key from the config + let config = etherscan.load_config()?; + let chain = config.chain.unwrap_or_default(); + let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, etherscan_api_key.clone())?; + + // step 1. get the metadata from client + sh_println!("Downloading the source code of {address} from Etherscan...")?; + + let meta = Self::collect_metadata_from_client(address, &client).await?; + + // step 2. initialize an empty project + Self::init_an_empty_project(&root, install)?; + // canonicalize the root path + // note that at this point, the root directory must have been created + let root = dunce::canonicalize(&root)?; + + // step 3. parse the metadata + Self::parse_metadata(&meta, chain, &root, no_remappings_txt, keep_directory_structure) + .await?; + + // step 4. collect the compilation metadata + // if the etherscan api key is not set, we need to wait for 3 seconds between calls + sh_println!("Collecting the creation information of {address} from Etherscan...")?; + + if etherscan_api_key.is_empty() { + sh_warn!("Waiting for 5 seconds to avoid rate limit...")?; + tokio::time::sleep(Duration::from_secs(5)).await; + } + Self::collect_compilation_metadata(&meta, chain, address, &root, &client).await?; + + // step 5. git add and commit the changes if needed + if install.commit { + let git = Git::new(&root); + git.add(Some("--all"))?; + let msg = format!("chore: forge clone {address}"); + git.commit(&msg)?; + } + + Ok(()) + } + + /// Collect the metadata of the contract from the block explorer. + /// + /// * `address` - the address of the contract to be cloned. + /// * `client` - the client of the block explorer. + pub(crate) async fn collect_metadata_from_client( + address: Address, + client: &C, + ) -> Result { + let mut meta = client.contract_source_code(address).await?; + eyre::ensure!(meta.items.len() == 1, "contract not found or ill-formed"); + let meta = meta.items.remove(0); + eyre::ensure!(!meta.is_vyper(), "Vyper contracts are not supported"); + Ok(meta) + } + + /// Initialize an empty project at the root directory. + /// + /// * `root` - the root directory of the project. + /// * `enable_git` - whether to enable git for the project. + /// * `quiet` - whether to print messages. + pub(crate) fn init_an_empty_project(root: &Path, install: DependencyInstallOpts) -> Result<()> { + // let's try to init the project with default init args + let init_args = InitArgs { root: root.to_path_buf(), install, ..Default::default() }; + init_args.run().map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; + + // remove the unnecessary example contracts + // XXX (ZZ): this is a temporary solution until we have a proper way to remove contracts, + // e.g., add a field in the InitArgs to control the example contract generation + fs::remove_file(root.join("src/Counter.sol"))?; + fs::remove_file(root.join("test/Counter.t.sol"))?; + fs::remove_file(root.join("script/Counter.s.sol"))?; + + Ok(()) + } + + /// Collect the compilation metadata of the cloned contract. + /// This function compiles the cloned contract and collects the compilation metadata. + /// + /// * `meta` - the metadata of the contract (from Etherscan). + /// * `chain` - the chain where the contract to be cloned locates. + /// * `address` - the address of the contract to be cloned. + /// * `root` - the root directory of the cloned project. + /// * `client` - the client of the block explorer. + pub(crate) async fn collect_compilation_metadata( + meta: &Metadata, + chain: Chain, + address: Address, + root: &PathBuf, + client: &C, + ) -> Result<()> { + // compile the cloned contract + let compile_output = compile_project(root)?; + let (main_file, main_artifact) = find_main_contract(&compile_output, &meta.contract_name)?; + let main_file = main_file.strip_prefix(root)?.to_path_buf(); + let storage_layout = + main_artifact.storage_layout.to_owned().expect("storage layout not found"); + + // dump the metadata to the root directory + let creation_tx = client.contract_creation_data(address).await?; + let clone_meta = CloneMetadata { + path: main_file, + target_contract: meta.contract_name.clone(), + address, + chain_id: chain.id(), + creation_transaction: creation_tx.transaction_hash, + deployer: creation_tx.contract_creator, + constructor_arguments: meta.constructor_arguments.clone(), + storage_layout, + }; + let metadata_content = serde_json::to_string(&clone_meta)?; + let metadata_file = root.join(".clone.meta"); + fs::write(&metadata_file, metadata_content)?; + let mut perms = std::fs::metadata(&metadata_file)?.permissions(); + perms.set_readonly(true); + std::fs::set_permissions(&metadata_file, perms)?; + + Ok(()) + } + + /// Download and parse the source code from Etherscan. + /// + /// * `chain` - the chain where the contract to be cloned locates. + /// * `address` - the address of the contract to be cloned. + /// * `root` - the root directory to clone the contract into as a foundry project. + /// * `client` - the client of the block explorer. + /// * `no_remappings_txt` - whether to generate the remappings.txt file. + pub(crate) async fn parse_metadata( + meta: &Metadata, + chain: Chain, + root: &PathBuf, + no_remappings_txt: bool, + keep_directory_structure: bool, + ) -> Result<()> { + // dump sources and update the remapping in configuration + let remappings = dump_sources(meta, root, keep_directory_structure)?; + Config::update_at(root, |config, doc| { + let profile = config.profile.as_str().as_str(); + + // update the remappings in the configuration + let mut remapping_array = toml_edit::Array::new(); + for r in remappings { + remapping_array.push(r.to_string()); + } + doc[Config::PROFILE_SECTION][profile]["remappings"] = toml_edit::value(remapping_array); + + // make sure auto_detect_remappings is false (it is very important because cloned + // project may not follow the common remappings) + doc[Config::PROFILE_SECTION][profile]["auto_detect_remappings"] = + toml_edit::value(false); + true + })?; + + // update configuration + Config::update_at(root, |config, doc| { + update_config_by_metadata(config, doc, meta, chain).is_ok() + })?; + + // write remappings to remappings.txt if necessary + if !no_remappings_txt { + let remappings_txt = root.join("remappings.txt"); + eyre::ensure!( + !remappings_txt.exists(), + "remappings.txt already exists, please remove it first" + ); + + Config::update_at(root, |config, doc| { + let remappings_txt_content = + config.remappings.iter().map(|r| r.to_string()).collect::>().join("\n"); + if fs::write(&remappings_txt, remappings_txt_content).is_err() { + return false; + } + + let profile = config.profile.as_str().as_str(); + if let Some(elem) = doc[Config::PROFILE_SECTION][profile].as_table_mut() { + elem.remove_entry("remappings"); + true + } else { + false + } + })?; + } + + Ok(()) + } +} + +/// Update the configuration file with the metadata. +/// This function will update the configuration file with the metadata from the contract. +/// It will update the following fields: +/// - `auto_detect_solc` to `false` +/// - `solc_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 +/// - `cbor_metadata`, `use_literal_content`, and `bytecode_hash` +/// - `optimizer` to the value from the metadata +/// - `optimizer_runs` to the value from the metadata +/// - `optimizer_details` to the value from the metadata +/// - `yul_details`, `yul`, etc. +/// - `simpleCounterForLoopUncheckedIncrement` is ignored for now +/// - `remappings` and `stop_after` are pre-validated to be empty and None, respectively +/// - `model_checker`, `debug`, and `output_selection` are ignored for now +/// +/// Detailed information can be found from the following link: +/// - https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +/// - https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description +fn update_config_by_metadata( + config: &Config, + doc: &mut toml_edit::DocumentMut, + meta: &Metadata, + chain: Chain, +) -> Result<()> { + let profile = config.profile.as_str().as_str(); + + // macro to update the config if the value exists + macro_rules! update_if_needed { + ([$($key:expr),+], $value:expr) => { + { + if let Some(value) = $value { + let mut current = &mut doc[Config::PROFILE_SECTION][profile]; + $( + if let Some(nested_doc) = current.get_mut(&$key) { + current = nested_doc; + } else { + return Err(eyre::eyre!("cannot find the key: {}", $key)); + } + )+ + *current = toml_edit::value(value); + } + } + }; + } + + // update the chain id + doc[Config::PROFILE_SECTION][profile]["chain_id"] = toml_edit::value(chain.id() as i64); + + // disable auto detect solc and set the solc version + doc[Config::PROFILE_SECTION][profile]["auto_detect_solc"] = toml_edit::value(false); + let version = meta.compiler_version()?; + doc[Config::PROFILE_SECTION][profile]["solc_version"] = + toml_edit::value(format!("{}.{}.{}", version.major, version.minor, version.patch)); + + // get optimizer settings + // we ignore `model_checker`, `debug`, and `output_selection` for now, + // it seems they do not have impacts on the actual compilation + let Settings { optimizer, libraries, evm_version, via_ir, stop_after, metadata, .. } = + meta.settings()?; + eyre::ensure!(stop_after.is_none(), "stop_after should be None"); + + update_if_needed!(["evm_version"], evm_version.map(|v| v.to_string())); + update_if_needed!(["via_ir"], via_ir); + + // update metadata if needed + if let Some(metadata) = metadata { + update_if_needed!(["cbor_metadata"], metadata.cbor_metadata); + update_if_needed!(["use_literal_content"], metadata.use_literal_content); + update_if_needed!(["bytecode_hash"], metadata.bytecode_hash.map(|v| v.to_string())); + } + + // update optimizer settings if needed + update_if_needed!(["optimizer"], optimizer.enabled); + update_if_needed!(["optimizer_runs"], optimizer.runs.map(|v| v as i64)); + // update optimizer details if needed + if let Some(detail) = optimizer.details { + doc[Config::PROFILE_SECTION][profile]["optimizer_details"] = toml_edit::table(); + + update_if_needed!(["optimizer_details", "peephole"], detail.peephole); + update_if_needed!(["optimizer_details", "inliner"], detail.inliner); + update_if_needed!(["optimizer_details", "jumpdestRemover"], detail.jumpdest_remover); + update_if_needed!(["optimizer_details", "orderLiterals"], detail.order_literals); + update_if_needed!(["optimizer_details", "deduplicate"], detail.deduplicate); + update_if_needed!(["optimizer_details", "cse"], detail.cse); + update_if_needed!(["optimizer_details", "constantOptimizer"], detail.constant_optimizer); + update_if_needed!( + ["optimizer_details", "simpleCounterForLoopUncheckedIncrement"], + detail.simple_counter_for_loop_unchecked_increment + ); + update_if_needed!(["optimizer_details", "yul"], detail.yul); + + if let Some(yul_detail) = detail.yul_details { + doc[Config::PROFILE_SECTION][profile]["optimizer_details"]["yulDetails"] = + toml_edit::table(); + update_if_needed!( + ["optimizer_details", "yulDetails", "stackAllocation"], + yul_detail.stack_allocation + ); + update_if_needed!( + ["optimizer_details", "yulDetails", "optimizerSteps"], + yul_detail.optimizer_steps + ); + } + } + + // apply remapping on libraries + let path_config: ProjectPathsConfig = config.project_paths(); + let libraries = libraries + .apply(|libs| path_config.apply_lib_remappings(libs)) + .with_stripped_file_prefixes(&path_config.root); + + // update libraries + let mut lib_array = toml_edit::Array::new(); + for (path_to_lib, info) in libraries.libs { + for (lib_name, address) in info { + lib_array.push(format!("{}:{}:{}", path_to_lib.to_str().unwrap(), lib_name, address)); + } + } + doc[Config::PROFILE_SECTION][profile]["libraries"] = toml_edit::value(lib_array); + + Ok(()) +} + +/// Dump the contract sources to the root directory. +/// The sources are dumped to the `src` directory. +/// IO errors may be returned. +/// A list of remappings is returned +fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result> { + // get config + let path_config = ProjectPathsConfig::builder().build_with_root::(root); + // we will canonicalize the sources directory later + let src_dir = &path_config.sources; + let lib_dir = &path_config.libraries[0]; + let contract_name = &meta.contract_name; + let source_tree = meta.source_tree(); + + // then we move the sources to the correct directories + // we will first load existing remappings if necessary + // make sure this happens before dumping sources + let mut remappings: Vec = Remapping::find_many(root); + + // first we dump the sources to a temporary directory + let tmp_dump_dir = root.join("raw_sources"); + source_tree + .write_to(&tmp_dump_dir) + .map_err(|e| eyre::eyre!("failed to dump sources: {}", e))?; + + // check whether we need to re-organize directories in the original sources, since we do not + // want to put all the sources in the `src` directory if the original directory structure is + // well organized, e.g., a standard foundry project containing `src` and `lib` + // + // * if the user wants to keep the original directory structure, we should not re-organize. + // * if there is any other directory other than `src`, `contracts`, `lib`, `hardhat`, + // `forge-std`, + // or not started with `@`, we should not re-organize. + let to_reorg = !no_reorg && + std::fs::read_dir(tmp_dump_dir.join(contract_name))?.all(|e| { + let Ok(e) = e else { return false }; + let folder_name = e.file_name(); + folder_name == "src" || + folder_name == "lib" || + folder_name == "contracts" || + folder_name == "hardhat" || + folder_name == "forge-std" || + folder_name.to_string_lossy().starts_with('@') + }); + + // ensure `src` and `lib` directories exist + eyre::ensure!(Path::exists(&root.join(src_dir)), "`src` directory must exists"); + eyre::ensure!(Path::exists(&root.join(lib_dir)), "`lib` directory must exists"); + + // move source files + for entry in std::fs::read_dir(tmp_dump_dir.join(contract_name))? { + let entry = entry?; + let folder_name = entry.file_name(); + // special handling when we need to re-organize the directories: we flatten them. + if to_reorg { + if folder_name == "contracts" || folder_name == "src" || folder_name == "lib" { + // move all sub folders in contracts to src or lib + let new_dir = if folder_name == "lib" { lib_dir } else { src_dir }; + for e in read_dir(entry.path())? { + let e = e?; + let dest = new_dir.join(e.file_name()); + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(e.path(), &dest)?; + remappings.push(Remapping { + context: None, + name: format!( + "{}/{}", + folder_name.to_string_lossy(), + e.file_name().to_string_lossy() + ), + path: dest.to_string_lossy().to_string(), + }); + } + } else { + assert!( + folder_name == "hardhat" || + folder_name == "forge-std" || + folder_name.to_string_lossy().starts_with('@') + ); + // move these other folders to lib + let dest = lib_dir.join(&folder_name); + if folder_name == "forge-std" { + // let's use the provided forge-std directory + std::fs::remove_dir_all(&dest)?; + } + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(entry.path(), &dest)?; + remappings.push(Remapping { + context: None, + name: folder_name.to_string_lossy().to_string(), + path: dest.to_string_lossy().to_string(), + }); + } + } else { + // directly move the all folders into src + let dest = src_dir.join(&folder_name); + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(entry.path(), &dest)?; + if folder_name != "src" { + remappings.push(Remapping { + context: None, + name: folder_name.to_string_lossy().to_string(), + path: dest.to_string_lossy().to_string(), + }); + } + } + } + + // remove the temporary directory + std::fs::remove_dir_all(tmp_dump_dir)?; + + // add remappings in the metedata + for mut r in meta.settings()?.remappings { + if to_reorg { + // we should update its remapped path in the same way as we dump sources + // i.e., remove prefix `contracts` (if any) and add prefix `src` + let new_path = if r.path.starts_with("contracts") { + PathBuf::from("src").join(PathBuf::from(&r.path).strip_prefix("contracts")?) + } else if r.path.starts_with('@') || + r.path.starts_with("hardhat/") || + r.path.starts_with("forge-std/") + { + PathBuf::from("lib").join(PathBuf::from(&r.path)) + } else { + PathBuf::from(&r.path) + }; + r.path = new_path.to_string_lossy().to_string(); + remappings.push(r); + } else { + remappings.push(r); + } + } + + Ok(remappings.into_iter().map(|r| r.into_relative(root)).collect()) +} + +/// Compile the project in the root directory, and return the compilation result. +pub fn compile_project(root: &Path) -> Result { + let mut config = Config::load_with_root(root)?.sanitized(); + config.extra_output.push(ContractOutputSelection::StorageLayout); + let project = config.project()?; + let compiler = ProjectCompiler::new(); + compiler.compile(&project) +} + +/// Find the artifact of the contract with the specified name. +/// This function returns the path to the source file and the artifact. +pub fn find_main_contract<'a>( + compile_output: &'a ProjectCompileOutput, + contract: &str, +) -> Result<(PathBuf, &'a ConfigurableContractArtifact)> { + let mut rv = None; + for (f, c, a) in compile_output.artifacts_with_files() { + if contract == c { + // it is possible that we have multiple contracts with the same name + // in different files + // instead of throwing an error, we should handle this case in the future + if rv.is_some() { + return Err(eyre::eyre!("multiple contracts with the same name found")); + } + rv = Some((PathBuf::from(f), a)); + } + } + rv.ok_or_else(|| eyre::eyre!("contract not found")) +} + +/// EtherscanClient is a trait that defines the methods to interact with Etherscan. +/// It is defined as a wrapper of the `foundry_block_explorers::Client` to allow mocking. +#[cfg_attr(test, mockall::automock)] +pub(crate) trait EtherscanClient { + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result; + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result; +} + +impl EtherscanClient for Client { + #[inline] + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result { + self.contract_source_code(address).await + } + + #[inline] + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result { + self.contract_creation_data(address).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::hex; + use foundry_compilers::CompilerContract; + use foundry_test_utils::rpc::next_mainnet_etherscan_api_key; + use std::collections::BTreeMap; + + #[allow(clippy::disallowed_macros)] + fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { + println!("project_root: {root:#?}"); + compile_project(root).expect("compilation failure") + } + + fn assert_compilation_result( + compiled: ProjectCompileOutput, + contract_name: &str, + stripped_creation_code: &str, + ) { + compiled.compiled_contracts_by_compiler_version().iter().for_each(|(_, contracts)| { + contracts.iter().for_each(|(name, contract)| { + if name == contract_name { + let compiled_creation_code = + contract.bin_ref().expect("creation code not found"); + assert!( + hex::encode(compiled_creation_code.as_ref()) + .starts_with(stripped_creation_code), + "inconsistent creation code" + ); + } + }); + }); + } + + fn mock_etherscan(address: Address) -> impl super::EtherscanClient { + // load mock data + let mut mocked_data = BTreeMap::new(); + let data_folder = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/etherscan"); + // iterate each sub folder + for entry in std::fs::read_dir(data_folder).expect("failed to read test data folder") { + let entry = entry.expect("failed to read test data entry"); + let addr: Address = entry.file_name().to_string_lossy().parse().unwrap(); + let contract_data_dir = entry.path(); + // the metadata.json file contains the metadata of the contract + let metadata_file = contract_data_dir.join("metadata.json"); + let metadata: ContractMetadata = + serde_json::from_str(&std::fs::read_to_string(metadata_file).unwrap()) + .expect("failed to parse metadata.json"); + // the creation_data.json file contains the creation data of the contract + let creation_data_file = contract_data_dir.join("creation_data.json"); + let creation_data: ContractCreationData = + serde_json::from_str(&std::fs::read_to_string(creation_data_file).unwrap()) + .expect("failed to parse creation_data.json"); + // insert the data to the map + mocked_data.insert(addr, (metadata, creation_data)); + } + + let (metadata, creation_data) = mocked_data.get(&address).unwrap(); + let metadata = metadata.clone(); + let creation_data = *creation_data; + let mut mocked_client = super::MockEtherscanClient::new(); + mocked_client + .expect_contract_source_code() + .times(1) + .returning(move |_| Ok(metadata.clone())); + mocked_client + .expect_contract_creation_data() + .times(1) + .returning(move |_| Ok(creation_data)); + mocked_client + } + + /// Fetch the metadata and creation data from Etherscan and dump them to the testdata folder. + #[tokio::test(flavor = "multi_thread")] + #[ignore = "this test is used to dump mock data from Etherscan"] + async fn test_dump_mock_data() { + let address: Address = "0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F".parse().unwrap(); + let data_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/etherscan") + .join(address.to_string()); + // create folder if not exists + std::fs::create_dir_all(&data_folder).unwrap(); + // create metadata.json and creation_data.json + let client = Client::new(Chain::mainnet(), next_mainnet_etherscan_api_key()).unwrap(); + let meta = client.contract_source_code(address).await.unwrap(); + // dump json + let json = serde_json::to_string_pretty(&meta).unwrap(); + // write to metadata.json + std::fs::write(data_folder.join("metadata.json"), json).unwrap(); + let creation_data = client.contract_creation_data(address).await.unwrap(); + // dump json + let json = serde_json::to_string_pretty(&creation_data).unwrap(); + // write to creation_data.json + std::fs::write(data_folder.join("creation_data.json"), json).unwrap(); + } + + /// Run the clone command with the specified contract address and assert the compilation. + async fn one_test_case(address: Address, check_compilation_result: bool) { + let mut project_root = tempfile::tempdir().unwrap().path().to_path_buf(); + let client = mock_etherscan(address); + let meta = CloneArgs::collect_metadata_from_client(address, &client).await.unwrap(); + CloneArgs::init_an_empty_project(&project_root, DependencyInstallOpts::default()).unwrap(); + project_root = dunce::canonicalize(&project_root).unwrap(); + CloneArgs::parse_metadata(&meta, Chain::mainnet(), &project_root, false, false) + .await + .unwrap(); + CloneArgs::collect_compilation_metadata( + &meta, + Chain::mainnet(), + address, + &project_root, + &client, + ) + .await + .unwrap(); + let rv = assert_successful_compilation(&project_root); + if check_compilation_result { + let (contract_name, stripped_creation_code) = + pick_creation_info(&address.to_string()).expect("creation code not found"); + assert_compilation_result(rv, contract_name, stripped_creation_code); + } + std::fs::remove_dir_all(project_root).unwrap(); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_single_file_contract() { + let address = "0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_optimization_details() { + let address = "0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_libraries() { + let address = "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_metadata() { + let address = "0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_relative_import() { + let address = "0x3a23F943181408EAC424116Af7b7790c94Cb97a5".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_original_remappings() { + let address = "0x9ab6b21cdf116f611110b048987e58894786c244".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_relative_import2() { + let address = "0x044b75f554b886A065b9567891e45c79542d7357".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_nested_src() { + let address = "0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F".parse().unwrap(); + one_test_case(address, false).await + } + + fn pick_creation_info(address: &str) -> Option<(&'static str, &'static str)> { + for (addr, contract_name, creation_code) in CREATION_ARRAY.iter() { + if address == *addr { + return Some((contract_name, creation_code)); + } + } + + None + } + + // remember to remove CBOR metadata from the creation code + const CREATION_ARRAY: [(&str, &str, &str); 4] = [ + ("0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193", "BearXNFTStaking", "608060405234801561001057600080fd5b50613000806100206000396000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80638129fc1c11610130578063bca35a71116100b8578063dada55011161007c578063dada550114610458578063f2fde38b1461046b578063f83d08ba1461047e578063fbb0022714610486578063fccd7f721461048e57600080fd5b8063bca35a71146103fa578063bf9befb11461040d578063c89d5b8b14610416578063d5d423001461041e578063d976e09f1461042657600080fd5b8063b1c92f95116100ff578063b1c92f95146103c5578063b549445c146103ce578063b81f8e89146103d6578063b9ade5b7146103de578063ba0848db146103e757600080fd5b80638129fc1c146103905780638da5cb5b14610398578063aaed083b146103a9578063b10dcc93146103b257600080fd5b8063367c164e116101b35780635923489b116101825780635923489b146103245780636e2751211461034f578063706ce3e114610362578063715018a614610375578063760a2e8a1461037d57600080fd5b8063367c164e146102bd57806338ff8a85146102d05780633a17f4f0146102f1578063426233601461030457600080fd5b8063206635e7116101fa578063206635e71461026d5780632afe761a146102805780632bd30f1114610289578063305f839a146102ab57806333ddacd1146102b457600080fd5b8062944f621461022b5780630d00368b146102405780630e8feed41461025c578063120957fd14610264575b600080fd5b61023e610239366004612aa4565b6104bc565b005b61024960735481565b6040519081526020015b60405180910390f35b61023e61053a565b610249606d5481565b61023e61027b366004612b2c565b61057e565b610249606f5481565b60785461029b90610100900460ff1681565b6040519015158152602001610253565b61024960715481565b61024960765481565b61023e6102cb366004612bc2565b6105d1565b6102e36102de366004612aa4565b610829565b604051610253929190612c16565b61023e6102ff366004612aa4565b6109e1565b610317610312366004612aa4565b610a56565b6040516102539190612c2f565b606a54610337906001600160a01b031681565b6040516001600160a01b039091168152602001610253565b6102e361035d366004612aa4565b610b4c565b606b54610337906001600160a01b031681565b61023e610cf8565b61029b61038b366004612aa4565b610d2e565b61023e610dc2565b6033546001600160a01b0316610337565b61024960705481565b61023e6103c0366004612b2c565b610fc0565b610249606e5481565b61023e611236565b61023e6112bb565b61024960725481565b6102e36103f5366004612aa4565b6112ef565b61023e610408366004612aa4565b61149b565b610249606c5481565b610249611510565b606f54610249565b610439610434366004612aa4565b611594565b6040805192151583526001600160a01b03909116602083015201610253565b606954610337906001600160a01b031681565b61023e610479366004612aa4565b6115cf565b61023e611667565b61023e6116a0565b6104a161049c366004612aa4565b6116e1565b60408051938452602084019290925290820152606001610253565b6033546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690612c42565b60405180910390fd5b606b546001600160a01b03163b6105185760405162461bcd60e51b81526004016104e690612c77565b606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061054533611594565b509050806105655760405162461bcd60e51b81526004016104e690612cae565b61056e33610d2e565b61057b5761057b33611c73565b50565b610587336116e1565b505060765560005b81518110156105cd576105bb8282815181106105ad576105ad612cda565b602002602001015133611d7b565b806105c581612d06565b91505061058f565b5050565b606a54604051636eb1769f60e11b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b602482015282916001600160a01b03169063dd62ed3e90604401602060405180830381865afa158015610633573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106579190612d21565b10156106bb5760405162461bcd60e51b815260206004820152602d60248201527f596f75206861766520746f20617070726f766520726f6f747820746f2073746160448201526c1ada5b99c818dbdb9d1c9858dd609a1b60648201526084016104e6565b606a546040516323b872dd60e01b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b6024820152604481018390526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015610726573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074a9190612d3a565b50606a546040516326c7e79d60e21b8152600481018390526001600160a01b0390911690639b1f9e7490602401600060405180830381600087803b15801561079157600080fd5b505af11580156107a5573d6000803e3d6000fd5b5050606b546001600160a01b031691506379c650689050336107c88460056120a1565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561080e57600080fd5b505af1158015610822573d6000803e3d6000fd5b5050505050565b600060606000805b6001600160a01b0385166000908152607460205260409020548110156108bc576001600160a01b0385166000908152607460205260409020805461089791908390811061088057610880612cda565b906000526020600020906005020160000154612129565b156108aa576108a7600183612d5c565b91505b806108b481612d06565b915050610831565b5060008167ffffffffffffffff8111156108d8576108d8612ac1565b604051908082528060200260200182016040528015610901578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461095791908390811061088057610880612cda565b156109c3576001600160a01b038716600090815260746020526040902080548290811061098657610986612cda565b9060005260206000209060050201600001548383815181106109aa576109aa612cda565b60209081029190910101526109c0826001612154565b91505b806109cd81612d06565b915050610908565b50919590945092505050565b6033546001600160a01b03163314610a0b5760405162461bcd60e51b81526004016104e690612c42565b6069546001600160a01b03163b610a345760405162461bcd60e51b81526004016104e690612c77565b606980546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0381166000908152607460205260408120546060919067ffffffffffffffff811115610a8b57610a8b612ac1565b604051908082528060200260200182016040528015610ab4578160200160208202803683370190505b50905060005b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b0384166000908152607460205260409020805482908110610b0457610b04612cda565b906000526020600020906005020160000154828281518110610b2857610b28612cda565b602090810291909101015280610b3d81612d06565b915050610aba565b5092915050565b600060606000805b6001600160a01b038516600090815260746020526040902054811015610bdf576001600160a01b03851660009081526074602052604090208054610bba919083908110610ba357610ba3612cda565b9060005260206000209060050201600001546121b3565b15610bcd57610bca826001612154565b91505b80610bd781612d06565b915050610b54565b5060008167ffffffffffffffff811115610bfb57610bfb612ac1565b604051908082528060200260200182016040528015610c24578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b03871660009081526074602052604090208054610c7a919083908110610ba357610ba3612cda565b15610ce6576001600160a01b0387166000908152607460205260409020805482908110610ca957610ca9612cda565b906000526020600020906005020160000154838381518110610ccd57610ccd612cda565b6020908102919091010152610ce3826001612154565b91505b80610cf081612d06565b915050610c2b565b6033546001600160a01b03163314610d225760405162461bcd60e51b81526004016104e690612c42565b610d2c60006121d0565b565b60006001815b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b03841660009081526074602052604081208054610d9a919084908110610d8357610d83612cda565b906000526020600020906005020160010154612222565b9050603c8111610daa5750610db0565b60009250505b80610dba81612d06565b915050610d34565b600054610100900460ff16610ddd5760005460ff1615610de1565b303b155b610e445760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104e6565b600054610100900460ff16158015610e66576000805461ffff19166101011790555b610e6e61223c565b606580546001600160a01b0319908116737a250d5630b4cf539739df2c5dacb4c659f2488d1790915560668054821673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2179055620151806067556312cc030060685560698054821673e22e1e620dffb03065cd77db0162249c0c91bf01179055606a8054821673d718ad25285d65ef4d79262a6cd3aea6a8e01023179055606b80549091167399cfdf48d0ba4885a73786148a2f89d86c7021701790556000606c5568056bc75e2d63100000606d556802b5e3af16b1880000606e55690257058e269742680000606f819055681b1ae4d6e2ef5000006070819055610bb8607181905591610f709190612d74565b610f7a9190612d8b565b607255607154606e54606d54610f909190612d74565b610f9a9190612d8b565b60735560006076556078805460ff19169055801561057b576000805461ff001916905550565b6000610fcb33611594565b50905080610feb5760405162461bcd60e51b81526004016104e690612cae565b607854610100900460ff161561102c5760405162461bcd60e51b8152602060048201526006602482015265131bd8dad95960d21b60448201526064016104e6565b600061103733610a56565b90508051835111156110775760405162461bcd60e51b81526020600482015260096024820152684964206572726f727360b81b60448201526064016104e6565b6000805b84518110156110fd5760005b83518110156110ea578381815181106110a2576110a2612cda565b60200260200101518683815181106110bc576110bc612cda565b602002602001015114156110d8576110d5836001612154565b92505b806110e281612d06565b915050611087565b50806110f581612d06565b91505061107b565b50835181141561123057835161112761111e82678ac7230489e800006120a1565b606f5490612154565b606f55611132612273565b600060768190555b855181101561122d5760695486516001600160a01b03909116906323b872dd90309033908a908690811061117057611170612cda565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b5050606c80549250905060006111f383612dad565b919050555061121b3387838151811061120e5761120e612cda565b602002602001015161229b565b8061122581612d06565b91505061113a565b50505b50505050565b60785460ff166112ac5760005b60755481101561057b576001607760006075848154811061126657611266612cda565b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055806112a481612d06565b915050611243565b6078805460ff19166001179055565b60006112c633611594565b509050806112e65760405162461bcd60e51b81526004016104e690612cae565b61057b33612470565b600060606000805b6001600160a01b038516600090815260746020526040902054811015611382576001600160a01b0385166000908152607460205260409020805461135d91908390811061134657611346612cda565b906000526020600020906005020160000154612574565b156113705761136d600183612d5c565b91505b8061137a81612d06565b9150506112f7565b5060008167ffffffffffffffff81111561139e5761139e612ac1565b6040519080825280602002602001820160405280156113c7578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461141d91908390811061134657611346612cda565b15611489576001600160a01b038716600090815260746020526040902080548290811061144c5761144c612cda565b90600052602060002090600502016000015483838151811061147057611470612cda565b6020908102919091010152611486826001612154565b91505b8061149381612d06565b9150506113ce565b6033546001600160a01b031633146114c55760405162461bcd60e51b81526004016104e690612c42565b606a546001600160a01b03163b6114ee5760405162461bcd60e51b81526004016104e690612c77565b606a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000806064606f5460016115249190612dc4565b61152e9190612d8b565b611539906001612d5c565b606d546115469190612dc4565b90506000606d54826115589190612d74565b905060006001606d548361156c9190612d8b565b6115769190612d8b565b611581906001612dc4565b61158c906064612dc4565b949350505050565b6001600160a01b038116600090815260776020526040812054819060ff161515600114156115c457506001929050565b506000928392509050565b6033546001600160a01b031633146115f95760405162461bcd60e51b81526004016104e690612c42565b6001600160a01b03811661165e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016104e6565b61057b816121d0565b73d0d725208fd36be1561050fc1dd6a651d7ea7c89331415610d2c576078805461ff001981166101009182900460ff1615909102179055565b60006116ab33611594565b509050806116cb5760405162461bcd60e51b81526004016104e690612cae565b6116d433610d2e565b61057b5761057b336125aa565b600080808080808087816116f482610829565b509050600061170283610b4c565b50905060058110611a3b57600160005b6001600160a01b038516600090815260746020526040902054811015611a0e576001600160a01b0385166000908152607460205260408120805461177891908490811061176157611761612cda565b906000526020600020906005020160040154612222565b6001600160a01b038716600090815260746020526040902080549192506117a99184908110610ba357610ba3612cda565b806117de57506001600160a01b038616600090815260746020526040902080546117de91908490811061088057610880612cda565b80156117ea5750600181105b156117f457600092505b82801561181857506001600160a01b03861660009081526074602052604090205415155b156118715761186a8561182c83600a612dc4565b6118369190612dc4565b6118648661184585600a612dc4565b61184f9190612dc4565b60765461186490670de0b6b3a7640000612798565b90612154565b995061188e565b8261188e5760765461188b90670de0b6b3a7640000612798565b99505b6001600160a01b038616600090815260746020526040902080546118bd91908490811061088057610880612cda565b15611950576001600160a01b038616600090815260746020526040812080546119089190859081106118f1576118f1612cda565b906000526020600020906005020160020154612222565b90508061192861192182680ad78ebc5ac6200000612dc4565b8c90612154565b9a5061194761194082680ad78ebc5ac6200000612dc4565b8b90612154565b995050506119fb565b6001600160a01b0386166000908152607460205260409020805461197f919084908110610ba357610ba3612cda565b156119fb576001600160a01b038616600090815260746020526040812080546119b39190859081106118f1576118f1612cda565b905060008190506119d76002606d54846119cd9190612dc4565b6119219190612d8b565b9a506119f66002606d54836119ec9190612dc4565b6119409190612d8b565b995050505b5080611a0681612d06565b915050611712565b508515611a3557606b54606654611a32916001600160a01b039081169116886127da565b94505b50611c4f565b60005b6001600160a01b038416600090815260746020526040902054811015611c28576001600160a01b03841660009081526074602052604090208054611a8d91908390811061088057610880612cda565b15611b9f576001600160a01b03841660009081526074602052604081208054611ac191908490811061176157611761612cda565b9050611ad8611ad182600a612dc4565b8a90612154565b98506000611b1560746000886001600160a01b03166001600160a01b0316815260200190815260200160002084815481106118f1576118f1612cda565b9050611b2d611ad182680ad78ebc5ac6200000612dc4565b98506000611b8160746000896001600160a01b03166001600160a01b031681526020019081526020016000208581548110611b6a57611b6a612cda565b906000526020600020906005020160030154612222565b9050611b99611ad182680ad78ebc5ac6200000612dc4565b98505050505b6001600160a01b03841660009081526074602052604090208054611bce919083908110610ba357610ba3612cda565b15611c16576001600160a01b03841660009081526074602052604081208054611c0291908490811061176157611761612cda565b9050611c12611ad182600a612dc4565b9850505b80611c2081612d06565b915050611a3e565b508415611c4f57606b54606654611c4c916001600160a01b039081169116876127da565b93505b611c6187670de0b6b3a7640000612dc4565b9b959a50929850939650505050505050565b6000611c7e826116e1565b5091505080156105cd57606b5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af1158015611cdb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cff9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b0383166000908152607460205260409020805442919083908110611d5057611d50612cda565b600091825260209091206002600590920201015580611d6e81612d06565b915050611d03565b505050565b607054606f5410611db857607354606d6000828254611d9a9190612d74565b9091555050606f54611db490678ac7230489e80000612905565b606f555b6069546040516331a9108f60e11b8152600481018490526001600160a01b03838116921690636352211e90602401602060405180830381865afa158015611e03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e279190612de3565b6001600160a01b031614611e7d5760405162461bcd60e51b815260206004820152601e60248201527f596f7520617265206e6f742061206f776e6572206f6620746865206e6674000060448201526064016104e6565b60695460405163e985e9c560e01b81523360048201523060248201526001600160a01b039091169063e985e9c590604401602060405180830381865afa158015611ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611eef9190612d3a565b1515600114611f575760405162461bcd60e51b815260206004820152602e60248201527f596f752073686f756c6420617070726f7665206e667420746f2074686520737460448201526d185ada5b99c818dbdb9d1c9858dd60921b60648201526084016104e6565b6069546040516323b872dd60e01b81526001600160a01b03838116600483015230602483015260448201859052909116906323b872dd90606401600060405180830381600087803b158015611fab57600080fd5b505af1158015611fbf573d6000803e3d6000fd5b505050506000611fce82611594565b50905060006040518060a001604052808581526020014281526020014281526020014281526020014281525090506120126001606c5461215490919063ffffffff16565b606c556001600160a01b03831660009081526074602090815260408083208054600181810183559185529383902085516005909502019384559184015191830191909155820151600282015560608201516003820155608082015160049091015581611230576001600160a01b0383166000908152607760205260409020805460ff1916600117905550505050565b6000826120b057506000612123565b60006120bc8385612dc4565b9050826120c98583612d8b565b146121205760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b90505b92915050565b60008064e8d4a510008310158015612146575064e8d4a510058311155b156121235750600192915050565b6000806121618385612d5c565b9050838110156121205760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b600080610e7483116121c757506001612123565b50600092915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6067546000906122328342612d74565b6121239190612d8b565b600054610100900460ff166122635760405162461bcd60e51b81526004016104e690612e00565b61226b612947565b610d2c61296e565b61227c33612470565b61228533610d2e565b610d2c5761229233611c73565b610d2c336125aa565b60005b6001600160a01b038316600090815260746020526040902054811015612428576001600160a01b03831660009081526074602052604090208054839190839081106122eb576122eb612cda565b9060005260206000209060050201600001541415612416576001600160a01b0383166000908152607460205260409020805461232990600190612d74565b8154811061233957612339612cda565b906000526020600020906005020160746000856001600160a01b03166001600160a01b03168152602001908152602001600020828154811061237d5761237d612cda565b60009182526020808320845460059093020191825560018085015490830155600280850154908301556003808501549083015560049384015493909101929092556001600160a01b03851681526074909152604090208054806123e2576123e2612e4b565b6000828152602081206005600019909301928302018181556001810182905560028101829055600381018290556004015590555b8061242081612d06565b91505061229e565b506001600160a01b0382166000908152607460205260409020546105cd576001600160a01b038216600090815260746020526040812061246791612a3f565b6105cd8261299e565b600061247b826116e1565b509091505080156105cd57606a5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af11580156124d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fd9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b038316600090815260746020526040902080544291908390811061254e5761254e612cda565b60009182526020909120600460059092020101558061256c81612d06565b915050612501565b60006509184e72a00682101561258c57506000919050565b6509184e72b4b38211156125a257506000919050565b506001919050565b60006125b5826116e1565b5091505080156105cd57606b5460655460405163095ea7b360e01b81526001600160a01b0391821660048201526024810184905291169063095ea7b3906044016020604051808303816000875af1158015612614573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126389190612d3a565b5060408051600280825260608083018452926020830190803683375050606b5482519293506001600160a01b03169183915060009061267957612679612cda565b6001600160a01b0392831660209182029290920101526066548251911690829060019081106126aa576126aa612cda565b6001600160a01b03928316602091820292909201015260655460405163791ac94760e01b815291169063791ac947906126f0908590600090869089904290600401612e9a565b600060405180830381600087803b15801561270a57600080fd5b505af115801561271e573d6000803e3d6000fd5b5050505060005b6001600160a01b038416600090815260746020526040902054811015611230576001600160a01b038416600090815260746020526040902080544291908390811061277257612772612cda565b60009182526020909120600360059092020101558061279081612d06565b915050612725565b600061212083836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506129d7565b6040805160028082526060808301845260009390929190602083019080368337019050509050848160008151811061281457612814612cda565b60200260200101906001600160a01b031690816001600160a01b031681525050838160018151811061284857612848612cda565b6001600160a01b03928316602091820292909201015260655460405163d06ca61f60e01b8152600092919091169063d06ca61f9061288c9087908690600401612ed6565b600060405180830381865afa1580156128a9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128d19190810190612eef565b905080600183516128e29190612d74565b815181106128f2576128f2612cda565b6020026020010151925050509392505050565b600061212083836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612a0e565b600054610100900460ff16610d2c5760405162461bcd60e51b81526004016104e690612e00565b600054610100900460ff166129955760405162461bcd60e51b81526004016104e690612e00565b610d2c336121d0565b6000806129aa83611594565b915091508115611d76576001600160a01b03166000908152607760205260409020805460ff191690555050565b600081836129f85760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d8b565b95945050505050565b60008184841115612a325760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d74565b508054600082556005029060005260206000209081019061057b91905b80821115612a8b5760008082556001820181905560028201819055600382018190556004820155600501612a5c565b5090565b6001600160a01b038116811461057b57600080fd5b600060208284031215612ab657600080fd5b813561212081612a8f565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612b0057612b00612ac1565b604052919050565b600067ffffffffffffffff821115612b2257612b22612ac1565b5060051b60200190565b60006020808385031215612b3f57600080fd5b823567ffffffffffffffff811115612b5657600080fd5b8301601f81018513612b6757600080fd5b8035612b7a612b7582612b08565b612ad7565b81815260059190911b82018301908381019087831115612b9957600080fd5b928401925b82841015612bb757833582529284019290840190612b9e565b979650505050505050565b600060208284031215612bd457600080fd5b5035919050565b600081518084526020808501945080840160005b83811015612c0b57815187529582019590820190600101612bef565b509495945050505050565b82815260406020820152600061158c6040830184612bdb565b6020815260006121206020830184612bdb565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526017908201527f41646472657373206973206e6f7420636f6e7472616374000000000000000000604082015260600190565b6020808252601290820152712cb7ba9030b932903737ba1039ba30b5b2b960711b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d1a57612d1a612cf0565b5060010190565b600060208284031215612d3357600080fd5b5051919050565b600060208284031215612d4c57600080fd5b8151801515811461212057600080fd5b60008219821115612d6f57612d6f612cf0565b500190565b600082821015612d8657612d86612cf0565b500390565b600082612da857634e487b7160e01b600052601260045260246000fd5b500490565b600081612dbc57612dbc612cf0565b506000190190565b6000816000190483118215151615612dde57612dde612cf0565b500290565b600060208284031215612df557600080fd5b815161212081612a8f565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052603160045260246000fd5b600081518084526020808501945080840160005b83811015612c0b5781516001600160a01b031687529582019590820190600101612e75565b85815284602082015260a060408201526000612eb960a0830186612e61565b6001600160a01b0394909416606083015250608001529392505050565b82815260406020820152600061158c6040830184612e61565b60006020808385031215612f0257600080fd5b825167ffffffffffffffff811115612f1957600080fd5b8301601f81018513612f2a57600080fd5b8051612f38612b7582612b08565b81815260059190911b82018301908381019087831115612f5757600080fd5b928401925b82841015612bb757835182529284019290840190612f5c565b600060208083528351808285015260005b81811015612fa257858101830151858201604001528201612f86565b81811115612fb4576000604083870101525b50601f01601f191692909201604001939250505056fe"), + ("0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545", "GovernorCharlieDelegate", "608060405234801561001057600080fd5b50613e45806100206000396000f3fe60806040526004361061031a5760003560e01c80637b3c71d3116101ab578063d50572ee116100f7578063f0843ba811610095578063fc176c041161006f578063fc176c0414610b82578063fc4eee4214610ba2578063fc66ff1414610bb8578063fe0d94c114610bd857600080fd5b8063f0843ba814610b12578063f2b0653714610b32578063f682e04c14610b6257600080fd5b8063de7bc127116100d1578063de7bc127146109ec578063deaaa7cc14610a02578063e23a9a5214610a36578063e837159c14610afc57600080fd5b8063d50572ee146109a0578063da35c664146109b6578063ddf0b009146109cc57600080fd5b8063a6d8784a11610164578063c1a287e21161013e578063c1a287e214610933578063c4d66de81461094a578063c5a8425d1461096a578063c9fb9e871461098a57600080fd5b8063a6d8784a146108e7578063abaac6a8146108fd578063b58131b01461091d57600080fd5b80637b3c71d31461083c5780637bdbe4d01461085c5780637cae57bb14610871578063806bd5811461088757806386d37e8b146108a757806399533365146108c757600080fd5b80632fedff591161026a5780633e4f49e61161022357806350442098116101fd578063504420981461074657806356781388146107665780635c60da1b1461078657806366176743146107be57600080fd5b80633e4f49e6146106d957806340e58ee5146107065780634d6733d21461072657600080fd5b80632fedff59146105ee578063328dd9821461060e57806338bd0dda1461063e5780633932abb11461066b5780633af32abf146106815780633bccf4fd146106b957600080fd5b8063158ef93e116102d757806318b62629116102b157806318b626291461056e5780631dfb1b5a1461058457806320606b70146105a457806324bc1a64146105d857600080fd5b8063158ef93e146104f757806317977c611461052157806317ba1b8b1461054e57600080fd5b8063013cf08b1461031f57806302a251a31461042857806306fdde031461044c5780630825f38f146104a25780630ea2d98c146104b7578063140499ea146104d7575b600080fd5b34801561032b57600080fd5b506103b361033a3660046132ee565b60096020819052600091825260409091208054600182015460028301546007840154600885015495850154600a860154600b870154600c880154600d890154600e9099015497996001600160a01b0390971698959794969593949293919260ff808316936101008404821693620100009004909116918d565b604080519d8e526001600160a01b03909c1660208e01529a8c019990995260608b019790975260808a019590955260a089019390935260c088019190915260e08701521515610100860152151561012085015215156101408401526101608301526101808201526101a0015b60405180910390f35b34801561043457600080fd5b5061043e60045481565b60405190815260200161041f565b34801561045857600080fd5b506104956040518060400160405280601a81526020017f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000081525081565b60405161041f9190613363565b6104b56104b0366004613450565b610beb565b005b3480156104c357600080fd5b506104b56104d23660046132ee565b610e61565b3480156104e357600080fd5b506104b56104f23660046134d6565b610ec6565b34801561050357600080fd5b506012546105119060ff1681565b604051901515815260200161041f565b34801561052d57600080fd5b5061043e61053c3660046134d6565b600a6020526000908152604090205481565b34801561055a57600080fd5b506104b56105693660046132ee565b610f07565b34801561057a57600080fd5b5061043e600f5481565b34801561059057600080fd5b506104b561059f3660046132ee565b610f64565b3480156105b057600080fd5b5061043e7f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b3480156105e457600080fd5b5061043e60015481565b3480156105fa57600080fd5b506104b56106093660046132ee565b610fc1565b34801561061a57600080fd5b5061062e6106293660046132ee565b61101e565b60405161041f94939291906135ba565b34801561064a57600080fd5b5061043e6106593660046134d6565b600d6020526000908152604090205481565b34801561067757600080fd5b5061043e60035481565b34801561068d57600080fd5b5061051161069c3660046134d6565b6001600160a01b03166000908152600d6020526040902054421090565b3480156106c557600080fd5b506104b56106d4366004613623565b6112af565b3480156106e557600080fd5b506106f96106f43660046132ee565b611516565b60405161041f9190613687565b34801561071257600080fd5b506104b56107213660046132ee565b61169e565b34801561073257600080fd5b506104b56107413660046136af565b611b80565b34801561075257600080fd5b506104b56107613660046132ee565b611c45565b34801561077257600080fd5b506104b56107813660046136d9565b611ca2565b34801561079257600080fd5b506000546107a6906001600160a01b031681565b6040516001600160a01b03909116815260200161041f565b3480156107ca57600080fd5b506108146107d9366004613705565b601160209081526000928352604080842090915290825290205460ff808216916101008104909116906201000090046001600160601b031683565b60408051931515845260ff90921660208401526001600160601b03169082015260600161041f565b34801561084857600080fd5b506104b5610857366004613728565b611d09565b34801561086857600080fd5b5061043e600a81565b34801561087d57600080fd5b5061043e600c5481565b34801561089357600080fd5b506104b56108a23660046132ee565b611d58565b3480156108b357600080fd5b506104b56108c23660046132ee565b611db5565b3480156108d357600080fd5b506104b56108e23660046134d6565b611e12565b3480156108f357600080fd5b5061043e60155481565b34801561090957600080fd5b506104b56109183660046132ee565b611e8b565b34801561092957600080fd5b5061043e60055481565b34801561093f57600080fd5b5061043e6212750081565b34801561095657600080fd5b506104b56109653660046134d6565b611ee8565b34801561097657600080fd5b50600e546107a6906001600160a01b031681565b34801561099657600080fd5b5061043e60135481565b3480156109ac57600080fd5b5061043e60025481565b3480156109c257600080fd5b5061043e60075481565b3480156109d857600080fd5b506104b56109e73660046132ee565b611fd9565b3480156109f857600080fd5b5061043e60105481565b348015610a0e57600080fd5b5061043e7f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f81565b348015610a4257600080fd5b50610acc610a51366004613705565b60408051606081018252600080825260208201819052918101919091525060009182526011602090815260408084206001600160a01b03939093168452918152918190208151606081018352905460ff8082161515835261010082041693820193909352620100009092046001600160601b03169082015290565b6040805182511515815260208084015160ff1690820152918101516001600160601b03169082015260600161041f565b348015610b0857600080fd5b5061043e60145481565b348015610b1e57600080fd5b506104b5610b2d3660046132ee565b61238f565b348015610b3e57600080fd5b50610511610b4d3660046132ee565b600b6020526000908152604090205460ff1681565b348015610b6e57600080fd5b5061043e610b7d3660046139b0565b6123ec565b348015610b8e57600080fd5b506104b5610b9d3660046132ee565b612a48565b348015610bae57600080fd5b5061043e60065481565b348015610bc457600080fd5b506008546107a6906001600160a01b031681565b6104b5610be63660046132ee565b612aa5565b60008585858585604051602001610c06959493929190613a91565b60408051601f1981840301815291815281516020928301206000818152600b90935291205490915060ff16610c7b5760405162461bcd60e51b81526020600482015260166024820152753a3c103430b9b713ba103132b2b71038bab2bab2b21760511b60448201526064015b60405180910390fd5b81421015610ccb5760405162461bcd60e51b815260206004820152601d60248201527f7478206861736e2774207375727061737365642074696d656c6f636b2e0000006044820152606401610c72565b610cd86212750083613af3565b421115610d165760405162461bcd60e51b815260206004820152600c60248201526b3a3c1034b99039ba30b6329760a11b6044820152606401610c72565b6000818152600b60205260409020805460ff191690558351606090610d3c575082610d68565b848051906020012084604051602001610d56929190613b0b565b60405160208183030381529060405290505b6000876001600160a01b03168783604051610d839190613b3c565b60006040518083038185875af1925050503d8060008114610dc0576040519150601f19603f3d011682016040523d82523d6000602084013e610dc5565b606091505b5050905080610e0f5760405162461bcd60e51b81526020600482015260166024820152753a3c1032bc32b1baba34b7b7103932bb32b93a32b21760511b6044820152606401610c72565b876001600160a01b0316837fa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e789898989604051610e4f9493929190613b58565b60405180910390a35050505050505050565b333014610e805760405162461bcd60e51b8152600401610c7290613b95565b600480549082905560408051828152602081018490527f7e3f7f0708a84de9203036abaa450dccc85ad5ff52f78c170f3edb55cf5e882891015b60405180910390a15050565b333014610ee55760405162461bcd60e51b8152600401610c7290613b95565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610f265760405162461bcd60e51b8152600401610c7290613b95565b600580549082905560408051828152602081018490527fccb45da8d5717e6c4544694297c4ba5cf151d455c9bb0ed4fc7a38411bc054619101610eba565b333014610f835760405162461bcd60e51b8152600401610c7290613b95565b600380549082905560408051828152602081018490527fc565b045403dc03c2eea82b81a0465edad9e2e7fc4d97e11421c209da93d7a939101610eba565b333014610fe05760405162461bcd60e51b8152600401610c7290613b95565b601480549082905560408051828152602081018490527f519a192fe8db9e38785eb494c69f530ddb21b9e34322f8d08fe29bd3849749889101610eba565b606080606080600060096000878152602001908152602001600020905080600301816004018260050183600601838054806020026020016040519081016040528092919081815260200182805480156110a057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611082575b50505050509350828054806020026020016040519081016040528092919081815260200182805480156110f257602002820191906000526020600020905b8154815260200190600101908083116110de575b5050505050925081805480602002602001604051908101604052809291908181526020016000905b828210156111c657838290600052602060002001805461113990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461116590613bcc565b80156111b25780601f10611187576101008083540402835291602001916111b2565b820191906000526020600020905b81548152906001019060200180831161119557829003601f168201915b50505050508152602001906001019061111a565b50505050915080805480602002602001604051908101604052809291908181526020016000905b8282101561129957838290600052602060002001805461120c90613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461123890613bcc565b80156112855780601f1061125a57610100808354040283529160200191611285565b820191906000526020600020905b81548152906001019060200180831161126857829003601f168201915b5050505050815260200190600101906111ed565b5050505090509450945094509450509193509193565b604080518082018252601a81527f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000060209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f75a838dcd8ee5903cc7f4a5799344d0080864f57a6e9911f8bdfb4c8ddce9b5481840152466060820152306080808301919091528351808303909101815260a0820184528051908301207f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f60c083015260e0820189905260ff8816610100808401919091528451808403909101815261012083019094528351939092019290922061190160f01b6101408401526101428301829052610162830181905290916000906101820160408051601f198184030181528282528051602091820120600080855291840180845281905260ff8a169284019290925260608301889052608083018790529092509060019060a0016020604051602081039080840390855afa15801561143c573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661149f5760405162461bcd60e51b815260206004820181905260248201527f63617374566f746542795369673a20696e76616c6964207369676e61747572656044820152606401610c72565b88816001600160a01b03167fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda48a6114d7858e8e612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a3505050505050505050565b6000816007541015801561152b575060065482115b6115775760405162461bcd60e51b815260206004820152601a60248201527f73746174653a20696e76616c69642070726f706f73616c2069640000000000006044820152606401610c72565b600082815260096020908152604080832060018101546001600160a01b03168452600d90925290912054600c82015442919091109060ff16156115be575060029392505050565b816007015443116115d3575060009392505050565b816008015443116115e8575060019392505050565b8080156115fc575081600d015482600a0154115b80611618575080158015611618575081600a0154826009015411155b80611633575080158015611633575081600d01548260090154105b15611642575060039392505050565b6002820154611655575060049392505050565b600c820154610100900460ff1615611671575060079392505050565b6212750082600201546116849190613af3565b4210611694575060069392505050565b5060059392505050565b60076116a982611516565b60078111156116ba576116ba613671565b14156117085760405162461bcd60e51b815260206004820152601d60248201527f63616e742063616e63656c2065786563757465642070726f706f73616c0000006044820152606401610c72565b600081815260096020526040902060018101546001600160a01b0316336001600160a01b0316146119755760018101546001600160a01b03166000908152600d6020526040902054421015611878576005546008546001838101546001600160a01b039283169263782d6fe1929116906117829043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613c1e565b6001600160601b03161080156118275750600e546001600160a01b0316336001600160a01b0316145b6118735760405162461bcd60e51b815260206004820152601c60248201527f63616e63656c3a2077686974656c69737465642070726f706f736572000000006044820152606401610c72565b611975565b6005546008546001838101546001600160a01b039283169263782d6fe1929116906118a39043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156118e757600080fd5b505afa1580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190613c1e565b6001600160601b0316106119755760405162461bcd60e51b815260206004820181905260248201527f63616e63656c3a2070726f706f7365722061626f7665207468726573686f6c646044820152606401610c72565b600c8101805460ff1916600117905560005b6003820154811015611b5057611b3e8260030182815481106119ab576119ab613c47565b6000918252602090912001546004840180546001600160a01b0390921691849081106119d9576119d9613c47565b90600052602060002001548460050184815481106119f9576119f9613c47565b906000526020600020018054611a0e90613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611a3a90613bcc565b8015611a875780601f10611a5c57610100808354040283529160200191611a87565b820191906000526020600020905b815481529060010190602001808311611a6a57829003601f168201915b5050505050856006018581548110611aa157611aa1613c47565b906000526020600020018054611ab690613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611ae290613bcc565b8015611b2f5780601f10611b0457610100808354040283529160200191611b2f565b820191906000526020600020905b815481529060010190602001808311611b1257829003601f168201915b50505050508660020154612f12565b80611b4881613c5d565b915050611987565b5060405182907f789cf55be980739dad1d0699b93b58e806b51c9d96619bfa8fe0a28abaa7b30c90600090a25050565b333014611b9f5760405162461bcd60e51b8152600401610c7290613b95565b42601554611bad9190613af3565b8110611bf45760405162461bcd60e51b81526020600482015260166024820152750caf0e0d2e4c2e8d2dedc40caf0c6cacac8e640dac2f60531b6044820152606401610c72565b6001600160a01b0382166000818152600d6020908152604091829020849055815192835282018390527f4e7b7545bc5744d0e30425959f4687475774b6c7edad77d24cb51c7d967d45159101610eba565b333014611c645760405162461bcd60e51b8152600401610c7290613b95565b601080549082905560408051828152602081018490527f2a61b867418a359864adca8bb250ea65ee8bd41dbfd0279198d8e7552d4a27c29101610eba565b81337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda483611cd1838583612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a35050565b83337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda485611d38838583612c90565b8686604051611d4a9493929190613c78565b60405180910390a350505050565b333014611d775760405162461bcd60e51b8152600401610c7290613b95565b601380549082905560408051828152602081018490527f8cb5451eee8feb516cec9cd600201bbc31a30886d70c841a085a3fa69a4294d19101610eba565b333014611dd45760405162461bcd60e51b8152600401610c7290613b95565b600180549082905560408051828152602081018490527fa74554b0f53da47d07ec571d712428b3720460f54f81375fbcf78f6b5f72e7ed9101610eba565b333014611e315760405162461bcd60e51b8152600401610c7290613b95565b600e80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f80a07e73e552148844a9c216d9724212d609cfa54e9c1a2e97203bdd2c4ad3419101610eba565b333014611eaa5760405162461bcd60e51b8152600401610c7290613b95565b600f80549082905560408051828152602081018490527f80a384652af83fc00bfd40ef94edda7ede83e7db39931b2c889821573f314e239101610eba565b60125460ff1615611f3b5760405162461bcd60e51b815260206004820152601860248201527f616c7265616479206265656e20696e697469616c697a656400000000000000006044820152606401610c72565b600880546001600160a01b0319166001600160a01b0392909216919091179055619d8060045561335460035569d3c21bcecceda10000006005556202a300600c5560006007556a084595161401484a00000060019081556a21165458500521280000006002556119aa600f5561a8c06010556a01a784379d99db420000006013556146506014556301e133806015556012805460ff19169091179055565b6004611fe482611516565b6007811115611ff557611ff5613671565b146120425760405162461bcd60e51b815260206004820152601f60248201527f63616e206f6e6c792062652071756575656420696620737563636565646564006044820152606401610c72565b6000818152600960205260408120600e8101549091906120629042613af3565b905060005b600383015481101561234d57600b600084600301838154811061208c5761208c613c47565b6000918252602090912001546004860180546001600160a01b0390921691859081106120ba576120ba613c47565b90600052602060002001548660050185815481106120da576120da613c47565b906000526020600020018760060186815481106120f9576120f9613c47565b9060005260206000200187604051602001612118959493929190613d62565b60408051601f198184030181529181528151602092830120835290820192909252016000205460ff161561218e5760405162461bcd60e51b815260206004820152601760248201527f70726f706f73616c20616c7265616479207175657565640000000000000000006044820152606401610c72565b61233a8360030182815481106121a6576121a6613c47565b6000918252602090912001546004850180546001600160a01b0390921691849081106121d4576121d4613c47565b90600052602060002001548560050184815481106121f4576121f4613c47565b90600052602060002001805461220990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461223590613bcc565b80156122825780601f1061225757610100808354040283529160200191612282565b820191906000526020600020905b81548152906001019060200180831161226557829003601f168201915b505050505086600601858154811061229c5761229c613c47565b9060005260206000200180546122b190613bcc565b80601f01602080910402602001604051908101604052809291908181526020018280546122dd90613bcc565b801561232a5780601f106122ff5761010080835404028352916020019161232a565b820191906000526020600020905b81548152906001019060200180831161230d57829003601f168201915b50505050508688600e0154612fac565b508061234581613c5d565b915050612067565b506002820181905560405181815283907f9a2e42fd6722813d69113e7d0079d3d940171428df7373df9c7f7617cfda28929060200160405180910390a2505050565b3330146123ae5760405162461bcd60e51b8152600401610c7290613b95565b600280549082905560408051828152602081018490527fc2adf06da6765dba7faaccde4c0ce3f91c35dd3390e7f0b6bc2844202c9fa9529101610eba565b6000600154600014156124365760405162461bcd60e51b8152602060048201526012602482015271436861726c6965206e6f742061637469766560701b6044820152606401610c72565b6005546008546001600160a01b031663782d6fe133612456600143613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b15801561249a57600080fd5b505afa1580156124ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124d29190613c1e565b6001600160601b03161015806124ec57506124ec3361069c565b6125385760405162461bcd60e51b815260206004820152601e60248201527f766f7465732062656c6f772070726f706f73616c207468726573686f6c6400006044820152606401610c72565b8551875114801561254a575084518751145b8015612557575083518751145b6125a35760405162461bcd60e51b815260206004820152601a60248201527f696e666f726d6174696f6e206172697479206d69736d617463680000000000006044820152606401610c72565b86516125e85760405162461bcd60e51b81526020600482015260146024820152736d7573742070726f7669646520616374696f6e7360601b6044820152606401610c72565b600a8751111561262d5760405162461bcd60e51b815260206004820152601060248201526f746f6f206d616e7920616374696f6e7360801b6044820152606401610c72565b336000908152600a6020526040902054801561271657600061264e82611516565b9050600181600781111561266457612664613671565b14156126b25760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b60008160078111156126c6576126c6613671565b14156127145760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b505b6007805490600061272683613c5d565b9190505550600060405180610220016040528060075481526020016127483390565b6001600160a01b03168152602001600081526020018a8152602001898152602001888152602001878152602001600354436127839190613af3565b8152602001600454600354436127999190613af3565b6127a39190613af3565b815260200160008152602001600081526020016000815260200160001515815260200160001515815260200185151581526020016001548152602001600c5481525090508380156127fa57506127f83361069c565b155b1561282c574360e08201819052600f5461281391613af3565b6101008201526002546101e08201526010546102008201525b6128353361069c565b15612876576013546101e08201526014546128509043613af3565b60e08201526004546014546128659043613af3565b61286f9190613af3565b6101008201525b805160009081526009602090815260409182902083518155818401516001820180546001600160a01b0319166001600160a01b03909216919091179055918301516002830155606083015180518493926128d792600385019291019061309d565b50608082015180516128f3916004840191602090910190613102565b5060a0820151805161290f91600584019160209091019061313d565b5060c0820151805161292b916006840191602090910190613196565b5060e08281015160078301556101008084015160088401556101208401516009840155610140840151600a80850191909155610160850151600b850155610180850151600c850180546101a08801516101c089015161ffff1990921693151561ff0019169390931792151585029290921762ff0000191662010000921515929092029190911790556101e0850151600d85015561020090940151600e9093019290925583516020808601516001600160a01b0316600090815294905260409384902055830151835191840151925190923392917f7d84a6263ae0d98d3329bd7b46bb4e8d6f98cd35a7adb45c274c8b7fd5ebd5e091612a33918f918f918f918f918f90613d9b565b60405180910390a45198975050505050505050565b333014612a675760405162461bcd60e51b8152600401610c7290613b95565b600c80549082905560408051828152602081018490527fed0229422af39d4d7d33f7a27d31d6f5cb20ec628293da58dd6e8a528ed466be9101610eba565b6005612ab082611516565b6007811115612ac157612ac1613671565b14612b0e5760405162461bcd60e51b815260206004820152601c60248201527f63616e206f6e6c792062652065786563276420696620717565756564000000006044820152606401610c72565b6000818152600960205260408120600c8101805461ff001916610100179055905b6003820154811015612c6057306001600160a01b0316630825f38f836004018381548110612b5f57612b5f613c47565b9060005260206000200154846003018481548110612b7f57612b7f613c47565b6000918252602090912001546004860180546001600160a01b039092169186908110612bad57612bad613c47565b9060005260206000200154866005018681548110612bcd57612bcd613c47565b90600052602060002001876006018781548110612bec57612bec613c47565b9060005260206000200188600201546040518763ffffffff1660e01b8152600401612c1b959493929190613d62565b6000604051808303818588803b158015612c3457600080fd5b505af1158015612c48573d6000803e3d6000fd5b50505050508080612c5890613c5d565b915050612b2f565b5060405182907f712ae1383f79ac853f8d882153778e0260ef8f03b504e2866e0593e04d2b291f90600090a25050565b60006001612c9d84611516565b6007811115612cae57612cae613671565b14612cee5760405162461bcd60e51b815260206004820152601060248201526f1d9bdd1a5b99c81a5cc818db1bdcd95960821b6044820152606401610c72565b60028260ff161115612d365760405162461bcd60e51b8152602060048201526011602482015270696e76616c696420766f7465207479706560781b6044820152606401610c72565b6000838152600960209081526040808320601183528184206001600160a01b0389168552909252909120805460ff1615612da85760405162461bcd60e51b81526020600482015260136024820152721d9bdd195c88185b1c9958591e481d9bdd1959606a1b6044820152606401610c72565b600854600783015460405163782d6fe160e01b81526000926001600160a01b03169163782d6fe191612df2918b916004016001600160a01b03929092168252602082015260400190565b60206040518083038186803b158015612e0a57600080fd5b505afa158015612e1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e429190613c1e565b905060ff8516612e6f57806001600160601b031683600a0154612e659190613af3565b600a840155612ec9565b8460ff1660011415612e9e57806001600160601b03168360090154612e949190613af3565b6009840155612ec9565b8460ff1660021415612ec957806001600160601b031683600b0154612ec39190613af3565b600b8401555b81546001600160601b03821662010000026dffffffffffffffffffffffff00001960ff88166101000261ffff199093169290921760011791909116179091559150509392505050565b60008585858585604051602001612f2d959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916905591506001600160a01b0387169082907f2fffc091a501fd91bfbff27141450d3acb40fb8e6d8382b243ec7a812a3aaf8790612f9c908990899089908990613b58565b60405180910390a3505050505050565b6000612fb88242613af3565b831015612ffd5760405162461bcd60e51b815260206004820152601360248201527236bab9ba1039b0ba34b9b33c903232b630bc9760691b6044820152606401610c72565b60008787878787604051602001613018959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916600117905591506001600160a01b0389169082907f76e2796dc3a81d57b0e8504b647febcbeeb5f4af818e164f11eef8131a6a763f9061308a908b908b908b908b90613b58565b60405180910390a3979650505050505050565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906130bd565b506130fe9291506131ef565b5090565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f2578251825591602001919060010190613122565b82805482825590600052602060002090810192821561318a579160200282015b8281111561318a578251805161317a918491602090910190613204565b509160200191906001019061315d565b506130fe929150613277565b8280548282559060005260206000209081019282156131e3579160200282015b828111156131e357825180516131d3918491602090910190613204565b50916020019190600101906131b6565b506130fe929150613294565b5b808211156130fe57600081556001016131f0565b82805461321090613bcc565b90600052602060002090601f01602090048101928261323257600085556130f2565b82601f1061324b57805160ff19168380011785556130f2565b828001600101855582156130f257918201828111156130f2578251825591602001919060010190613122565b808211156130fe57600061328b82826132b1565b50600101613277565b808211156130fe5760006132a882826132b1565b50600101613294565b5080546132bd90613bcc565b6000825580601f106132cd575050565b601f0160209004906000526020600020908101906132eb91906131ef565b50565b60006020828403121561330057600080fd5b5035919050565b60005b8381101561332257818101518382015260200161330a565b83811115613331576000848401525b50505050565b6000815180845261334f816020860160208601613307565b601f01601f19169290920160200192915050565b6020815260006133766020830184613337565b9392505050565b80356001600160a01b038116811461339457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156133d8576133d8613399565b604052919050565b600082601f8301126133f157600080fd5b813567ffffffffffffffff81111561340b5761340b613399565b61341e601f8201601f19166020016133af565b81815284602083860101111561343357600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561346857600080fd5b6134718661337d565b945060208601359350604086013567ffffffffffffffff8082111561349557600080fd5b6134a189838a016133e0565b945060608801359150808211156134b757600080fd5b506134c4888289016133e0565b95989497509295608001359392505050565b6000602082840312156134e857600080fd5b6133768261337d565b600081518084526020808501945080840160005b8381101561352a5781516001600160a01b031687529582019590820190600101613505565b509495945050505050565b600081518084526020808501945080840160005b8381101561352a57815187529582019590820190600101613549565b600081518084526020808501808196508360051b8101915082860160005b858110156135ad57828403895261359b848351613337565b98850198935090840190600101613583565b5091979650505050505050565b6080815260006135cd60808301876134f1565b82810360208401526135df8187613535565b905082810360408401526135f38186613565565b905082810360608401526136078185613565565b979650505050505050565b803560ff8116811461339457600080fd5b600080600080600060a0868803121561363b57600080fd5b8535945061364b60208701613612565b935061365960408701613612565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052602160045260246000fd5b60208101600883106136a957634e487b7160e01b600052602160045260246000fd5b91905290565b600080604083850312156136c257600080fd5b6136cb8361337d565b946020939093013593505050565b600080604083850312156136ec57600080fd5b823591506136fc60208401613612565b90509250929050565b6000806040838503121561371857600080fd5b823591506136fc6020840161337d565b6000806000806060858703121561373e57600080fd5b8435935061374e60208601613612565b9250604085013567ffffffffffffffff8082111561376b57600080fd5b818701915087601f83011261377f57600080fd5b81358181111561378e57600080fd5b8860208285010111156137a057600080fd5b95989497505060200194505050565b600067ffffffffffffffff8211156137c9576137c9613399565b5060051b60200190565b600082601f8301126137e457600080fd5b813560206137f96137f4836137af565b6133af565b82815260059290921b8401810191818101908684111561381857600080fd5b8286015b8481101561383a5761382d8161337d565b835291830191830161381c565b509695505050505050565b600082601f83011261385657600080fd5b813560206138666137f4836137af565b82815260059290921b8401810191818101908684111561388557600080fd5b8286015b8481101561383a5780358352918301918301613889565b600082601f8301126138b157600080fd5b813560206138c16137f4836137af565b82815260059290921b840181019181810190868411156138e057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139045760008081fd5b6139128986838b01016133e0565b8452509183019183016138e4565b600082601f83011261393157600080fd5b813560206139416137f4836137af565b82815260059290921b8401810191818101908684111561396057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139845760008081fd5b6139928986838b01016133e0565b845250918301918301613964565b8035801515811461339457600080fd5b60008060008060008060c087890312156139c957600080fd5b863567ffffffffffffffff808211156139e157600080fd5b6139ed8a838b016137d3565b97506020890135915080821115613a0357600080fd5b613a0f8a838b01613845565b96506040890135915080821115613a2557600080fd5b613a318a838b016138a0565b95506060890135915080821115613a4757600080fd5b613a538a838b01613920565b94506080890135915080821115613a6957600080fd5b50613a7689828a016133e0565b925050613a8560a088016139a0565b90509295509295509295565b60018060a01b038616815284602082015260a060408201526000613ab860a0830186613337565b8281036060840152613aca8186613337565b9150508260808301529695505050505050565b634e487b7160e01b600052601160045260246000fd5b60008219821115613b0657613b06613add565b500190565b6001600160e01b0319831681528151600090613b2e816004850160208701613307565b919091016004019392505050565b60008251613b4e818460208701613307565b9190910192915050565b848152608060208201526000613b716080830186613337565b8281036040840152613b838186613337565b91505082606083015295945050505050565b60208082526017908201527f6d75737420636f6d652066726f6d2074686520676f762e000000000000000000604082015260600190565b600181811c90821680613be057607f821691505b60208210811415613c0157634e487b7160e01b600052602260045260246000fd5b50919050565b600082821015613c1957613c19613add565b500390565b600060208284031215613c3057600080fd5b81516001600160601b038116811461337657600080fd5b634e487b7160e01b600052603260045260246000fd5b6000600019821415613c7157613c71613add565b5060010190565b60ff851681526001600160601b038416602082015260606040820152816060820152818360808301376000818301608090810191909152601f909201601f191601019392505050565b8054600090600181811c9080831680613cdb57607f831692505b6020808410821415613cfd57634e487b7160e01b600052602260045260246000fd5b838852818015613d145760018114613d2857613d56565b60ff19861689830152604089019650613d56565b876000528160002060005b86811015613d4e5781548b8201850152908501908301613d33565b8a0183019750505b50505050505092915050565b60018060a01b038616815284602082015260a060408201526000613d8960a0830186613cc1565b8281036060840152613aca8186613cc1565b60c081526000613dae60c08301896134f1565b8281036020840152613dc08189613535565b90508281036040840152613dd48188613565565b90508281036060840152613de88187613565565b905084608084015282810360a0840152613e028185613337565b999850505050505050505056fe"), + ("0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", "PoolExercise", "6101c06040523480156200001257600080fd5b50604051620030713803806200307183398101604081905262000035916200016a565b6001600160a01b038681166101005285811660805284811660a05283811660c052821660e052600f81900b61012052858585858585620000846000808062000101602090811b6200011917901c565b6101408181525050620000a660016000806200010160201b620001191760201c565b6101608181525050620000c860026000806200010160201b620001191760201c565b6101808181525050620000ea60036000806200010160201b620001191760201c565b6101a05250620002319a5050505050505050505050565b600081600f0b6080846001600160401b0316901b60f88660078111156200012c576200012c620001f4565b6200013992911b6200020a565b6200014591906200020a565b949350505050565b80516001600160a01b03811681146200016557600080fd5b919050565b60008060008060008060c087890312156200018457600080fd5b6200018f876200014d565b95506200019f602088016200014d565b9450620001af604088016200014d565b9350620001bf606088016200014d565b9250620001cf608088016200014d565b915060a087015180600f0b8114620001e657600080fd5b809150509295509295509295565b634e487b7160e01b600052602160045260246000fd5b600082198211156200022c57634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051612d6f6200030260003960008181610e1c015261137b015260008181610e420152818161135101526113f4015260008181611327015281816114b801526122c90152600081816112fe015281816113cb0152818161148f015281816114e101526122ef0152600081816103a8015281816107ed0152610cda0152600050506000818161176601526117b201526000610485015260008181611964015261212c015260005050612d6f6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063477130981461003b578063b50e7ee314610050575b600080fd5b61004e610049366004612986565b610063565b005b61004e61005e3660046129c7565b610109565b336001600160a01b038416146100f9576001600160a01b03831660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff166100f95760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b61010483838361015f565b505050565b6101156000838361015f565b5050565b600081600f0b60808467ffffffffffffffff16901b60f8866007811115610142576101426129e9565b61014d92911b612a15565b6101579190612a15565b949350505050565b608082901c8260006001600160a01b0386161560f883901c600481600781111561018b5761018b6129e9565b14806101a8575060068160078111156101a6576101a66129e9565b145b6101e35760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964207479706560a01b60448201526064016100f0565b8115806101f95750428567ffffffffffffffff16105b6102335760405162461bcd60e51b815260206004820152600b60248201526a1b9bdd08195e1c1a5c995960aa1b60448201526064016100f0565b6004816007811115610247576102476129e9565b149250506000610262600080516020612d1a83398151915290565b9050600061026f826104c0565b9050428667ffffffffffffffff16101561029a576102978267ffffffffffffffff8816610526565b90505b82806102be5750836102b45784600f0b81600f0b126102be565b84600f0b81600f0b135b6102f45760405162461bcd60e51b81526020600482015260076024820152666e6f742049544d60c81b60448201526064016100f0565b6000841561033a5785600f0b82600f0b1315610335576103328861032984610320600f82900b8b61061e565b600f0b90610659565b600f0b906106b1565b90505b610367565b85600f0b82600f0b12156103675761036461035d89610329600f8a900b8661061e565b8490610719565b90505b6000841561038c5761037b89838c89610754565b6103859082612a15565b9050610454565b6103978b8b8b6108cb565b600082156103ff576103d58c6103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b866106b1565b610a5d565b90506103e18183612a15565b91506103ff8c6103f089610a8c565b6103fa8487612a2d565b610ae1565b604080518c8152602081018c9052908101849052606081018290526001600160a01b038d16907f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca19060800160405180910390a2505b610474898361046e6104678a6000610bb1565b8c8c610119565b89610be4565b61047e9082612a15565b90506104b37f00000000000000000000000000000000000000000000000000000000000000006104ad88610e13565b83610e67565b5050505050505050505050565b60004282600c015414156104de576104d88242610e82565b92915050565b6104e782610eb0565b90506104f38242610e82565b600f0b61050557610505824283610fd3565b42600c830155610516826001611051565b610521826000611051565b919050565b600080610535610e1084612a5a565b600881901c6000818152601287016020526040812054929350909160ff84169190821b821c90610568620e100042612a5a565b90505b811580156105795750808411155b156105a65760128801600061058d86612a7c565b955085815260200190815260200160002054915061056b565b600060805b80156105d25783811c156105ca576105c38183612a15565b93811c9391505b60011c6105ab565b5060118901600060018360086105e88a84612a15565b6105f392911b612a2d565b6105fd9190612a2d565b8152602081019190915260400160002054600f0b9998505050505050505050565b6000600f82810b9084900b0360016001607f1b03198112801590610649575060016001607f1b038113155b61065257600080fd5b9392505050565b600081600f0b6000141561066c57600080fd5b600082600f0b604085600f0b901b8161068757610687612a44565b05905060016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000816106c0575060006104d8565b600083600f0b12156106d157600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b0381111561070057600080fd5b60401b811981111561071157600080fd5b019392505050565b600080610737838560030160149054906101000a900460ff166110e1565b9050610157818560030160159054906101000a900460ff166110f7565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb602052604081205b85156108c25760006107a9600161079884611112565b6107a29190612a2d565b839061111c565b905060006107b78287611128565b9050878111156107c45750865b600080881561084657896107d8848b612a97565b6107e29190612a5a565b9150610815846103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b90506108218187612a15565b955061082d828a612a2d565b98506108468461083c89610a8c565b6103fa8486612a2d565b610850838b612a2d565b99506001600160a01b0384167f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca189856108898587612a2d565b604080519384526020840192909252908201526060810184905260800160405180910390a26108b98489856108cb565b50505050610782565b50949350505050565b6001600160a01b03831661092d5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b60648201526084016100f0565b61095b3384600061093d866111db565b610946866111db565b60405180602001604052806000815250611226565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156109fc5760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b60648201526084016100f0565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600080610a6984611762565b9050612710610a788285612a97565b610a829190612a5a565b6101579084612a2d565b600081610ab157600080516020612d1a833981519152546001600160a01b03166104d8565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b80610aeb57505050565b60405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb90604401602060405180830381600087803b158015610b3557600080fd5b505af1158015610b49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6d9190612ab6565b6101045760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b60448201526064016100f0565b60008215610bcf5781610bc5576005610bc8565b60045b90506104d8565b81610bdb576007610652565b60069392505050565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb60205260408120835b8615610e09576000610c3a6001610c2985611112565b610c339190612a2d565b849061111c565b90506000610c488288611128565b905088811115610c555750875b600089610c62838b612a97565b610c6c9190612a5a565b9050610c78818a612a2d565b9850610c84828b612a2d565b9950600087610cc35781610cb4610c9f600f88900b866106b1565b600080516020612d1a83398151915290610719565b610cbe9190612a2d565b610ccd565b610ccd8284612a2d565b90506000610d02856103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b9050610d0e8189612a15565b975082610d2a600080516020612d1a833981519152878c61182c565b15610d5457610d4386610d3d8486612a2d565b8c611869565b610d4d8282612a15565b9050610d7d565b610d7086610d618c610e13565b610d6b8587612a2d565b610e67565b610d7a8382612a15565b90505b610d97600080516020612d1a833981519152878c8461192c565b610da2868c876108cb565b6001600160a01b0386167f69a2ef6bf9e7ff92cbf1b71963ba1751b1abe8f99e3b3aae2ab99e416df614938c610dd88587612a2d565b60408051928352602083019190915281018890526060810185905260800160405180910390a2505050505050610c13565b5050949350505050565b600081610e40577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b61010483838360405180602001604052806000815250611a6c565b60006011830181610e95610e1085612a5a565b8152602081019190915260400160002054600f0b9392505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0357600080fd5b505afa158015610f17573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f3b9190612ad8565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f8f57600080fd5b505afa158015610fa3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc79190612ad8565b90506101578282611b93565b6000610fe1610e1084612a5a565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061101a60ff80831690612a2d565b6001901b846012016000600884901c815260200190815260200160002060008282546110469190612a15565b909155505050505050565b80151560009081526013830160205260409020805415806110725750805442105b1561107c57505050565b60006110888484611c2e565b90506110c384826110bd6110b286600101546110ad898b611c9890919063ffffffff16565b6110e1565b600f86900b90611cc6565b86611cf9565b50501515600090815260139091016020526040812081815560010155565b6000610652836110f284600a612bd5565b611d76565b600061065261110783600a612bd5565b600f85900b906106b1565b60006104d8825490565b60006106528383611dad565b60006001600160a01b0383166111945760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b60648201526084016100f0565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6040805160018082528183019092526060916000919060208083019080368337019050509050828160008151811061121557611215612be4565b602090810291909101015292915050565b611234868686868686611e33565b600080516020612d1a83398151915260005b845181101561175857600085828151811061126357611263612be4565b60200260200101519050600085838151811061128157611281612be4565b60200260200101519050806000141561129b575050611746565b6001600160a01b0389166112b8576112b66015850183612011565b505b6001600160a01b0388161580156112e857506000828152600080516020612cfa8339815191526020526040902054155b156112fc576112fa601585018361201d565b505b7f000000000000000000000000000000000000000000000000000000000000000082148061134957507f000000000000000000000000000000000000000000000000000000000000000082145b8061137357507f000000000000000000000000000000000000000000000000000000000000000082145b8061139d57507f000000000000000000000000000000000000000000000000000000000000000082145b1561148d576001600160a01b038916158015906113c257506001600160a01b03881615155b1561148d5760007f000000000000000000000000000000000000000000000000000000000000000083148061141657507f000000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d870160209081526040808320841515845290915290205490915042906114509062015180612a15565b1061148b5760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b60448201526064016100f0565b505b7f00000000000000000000000000000000000000000000000000000000000000008214806114da57507f000000000000000000000000000000000000000000000000000000000000000082145b15611682577f00000000000000000000000000000000000000000000000000000000000000008214600061150e8683612029565b90506001600160a01b038b161561163857600061152b8c86611128565b9050818111801561154557506115418285612a15565b8111155b156115dd576001600160a01b038c166000908152601488016020908152604080832086151580855260138c01845282852054855290835281842090845290915290205484906115949083612a2d565b10156115d25760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b60448201526064016100f0565b6115dd878d85612043565b6001600160a01b038b161561163657611611878d858c8a8151811061160457611604612be4565b602002602001015161192c565b611636878c858c8a8151811061162957611629612be4565b60200260200101516120f4565b505b6001600160a01b038a161561167f5760006116538b86611128565b905081811115801561166d57508161166b8583612a15565b115b1561167d5761167d878c85612218565b505b50505b60f882901c826001600160a01b038b16158015906116a857506001600160a01b038a1615155b80156116e0575060058260078111156116c3576116c36129e9565b14806116e0575060078260078111156116de576116de6129e9565b145b1561174157600060058360078111156116fb576116fb6129e9565b1490506000816117225761171d611716600f85900b876106b1565b8990610719565b611724565b845b9050611732888e848461192c565b61173e888d84846120f4565b50505b505050505b8061175081612a7c565b915050611246565b5050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615610521576040516303793c8d60e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906306f2791a9060240160206040518083038186803b1580156117f457600080fd5b505afa158015611808573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d89190612ad8565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061186057504281115b95945050505050565b600080516020612d1a83398151915261188b84611885846122c0565b85610e67565b60006101048061189b8142612a5a565b6118a59190612a97565b6118af9190612a15565b6001600160a01b03861660009081526014840160209081526040808320848452825280832087151584529091528120805492935086929091906118f3908490612a15565b90915550508215156000908152601383016020526040812060018101805491928792611920908490612a15565b90915550505550505050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b863087866119978982612a2d565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156119fa57600080fd5b505af1158015611a0e573d6000803e3d6000fd5b505050508282611a1e9190612a2d565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a2d565b9315156000908152601890960160205250506040909320555050565b6001600160a01b038416611acc5760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b60648201526084016100f0565b611aeb33600086611adc876111db565b611ae5876111db565b86611226565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611b3e908490612a15565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b600081611b9f57600080fd5b600080841215611bb457836000039350600190505b6000831215611bc65760009290920391155b6000611bd28585612314565b90508115611c00576001607f1b816001600160801b03161115611bf457600080fd5b60000391506104d89050565b60016001607f1b03816001600160801b03161115611c1d57600080fd5b91506104d89050565b505092915050565b600080611c4b83611c40576001611c43565b60005b600080610119565b831515600090815260138601602052604090206001015490915061015790600080516020612cfa83398151915260008481526020919091526040902054611c929190612a2d565b6110ad86865b600081611cb3576003830154600160a81b900460ff16610652565b505060030154600160a01b900460ff1690565b6000600f83810b9083900b0160016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000611d058583612476565b90506000611d16868387878761248f565b9050611d23868285612595565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b600081611d8257600080fd5b6000611d8e8484612314565b905060016001607f1b036001600160801b038216111561065257600080fd5b81546000908210611e0b5760405162461bcd60e51b815260206004820152602260248201527f456e756d657261626c655365743a20696e646578206f7574206f6620626f756e604482015261647360f01b60648201526084016100f0565b826000018281548110611e2057611e20612be4565b9060005260206000200154905092915050565b836001600160a01b0316856001600160a01b031614612009576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020612cfa833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b87518110156104b3576000878281518110611edf57611edf612be4565b602002602001015190506000811115611ff6576000898381518110611f0657611f06612be4565b6020026020010151905060006001600160a01b03168c6001600160a01b03161415611f545760008181526020889052604081208054849290611f49908490612a15565b90915550611f8a9050565b81611f5f8d83611128565b1415611f8a576000818152602087905260409020611f7d908d6125ec565b50611f88858261201d565b505b6001600160a01b038b16611fc15760008181526020889052604081208054849290611fb6908490612a2d565b90915550611ff49050565b611fcb8b82611128565b611ff4576000818152602087905260409020611fe7908c612601565b50611ff28482612011565b505b505b508061200181612a7c565b915050611ec2565b505050505050565b60006106528383612612565b60006106528383612661565b60008161203a578260040154610652565b50506005015490565b6001600160a01b03821661205657600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061208184838361274c565b61208c575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b8630878661215f8982612a15565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156121c257600080fd5b505af11580156121d6573d6000803e3d6000fd5b5050505082826121e69190612a15565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a15565b6001600160a01b03821661222b57600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061225684838361274c565b15612262575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6000816122ed577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b60008161232057600080fd5b60006001600160c01b03841161234b5782604085901b8161234357612343612a44565b049050612462565b60c084811c6401000000008110612364576020918201911c5b620100008110612376576010918201911c5b6101008110612387576008918201911c5b60108110612397576004918201911c5b600481106123a7576002918201911c5b600281106123b6576001820191505b60bf820360018603901c6001018260ff0387901b816123d7576123d7612a44565b0492506001600160801b038311156123ee57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b8281101561241a576001820391505b608084901b92900382811015612431576001820391505b829003608084901c821461244757612447612bfa565b88818161245657612456612a44565b04870196505050505050505b6001600160801b0381111561065257600080fd5b60006124828383612798565b90506106528382846127bf565b600080826124a4576019870154600f0b6124b4565b6019870154600160801b9004600f0b5b905080600f0b600014156124cc57506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b6064820152730f6e8ef18fb5bb61d545fee60f779d8aed60408f9063e101a89b9060840160206040518083038186803b15801561253257600080fd5b505af4158015612546573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061256a9190612c10565b915067b33333333333333382600f0b121561258b5767b33333333333333391505b5095945050505050565b80156125c5576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b6000610652836001600160a01b038416612661565b6000610652836001600160a01b0384165b6000818152600183016020526040812054612659575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556104d8565b5060006104d8565b60008181526001830160205260408120548015612742576000612685600183612a2d565b8554909150600090869061269b90600190612a2d565b815481106126ab576126ab612be4565b90600052602060002001549050808660000183815481106126ce576126ce612be4565b6000918252602090912001556126e5826001612a15565b6000828152600188016020526040902055855486908061270757612707612c33565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506104d8565b60009150506104d8565b6001600160a01b0383811660009081526020849052604081205490911615158061015757506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6000816127b3576008830154600160801b9004600f0b610652565b505060090154600f0b90565b600080826127d15784600a01546127d7565b84600b01545b6127e19042612a2d565b905061a8c0811115612800576127f961a8c082612a2d565b9050612809565b83915050610652565b600061281782613840611d76565b9050600061282a85611c40576001611c43565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020612cfa83398151915290935290832054939450926128889161287891612a2d565b6128829084612a2d565b83611d76565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e666666666666666608082018190526801000000000000000060a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b8152919250730f6e8ef18fb5bb61d545fee60f779d8aed60408f91634916d70d9161292991600401612c49565b60206040518083038186803b15801561294157600080fd5b505af4158015612955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129799190612c10565b9998505050505050505050565b60008060006060848603121561299b57600080fd5b83356001600160a01b03811681146129b257600080fd5b95602085013595506040909401359392505050565b600080604083850312156129da57600080fd5b50508035926020909101359150565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115612a2857612a286129ff565b500190565b600082821015612a3f57612a3f6129ff565b500390565b634e487b7160e01b600052601260045260246000fd5b600082612a7757634e487b7160e01b600052601260045260246000fd5b500490565b6000600019821415612a9057612a906129ff565b5060010190565b6000816000190483118215151615612ab157612ab16129ff565b500290565b600060208284031215612ac857600080fd5b8151801515811461065257600080fd5b600060208284031215612aea57600080fd5b5051919050565b600181815b80851115612b2c578160001904821115612b1257612b126129ff565b80851615612b1f57918102915b93841c9390800290612af6565b509250929050565b600082612b43575060016104d8565b81612b50575060006104d8565b8160018114612b665760028114612b7057612b8c565b60019150506104d8565b60ff841115612b8157612b816129ff565b50506001821b6104d8565b5060208310610133831016604e8410600b8410161715612baf575081810a6104d8565b612bb98383612af1565b8060001904821115612bcd57612bcd6129ff565b029392505050565b600061065260ff841683612b34565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052600160045260246000fd5b600060208284031215612c2257600080fd5b815180600f0b811461065257600080fd5b634e487b7160e01b600052603160045260246000fd5b6000610120820190508251600f0b82526020830151600f0b60208301526040830151612c7a6040840182600f0b9052565b506060830151612c8f6060840182600f0b9052565b506080830151612ca46080840182600f0b9052565b5060a0830151612cb960a0840182600f0b9052565b5060c0830151612cce60c0840182600f0b9052565b5060e0830151612ce360e0840182600f0b9052565b5061010080840151611c2682850182600f0b905256feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eb"), + ("0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05", "MainchainGatewayV2", "608060405234801561001057600080fd5b506000805460ff1916905561582e806200002b6000396000f3fe60806040526004361061032d5760003560e01c80639157921c116101a5578063b2975794116100ec578063d547741f11610095578063dafae4081161006f578063dafae4081461096e578063dff525e11461098e578063e400327c146109ae578063e75235b8146109ce5761033c565b8063d547741f14610901578063d55ed10314610921578063d64af2a61461094e5761033c565b8063cdb67444116100c6578063cdb674441461089c578063cdf64a76146108b4578063d19773d2146108d45761033c565b8063b29757941461082f578063b9c362091461085c578063ca15c8731461087c5761033c565b8063a3912ec81161014e578063affed0e011610128578063affed0e0146107cc578063b1a2567e146107e2578063b1d08a03146108025761033c565b8063a3912ec81461033a578063ab7965661461077f578063ac78dfe8146107ac5761033c565b8063994390891161017f57806399439089146107155780639dcc4da314610735578063a217fddf1461076a5761033c565b80639157921c1461068f57806391d14854146106af57806393c5678f146106f55761033c565b806336568abe116102745780635c975abb1161021d5780637de5dedd116101f75780637de5dedd146106115780638456cb59146106265780638f34e3471461063b5780639010d07c1461066f5761033c565b80635c975abb146105ac5780636932be98146105c45780636c1ce670146105f15761033c565b80634d0d66731161024e5780634d0d66731461052f5780634d493f4e1461054f57806359122f6b1461057f5761033c565b806336568abe146104e75780633f4ba83a146105075780634b14557e1461051c5761033c565b80631d4a7210116102d65780632f2ff15d116102b05780632f2ff15d1461049b578063302d12db146104bb5780633644e515146104d25761033c565b80631d4a721014610428578063248a9ca3146104555780632dfdf0b5146104855761033c565b8063180ff1e911610307578063180ff1e9146103d55780631a8e55b0146103e85780631b6e7594146104085761033c565b806301ffc9a71461034457806317ce2dd41461037957806317fcb39b1461039d5761033c565b3661033c5761033a6109e6565b005b61033a6109e6565b34801561035057600080fd5b5061036461035f366004614843565b610a69565b60405190151581526020015b60405180910390f35b34801561038557600080fd5b5061038f60755481565b604051908152602001610370565b3480156103a957600080fd5b506074546103bd906001600160a01b031681565b6040516001600160a01b039091168152602001610370565b61033a6103e33660046148f4565b610aad565b3480156103f457600080fd5b5061033a6104033660046149e6565b610dbd565b34801561041457600080fd5b5061033a610423366004614a52565b610e8f565b34801561043457600080fd5b5061038f610443366004614aec565b603e6020526000908152604090205481565b34801561046157600080fd5b5061038f610470366004614b09565b60009081526072602052604090206001015490565b34801561049157600080fd5b5061038f60765481565b3480156104a757600080fd5b5061033a6104b6366004614b22565b610f64565b3480156104c757600080fd5b5061038f620f424081565b3480156104de57600080fd5b5060775461038f565b3480156104f357600080fd5b5061033a610502366004614b22565b610f8f565b34801561051357600080fd5b5061033a61101b565b61033a61052a366004614b52565b611083565b34801561053b57600080fd5b5061036461054a366004614b7d565b6110e1565b34801561055b57600080fd5b5061036461056a366004614b09565b607a6020526000908152604090205460ff1681565b34801561058b57600080fd5b5061038f61059a366004614aec565b603a6020526000908152604090205481565b3480156105b857600080fd5b5060005460ff16610364565b3480156105d057600080fd5b5061038f6105df366004614b09565b60796020526000908152604090205481565b3480156105fd57600080fd5b5061036461060c366004614c06565b61118c565b34801561061d57600080fd5b5061038f61119f565b34801561063257600080fd5b5061033a611234565b34801561064757600080fd5b5061038f7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e481565b34801561067b57600080fd5b506103bd61068a366004614c32565b61129c565b34801561069b57600080fd5b5061033a6106aa366004614c54565b6112b4565b3480156106bb57600080fd5b506103646106ca366004614b22565b60009182526072602090815260408084206001600160a01b0393909316845291905290205460ff1690565b34801561070157600080fd5b5061033a6107103660046149e6565b6115ca565b34801561072157600080fd5b506003546103bd906001600160a01b031681565b34801561074157600080fd5b50610755610750366004614c32565b611696565b60408051928352602083019190915201610370565b34801561077657600080fd5b5061038f600081565b34801561078b57600080fd5b5061038f61079a366004614aec565b603c6020526000908152604090205481565b3480156107b857600080fd5b506103646107c7366004614b09565b61172f565b3480156107d857600080fd5b5061038f60045481565b3480156107ee57600080fd5b5061033a6107fd3660046149e6565b6117ce565b34801561080e57600080fd5b5061038f61081d366004614aec565b60396020526000908152604090205481565b34801561083b57600080fd5b5061084f61084a366004614aec565b61189a565b6040516103709190614ca5565b34801561086857600080fd5b50610755610877366004614c32565b611992565b34801561088857600080fd5b5061038f610897366004614b09565b611a17565b3480156108a857600080fd5b50603754603854610755565b3480156108c057600080fd5b5061033a6108cf366004614aec565b611a2e565b3480156108e057600080fd5b5061038f6108ef366004614aec565b603b6020526000908152604090205481565b34801561090d57600080fd5b5061033a61091c366004614b22565b611a97565b34801561092d57600080fd5b5061038f61093c366004614aec565b603d6020526000908152604090205481565b34801561095a57600080fd5b5061033a610969366004614aec565b611abd565b34801561097a57600080fd5b50610364610989366004614b09565b611b26565b34801561099a57600080fd5b5061033a6109a9366004614cd2565b611bbd565b3480156109ba57600080fd5b5061033a6109c93660046149e6565b611cc7565b3480156109da57600080fd5b50600154600254610755565b60005460ff1615610a315760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b60448201526064015b60405180910390fd5b6074546001600160a01b03163314610a6757610a4b614802565b338152604080820151349101528051610a65908290611d93565b505b565b60006001600160e01b031982167f5a05180f000000000000000000000000000000000000000000000000000000001480610aa75750610aa78261210a565b92915050565b607154610100900460ff16610ac85760715460ff1615610acc565b303b155b610b3e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a28565b607154610100900460ff16158015610b60576071805461ffff19166101011790555b610b6b60008d612171565b6075899055610b798b61217b565b610b828a6121dd565b610c29604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f159f52c1e3a2b6a6aad3950adf713516211484e0516dad685ea662a094b7c43b918101919091527fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a560608201524660808201523060a082015260c00160408051601f198184030181529190528051602090910120607755565b610c338887612238565b5050610c3f87876122f8565b5050610c496123d3565b6000610c558680614da6565b90501115610d1657610c7e610c6a8680614da6565b610c776020890189614da6565b8787612467565b610ca4610c8b8680614da6565b8660005b602002810190610c9f9190614da6565b612666565b610cca610cb18680614da6565b8660015b602002810190610cc59190614da6565b612779565b610cf0610cd78680614da6565b8660025b602002810190610ceb9190614da6565b61288c565b610d16610cfd8680614da6565b8660035b602002810190610d119190614da6565b612a30565b60005b610d266040870187614da6565b9050811015610d9c57610d8a7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e4610d606040890189614da6565b84818110610d7057610d70614d90565b9050602002016020810190610d859190614aec565b612b43565b80610d9481614e06565b915050610d19565b508015610daf576071805461ff00191690555b505050505050505050505050565b6000805160206157b9833981519152546001600160a01b03163314610e1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82610e7d5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612779565b50505050565b6000805160206157b9833981519152546001600160a01b03163314610eef5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b84610f4e5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b610f5c868686868686612467565b505050505050565b600082815260726020526040902060010154610f808133612b65565b610f8a8383612b43565b505050565b6001600160a01b038116331461100d5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c6600000000000000000000000000000000006064820152608401610a28565b6110178282612be5565b5050565b6000805160206157b9833981519152546001600160a01b0316331461107b5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a67612c07565b60005460ff16156110c95760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b610a656110db36839003830183614ec0565b33611d93565b6000805460ff16156111285760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b611184848484808060200260200160405190810160405280939291908181526020016000905b8282101561117a5761116b60608302860136819003810190614f13565b8152602001906001019061114e565b5050505050612ca3565b949350505050565b600061119883836133bc565b9392505050565b600061122f600360009054906101000a90046001600160a01b03166001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b1580156111f257600080fd5b505afa158015611206573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061122a9190614f89565b613480565b905090565b6000805160206157b9833981519152546001600160a01b031633146112945760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a676134b6565b60008281526073602052604081206111989083613531565b7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e46112df8133612b65565b60006112f86112f336859003850185614ff0565b61353d565b905061130c6112f336859003850185614ff0565b8335600090815260796020526040902054146113765760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b82356000908152607a602052604090205460ff166113fc5760405162461bcd60e51b815260206004820152603160248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220617060448201527f70726f766564207769746864726177616c0000000000000000000000000000006064820152608401610a28565b82356000908152607a602052604090819020805460ff19169055517fd639511b37b3b002cca6cfe6bca0d833945a5af5a045578a0627fc43b79b26309061144690839086906150c4565b60405180910390a160006114606080850160608601614aec565b9050600061147661012086016101008701615151565b600181111561148757611487614c71565b141561154f5760006114a2368690038601610100870161516e565b6001600160a01b0383166000908152603b60205260409020549091506114ce90610140870135906135c6565b604082015260006114e8368790038701610100880161516e565b60408301519091506114ff9061014088013561518a565b604082015260745461151f908390339086906001600160a01b03166135e0565b6115486115326060880160408901614aec565b60745483919086906001600160a01b03166135e0565b505061158b565b61158b6115626060860160408701614aec565b60745483906001600160a01b03166115833689900389016101008a0161516e565b9291906135e0565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d82856040516115bc9291906150c4565b60405180910390a150505050565b6000805160206157b9833981519152546001600160a01b0316331461162a5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261168a5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612666565b6000806116b86000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146117115760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b84846122f8565b90925090506117286123d3565b9250929050565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190614f89565b6037546117b991906151a1565b6038546117c690846151a1565b101592915050565b6000805160206157b9833981519152546001600160a01b0316331461182e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261188e5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e898484848461288c565b60408051808201909152600080825260208201526001600160a01b0382166000908152607860205260409081902081518083019092528054829060ff1660018111156118e8576118e8614c71565b60018111156118f9576118f9614c71565b815290546001600160a01b036101009091048116602092830152908201519192501661198d5760405162461bcd60e51b815260206004820152602560248201527f4d61696e636861696e4761746577617956323a20756e737570706f727465642060448201527f746f6b656e0000000000000000000000000000000000000000000000000000006064820152608401610a28565b919050565b6000806119b46000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b031614611a0d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b8484612238565b6000818152607360205260408120610aa790613a13565b6000805160206157b9833981519152546001600160a01b03163314611a8e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a65816121dd565b600082815260726020526040902060010154611ab38133612b65565b610f8a8383612be5565b6000805160206157b9833981519152546001600160a01b03163314611b1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a658161217b565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b158015611b6b57600080fd5b505afa158015611b7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba39190614f89565b600154611bb091906151a1565b6002546117c690846151a1565b6000805160206157b9833981519152546001600160a01b03163314611c1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b85611c7c5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b611c8a878787878787612467565b611c978787836000610c8f565b611ca48787836001610cb5565b611cb18787836002610cdb565b611cbe8787836003610d01565b50505050505050565b6000805160206157b9833981519152546001600160a01b03163314611d275760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82611d875760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612a30565b604080518082018252600080825260208201526074549184015190916001600160a01b031690611dc290613a1d565b60208401516001600160a01b0316611ee1573484604001516040015114611e375760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611e408161189a565b6040850151519092506001811115611e5a57611e5a614c71565b82516001811115611e6d57611e6d614c71565b14611ecd5760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b6001600160a01b0381166020850152612087565b3415611f3b5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611f48846020015161189a565b6040850151519092506001811115611f6257611f62614c71565b82516001811115611f7557611f75614c71565b14611fd55760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b60208401516040850151611fec9185903090613ac7565b83602001516001600160a01b0316816001600160a01b031614156120875760408481015181015190517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b15801561206e57600080fd5b505af1158015612082573d6000803e3d6000fd5b505050505b607680546000918261209883614e06565b91905055905060006120bf858386602001516075548a613ce190949392919063ffffffff16565b90507fd7b25068d9dc8d00765254cfb7f5070f98d263c8d68931d937c7362fa738048b6120eb8261353d565b826040516120fa9291906151c0565b60405180910390a1505050505050565b60006001600160e01b031982167f7965db0b000000000000000000000000000000000000000000000000000000001480610aa757507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614610aa7565b6110178282612b43565b6074805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f9d2334c23be647e994f27a72c5eee42a43d5bdcfe15bb88e939103c2b114cbaf906020015b60405180910390a150565b6003805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527fef40dc07567635f84f5edbd2f8dbc16b40d9d282dd8e7e6f4ff58236b6836169906020016121d2565b6000808284111561228b5760405162461bcd60e51b815260206004820152601c60248201527f4761746577617956323a20696e76616c6964207468726573686f6c64000000006044820152606401610a28565b505060018054600280549285905583905560048054919291849186919060006122b383614e06565b9091555060408051868152602081018690527f976f8a9c5bdf8248dec172376d6e2b80a8e3df2f0328e381c6db8e1cf138c0f891015b60405180910390a49250929050565b600080828411156123715760405162461bcd60e51b815260206004820152602760248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64000000000000000000000000000000000000000000000000006064820152608401610a28565b5050603780546038805492859055839055600480549192918491869190600061239983614e06565b9091555060408051868152602081018690527f31312c97b89cc751b832d98fd459b967a2c3eef3b49757d1cf5ebaa12bb6eee191016122e9565b6002546037546123e391906151a1565b6038546001546123f391906151a1565b1115610a675760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64730000000000000000000000000000000000000000000000006064820152608401610a28565b848314801561247557508481145b6124e75760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206172726160448201527f79206c656e6774680000000000000000000000000000000000000000000000006064820152608401610a28565b60005b8581101561262c5784848281811061250457612504614d90565b90506020020160208101906125199190614aec565b6078600089898581811061252f5761252f614d90565b90506020020160208101906125449190614aec565b6001600160a01b039081168252602082019290925260400160002080547fffffffffffffffffffffff0000000000000000000000000000000000000000ff1661010093909216929092021790558282828181106125a3576125a3614d90565b90506020020160208101906125b89190615151565b607860008989858181106125ce576125ce614d90565b90506020020160208101906125e39190614aec565b6001600160a01b031681526020810191909152604001600020805460ff19166001838181111561261557612615614c71565b02179055508061262481614e06565b9150506124ea565b507fa4f03cc9c0e0aeb5b71b4ec800702753f65748c2cf3064695ba8e8b46be704448686868686866040516120fa969594939291906152c1565b8281146126c85760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612743578282828181106126e5576126e5614d90565b905060200201356039600087878581811061270257612702614d90565b90506020020160208101906127179190614aec565b6001600160a01b031681526020810191909152604001600020558061273b81614e06565b9150506126cb565b507f80bc635c452ae67f12f9b6f12ad4daa6dbbc04eeb9ebb87d354ce10c0e210dc0848484846040516115bc9493929190615339565b8281146127db5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612856578282828181106127f8576127f8614d90565b90506020020135603a600087878581811061281557612815614d90565b905060200201602081019061282a9190614aec565b6001600160a01b031681526020810191909152604001600020558061284e81614e06565b9150506127de565b507f64557254143204d91ba2d95acb9fda1e5fea55f77efd028685765bc1e94dd4b5848484846040516115bc9493929190615339565b8281146128ee5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b838110156129fa57620f424083838381811061290f5761290f614d90565b90506020020135111561298a5760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420706560448201527f7263656e746167650000000000000000000000000000000000000000000000006064820152608401610a28565b82828281811061299c5761299c614d90565b90506020020135603b60008787858181106129b9576129b9614d90565b90506020020160208101906129ce9190614aec565b6001600160a01b03168152602081019190915260400160002055806129f281614e06565b9150506128f1565b507fb05f5de88ae0294ebb6f67c5af2fcbbd593cc6bdfe543e2869794a4c8ce3ea50848484846040516115bc9493929190615339565b828114612a925760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612b0d57828282818110612aaf57612aaf614d90565b90506020020135603c6000878785818110612acc57612acc614d90565b9050602002016020810190612ae19190614aec565b6001600160a01b0316815260208101919091526040016000205580612b0581614e06565b915050612a95565b507fb5d2963614d72181b4df1f993d45b83edf42fa19710f0204217ba1b3e183bb73848484846040516115bc9493929190615339565b612b4d8282613db6565b6000828152607360205260409020610f8a9082613e58565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff1661101757612ba3816001600160a01b03166014613e6d565b612bae836020613e6d565b604051602001612bbf9291906153d0565b60408051601f198184030181529082905262461bcd60e51b8252610a2891600401615451565b612bef828261404e565b6000828152607360205260409020610f8a90826140d1565b60005460ff16612c595760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152606401610a28565b6000805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000823561014084013582612cbe6080870160608801614aec565b9050612cdb612cd6368890038801610100890161516e565b613a1d565b6001612ced6040880160208901615151565b6001811115612cfe57612cfe614c71565b14612d715760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964207265636560448201527f697074206b696e640000000000000000000000000000000000000000000000006064820152608401610a28565b60808601354614612de95760405162461bcd60e51b8152602060048201526024808201527f4d61696e636861696e4761746577617956323a20696e76616c6964206368616960448201527f6e206964000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6000612dfe61084a6080890160608a01614aec565b9050612e1261012088016101008901615151565b6001811115612e2357612e23614c71565b81516001811115612e3657612e36614c71565b148015612e675750612e4e60e0880160c08901614aec565b6001600160a01b031681602001516001600160a01b0316145b612ebf5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b60008481526079602052604090205415612f415760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220707260448201527f6f636573736564207769746864726177616c00000000000000000000000000006064820152608401610a28565b6001612f5561012089016101008a01615151565b6001811115612f6657612f66614c71565b1480612f795750612f7782846133bc565b155b612feb5760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a2072656163686564206461696c60448201527f79207769746864726177616c206c696d697400000000000000000000000000006064820152608401610a28565b6000612fff6112f3368a90038a018a614ff0565b9050600061300f607754836140e6565b6003549091506001600160a01b0316600061303d6130356101208d016101008e01615151565b878985614142565b60408051606081018252600080825260208201819052918101829052919b50919250819081906000805b8f5181101561323c578f818151811061308257613082614d90565b6020908102919091018101518051818301516040808401518151600081529586018083528f905260ff9093169085015260608401526080830152935060019060a0016020604051602081039080840390855afa1580156130e6573d6000803e3d6000fd5b505050602060405103519450846001600160a01b0316846001600160a01b0316106131795760405162461bcd60e51b815260206004820152602160248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206f72646560448201527f72000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6040517f953865650000000000000000000000000000000000000000000000000000000081526001600160a01b03808716600483015286955089169063953865659060240160206040518083038186803b1580156131d657600080fd5b505afa1580156131ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320e9190614f89565b6132189083615484565b915086821061322a576001955061323c565b8061323481614e06565b915050613067565b50846132b05760405162461bcd60e51b815260206004820152603660248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220696e60448201527f73756666696369656e7420766f746520776569676874000000000000000000006064820152608401610a28565b50505060008a81526079602052604090208690555050881561332c576000888152607a602052604090819020805460ff19166001179055517f89e52969465b1f1866fc5d46fd62de953962e9cb33552443cd999eba05bd20dc906133179086908e906150c4565b60405180910390a15050505050505050610aa7565b6133368688614233565b61337561334960608d0160408e01614aec565b87607460009054906101000a90046001600160a01b03168e61010001803603810190611583919061516e565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d848c6040516133a69291906150c4565b60405180910390a1505050505050505092915050565b6001600160a01b0382166000908152603a602052604081205482106133e357506000610aa7565b60006133f2620151804261549c565b6001600160a01b0385166000908152603e60205260409020549091508111156134385750506001600160a01b0382166000908152603c6020526040902054811015610aa7565b6001600160a01b0384166000908152603d602052604090205461345c908490615484565b6001600160a01b0385166000908152603c602052604090205411159150610aa79050565b600060025460016002548460015461349891906151a1565b6134a29190615484565b6134ac919061518a565b610aa7919061549c565b60005460ff16156134fc5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b6000805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612c863390565b600061119883836142c3565b60007fb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea60001b8260000151836020015161357a85604001516142ed565b61358786606001516142ed565b6135948760800151614350565b6040516020016135a9969594939291906154be565b604051602081830303815290604052805190602001209050919050565b6000620f42406135d683856151a1565b611198919061549c565b6000816001600160a01b0316836001600160a01b031614156136905760408086015190516001600160a01b0386169180156108fc02916000818181858888f1935050505061368b57816001600160a01b031663d0e30db086604001516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561366757600080fd5b505af115801561367b573d6000803e3d6000fd5b505050505061368b858585614393565b613a0c565b6000855160018111156136a5576136a5614c71565b1415613866576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000906001600160a01b038516906370a082319060240160206040518083038186803b15801561370657600080fd5b505afa15801561371a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061373e9190614f89565b9050856040015181101561385557836001600160a01b03166340c10f193083896040015161376c919061518a565b6040516001600160a01b03909216602483015260448201526064016040516020818303038152906040529060e01b6020820180516001600160e01b0383818316178352505050506040516137c091906154f8565b6000604051808303816000865af19150503d80600081146137fd576040519150601f19603f3d011682016040523d82523d6000602084013e613802565b606091505b505080925050816138555760405162461bcd60e51b815260206004820152601b60248201527f546f6b656e3a204552433230206d696e74696e67206661696c656400000000006044820152606401610a28565b613860868686614393565b50613a0c565b60018551600181111561387b5761387b614c71565b141561399e5761389083858760200151614437565b61368b57602085810151604080516001600160a01b038881166024830152604480830194909452825180830390940184526064909101825292820180516001600160e01b03167f40c10f1900000000000000000000000000000000000000000000000000000000179052519185169161390991906154f8565b6000604051808303816000865af19150503d8060008114613946576040519150601f19603f3d011682016040523d82523d6000602084013e61394b565b606091505b5050809150508061368b5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e3a20455243373231206d696e74696e67206661696c6564000000006044820152606401610a28565b60405162461bcd60e51b815260206004820152602160248201527f546f6b656e3a20756e737570706f7274656420746f6b656e207374616e64617260448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b5050505050565b6000610aa7825490565b600081516001811115613a3257613a32614c71565b148015613a43575060008160400151115b8015613a5157506020810151155b80613a7b5750600181516001811115613a6c57613a6c614c71565b148015613a7b57506040810151155b610a655760405162461bcd60e51b815260206004820152601360248201527f546f6b656e3a20696e76616c696420696e666f000000000000000000000000006044820152606401610a28565b600060608186516001811115613adf57613adf614c71565b1415613bbd5760408681015181516001600160a01b038881166024830152878116604483015260648083019390935283518083039093018352608490910183526020820180516001600160e01b03166323b872dd60e01b179052915191851691613b4991906154f8565b6000604051808303816000865af19150503d8060008114613b86576040519150601f19603f3d011682016040523d82523d6000602084013e613b8b565b606091505b509092509050818015613bb6575080511580613bb6575080806020019051810190613bb69190615514565b9150613c84565b600186516001811115613bd257613bd2614c71565b141561399e57602086810151604080516001600160a01b0389811660248301528881166044830152606480830194909452825180830390940184526084909101825292820180516001600160e01b03166323b872dd60e01b1790525191851691613c3c91906154f8565b6000604051808303816000865af19150503d8060008114613c79576040519150601f19603f3d011682016040523d82523d6000602084013e613c7e565b606091505b50909250505b81610f5c57613c92866144e2565b613ca6866001600160a01b03166014613e6d565b613cba866001600160a01b03166014613e6d565b613cce866001600160a01b03166014613e6d565b604051602001612bbf9493929190615536565b613d516040805160a08101825260008082526020808301829052835160608082018652838252818301849052818601849052848601919091528451808201865283815280830184905280860184905281850152845190810185528281529081018290529283015290608082015290565b83815260006020820181905250604080820180516001600160a01b039788169052602080890151825190891690820152905146908301528751606084018051918916909152805195909716940193909352935182015292909201516080820152919050565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff166110175760008281526072602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613e143390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000611198836001600160a01b03841661454f565b60606000613e7c8360026151a1565b613e87906002615484565b67ffffffffffffffff811115613e9f57613e9f614e21565b6040519080825280601f01601f191660200182016040528015613ec9576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110613f0057613f00614d90565b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110613f4b57613f4b614d90565b60200101906001600160f81b031916908160001a9053506000613f6f8460026151a1565b613f7a906001615484565b90505b6001811115613fff577f303132333435363738396162636465660000000000000000000000000000000085600f1660108110613fbb57613fbb614d90565b1a60f81b828281518110613fd157613fd1614d90565b60200101906001600160f81b031916908160001a90535060049490941c93613ff881615606565b9050613f7d565b5083156111985760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610a28565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff16156110175760008281526072602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000611198836001600160a01b03841661459e565b604080517f19010000000000000000000000000000000000000000000000000000000000006020808301919091526022820185905260428083018590528351808403909101815260629092019092528051910120600090611198565b6000806000836001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b15801561418057600080fd5b505afa158015614194573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141b89190614f89565b90506141c381613480565b925060008760018111156141d9576141d9614c71565b1415614229576001600160a01b038616600090815260396020526040902054851061420a5761420781614691565b92505b6001600160a01b0386166000908152603a602052604090205485101591505b5094509492505050565b6000614242620151804261549c565b6001600160a01b0384166000908152603e6020526040902054909150811115614291576001600160a01b03929092166000908152603e6020908152604080832094909455603d90529190912055565b6001600160a01b0383166000908152603d6020526040812080548492906142b9908490615484565b9091555050505050565b60008260000182815481106142da576142da614d90565b9060005260206000200154905092915050565b805160208083015160408085015190516000946135a9947f353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e87649491939192019384526001600160a01b03928316602085015291166040830152606082015260800190565b805160208083015160408085015190516000946135a9947f1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d94919391920161561d565b600080845160018111156143a9576143a9614c71565b14156143c5576143be828486604001516146a9565b90506143ef565b6001845160018111156143da576143da614c71565b141561399e576143be82848660200151614437565b80610e89576143fd846144e2565b614411846001600160a01b03166014613e6d565b614425846001600160a01b03166014613e6d565b604051602001612bbf93929190615648565b604080513060248201526001600160a01b038481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b1790529151600092861691614495916154f8565b6000604051808303816000865af19150503d80600081146144d2576040519150601f19603f3d011682016040523d82523d6000602084013e6144d7565b606091505b509095945050505050565b606061450d826000015160018111156144fd576144fd614c71565b6001600160a01b03166001613e6d565b61451a8360200151614795565b6145278460400151614795565b604051602001614539939291906156d9565b6040516020818303038152906040529050919050565b600081815260018301602052604081205461459657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aa7565b506000610aa7565b600081815260018301602052604081205480156146875760006145c260018361518a565b85549091506000906145d69060019061518a565b905081811461463b5760008660000182815481106145f6576145f6614d90565b906000526020600020015490508087600001848154811061461957614619614d90565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061464c5761464c6157a2565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610aa7565b6000915050610aa7565b600060385460016038548460375461349891906151a1565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b03167fa9059cbb0000000000000000000000000000000000000000000000000000000017905291516000926060929087169161471f91906154f8565b6000604051808303816000865af19150503d806000811461475c576040519150601f19603f3d011682016040523d82523d6000602084013e614761565b606091505b50909250905081801561478c57508051158061478c57508080602001905181019061478c9190615514565b95945050505050565b6060816147d557505060408051808201909152600481527f3078303000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156147f857806147e981614e06565b915050600882901c91506147d9565b6111848482613e6d565b604080516060810182526000808252602082015290810161483e6040805160608101909152806000815260200160008152602001600081525090565b905290565b60006020828403121561485557600080fd5b81356001600160e01b03198116811461119857600080fd5b6001600160a01b0381168114610a6557600080fd5b803561198d8161486d565b8060608101831015610aa757600080fd5b8060808101831015610aa757600080fd5b60008083601f8401126148c157600080fd5b50813567ffffffffffffffff8111156148d957600080fd5b6020830191508360208260051b850101111561172857600080fd5b60008060008060008060008060008060006101408c8e03121561491657600080fd5b61491f8c614882565b9a5061492d60208d01614882565b995061493b60408d01614882565b985060608c0135975060808c0135965060a08c0135955060c08c0135945067ffffffffffffffff8060e08e0135111561497357600080fd5b6149838e60e08f01358f0161488d565b9450806101008e0135111561499757600080fd5b6149a88e6101008f01358f0161489e565b9350806101208e013511156149bc57600080fd5b506149ce8d6101208e01358e016148af565b81935080925050509295989b509295989b9093969950565b600080600080604085870312156149fc57600080fd5b843567ffffffffffffffff80821115614a1457600080fd5b614a20888389016148af565b90965094506020870135915080821115614a3957600080fd5b50614a46878288016148af565b95989497509550505050565b60008060008060008060608789031215614a6b57600080fd5b863567ffffffffffffffff80821115614a8357600080fd5b614a8f8a838b016148af565b90985096506020890135915080821115614aa857600080fd5b614ab48a838b016148af565b90965094506040890135915080821115614acd57600080fd5b50614ada89828a016148af565b979a9699509497509295939492505050565b600060208284031215614afe57600080fd5b81356111988161486d565b600060208284031215614b1b57600080fd5b5035919050565b60008060408385031215614b3557600080fd5b823591506020830135614b478161486d565b809150509250929050565b600060a08284031215614b6457600080fd5b50919050565b60006101608284031215614b6457600080fd5b60008060006101808486031215614b9357600080fd5b614b9d8585614b6a565b925061016084013567ffffffffffffffff80821115614bbb57600080fd5b818601915086601f830112614bcf57600080fd5b813581811115614bde57600080fd5b876020606083028501011115614bf357600080fd5b6020830194508093505050509250925092565b60008060408385031215614c1957600080fd5b8235614c248161486d565b946020939093013593505050565b60008060408385031215614c4557600080fd5b50508035926020909101359150565b60006101608284031215614c6757600080fd5b6111988383614b6a565b634e487b7160e01b600052602160045260246000fd5b60028110610a6557634e487b7160e01b600052602160045260246000fd5b81516040820190614cb581614c87565b808352506001600160a01b03602084015116602083015292915050565b60008060008060008060006080888a031215614ced57600080fd5b873567ffffffffffffffff80821115614d0557600080fd5b614d118b838c016148af565b909950975060208a0135915080821115614d2a57600080fd5b614d368b838c016148af565b909750955060408a0135915080821115614d4f57600080fd5b614d5b8b838c016148af565b909550935060608a0135915080821115614d7457600080fd5b50614d818a828b0161489e565b91505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112614dbd57600080fd5b83018035915067ffffffffffffffff821115614dd857600080fd5b6020019150600581901b360382131561172857600080fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415614e1a57614e1a614df0565b5060010190565b634e487b7160e01b600052604160045260246000fd5b6040516060810167ffffffffffffffff81118282101715614e6857634e487b7160e01b600052604160045260246000fd5b60405290565b60028110610a6557600080fd5b600060608284031215614e8d57600080fd5b614e95614e37565b90508135614ea281614e6e565b80825250602082013560208201526040820135604082015292915050565b600060a08284031215614ed257600080fd5b614eda614e37565b8235614ee58161486d565b81526020830135614ef58161486d565b6020820152614f078460408501614e7b565b60408201529392505050565b600060608284031215614f2557600080fd5b6040516060810181811067ffffffffffffffff82111715614f5657634e487b7160e01b600052604160045260246000fd5b604052823560ff81168114614f6a57600080fd5b8152602083810135908201526040928301359281019290925250919050565b600060208284031215614f9b57600080fd5b5051919050565b600060608284031215614fb457600080fd5b614fbc614e37565b90508135614fc98161486d565b81526020820135614fd98161486d565b806020830152506040820135604082015292915050565b6000610160828403121561500357600080fd5b60405160a0810181811067ffffffffffffffff8211171561503457634e487b7160e01b600052604160045260246000fd5b60405282358152602083013561504981614e6e565b602082015261505b8460408501614fa2565b604082015261506d8460a08501614fa2565b6060820152615080846101008501614e7b565b60808201529392505050565b80356150978161486d565b6001600160a01b0390811683526020820135906150b38261486d565b166020830152604090810135910152565b6000610180820190508382528235602083015260208301356150e581614e6e565b6150ee81614c87565b80604084015250615105606083016040850161508c565b61511560c0830160a0850161508c565b61012061010084013561512781614e6e565b61513081614c87565b81840152830135610140808401919091529092013561016090910152919050565b60006020828403121561516357600080fd5b813561119881614e6e565b60006060828403121561518057600080fd5b6111988383614e7b565b60008282101561519c5761519c614df0565b500390565b60008160001904831182151516156151bb576151bb614df0565b500290565b6000610180820190508382528251602083015260208301516151e181614c87565b6040838101919091528381015180516001600160a01b03908116606086015260208201511660808501529081015160a084015250606083015180516001600160a01b0390811660c085015260208201511660e08401526040810151610100840152506080830151805161525381614c87565b6101208401526020810151610140840152604001516101609092019190915292915050565b8183526000602080850194508260005b858110156152b657813561529b8161486d565b6001600160a01b031687529582019590820190600101615288565b509495945050505050565b6060815260006152d560608301888a615278565b6020838203818501526152e982888a615278565b8481036040860152858152869250810160005b8681101561532a57833561530f81614e6e565b61531881614c87565b825292820192908201906001016152fc565b509a9950505050505050505050565b60408152600061534d604083018688615278565b82810360208401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84111561538557600080fd5b8360051b80866020840137600091016020019081529695505050505050565b60005b838110156153bf5781810151838201526020016153a7565b83811115610e895750506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516154088160178501602088016153a4565b7f206973206d697373696e6720726f6c652000000000000000000000000000000060179184019182015283516154458160288401602088016153a4565b01602801949350505050565b60208152600082518060208401526154708160408501602087016153a4565b601f01601f19169190910160400192915050565b6000821982111561549757615497614df0565b500190565b6000826154b957634e487b7160e01b600052601260045260246000fd5b500490565b8681526020810186905260c081016154d586614c87565b8560408301528460608301528360808301528260a0830152979650505050505050565b6000825161550a8184602087016153a4565b9190910192915050565b60006020828403121561552657600080fd5b8151801515811461119857600080fd5b7f546f6b656e3a20636f756c64206e6f74207472616e7366657220000000000000815260008551602061556f82601a8601838b016153a4565b7f2066726f6d200000000000000000000000000000000000000000000000000000601a9285019283015286516155aa81838501848b016153a4565b630103a37960e51b92018181019290925285516155cd81602485018985016153a4565b660103a37b5b2b7160cd1b6024939091019283015284516155f481602b85018489016153a4565b91909101602b01979650505050505050565b60008161561557615615614df0565b506000190190565b8481526080810161562d85614c87565b84602083015283604083015282606083015295945050505050565b7f546f6b656e3a20636f756c64206e6f74207472616e736665722000000000000081526000845161568081601a8501602089016153a4565b630103a37960e51b601a9184019182015284516156a481601e8401602089016153a4565b660103a37b5b2b7160cd1b601e929091019182015283516156cc8160258401602088016153a4565b0160250195945050505050565b7f546f6b656e496e666f280000000000000000000000000000000000000000000081526000845161571181600a8501602089016153a4565b80830190507f2c0000000000000000000000000000000000000000000000000000000000000080600a830152855161575081600b850160208a016153a4565b600b920191820152835161576b81600c8401602088016153a4565b7f2900000000000000000000000000000000000000000000000000000000000000600c9290910191820152600d0195945050505050565b634e487b7160e01b600052603160045260246000fdfeb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610348617350726f787941646d696e3a20756e617574686f72697a65642073656e64") + ]; +} diff --git a/crates/forge/bin/cmd/compiler.rs b/crates/forge/bin/cmd/compiler.rs new file mode 100644 index 0000000000000..19e4354ca9cb4 --- /dev/null +++ b/crates/forge/bin/cmd/compiler.rs @@ -0,0 +1,168 @@ +use clap::{Parser, Subcommand, ValueHint}; +use eyre::Result; +use foundry_common::shell; +use foundry_compilers::{artifacts::EvmVersion, Graph}; +use foundry_config::Config; +use semver::Version; +use serde::Serialize; +use std::{collections::BTreeMap, path::PathBuf}; + +/// CLI arguments for `forge compiler`. +#[derive(Debug, Parser)] +pub struct CompilerArgs { + #[command(subcommand)] + pub sub: CompilerSubcommands, +} + +impl CompilerArgs { + pub fn run(self) -> Result<()> { + match self.sub { + CompilerSubcommands::Resolve(args) => args.run(), + } + } +} + +#[derive(Debug, Subcommand)] +pub enum CompilerSubcommands { + /// Retrieves the resolved version(s) of the compiler within the project. + #[command(visible_alias = "r")] + Resolve(ResolveArgs), +} + +/// Resolved compiler within the project. +#[derive(Serialize)] +struct ResolvedCompiler { + /// Compiler version. + version: Version, + /// Max supported EVM version of compiler. + #[serde(skip_serializing_if = "Option::is_none")] + evm_version: Option, + /// Source paths. + #[serde(skip_serializing_if = "Vec::is_empty")] + paths: Vec, +} + +/// CLI arguments for `forge compiler resolve`. +#[derive(Debug, Parser)] +pub struct ResolveArgs { + /// The root directory + #[arg(long, short, value_hint = ValueHint::DirPath, value_name = "PATH")] + root: Option, + + /// Skip files that match the given regex pattern. + #[arg(long, short, value_name = "REGEX")] + skip: Option, +} + +impl ResolveArgs { + pub fn run(self) -> Result<()> { + let Self { root, skip } = self; + + let root = root.unwrap_or_else(|| PathBuf::from(".")); + let config = Config::load_with_root(&root)?; + let project = config.project()?; + + let graph = Graph::resolve(&project.paths)?; + let sources = graph.into_sources_by_version(&project)?.sources; + + let mut output: BTreeMap> = BTreeMap::new(); + + for (language, sources) in sources { + let mut versions_with_paths: Vec = sources + .iter() + .map(|(version, sources, _)| { + let paths: Vec = sources + .iter() + .filter_map(|(path_file, _)| { + let path_str = path_file + .strip_prefix(&project.paths.root) + .unwrap_or(path_file) + .to_path_buf() + .display() + .to_string(); + + // Skip files that match the given regex pattern. + if let Some(ref regex) = skip { + if regex.is_match(&path_str) { + return None; + } + } + + Some(path_str) + }) + .collect(); + + let evm_version = if shell::verbosity() > 1 { + Some( + EvmVersion::default() + .normalize_version_solc(version) + .unwrap_or_default(), + ) + } else { + None + }; + + ResolvedCompiler { version: version.clone(), evm_version, paths } + }) + .filter(|version| !version.paths.is_empty()) + .collect(); + + // Sort by SemVer version. + versions_with_paths.sort_by(|v1, v2| Version::cmp(&v1.version, &v2.version)); + + // Skip language if no paths are found after filtering. + if !versions_with_paths.is_empty() { + // Clear paths if verbosity is 0, performed only after filtering to avoid being + // skipped. + if shell::verbosity() == 0 { + versions_with_paths.iter_mut().for_each(|version| version.paths.clear()); + } + + output.insert(language.to_string(), versions_with_paths); + } + } + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&output)?)?; + return Ok(()); + } + + for (language, compilers) in &output { + match shell::verbosity() { + 0 => sh_println!("{language}:")?, + _ => sh_println!("{language}:\n")?, + } + + for resolved_compiler in compilers { + let version = &resolved_compiler.version; + match shell::verbosity() { + 0 => sh_println!("- {version}")?, + _ => { + if let Some(evm) = &resolved_compiler.evm_version { + sh_println!("{version} (<= {evm}):")? + } else { + sh_println!("{version}:")? + } + } + } + + if shell::verbosity() > 0 { + let paths = &resolved_compiler.paths; + for (idx, path) in paths.iter().enumerate() { + if idx == paths.len() - 1 { + sh_println!("└── {path}\n")? + } else { + sh_println!("├── {path}")? + } + } + } + } + + if shell::verbosity() == 0 { + sh_println!()? + } + } + + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/config.rs b/crates/forge/bin/cmd/config.rs index a8e33cdba38ca..a85e2391346da 100644 --- a/crates/forge/bin/cmd/config.rs +++ b/crates/forge/bin/cmd/config.rs @@ -2,59 +2,59 @@ use super::build::BuildArgs; use clap::Parser; use eyre::Result; use foundry_cli::utils::LoadConfig; -use foundry_common::{evm::EvmArgs, term::cli_warn}; +use foundry_common::{evm::EvmArgs, shell}; use foundry_config::fix::fix_tomls; -foundry_config::impl_figment_convert!(ConfigArgs, opts, evm_opts); +foundry_config::impl_figment_convert!(ConfigArgs, build, evm); /// CLI arguments for `forge config`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct ConfigArgs { /// Print only a basic set of the currently set config values. - #[clap(long)] + #[arg(long)] basic: bool, - /// Print currently set config values as JSON. - #[clap(long)] - json: bool, - /// Attempt to fix any configuration warnings. - #[clap(long)] + #[arg(long)] fix: bool, // support nested build arguments - #[clap(flatten)] - opts: BuildArgs, + #[command(flatten)] + build: BuildArgs, - #[clap(flatten)] - evm_opts: EvmArgs, + #[command(flatten)] + evm: EvmArgs, } impl ConfigArgs { pub fn run(self) -> Result<()> { if self.fix { for warning in fix_tomls() { - cli_warn!("{warning}"); + sh_warn!("{warning}")?; } return Ok(()) } - let config = self.try_load_config_unsanitized_emit_warnings()?; + let config = self + .load_config_unsanitized()? + .normalized_optimizer_settings() + // we explicitly normalize the version, so mimic the behavior when invoking solc + .normalized_evm_version(); let s = if self.basic { let config = config.into_basic(); - if self.json { + if shell::is_json() { serde_json::to_string_pretty(&config)? } else { config.to_string_pretty()? } - } else if self.json { + } else if shell::is_json() { serde_json::to_string_pretty(&config)? } else { config.to_string_pretty()? }; - println!("{s}"); + sh_println!("{s}")?; Ok(()) } } diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index cab986aa2bfe8..3a533131f3ff6 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -1,137 +1,177 @@ -use super::{install, test::FilterArgs}; -use clap::{Parser, ValueEnum}; -use ethers::{ - abi::Address, - prelude::{ - artifacts::{Ast, CompactBytecode, CompactDeployedBytecode}, - Artifact, Bytes, Project, ProjectCompileOutput, U256, - }, - solc::{artifacts::contract::CompactContractBytecode, sourcemap::SourceMap}, -}; +use super::{install, test::TestArgs, watch::WatchArgs}; +use alloy_primitives::{map::HashMap, Address, Bytes, U256}; +use clap::{Parser, ValueEnum, ValueHint}; use eyre::{Context, Result}; use forge::{ coverage::{ - analysis::SourceAnalyzer, anchors::find_anchors, ContractId, CoverageReport, - CoverageReporter, DebugReporter, ItemAnchor, LcovReporter, SummaryReporter, + analysis::{SourceAnalysis, SourceFile, SourceFiles}, + anchors::find_anchors, + BytecodeReporter, ContractId, CoverageReport, CoverageReporter, CoverageSummaryReporter, + DebugReporter, ItemAnchor, LcovReporter, }, - executor::{inspector::CheatsConfig, opts::EvmOpts}, - result::SuiteResult, - revm::primitives::SpecId, - utils::{build_ic_pc_map, ICPCMap}, - MultiContractRunnerBuilder, TestOptions, + opts::EvmOpts, + utils::IcPcMap, + MultiContractRunnerBuilder, }; -use foundry_cli::{ - opts::CoreBuildArgs, - p_println, - utils::{LoadConfig, STATIC_FUZZ_SEED}, +use foundry_cli::utils::{LoadConfig, STATIC_FUZZ_SEED}; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::{ + artifacts::{ + sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode, SolcLanguage, Source, + }, + compilers::multi::MultiCompiler, + Artifact, ArtifactId, Project, ProjectCompileOutput, ProjectPathsConfig, +}; +use foundry_config::Config; +use rayon::prelude::*; +use semver::{Version, VersionReq}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, }; -use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs}; -use foundry_config::{Config, SolcReq}; -use foundry_evm::utils::evm_spec; -use semver::Version; -use std::{collections::HashMap, sync::mpsc::channel}; -use tracing::trace; -use yansi::Paint; - -/// A map, keyed by contract ID, to a tuple of the deployment source map and the runtime source map. -type SourceMaps = HashMap; // Loads project's figment and merges the build cli arguments into it -foundry_config::impl_figment_convert!(CoverageArgs, opts, evm_opts); +foundry_config::impl_figment_convert!(CoverageArgs, test); /// CLI arguments for `forge coverage`. -#[derive(Debug, Clone, Parser)] +#[derive(Parser)] pub struct CoverageArgs { /// The report type to use for coverage. /// /// This flag can be used multiple times. - #[clap(long, value_enum, default_value = "summary")] + #[arg(long, value_enum, default_value = "summary")] report: Vec, + /// The version of the LCOV "tracefile" format to use. + /// + /// Format: `MAJOR[.MINOR]`. + /// + /// Main differences: + /// - `1.x`: The original v1 format. + /// - `2.0`: Adds support for "line end" numbers for functions. + /// - `2.2`: Changes the format of functions. + #[arg(long, default_value = "1", value_parser = parse_lcov_version)] + lcov_version: Version, + /// Enable viaIR with minimum optimization /// /// This can fix most of the "stack too deep" errors while resulting a /// relatively accurate source map. - #[clap(long)] + #[arg(long)] ir_minimum: bool, - #[clap(flatten)] - filter: FilterArgs, - - #[clap(flatten)] - evm_opts: EvmArgs, - - #[clap(flatten)] - opts: CoreBuildArgs, + /// The path to output the report. + /// + /// If not specified, the report will be stored in the root of the project. + #[arg( + long, + short, + value_hint = ValueHint::FilePath, + value_name = "PATH" + )] + report_file: Option, + + /// Whether to include libraries in the coverage report. + #[arg(long)] + include_libs: bool, + + /// The coverage reporters to use. Constructed from the other fields. + #[arg(skip)] + reporters: Vec>, + + #[command(flatten)] + test: TestArgs, } impl CoverageArgs { - pub async fn run(self) -> Result<()> { - let (mut config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; + pub async fn run(mut self) -> Result<()> { + let (mut config, evm_opts) = self.load_config_and_evm_opts()?; // install missing dependencies - if install::install_missing_dependencies(&mut config, self.build_args().silent) && - config.auto_detect_remappings - { + if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings - config = self.load_config(); + config = self.load_config()?; } // Set fuzz seed so coverage reports are deterministic - config.fuzz.seed = Some(U256::from_big_endian(&STATIC_FUZZ_SEED)); + config.fuzz.seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); + + // Coverage analysis requires the Solc AST output. + config.ast = true; + + let (paths, output) = { + let (project, output) = self.build(&config)?; + (project.paths, output) + }; + + self.populate_reporters(&paths.root); - let (project, output) = self.build(&config)?; - p_println!(!self.opts.silent => "Analysing contracts..."); - let report = self.prepare(&config, output.clone())?; + sh_println!("Analysing contracts...")?; + let report = self.prepare(&paths, &output)?; - p_println!(!self.opts.silent => "Running tests..."); - self.collect(project, output, report, config, evm_opts).await + sh_println!("Running tests...")?; + self.collect(&paths.root, &output, report, Arc::new(config), evm_opts).await } - /// Builds the project. - fn build(&self, config: &Config) -> Result<(Project, ProjectCompileOutput)> { - // Set up the project - let project = { - let mut project = config.ephemeral_no_artifacts_project()?; - - if self.ir_minimum { - // TODO: How to detect solc version if the user does not specify a solc version in - // config case1: specify local installed solc ? - // case2: mutliple solc versions used and auto_detect_solc == true - if let Some(SolcReq::Version(version)) = &config.solc { - if *version < Version::new(0, 8, 13) { - return Err(eyre::eyre!( - "viaIR with minimum optimization is only available in Solidity 0.8.13 and above." - )); - } + fn populate_reporters(&mut self, root: &Path) { + self.reporters = self + .report + .iter() + .map(|report_kind| match report_kind { + CoverageReportKind::Summary => { + Box::::default() as Box } + CoverageReportKind::Lcov => { + let path = + root.join(self.report_file.as_deref().unwrap_or("lcov.info".as_ref())); + Box::new(LcovReporter::new(path, self.lcov_version.clone())) + } + CoverageReportKind::Bytecode => Box::new(BytecodeReporter::new( + root.to_path_buf(), + root.join("bytecode-coverage"), + )), + CoverageReportKind::Debug => Box::new(DebugReporter), + }) + .collect::>(); + } - // print warning message - p_println!(!self.opts.silent => "{}", - Paint::yellow( - concat!( - "Warning! \"--ir-minimum\" flag enables viaIR with minimum optimization, which can result in inaccurate source mappings.\n", - "Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n", - "Note that \"viaIR\" is only available in Solidity 0.8.13 and above.\n", - "See more:\n", - "https://github.com/foundry-rs/foundry/issues/3357\n" - ))); - - // Enable viaIR with minimum optimization - // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 - // And also in new releases of solidity: - // https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 - project.solc_config.settings = - project.solc_config.settings.with_via_ir_minimum_optimization() - } else { - project.solc_config.settings.optimizer.disable(); - project.solc_config.settings.optimizer.runs = None; - project.solc_config.settings.optimizer.details = None; - project.solc_config.settings.via_ir = None; - } - - project - }; + /// Builds the project. + fn build(&self, config: &Config) -> Result<(Project, ProjectCompileOutput)> { + let mut project = config.ephemeral_project()?; + + if self.ir_minimum { + // print warning message + sh_warn!( + "`--ir-minimum` enables `viaIR` with minimum optimization, \ + which can result in inaccurate source mappings.\n\ + Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n\ + Note that `viaIR` is production ready since Solidity 0.8.13 and above.\n\ + See more: https://github.com/foundry-rs/foundry/issues/3357" + )?; + + // Enable viaIR with minimum optimization: https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + // And also in new releases of Solidity: https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 + project.settings.solc.settings = + project.settings.solc.settings.with_via_ir_minimum_optimization(); + + // Sanitize settings for solc 0.8.4 if version cannot be detected: https://github.com/foundry-rs/foundry/issues/9322 + // But keep the EVM version: https://github.com/ethereum/solidity/issues/15775 + let evm_version = project.settings.solc.evm_version; + let version = config.solc_version().unwrap_or_else(|| Version::new(0, 8, 4)); + project.settings.solc.settings.sanitize(&version, SolcLanguage::Solidity); + project.settings.solc.evm_version = evm_version; + } else { + sh_warn!( + "optimizer settings and `viaIR` have been disabled for accurate coverage reports.\n\ + If you encounter \"stack too deep\" errors, consider using `--ir-minimum` which \ + enables `viaIR` with minimum optimization resolving most of the errors" + )?; + + project.settings.solc.optimizer.disable(); + project.settings.solc.optimizer.runs = None; + project.settings.solc.optimizer.details = None; + project.settings.solc.via_ir = None; + } let output = ProjectCompiler::default() .compile(&project)? @@ -141,134 +181,71 @@ impl CoverageArgs { } /// Builds the coverage report. - #[tracing::instrument(name = "prepare coverage", skip_all)] - fn prepare(&self, config: &Config, output: ProjectCompileOutput) -> Result { - let project_paths = config.project_paths(); - - // Extract artifacts - let (artifacts, sources) = output.into_artifacts_with_sources(); + #[instrument(name = "prepare", skip_all)] + fn prepare( + &self, + project_paths: &ProjectPathsConfig, + output: &ProjectCompileOutput, + ) -> Result { let mut report = CoverageReport::default(); - // Collect ASTs and sources - let mut versioned_asts: HashMap> = HashMap::new(); - let mut versioned_sources: HashMap> = HashMap::new(); - for (path, mut source_file, version) in sources.into_sources_with_version() { - // Filter out dependencies - if project_paths.has_library_ancestor(std::path::Path::new(&path)) { - continue - } + // Collect source files. + let mut versioned_sources = HashMap::>::default(); + for (path, source_file, version) in output.output().sources.sources_with_version() { + report.add_source(version.clone(), source_file.id as usize, path.clone()); - if let Some(ast) = source_file.ast.take() { - versioned_asts - .entry(version.clone()) - .or_default() - .insert(source_file.id as usize, ast); + // Filter out dependencies. + if !self.include_libs && project_paths.has_library_ancestor(path) { + continue; + } - let file = project_paths.root.join(&path); + if let Some(ast) = &source_file.ast { + let file = project_paths.root.join(path); trace!(root=?project_paths.root, ?file, "reading source file"); - versioned_sources.entry(version.clone()).or_default().insert( - source_file.id as usize, - fs::read_to_string(&file) + let source = SourceFile { + ast, + source: Source::read(&file) .wrap_err("Could not read source code for analysis")?, - ); - report.add_source(version, source_file.id as usize, path); + }; + versioned_sources + .entry(version.clone()) + .or_default() + .sources + .insert(source_file.id as usize, source); } } - // Get source maps and bytecodes - let (source_maps, bytecodes): (SourceMaps, HashMap) = artifacts - .into_iter() - .map(|(id, artifact)| (id, CompactContractBytecode::from(artifact))) + // Get source maps and bytecodes. + let artifacts: Vec = output + .artifact_ids() + .par_bridge() // This parses source maps, so we want to run it in parallel. .filter_map(|(id, artifact)| { - let contract_id = ContractId { - version: id.version.clone(), - source_id: *report - .get_source_id(id.version, id.source.to_string_lossy().to_string())?, - contract_name: id.name, - }; - let source_maps = ( - contract_id.clone(), - ( - artifact.get_source_map()?.ok()?, - artifact - .get_deployed_bytecode() - .as_ref()? - .bytecode - .as_ref()? - .source_map()? - .ok()?, - ), - ); - let bytecodes = ( - contract_id, - ( - artifact - .get_bytecode() - .and_then(|bytecode| dummy_link_bytecode(bytecode.into_owned()))?, - artifact.get_deployed_bytecode().and_then(|bytecode| { - dummy_link_deployed_bytecode(bytecode.into_owned()) - })?, - ), - ); - - Some((source_maps, bytecodes)) - }) - .unzip(); - - // Build IC -> PC mappings - // - // The source maps are indexed by *instruction counters*, which are the indexes of - // instructions in the bytecode *minus any push bytes*. - // - // Since our coverage inspector collects hit data using program counters, the anchors also - // need to be based on program counters. - // TODO: Index by contract ID - let ic_pc_maps: HashMap = bytecodes - .iter() - .map(|(id, bytecodes)| { - // TODO: Creation bytecode as well - ( - id.clone(), - ( - build_ic_pc_map(SpecId::LATEST, bytecodes.0.as_ref()), - build_ic_pc_map(SpecId::LATEST, bytecodes.1.as_ref()), - ), - ) + let source_id = report.get_source_id(id.version.clone(), id.source.clone())?; + ArtifactData::new(&id, source_id, artifact) }) .collect(); - // Add coverage items - for (version, asts) in versioned_asts.into_iter() { - let source_analysis = SourceAnalyzer::new( - version.clone(), - asts, - versioned_sources.remove(&version).ok_or_else(|| { - eyre::eyre!( - "File tree is missing source code, cannot perform coverage analysis" - ) - })?, - )? - .analyze()?; - let anchors: HashMap> = source_analysis - .contract_items - .iter() - .filter_map(|(contract_id, item_ids)| { - // TODO: Creation source map/bytecode as well - Some(( - contract_id.clone(), - find_anchors( - &bytecodes.get(contract_id)?.1, - &source_maps.get(contract_id)?.1, - &ic_pc_maps.get(contract_id)?.1, - item_ids, - &source_analysis.items, - ), - )) + // Add coverage items. + for (version, sources) in &versioned_sources { + let source_analysis = SourceAnalysis::new(sources)?; + let anchors = artifacts + .par_iter() + .filter(|artifact| artifact.contract_id.version == *version) + .map(|artifact| { + let creation_code_anchors = artifact.creation.find_anchors(&source_analysis); + let deployed_code_anchors = artifact.deployed.find_anchors(&source_analysis); + (artifact.contract_id.clone(), (creation_code_anchors, deployed_code_anchors)) }) - .collect(); - report.add_items(version, source_analysis.items); - report.add_anchors(anchors); + .collect_vec_list(); + report.add_anchors(anchors.into_iter().flatten()); + report.add_analysis(version.clone(), source_analysis); + } + + if self.reporters.iter().any(|reporter| reporter.needs_source_maps()) { + report.add_source_maps(artifacts.into_iter().map(|artifact| { + (artifact.contract_id, (artifact.creation.source_map, artifact.deployed.source_map)) + })); } Ok(report) @@ -276,95 +253,100 @@ impl CoverageArgs { /// Runs tests, collects coverage data and generates the final report. async fn collect( - self, - project: Project, - output: ProjectCompileOutput, + mut self, + root: &Path, + output: &ProjectCompileOutput, mut report: CoverageReport, - config: Config, + config: Arc, evm_opts: EvmOpts, ) -> Result<()> { - let root = project.paths.root; + let verbosity = evm_opts.verbosity; // Build the contract runner - let evm_spec = evm_spec(&config.evm_version); let env = evm_opts.evm_env().await?; - let mut runner = MultiContractRunnerBuilder::default() + let runner = MultiContractRunnerBuilder::new(config.clone()) .initial_balance(evm_opts.initial_balance) - .evm_spec(evm_spec) + .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new(&config, &evm_opts)) - .with_test_options(TestOptions { fuzz: config.fuzz, ..Default::default() }) .set_coverage(true) - .build(root.clone(), output, env, evm_opts)?; + .build::(root, output, env, evm_opts)?; - // Run tests let known_contracts = runner.known_contracts.clone(); - let filter = self.filter; - let (tx, rx) = channel::<(String, SuiteResult)>(); - let handle = - tokio::task::spawn( - async move { runner.test(filter, Some(tx), Default::default()).await }, - ); + + let filter = self.test.filter(&config); + let outcome = self.test.run_tests(runner, config, verbosity, &filter, output).await?; + + outcome.ensure_ok(false)?; // Add hit data to the coverage report - for (artifact_id, hits) in rx - .into_iter() - .flat_map(|(_, suite)| suite.test_results.into_values()) - .filter_map(|mut result| result.coverage.take()) - .flat_map(|hit_maps| { - hit_maps.0.into_values().filter_map(|map| { - Some((known_contracts.find_by_code(map.bytecode.as_ref())?.0, map)) - }) - }) - { - // TODO: Note down failing tests - if let Some(source_id) = report.get_source_id( - artifact_id.version.clone(), - artifact_id.source.to_string_lossy().to_string(), - ) { - let source_id = *source_id; - // TODO: Distinguish between creation/runtime in a smart way + let data = outcome.results.iter().flat_map(|(_, suite)| { + let mut hits = Vec::new(); + for result in suite.test_results.values() { + let Some(hit_maps) = result.coverage.as_ref() else { continue }; + for map in hit_maps.0.values() { + if let Some((id, _)) = known_contracts.find_by_deployed_code(map.bytecode()) { + hits.push((id, map, true)); + } else if let Some((id, _)) = + known_contracts.find_by_creation_code(map.bytecode()) + { + hits.push((id, map, false)); + } + } + } + hits + }); + + for (artifact_id, map, is_deployed_code) in data { + if let Some(source_id) = + report.get_source_id(artifact_id.version.clone(), artifact_id.source.clone()) + { report.add_hit_map( &ContractId { version: artifact_id.version.clone(), source_id, - contract_name: artifact_id.name.clone(), + contract_name: artifact_id.name.as_str().into(), }, - &hits, - ); + map, + is_deployed_code, + )?; } } - // Reattach the thread - let _ = handle.await; + // Filter out ignored sources from the report. + if let Some(not_re) = &filter.args().coverage_pattern_inverse { + let file_root = filter.paths().root.as_path(); + report.retain_sources(|path: &Path| { + let path = path.strip_prefix(file_root).unwrap_or(path); + !not_re.is_match(&path.to_string_lossy()) + }); + } - // Output final report - for report_kind in self.report { - match report_kind { - CoverageReportKind::Summary => SummaryReporter::default().report(&report), - // TODO: Sensible place to put the LCOV file - CoverageReportKind::Lcov => { - LcovReporter::new(&mut fs::create_file(root.join("lcov.info"))?).report(&report) - } - CoverageReportKind::Debug => DebugReporter.report(&report), - }?; + // Output final reports. + for reporter in &mut self.reporters { + reporter.report(&report)?; } + Ok(()) } - /// Returns the flattened [`CoreBuildArgs`] - pub fn build_args(&self) -> &CoreBuildArgs { - &self.opts + pub(crate) fn is_watch(&self) -> bool { + self.test.is_watch() + } + + pub(crate) fn watch(&self) -> &WatchArgs { + &self.test.watch } } -// TODO: HTML -#[derive(Debug, Clone, ValueEnum)] +/// Coverage reports to generate. +#[derive(Clone, Debug, Default, ValueEnum)] pub enum CoverageReportKind { + #[default] Summary, Lcov, Debug, + Bytecode, } /// Helper function that will link references in unlinked bytecode to the 0 address. @@ -374,7 +356,7 @@ fn dummy_link_bytecode(mut obj: CompactBytecode) -> Option { let link_references = obj.link_references.clone(); for (file, libraries) in link_references { for library in libraries.keys() { - obj.link(&file, library, Address::zero()); + obj.link(&file, library, Address::ZERO); } } @@ -388,3 +370,85 @@ fn dummy_link_bytecode(mut obj: CompactBytecode) -> Option { fn dummy_link_deployed_bytecode(obj: CompactDeployedBytecode) -> Option { obj.bytecode.and_then(dummy_link_bytecode) } + +pub struct ArtifactData { + pub contract_id: ContractId, + pub creation: BytecodeData, + pub deployed: BytecodeData, +} + +impl ArtifactData { + pub fn new(id: &ArtifactId, source_id: usize, artifact: &impl Artifact) -> Option { + Some(Self { + contract_id: ContractId { + version: id.version.clone(), + source_id, + contract_name: id.name.as_str().into(), + }, + creation: BytecodeData::new( + artifact.get_source_map()?.ok()?, + artifact + .get_bytecode() + .and_then(|bytecode| dummy_link_bytecode(bytecode.into_owned()))?, + ), + deployed: BytecodeData::new( + artifact.get_source_map_deployed()?.ok()?, + artifact + .get_deployed_bytecode() + .and_then(|bytecode| dummy_link_deployed_bytecode(bytecode.into_owned()))?, + ), + }) + } +} + +pub struct BytecodeData { + source_map: SourceMap, + bytecode: Bytes, + /// The instruction counter to program counter mapping. + /// + /// The source maps are indexed by *instruction counters*, which are the indexes of + /// instructions in the bytecode *minus any push bytes*. + /// + /// Since our coverage inspector collects hit data using program counters, the anchors + /// also need to be based on program counters. + ic_pc_map: IcPcMap, +} + +impl BytecodeData { + fn new(source_map: SourceMap, bytecode: Bytes) -> Self { + let ic_pc_map = IcPcMap::new(&bytecode); + Self { source_map, bytecode, ic_pc_map } + } + + pub fn find_anchors(&self, source_analysis: &SourceAnalysis) -> Vec { + find_anchors(&self.bytecode, &self.source_map, &self.ic_pc_map, source_analysis) + } +} + +fn parse_lcov_version(s: &str) -> Result { + let vr = VersionReq::parse(&format!("={s}")).map_err(|e| e.to_string())?; + let [c] = &vr.comparators[..] else { + return Err("invalid version".to_string()); + }; + if c.op != semver::Op::Exact { + return Err("invalid version".to_string()); + } + if !c.pre.is_empty() { + return Err("pre-releases are not supported".to_string()); + } + Ok(Version::new(c.major, c.minor.unwrap_or(0), c.patch.unwrap_or(0))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn lcov_version() { + assert_eq!(parse_lcov_version("0").unwrap(), Version::new(0, 0, 0)); + assert_eq!(parse_lcov_version("1").unwrap(), Version::new(1, 0, 0)); + assert_eq!(parse_lcov_version("1.0").unwrap(), Version::new(1, 0, 0)); + assert_eq!(parse_lcov_version("1.1").unwrap(), Version::new(1, 1, 0)); + assert_eq!(parse_lcov_version("1.11").unwrap(), Version::new(1, 11, 0)); + } +} diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index ad82163aa72f6..ab3eb2c41cff7 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -1,90 +1,128 @@ -use super::{retry::RetryArgs, verify}; +use crate::cmd::install; +use alloy_chains::Chain; +use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; +use alloy_json_abi::{Constructor, JsonAbi}; +use alloy_network::{AnyNetwork, AnyTransactionReceipt, EthereumWallet, TransactionBuilder}; +use alloy_primitives::{hex, Address, Bytes}; +use alloy_provider::{PendingTransactionError, Provider, ProviderBuilder}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; +use alloy_transport::TransportError; use clap::{Parser, ValueHint}; -use ethers::{ - abi::{Abi, Constructor, Token}, - prelude::{artifacts::BytecodeObject, ContractFactory, Middleware, MiddlewareBuilder}, - solc::{info::ContractInfo, utils::canonicalized}, - types::{transaction::eip2718::TypedTransaction, Chain}, - utils::to_checksum, -}; use eyre::{Context, Result}; +use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::{ - opts::{CoreBuildArgs, EthereumOpts, EtherscanOpts, TransactionOpts}, + opts::{BuildOpts, EthereumOpts, EtherscanOpts, TransactionOpts}, utils::{self, read_constructor_args_file, remove_contract, LoadConfig}, }; -use foundry_common::{abi::parse_tokens, compile, estimate_eip1559_fees}; +use foundry_common::{ + compile::{self}, + fmt::parse_tokens, + shell, +}; +use foundry_compilers::{ + artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, ArtifactId, +}; +use foundry_config::{ + figment::{ + self, + value::{Dict, Map}, + Metadata, Profile, + }, + merge_impl_figment_convert, Config, +}; use serde_json::json; -use std::{path::PathBuf, sync::Arc}; +use std::{borrow::Borrow, marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; + +merge_impl_figment_convert!(CreateArgs, build, eth); /// CLI arguments for `forge create`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct CreateArgs { /// The contract identifier in the form `:`. contract: ContractInfo, /// The constructor arguments. - #[clap( + #[arg( long, num_args(1..), conflicts_with = "constructor_args_path", value_name = "ARGS", + allow_hyphen_values = true, )] constructor_args: Vec, /// The path to a file containing the constructor arguments. - #[clap( + #[arg( long, value_hint = ValueHint::FilePath, value_name = "PATH", )] constructor_args_path: Option, - /// Print the deployment information as JSON. - #[clap(long, help_heading = "Display options")] - json: bool, + /// Broadcast the transaction. + #[arg(long)] + pub broadcast: bool, /// Verify contract after creation. - #[clap(long)] + #[arg(long)] verify: bool, /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender - #[clap(long, requires = "from")] + #[arg(long, requires = "from")] unlocked: bool, - #[clap(flatten)] - opts: CoreBuildArgs, + /// Prints the standard json compiler input if `--verify` is provided. + /// + /// The standard json compiler input can be used to manually submit contract verification in + /// the browser. + #[arg(long, requires = "verify")] + show_standard_json_input: bool, + + /// Timeout to use for broadcasting transactions. + #[arg(long, env = "ETH_TIMEOUT")] + pub timeout: Option, - #[clap(flatten)] + #[command(flatten)] + build: BuildOpts, + + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, - #[clap(flatten)] - pub verifier: verify::VerifierArgs, + #[command(flatten)] + pub verifier: VerifierArgs, - #[clap(flatten)] + #[command(flatten)] retry: RetryArgs, } impl CreateArgs { /// Executes the command to create a contract pub async fn run(mut self) -> Result<()> { + let mut config = self.load_config()?; + + // Install missing dependencies. + if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + // need to re-configure here to also catch additional remappings + config = self.load_config()?; + } + // Find Project & Compile - let project = self.opts.project()?; - let mut output = if self.json || self.opts.silent { - // Suppress compile stdout messages when printing json output or when silent - compile::suppress_compile(&project) + let project = config.project()?; + + let target_path = if let Some(ref mut path) = self.contract.path { + canonicalize(project.root().join(path))? } else { - compile::compile(&project, false, false) - }?; + project.find_contract_path(&self.contract.name)? + }; - if let Some(ref mut path) = self.contract.path { - // paths are absolute in the project's output - *path = canonicalized(project.root().join(&path)).to_string_lossy().to_string(); - } + let output = compile::compile_target(&target_path, &project, shell::is_json())?; - let (abi, bin, _) = remove_contract(&mut output, &self.contract)?; + let (abi, bin, id) = remove_contract(output, &target_path, &self.contract.name)?; let bin = match bin.object { BytecodeObject::Bytecode(_) => bin.object, @@ -102,35 +140,71 @@ impl CreateArgs { }; // Add arguments to constructor - let config = self.eth.try_load_config_emit_warnings()?; + let params = if let Some(constructor) = &abi.constructor { + let constructor_args = + self.constructor_args_path.clone().map(read_constructor_args_file).transpose()?; + self.parse_constructor_args( + constructor, + constructor_args.as_deref().unwrap_or(&self.constructor_args), + )? + } else { + vec![] + }; + let provider = utils::get_provider(&config)?; - let params = match abi.constructor { - Some(ref v) => { - let constructor_args = - if let Some(ref constructor_args_path) = self.constructor_args_path { - read_constructor_args_file(constructor_args_path.to_path_buf())? - } else { - self.constructor_args.clone() - }; - self.parse_constructor_args(v, &constructor_args)? - } - None => vec![], + + // respect chain, if set explicitly via cmd args + let chain_id = if let Some(chain_id) = self.chain_id() { + chain_id + } else { + provider.get_chain_id().await? }; - let chain_id = provider.get_chainid().await?.as_u64(); + // Whether to broadcast the transaction or not + let dry_run = !self.broadcast; + if self.unlocked { // Deploy with unlocked account let sender = self.eth.wallet.from.expect("required"); - let provider = provider.with_sender(sender); - self.deploy(abi, bin, params, provider, chain_id).await + self.deploy( + abi, + bin, + params, + provider, + chain_id, + sender, + config.transaction_timeout, + id, + dry_run, + ) + .await } else { // Deploy with signer - let signer = self.eth.wallet.signer(chain_id).await?; - let provider = provider.with_signer(signer); - self.deploy(abi, bin, params, provider, chain_id).await + let signer = self.eth.wallet.signer().await?; + let deployer = signer.address(); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .wallet(EthereumWallet::new(signer)) + .on_provider(provider); + self.deploy( + abi, + bin, + params, + provider, + chain_id, + deployer, + config.transaction_timeout, + id, + dry_run, + ) + .await } } + /// Returns the provided chain id, if any. + fn chain_id(&self) -> Option { + self.eth.etherscan.chain.map(|chain| chain.id()) + } + /// Ensures the verify command can be executed. /// /// This is supposed to check any things that might go wrong when preparing a verify request @@ -141,13 +215,14 @@ impl CreateArgs { &self, constructor_args: Option, chain: u64, + id: &ArtifactId, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, // since we don't know the address yet. - let verify = verify::VerifyArgs { + let mut verify = VerifyArgs { address: Default::default(), - contract: self.contract.clone(), - compiler_version: None, + contract: Some(self.contract.clone()), + compiler_version: Some(id.version.to_string()), constructor_args, constructor_args_path: None, num_of_optimizations: None, @@ -155,181 +230,414 @@ impl CreateArgs { key: self.eth.etherscan.key.clone(), chain: Some(chain.into()), }, + rpc: Default::default(), flatten: false, force: false, + skip_is_verified_check: true, watch: true, retry: self.retry, - libraries: vec![], + libraries: self.build.libraries.clone(), root: None, verifier: self.verifier.clone(), - show_standard_json_input: false, + via_ir: self.build.via_ir, + evm_version: self.build.compiler.evm_version, + show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, + compilation_profile: Some(id.profile.to_string()), }; - verify.verification_provider()?.preflight_check(verify).await?; + + // Check config for Etherscan API Keys to avoid preflight check failing if no + // ETHERSCAN_API_KEY value set. + let config = verify.load_config()?; + verify.etherscan.key = + config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); + + let context = verify.resolve_context().await?; + + verify.verification_provider()?.preflight_verify_check(verify, context).await?; Ok(()) } /// Deploys the contract - async fn deploy( + #[allow(clippy::too_many_arguments)] + async fn deploy>( self, - abi: Abi, + abi: JsonAbi, bin: BytecodeObject, - args: Vec, - provider: M, + args: Vec, + provider: P, chain: u64, + deployer_address: Address, + timeout: u64, + id: ArtifactId, + dry_run: bool, ) -> Result<()> { - let deployer_address = - provider.default_sender().expect("no sender address set for provider"); let bin = bin.into_bytes().unwrap_or_else(|| { panic!("no bytecode found in bin object for {}", self.contract.name) }); let provider = Arc::new(provider); - let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone()); + let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone(), timeout); let is_args_empty = args.is_empty(); - let deployer = - factory.deploy_tokens(args.clone()).context("Failed to deploy contract").map_err(|e| { + let mut deployer = + factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { if is_args_empty { - e.wrap_err("No arguments provided for contract constructor. Consider --constructor-args or --constructor-args-path") + e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") } else { e } })?; - let is_legacy = self.tx.legacy || - Chain::try_from(chain).map(|x| Chain::is_legacy(&x)).unwrap_or_default(); - let mut deployer = if is_legacy { deployer.legacy() } else { deployer }; + let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); + + deployer.tx.set_from(deployer_address); + deployer.tx.set_chain_id(chain); + // `to` field must be set explicitly, cannot be None. + if deployer.tx.to.is_none() { + deployer.tx.set_create(); + } + deployer.tx.set_nonce(if let Some(nonce) = self.tx.nonce { + Ok(nonce.to()) + } else { + provider.get_transaction_count(deployer_address).await + }?); // set tx value if specified if let Some(value) = self.tx.value { deployer.tx.set_value(value); } - // fill tx first because if you target a lower gas than current base, eth_estimateGas - // will fail and create will fail - provider.fill_transaction(&mut deployer.tx, None).await?; - - // the max - let mut priority_fee = self.tx.priority_gas_price; - - // set gas price if specified - if let Some(gas_price) = self.tx.gas_price { + deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit { + Ok(gas_limit.to()) + } else { + provider.estimate_gas(&deployer.tx).await + }?); + + if is_legacy { + let gas_price = if let Some(gas_price) = self.tx.gas_price { + gas_price.to() + } else { + provider.get_gas_price().await? + }; deployer.tx.set_gas_price(gas_price); - } else if !is_legacy { - // estimate EIP1559 fees - let (max_fee, max_priority_fee) = estimate_eip1559_fees(&provider, Some(chain)) - .await - .wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; - deployer.tx.set_gas_price(max_fee); - if priority_fee.is_none() { - priority_fee = Some(max_priority_fee); - } - } - - // set gas limit if specified - if let Some(gas_limit) = self.tx.gas_limit { - deployer.tx.set_gas(gas_limit); - } - - // set nonce if specified - if let Some(nonce) = self.tx.nonce { - deployer.tx.set_nonce(nonce); - } - - // set priority fee if specified - if let Some(priority_fee) = priority_fee { - if is_legacy { - eyre::bail!("there is no priority fee for legacy txs"); - } - deployer.tx = match deployer.tx { - TypedTransaction::Eip1559(eip1559_tx_request) => TypedTransaction::Eip1559( - eip1559_tx_request.max_priority_fee_per_gas(priority_fee), - ), - _ => deployer.tx, + } else { + let estimate = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + let priority_fee = if let Some(priority_fee) = self.tx.priority_gas_price { + priority_fee.to() + } else { + estimate.max_priority_fee_per_gas }; + let max_fee = if let Some(max_fee) = self.tx.gas_price { + max_fee.to() + } else { + estimate.max_fee_per_gas + }; + + deployer.tx.set_max_fee_per_gas(max_fee); + deployer.tx.set_max_priority_fee_per_gas(priority_fee); } // Before we actually deploy the contract we try check if the verify settings are valid let mut constructor_args = None; if self.verify { if !args.is_empty() { - // we're passing an empty vec to the `encode_input` of the constructor because we - // only need the constructor arguments and the encoded input is - // `code + args` - let code = Vec::new(); let encoded_args = abi .constructor() .ok_or_else(|| eyre::eyre!("could not find constructor"))? - .encode_input(code, &args)?; + .abi_encode_input(&args)?; constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(constructor_args.clone(), chain).await?; + self.verify_preflight_check(constructor_args.clone(), chain, &id).await?; + } + + if dry_run { + if !shell::is_json() { + sh_warn!("Dry run enabled, not broadcasting transaction\n")?; + + sh_println!("Contract: {}", self.contract.name)?; + sh_println!( + "Transaction: {}", + serde_json::to_string_pretty(&deployer.tx.clone())? + )?; + sh_println!("ABI: {}\n", serde_json::to_string_pretty(&abi)?)?; + + sh_warn!("To broadcast this transaction, add --broadcast to the previous command. See forge create --help for more.")?; + } else { + let output = json!({ + "contract": self.contract.name, + "transaction": &deployer.tx, + "abi":&abi + }); + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; + } + + return Ok(()); } // Deploy the actual contract let (deployed_contract, receipt) = deployer.send_with_receipt().await?; - let address = deployed_contract.address(); - if self.json { + let address = deployed_contract; + if shell::is_json() { let output = json!({ - "deployer": to_checksum(&deployer_address, None), - "deployedTo": to_checksum(&address, None), + "deployer": deployer_address.to_string(), + "deployedTo": address.to_string(), "transactionHash": receipt.transaction_hash }); - println!("{output}"); + sh_println!("{}", serde_json::to_string_pretty(&output)?)?; } else { - println!("Deployer: {}", to_checksum(&deployer_address, None)); - println!("Deployed to: {}", to_checksum(&address, None)); - println!("Transaction hash: {:?}", receipt.transaction_hash); + sh_println!("Deployer: {deployer_address}")?; + sh_println!("Deployed to: {address}")?; + sh_println!("Transaction hash: {:?}", receipt.transaction_hash)?; }; if !self.verify { - return Ok(()) + return Ok(()); } - println!("Starting contract verification..."); + sh_println!("Starting contract verification...")?; - let num_of_optimizations = - if self.opts.compiler.optimize { self.opts.compiler.optimizer_runs } else { None }; - let verify = verify::VerifyArgs { + let num_of_optimizations = if self.build.compiler.optimize.unwrap_or_default() { + self.build.compiler.optimizer_runs + } else { + None + }; + let verify = VerifyArgs { address, - contract: self.contract, - compiler_version: None, + contract: Some(self.contract), + compiler_version: Some(id.version.to_string()), constructor_args, constructor_args_path: None, num_of_optimizations, - etherscan: EtherscanOpts { key: self.eth.etherscan.key, chain: Some(chain.into()) }, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + rpc: Default::default(), flatten: false, force: false, + skip_is_verified_check: true, watch: true, retry: self.retry, - libraries: vec![], + libraries: self.build.libraries.clone(), root: None, verifier: self.verifier, - show_standard_json_input: false, + via_ir: self.build.via_ir, + evm_version: self.build.compiler.evm_version, + show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, + compilation_profile: Some(id.profile.to_string()), }; - println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier); + sh_println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier)?; verify.run().await } + /// Parses the given constructor arguments into a vector of `DynSolValue`s, by matching them + /// against the constructor's input params. + /// + /// Returns a list of parsed values that match the constructor's input params. fn parse_constructor_args( &self, constructor: &Constructor, constructor_args: &[String], - ) -> Result> { - let params = constructor - .inputs - .iter() - .zip(constructor_args) - .map(|(input, arg)| (&input.kind, arg.as_str())) - .collect::>(); - - parse_tokens(params, true) + ) -> Result> { + let mut params = Vec::with_capacity(constructor.inputs.len()); + for (input, arg) in constructor.inputs.iter().zip(constructor_args) { + // resolve the input type directly + let ty = input + .resolve() + .wrap_err_with(|| format!("Could not resolve constructor arg: input={input}"))?; + params.push((ty, arg)); + } + let params = params.iter().map(|(ty, arg)| (ty, arg.as_str())); + parse_tokens(params).map_err(Into::into) + } +} + +impl figment::Provider for CreateArgs { + fn metadata(&self) -> Metadata { + Metadata::named("Create Args Provider") + } + + fn data(&self) -> Result, figment::Error> { + let mut dict = Dict::default(); + if let Some(timeout) = self.timeout { + dict.insert("transaction_timeout".to_string(), timeout.into()); + } + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +/// `ContractFactory` is a [`DeploymentTxFactory`] object with an +/// [`Arc`] middleware. This type alias exists to preserve backwards +/// compatibility with less-abstract Contracts. +/// +/// For full usage docs, see [`DeploymentTxFactory`]. +pub type ContractFactory

= DeploymentTxFactory

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

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

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

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

{ + /// Broadcasts the contract deployment transaction and after waiting for it to + /// be sufficiently confirmed (default: 1), it returns a tuple with + /// the [`Contract`](crate::Contract) struct at the deployed contract's address + /// and the corresponding [`AnyReceipt`]. + pub async fn send_with_receipt( + self, + ) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> { + let receipt = self + .client + .borrow() + .send_transaction(self.tx) + .await? + .with_required_confirmations(self.confs as u64) + .with_timeout(Some(Duration::from_secs(self.timeout))) + .get_receipt() + .await?; + + let address = + receipt.contract_address.ok_or(ContractDeploymentError::ContractNotDeployed)?; + + Ok((address, receipt)) + } +} + +/// To deploy a contract to the Ethereum network, a `ContractFactory` can be +/// created which manages the Contract bytecode and Application Binary Interface +/// (ABI), usually generated from the Solidity compiler. +/// +/// Once the factory's deployment transaction is mined with sufficient confirmations, +/// the [`Contract`](crate::Contract) object is returned. +/// +/// # Example +/// +/// ``` +/// # async fn foo() -> Result<(), Box> { +/// use alloy_primitives::Bytes; +/// use ethers_contract::ContractFactory; +/// use ethers_providers::{Provider, Http}; +/// +/// // get the contract ABI and bytecode +/// let abi = Default::default(); +/// let bytecode = Bytes::from_static(b"..."); +/// +/// // connect to the network +/// let client = Provider::::try_from("http://localhost:8545").unwrap(); +/// let client = std::sync::Arc::new(client); +/// +/// // create a factory which will be used to deploy instances of the contract +/// let factory = ContractFactory::new(abi, bytecode, client); +/// +/// // The deployer created by the `deploy` call exposes a builder which gets consumed +/// // by the async `send` call +/// let contract = factory +/// .deploy("initial value".to_string())? +/// .confirmations(0usize) +/// .send() +/// .await?; +/// println!("{}", contract.address()); +/// # Ok(()) +/// # } +#[derive(Clone, Debug)] +pub struct DeploymentTxFactory

{ + client: P, + abi: JsonAbi, + bytecode: Bytes, + timeout: u64, +} + +impl + Clone> DeploymentTxFactory

{ + /// Creates a factory for deployment of the Contract with bytecode, and the + /// constructor defined in the abi. The client will be used to send any deployment + /// transaction. + pub fn new(abi: JsonAbi, bytecode: Bytes, client: P, timeout: u64) -> Self { + Self { client, abi, bytecode, timeout } + } + + /// Create a deployment tx using the provided tokens as constructor + /// arguments + pub fn deploy_tokens( + self, + params: Vec, + ) -> Result, ContractDeploymentError> { + // Encode the constructor args & concatenate with the bytecode if necessary + let data: Bytes = match (self.abi.constructor(), params.is_empty()) { + (None, false) => return Err(ContractDeploymentError::ConstructorError), + (None, true) => self.bytecode.clone(), + (Some(constructor), _) => { + let input: Bytes = constructor + .abi_encode_input(¶ms) + .map_err(ContractDeploymentError::DetokenizationError)? + .into(); + // Concatenate the bytecode and abi-encoded constructor call. + self.bytecode.iter().copied().chain(input).collect() + } + }; + + // create the tx object. Since we're deploying a contract, `to` is `None` + let tx = WithOtherFields::new(TransactionRequest::default().input(data.into())); + + Ok(Deployer { client: self.client.clone(), tx, confs: 1, timeout: self.timeout }) + } +} + +#[derive(thiserror::Error, Debug)] +/// An Error which is thrown when interacting with a smart contract +pub enum ContractDeploymentError { + #[error("constructor is not defined in the ABI")] + ConstructorError, + #[error(transparent)] + DetokenizationError(#[from] alloy_dyn_abi::Error), + #[error("contract was not deployed")] + ContractNotDeployed, + #[error(transparent)] + RpcError(#[from] TransportError), +} + +impl From for ContractDeploymentError { + fn from(_err: PendingTransactionError) -> Self { + Self::ContractNotDeployed } } #[cfg(test)] mod tests { use super::*; + use alloy_primitives::I256; #[test] fn can_parse_create() { @@ -345,4 +653,57 @@ mod tests { assert_eq!(args.retry.retries, 10); assert_eq!(args.retry.delay, 30); } + #[test] + fn can_parse_chain_id() { + let args: CreateArgs = CreateArgs::parse_from([ + "foundry-cli", + "src/Domains.sol:Domains", + "--verify", + "--retries", + "10", + "--delay", + "30", + "--chain-id", + "9999", + ]); + assert_eq!(args.chain_id(), Some(9999)); + } + + #[test] + fn test_parse_constructor_args() { + let args: CreateArgs = CreateArgs::parse_from([ + "foundry-cli", + "src/Domains.sol:Domains", + "--constructor-args", + "Hello", + ]); + let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_name","type":"string","internalType":"string"}],"stateMutability":"nonpayable"}"#).unwrap(); + let params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap(); + assert_eq!(params, vec![DynSolValue::String("Hello".to_string())]); + } + + #[test] + fn test_parse_tuple_constructor_args() { + let args: CreateArgs = CreateArgs::parse_from([ + "foundry-cli", + "src/Domains.sol:Domains", + "--constructor-args", + "[(1,2), (2,3), (3,4)]", + ]); + let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_points","type":"tuple[]","internalType":"struct Point[]","components":[{"name":"x","type":"uint256","internalType":"uint256"},{"name":"y","type":"uint256","internalType":"uint256"}]}],"stateMutability":"nonpayable"}"#).unwrap(); + let _params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap(); + } + + #[test] + fn test_parse_int_constructor_args() { + let args: CreateArgs = CreateArgs::parse_from([ + "foundry-cli", + "src/Domains.sol:Domains", + "--constructor-args", + "-5", + ]); + let constructor: Constructor = serde_json::from_str(r#"{"type":"constructor","inputs":[{"name":"_name","type":"int256","internalType":"int256"}],"stateMutability":"nonpayable"}"#).unwrap(); + let params = args.parse_constructor_args(&constructor, &args.constructor_args).unwrap(); + assert_eq!(params, vec![DynSolValue::Int(I256::unchecked_from(-5), 256)]); + } } diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs deleted file mode 100644 index abd7362720870..0000000000000 --- a/crates/forge/bin/cmd/debug.rs +++ /dev/null @@ -1,59 +0,0 @@ -use super::{build::BuildArgs, retry::RETRY_VERIFY_ON_CREATE, script::ScriptArgs}; -use clap::{Parser, ValueHint}; -use eyre::Result; -use foundry_cli::opts::CoreBuildArgs; -use foundry_common::evm::{Breakpoints, EvmArgs}; -use std::path::PathBuf; - -// Loads project's figment and merges the build cli arguments into it -foundry_config::impl_figment_convert!(DebugArgs, opts, evm_opts); - -/// CLI arguments for `forge debug`. -#[derive(Debug, Clone, Parser)] -pub struct DebugArgs { - /// The contract you want to run. Either the file path or contract name. - /// - /// If multiple contracts exist in the same file you must specify the target contract with - /// --target-contract. - #[clap(value_hint = ValueHint::FilePath)] - pub path: PathBuf, - - /// Arguments to pass to the script function. - pub args: Vec, - - /// The name of the contract you want to run. - #[clap(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] - pub target_contract: Option, - - /// The signature of the function you want to call in the contract, or raw calldata. - #[clap(long, short, default_value = "run()", value_name = "SIGNATURE")] - pub sig: String, - - /// Open the script in the debugger. - #[clap(long)] - pub debug: bool, - - #[clap(flatten)] - pub opts: CoreBuildArgs, - - #[clap(flatten)] - pub evm_opts: EvmArgs, -} - -impl DebugArgs { - pub async fn debug(self, breakpoints: Breakpoints) -> Result<()> { - let script = ScriptArgs { - path: self.path.to_str().expect("Invalid path string.").to_string(), - args: self.args, - target_contract: self.target_contract, - sig: self.sig, - gas_estimate_multiplier: 130, - opts: BuildArgs { args: self.opts, ..Default::default() }, - evm_opts: self.evm_opts, - debug: true, - retry: RETRY_VERIFY_ON_CREATE, - ..Default::default() - }; - script.run_script(breakpoints).await - } -} diff --git a/crates/forge/bin/cmd/doc.rs b/crates/forge/bin/cmd/doc/mod.rs similarity index 51% rename from crates/forge/bin/cmd/doc.rs rename to crates/forge/bin/cmd/doc/mod.rs index 04e3cc87a5584..73b78618cbb0e 100644 --- a/crates/forge/bin/cmd/doc.rs +++ b/crates/forge/bin/cmd/doc/mod.rs @@ -1,23 +1,30 @@ +use super::watch::WatchArgs; use clap::{Parser, ValueHint}; use eyre::Result; -use forge_doc::{ContractInheritance, Deployments, DocBuilder, GitSource, Inheritdoc, Server}; +use forge_doc::{ + ContractInheritance, Deployments, DocBuilder, GitSource, InferInlineHyperlinks, Inheritdoc, +}; use foundry_cli::opts::GH_REPO_PREFIX_REGEX; -use foundry_config::{find_project_root_path, load_config_with_root}; +use foundry_common::compile::ProjectCompiler; +use foundry_config::{load_config_with_root, Config}; use std::{path::PathBuf, process::Command}; -#[derive(Debug, Clone, Parser)] +mod server; +use server::Server; + +#[derive(Clone, Debug, Parser)] pub struct DocArgs { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, /// The doc's output path. /// /// By default, it is the `docs/` in project root. - #[clap( + #[arg( long, short, value_hint = ValueHint::DirPath, @@ -26,33 +33,47 @@ pub struct DocArgs { out: Option, /// Build the `mdbook` from generated files. - #[clap(long, short)] + #[arg(long, short)] build: bool, /// Serve the documentation. - #[clap(long, short)] + #[arg(long, short)] serve: bool, + /// Open the documentation in a browser after serving. + #[arg(long, requires = "serve")] + open: bool, + /// Hostname for serving documentation. - #[clap(long, requires = "serve")] + #[arg(long, requires = "serve")] hostname: Option, + #[command(flatten)] + pub watch: WatchArgs, + /// Port for serving documentation. - #[clap(long, short, requires = "serve")] + #[arg(long, short, requires = "serve")] port: Option, /// The relative path to the `hardhat-deploy` or `forge-deploy` artifact directory. Leave blank /// for default. - #[clap(long)] + #[arg(long)] deployments: Option>, + + /// Whether to create docs for external libraries. + #[arg(long, short)] + include_libraries: bool, } impl DocArgs { - pub fn run(self) -> Result<()> { - let root = self.root.clone().unwrap_or(find_project_root_path(None)?); - let config = load_config_with_root(Some(root.clone())); + pub async fn run(self) -> Result<()> { + let config = self.config()?; + let root = &config.root; + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet(true); + let _output = compiler.compile(&project)?; - let mut doc_config = config.doc.clone(); + let mut doc_config = config.doc; if let Some(out) = self.out { doc_config.out = out; } @@ -74,34 +95,50 @@ impl DocArgs { } } - let commit = foundry_cli::utils::Git::new(&root).commit_hash(false).ok(); + let commit = foundry_cli::utils::Git::new(root).commit_hash(false, "HEAD").ok(); - let mut builder = DocBuilder::new(root.clone(), config.project_paths().sources) - .with_should_build(self.build) - .with_config(doc_config.clone()) - .with_fmt(config.fmt) - .with_preprocessor(ContractInheritance::default()) - .with_preprocessor(Inheritdoc::default()) - .with_preprocessor(GitSource { - root: root.clone(), - commit, - repository: doc_config.repository.clone(), - }); + let mut builder = DocBuilder::new( + root.clone(), + project.paths.sources, + project.paths.libraries, + self.include_libraries, + ) + .with_should_build(self.build) + .with_config(doc_config.clone()) + .with_fmt(config.fmt) + .with_preprocessor(ContractInheritance { include_libraries: self.include_libraries }) + .with_preprocessor(Inheritdoc::default()) + .with_preprocessor(InferInlineHyperlinks::default()) + .with_preprocessor(GitSource { + root: root.clone(), + commit, + repository: doc_config.repository.clone(), + }); // If deployment docgen is enabled, add the [Deployments] preprocessor if let Some(deployments) = self.deployments { - builder = builder.with_preprocessor(Deployments { root, deployments }); + builder = builder.with_preprocessor(Deployments { root: root.clone(), deployments }); } builder.build()?; if self.serve { Server::new(doc_config.out) - .with_hostname(self.hostname.unwrap_or("localhost".to_owned())) + .with_hostname(self.hostname.unwrap_or_else(|| "localhost".into())) .with_port(self.port.unwrap_or(3000)) + .open(self.open) .serve()?; } Ok(()) } + + /// Returns whether watch mode is enabled + pub fn is_watch(&self) -> bool { + self.watch.watch.is_some() + } + + pub fn config(&self) -> Result { + load_config_with_root(self.root.as_deref()) + } } diff --git a/crates/forge/bin/cmd/doc/server.rs b/crates/forge/bin/cmd/doc/server.rs new file mode 100644 index 0000000000000..09662270a4514 --- /dev/null +++ b/crates/forge/bin/cmd/doc/server.rs @@ -0,0 +1,107 @@ +use axum::{routing::get_service, Router}; +use forge_doc::mdbook::{utils::fs::get_404_output_file, MDBook}; +use std::{ + io, + net::{SocketAddr, ToSocketAddrs}, + path::PathBuf, +}; +use tower_http::services::{ServeDir, ServeFile}; + +/// The HTTP endpoint for the websocket used to trigger reloads when a file changes. +const LIVE_RELOAD_ENDPOINT: &str = "/__livereload"; + +/// Basic mdbook server. Given a path, hostname and port, serves the mdbook. +#[derive(Debug)] +pub struct Server { + path: PathBuf, + hostname: String, + port: usize, + open: bool, +} + +impl Default for Server { + fn default() -> Self { + Self { path: PathBuf::default(), hostname: "localhost".to_owned(), port: 3000, open: false } + } +} + +impl Server { + /// Create a new instance. + pub fn new(path: PathBuf) -> Self { + Self { path, ..Default::default() } + } + + /// Set the host to serve on. + pub fn with_hostname(mut self, hostname: String) -> Self { + self.hostname = hostname; + self + } + + /// Set the port to serve on. + pub fn with_port(mut self, port: usize) -> Self { + self.port = port; + self + } + + /// Set whether to open the browser after serving. + pub fn open(mut self, open: bool) -> Self { + self.open = open; + self + } + + /// Serve the mdbook. + pub fn serve(self) -> eyre::Result<()> { + let mut book = + MDBook::load(&self.path).map_err(|err| eyre::eyre!("failed to load book: {err:?}"))?; + + let reload = LIVE_RELOAD_ENDPOINT.strip_prefix('/').unwrap(); + book.config.set("output.html.live-reload-endpoint", reload).unwrap(); + // Override site-url for local serving of the 404 file + book.config.set("output.html.site-url", "/").unwrap(); + + book.build().map_err(|err| eyre::eyre!("failed to build book: {err:?}"))?; + + let address = format!("{}:{}", self.hostname, self.port); + let sockaddr: SocketAddr = address + .to_socket_addrs()? + .next() + .ok_or_else(|| eyre::eyre!("no address found for {}", address))?; + let build_dir = book.build_dir_for("html"); + let input_404 = book + .config + .get("output.html.input-404") + .and_then(|v| v.as_str()) + .map(ToString::to_string); + let file_404 = get_404_output_file(&input_404); + + let serving_url = format!("http://{address}"); + sh_println!("Serving on: {serving_url}")?; + + let thread_handle = std::thread::spawn(move || serve(build_dir, sockaddr, &file_404)); + + if self.open { + open(serving_url); + } + + match thread_handle.join() { + Ok(r) => r.map_err(Into::into), + Err(e) => std::panic::resume_unwind(e), + } + } +} + +#[tokio::main] +async fn serve(build_dir: PathBuf, address: SocketAddr, file_404: &str) -> io::Result<()> { + let file_404 = build_dir.join(file_404); + let svc = ServeDir::new(build_dir).not_found_service(ServeFile::new(file_404)); + let app = Router::new().nest_service("/", get_service(svc)); + let tcp_listener = tokio::net::TcpListener::bind(address).await?; + axum::serve(tcp_listener, app.into_make_service()).await +} + +fn open>(path: P) { + info!("Opening web browser"); + if let Err(e) = opener::open(path) { + error!("Error opening web browser: {}", e); + } +} diff --git a/crates/forge/bin/cmd/eip712.rs b/crates/forge/bin/cmd/eip712.rs new file mode 100644 index 0000000000000..6211695ab48d5 --- /dev/null +++ b/crates/forge/bin/cmd/eip712.rs @@ -0,0 +1,254 @@ +use clap::{Parser, ValueHint}; +use eyre::{Ok, OptionExt, Result}; +use foundry_cli::{opts::BuildOpts, utils::LoadConfig}; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::artifacts::{ + output_selection::OutputSelection, + visitor::{Visitor, Walk}, + ContractDefinition, EnumDefinition, SourceUnit, StructDefinition, TypeDescriptions, TypeName, +}; +use std::{collections::BTreeMap, fmt::Write, path::PathBuf}; + +foundry_config::impl_figment_convert!(Eip712Args, build); + +/// 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)] + build: BuildOpts, +} + +impl Eip712Args { + pub fn run(self) -> Result<()> { + let config = self.load_config()?; + let mut project = config.ephemeral_project()?; + let target_path = dunce::canonicalize(self.target_path)?; + project.update_output_selection(|selection| { + *selection = OutputSelection::ast_output_selection(); + }); + + let output = ProjectCompiler::new().files([target_path.clone()]).compile(&project)?; + + // Collect ASTs by getting them from sources and converting into strongly typed + // `SourceUnit`s. + let asts = output + .into_output() + .sources + .into_iter() + .filter_map(|(path, mut sources)| Some((path, sources.swap_remove(0).source_file.ast?))) + .map(|(path, ast)| { + Ok((path, serde_json::from_str::(&serde_json::to_string(&ast)?)?)) + }) + .collect::>>()?; + + let resolver = Resolver::new(&asts); + + let target_ast = asts + .get(&target_path) + .ok_or_else(|| eyre::eyre!("Could not find AST for target file {target_path:?}"))?; + + let structs_in_target = { + let mut collector = StructCollector::default(); + target_ast.walk(&mut collector); + collector.0 + }; + + for (id, _) in structs_in_target { + if let Some(resolved) = resolver.resolve_struct_eip712(id)? { + sh_println!("{resolved}\n")?; + } + } + + 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) -> Result> { + let mut subtypes = BTreeMap::new(); + subtypes.insert(self.structs[&id].name.clone(), id); + self.resolve_eip712_inner(id, &mut subtypes, true, None) + } + + fn resolve_eip712_inner( + &self, + id: usize, + subtypes: &mut BTreeMap, + append_subtypes: bool, + rename: Option<&str>, + ) -> Result> { + let def = &self.structs[&id]; + let mut result = format!("{}(", rename.unwrap_or(&def.name)); + + for (idx, member) in def.members.iter().enumerate() { + let Some(ty) = self.resolve_type( + member.type_name.as_ref().ok_or_eyre("missing type name")?, + subtypes, + )? + else { + return Ok(None) + }; + + write!(result, "{ty} {name}", name = member.name)?; + + if idx < def.members.len() - 1 { + result.push(','); + } + } + + result.push(')'); + + if !append_subtypes { + return Ok(Some(result)) + } + + for (subtype_name, subtype_id) in + subtypes.iter().map(|(name, id)| (name.clone(), *id)).collect::>() + { + if subtype_id == id { + continue + } + let Some(encoded_subtype) = + self.resolve_eip712_inner(subtype_id, subtypes, false, Some(&subtype_name))? + else { + return Ok(None) + }; + result.push_str(&encoded_subtype); + } + + Ok(Some(result)) + } + + /// 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() + } else { + // Otherwise, assign new name. + let mut i = 0; + let mut name = def.name.clone(); + while subtypes.contains_key(&name) { + i += 1; + name = format!("{}_{i}", def.name); + } + + subtypes.insert(name.clone(), def.id); + + // iterate over members to check if they are resolvable and to populate subtypes + for member in &def.members { + if self.resolve_type( + member.type_name.as_ref().ok_or_eyre("missing type name")?, + subtypes, + )? + .is_none() + { + return Ok(None) + } + } + name + }; + + return Ok(Some(name)) + } else { + return Ok(None) + } + } + } + } +} + +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 2f750877694ea..8bc5d44724619 100644 --- a/crates/forge/bin/cmd/flatten.rs +++ b/crates/forge/bin/cmd/flatten.rs @@ -1,23 +1,28 @@ use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::{ - opts::{CoreBuildArgs, ProjectPathsArgs}, + opts::{BuildOpts, ProjectPathOpts}, utils::LoadConfig, }; -use foundry_common::fs; +use foundry_common::{compile::with_compilation_reporter, fs}; +use foundry_compilers::{ + compilers::solc::SolcLanguage, + error::SolcError, + flatten::{Flattener, FlattenerError}, +}; use std::path::PathBuf; /// CLI arguments for `forge flatten`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct FlattenArgs { /// The path to the contract to flatten. - #[clap(value_hint = ValueHint::FilePath, value_name = "PATH")] + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")] pub target_path: PathBuf, /// The path to output the flattened contract. /// /// If not specified, the flattened contract will be output to stdout. - #[clap( + #[arg( long, short, value_hint = ValueHint::FilePath, @@ -25,48 +30,43 @@ pub struct FlattenArgs { )] pub output: Option, - #[clap(flatten)] - project_paths: ProjectPathsArgs, + #[command(flatten)] + project_paths: ProjectPathOpts, } impl FlattenArgs { pub fn run(self) -> Result<()> { - let FlattenArgs { target_path, output, project_paths } = self; + let Self { target_path, output, project_paths } = self; // flatten is a subset of `BuildArgs` so we can reuse that to get the config - let build_args = CoreBuildArgs { - project_paths, - out_path: Default::default(), - compiler: Default::default(), - ignored_error_codes: vec![], - deny_warnings: false, - no_auto_detect: false, - use_solc: None, - offline: false, - force: false, - libraries: vec![], - via_ir: false, - revert_strings: None, - silent: false, - build_info: false, - build_info_path: None, - }; - - let config = build_args.try_load_config_emit_warnings()?; + let build = BuildOpts { project_paths, ..Default::default() }; + let config = build.load_config()?; + let project = config.ephemeral_project()?; - let paths = config.project_paths(); let target_path = dunce::canonicalize(target_path)?; - let flattened = paths - .flatten(&target_path) - .map_err(|err| eyre::Error::msg(format!("Failed to flatten the file: {err}")))?; + + let flattener = + with_compilation_reporter(true, || Flattener::new(project.clone(), &target_path)); + + let flattened = match flattener { + Ok(flattener) => Ok(flattener.flatten()), + Err(FlattenerError::Compilation(_)) => { + // Fallback to the old flattening implementation if we couldn't compile the target + // successfully. This would be the case if the target has invalid + // syntax. (e.g. Solang) + project.paths.with_language::().flatten(&target_path) + } + Err(FlattenerError::Other(err)) => Err(err), + } + .map_err(|err: SolcError| eyre::eyre!("Failed to flatten: {err}"))?; match output { Some(output) => { fs::create_dir_all(output.parent().unwrap())?; fs::write(&output, flattened)?; - println!("Flattened file written at {}", output.display()); + sh_println!("Flattened file written at {}", output.display())?; } - None => println!("{flattened}"), + None => sh_println!("{flattened}")?, }; Ok(()) diff --git a/crates/forge/bin/cmd/fmt.rs b/crates/forge/bin/cmd/fmt.rs index bf8334d70b9fb..26f601d7bbac1 100644 --- a/crates/forge/bin/cmd/fmt.rs +++ b/crates/forge/bin/cmd/fmt.rs @@ -1,10 +1,11 @@ +use super::watch::WatchArgs; use clap::{Parser, ValueHint}; -use eyre::Result; -use forge_fmt::{format, parse, print_diagnostics_report}; +use eyre::{Context, Result}; +use forge_fmt::{format_to, parse}; use foundry_cli::utils::{FoundryPathExt, LoadConfig}; -use foundry_common::{fs, term::cli_warn}; -use foundry_config::impl_figment_convert_basic; -use foundry_utils::glob::expand_globs; +use foundry_common::fs; +use foundry_compilers::{compilers::solc::SolcLanguage, solc::SOLC_EXTENSIONS}; +use foundry_config::{filter::expand_globs, impl_figment_convert_basic}; use rayon::prelude::*; use similar::{ChangeTag, TextDiff}; use std::{ @@ -13,47 +14,47 @@ use std::{ io::{Read, Write as _}, path::{Path, PathBuf}, }; -use tracing::log::warn; -use yansi::Color; +use yansi::{Color, Paint, Style}; /// CLI arguments for `forge fmt`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct FmtArgs { /// Path to the file, directory or '-' to read from stdin. - #[clap(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] paths: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Run in 'check' mode. /// /// Exits with 0 if input is formatted correctly. /// Exits with 1 if formatting is required. - #[clap(long)] + #[arg(long)] check: bool, /// In 'check' and stdin modes, outputs raw formatted code instead of the diff. - #[clap(long, short)] + #[arg(long, short)] raw: bool, + + #[command(flatten)] + pub watch: WatchArgs, } impl_figment_convert_basic!(FmtArgs); -// === impl FmtArgs === - impl FmtArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; // Expand ignore globs and canonicalize from the get go - let ignored = expand_globs(&config.__root.0, config.fmt.ignore.iter())? + let ignored = expand_globs(&config.root, config.fmt.ignore.iter())? .iter() - .flat_map(foundry_utils::path::canonicalize_path) + .flat_map(foundry_common::fs::canonicalize_path) .collect::>(); let cwd = std::env::current_dir()?; @@ -61,7 +62,7 @@ impl FmtArgs { [] => { // Retrieve the project paths, and filter out the ignored ones. let project_paths: Vec = config - .project_paths() + .project_paths::() .input_files_iter() .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p)))) .collect(); @@ -83,7 +84,10 @@ impl FmtArgs { } if path.is_dir() { - inputs.extend(ethers::solc::utils::source_files_iter(path)); + inputs.extend(foundry_compilers::utils::source_files_iter( + path, + SOLC_EXTENSIONS, + )); } else if path.is_sol() { inputs.push(path.to_path_buf()); } else { @@ -96,15 +100,12 @@ impl FmtArgs { let format = |source: String, path: Option<&Path>| -> Result<_> { let name = match path { - Some(path) => { - path.strip_prefix(&config.__root.0).unwrap_or(path).display().to_string() - } + Some(path) => path.strip_prefix(&config.root).unwrap_or(path).display().to_string(), None => "stdin".to_string(), }; - let parsed = parse(&source).map_err(|diagnostics| { - let _ = print_diagnostics_report(&source, path, diagnostics); - eyre::eyre!("Failed to parse Solidity code for {name}. Leaving source unchanged.") + let parsed = parse(&source).wrap_err_with(|| { + format!("Failed to parse Solidity code for {name}. Leaving source unchanged.") })?; if !parsed.invalid_inline_config_items.is_empty() { @@ -112,12 +113,12 @@ impl FmtArgs { let mut lines = source[..loc.start().min(source.len())].split('\n'); let col = lines.next_back().unwrap().len() + 1; let row = lines.count() + 1; - cli_warn!("[{}:{}:{}] {}", name, row, col, warning); + sh_warn!("[{}:{}:{}] {}", name, row, col, warning)?; } } let mut output = String::new(); - format(&mut output, parsed, config.fmt.clone()).unwrap(); + format_to(&mut output, parsed, config.fmt.clone()).unwrap(); solang_parser::parse(&output, 0).map_err(|diags| { eyre::eyre!( @@ -126,17 +127,22 @@ impl FmtArgs { ) })?; + let diff = TextDiff::from_lines(&source, &output); + let new_format = diff.ratio() < 1.0; if self.check || path.is_none() { if self.raw { - print!("{output}"); + sh_print!("{output}")?; } - let diff = TextDiff::from_lines(&source, &output); - if diff.ratio() < 1.0 { + // If new format then compute diff summary. + if new_format { return Ok(Some(format_diff_summary(&name, &diff))) } } else if let Some(path) = path { - fs::write(path, output)?; + // If new format then write it on disk. + if new_format { + fs::write(path, output)?; + } } Ok(None) }; @@ -145,11 +151,11 @@ impl FmtArgs { Input::Stdin(source) => format(source, None).map(|diff| vec![diff]), Input::Paths(paths) => { if paths.is_empty() { - cli_warn!( + sh_warn!( "Nothing to format.\n\ HINT: If you are working outside of the project, \ try providing paths to your source files: `forge fmt `" - ); + )?; return Ok(()) } paths @@ -184,6 +190,11 @@ impl FmtArgs { Ok(()) } + + /// Returns whether `FmtArgs` was configured with `--watch` + pub fn is_watch(&self) -> bool { + self.watch.watch.is_some() + } } struct Line(Option); @@ -195,7 +206,7 @@ enum Input { } impl fmt::Display for Line { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { None => f.write_str(" "), Some(idx) => write!(f, "{:<4}", idx + 1), @@ -203,10 +214,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); @@ -219,24 +227,24 @@ where } for op in group { for change in diff.iter_inline_changes(&op) { - let dimmed = Color::Default.style().dimmed(); + let dimmed = Style::new().dim(); let (sign, s) = match change.tag() { - ChangeTag::Delete => ("-", Color::Red.style()), - ChangeTag::Insert => ("+", Color::Green.style()), + ChangeTag::Delete => ("-", Color::Red.foreground()), + ChangeTag::Insert => ("+", Color::Green.foreground()), ChangeTag::Equal => (" ", dimmed), }; let _ = write!( diff_summary, "{}{} |{}", - dimmed.paint(Line(change.old_index())), - dimmed.paint(Line(change.new_index())), - s.bold().paint(sign), + Line(change.old_index()).paint(dimmed), + Line(change.new_index()).paint(dimmed), + sign.paint(s.bold()), ); for (emphasized, value) in change.iter_strings_lossy() { let s = if emphasized { s.underline().bg(Color::Black) } else { s }; - let _ = write!(diff_summary, "{}", s.paint(value)); + let _ = write!(diff_summary, "{}", value.paint(s)); } if change.missing_newline() { diff --git a/crates/forge/bin/cmd/fourbyte.rs b/crates/forge/bin/cmd/fourbyte.rs deleted file mode 100644 index 86a889d6fe635..0000000000000 --- a/crates/forge/bin/cmd/fourbyte.rs +++ /dev/null @@ -1,93 +0,0 @@ -use clap::Parser; -use ethers::prelude::artifacts::output_selection::ContractOutputSelection; -use eyre::Result; -use foundry_cli::{ - opts::{CompilerArgs, CoreBuildArgs, ProjectPathsArgs}, - utils::FoundryPathExt, -}; -use foundry_common::{ - compile, - selectors::{import_selectors, SelectorImportData}, - shell, -}; -use yansi::Paint; - -/// CLI arguments for `forge upload-selectors`. -#[derive(Debug, Clone, Parser)] -pub struct UploadSelectorsArgs { - /// The name of the contract to upload selectors for. - #[clap(required_unless_present = "all")] - pub contract: Option, - - /// Upload selectors for all contracts in the project. - #[clap(long, required_unless_present = "contract")] - pub all: bool, - - #[clap(flatten)] - pub project_paths: ProjectPathsArgs, -} - -impl UploadSelectorsArgs { - /// Builds a contract and uploads the ABI to selector database - pub async fn run(self) -> Result<()> { - shell::println(Paint::yellow("Warning! This command is deprecated and will be removed in v1, use `forge selectors upload` instead"))?; - - let UploadSelectorsArgs { contract, all, project_paths } = self; - - let build_args = CoreBuildArgs { - project_paths: project_paths.clone(), - compiler: CompilerArgs { - extra_output: vec![ContractOutputSelection::Abi], - ..Default::default() - }, - ..Default::default() - }; - - let project = build_args.project()?; - let outcome = compile::suppress_compile(&project)?; - let artifacts = if all { - outcome - .into_artifacts_with_files() - .filter(|(file, _, _)| { - let is_sources_path = - file.starts_with(&project.paths.sources.to_string_lossy().to_string()); - let is_test = file.is_sol_test(); - - is_sources_path && !is_test - }) - .map(|(_, contract, artifact)| (contract, artifact)) - .collect() - } else { - let contract = contract.unwrap(); - let found_artifact = outcome.find_first(&contract); - let artifact = found_artifact - .ok_or_else(|| { - eyre::eyre!("Could not find artifact `{contract}` in the compiled artifacts") - })? - .clone(); - vec![(contract, artifact)] - }; - - let mut artifacts = artifacts.into_iter().peekable(); - while let Some((contract, artifact)) = artifacts.next() { - let abi = artifact.abi.ok_or(eyre::eyre!("Unable to fetch abi"))?; - if abi.abi.functions.is_empty() && - abi.abi.events.is_empty() && - abi.abi.errors.is_empty() - { - continue - } - - println!("Uploading selectors for {contract}..."); - - // upload abi to selector database - import_selectors(SelectorImportData::Abi(vec![abi])).await?.describe(); - - if artifacts.peek().is_some() { - println!() - } - } - - Ok(()) - } -} diff --git a/crates/forge/bin/cmd/geiger.rs b/crates/forge/bin/cmd/geiger.rs new file mode 100644 index 0000000000000..57dac0eb26c65 --- /dev/null +++ b/crates/forge/bin/cmd/geiger.rs @@ -0,0 +1,163 @@ +use clap::{Parser, ValueHint}; +use eyre::{Result, WrapErr}; +use foundry_cli::utils::LoadConfig; +use foundry_compilers::{resolver::parse::SolData, Graph}; +use foundry_config::{impl_figment_convert_basic, Config}; +use itertools::Itertools; +use solar_parse::{ast, ast::visit::Visit, interface::Session}; +use std::{ + ops::ControlFlow, + path::{Path, PathBuf}, +}; + +/// CLI arguments for `forge geiger`. +#[derive(Clone, Debug, Parser)] +pub struct GeigerArgs { + /// Paths to files or directories to detect. + #[arg( + conflicts_with = "root", + value_hint = ValueHint::FilePath, + value_name = "PATH", + num_args(1..), + )] + paths: Vec, + + /// The project's root path. + /// + /// By default root of the Git repository, if in one, + /// or the current working directory. + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + root: Option, + + /// Globs to ignore. + #[arg( + long, + value_hint = ValueHint::FilePath, + value_name = "PATH", + num_args(1..), + )] + ignore: Vec, + + #[arg(long, hide = true)] + check: bool, + #[arg(long, hide = true)] + full: bool, +} + +impl_figment_convert_basic!(GeigerArgs); + +impl GeigerArgs { + pub fn sources(&self, config: &Config) -> Result> { + let cwd = std::env::current_dir()?; + + let mut sources: Vec = { + if self.paths.is_empty() { + let paths = config.project_paths(); + Graph::::resolve(&paths)? + .files() + .keys() + .filter(|f| !paths.has_library_ancestor(f)) + .cloned() + .collect() + } else { + self.paths + .iter() + .flat_map(|path| foundry_common::fs::files_with_ext(path, "sol")) + .unique() + .collect() + } + }; + + sources.retain_mut(|path| { + let abs_path = if path.is_absolute() { path.clone() } else { cwd.join(&path) }; + *path = abs_path.strip_prefix(&cwd).unwrap_or(&abs_path).to_path_buf(); + !self.ignore.iter().any(|ignore| { + if ignore.is_absolute() { + abs_path.starts_with(ignore) + } else { + abs_path.starts_with(cwd.join(ignore)) + } + }) + }); + + Ok(sources) + } + + pub fn run(self) -> Result { + if self.check { + sh_warn!("`--check` is deprecated as it's now the default behavior\n")?; + } + if self.full { + sh_warn!("`--full` is deprecated as reports are not generated anymore\n")?; + } + + let config = self.load_config()?; + let sources = self.sources(&config).wrap_err("Failed to resolve files")?; + + if config.ffi { + sh_warn!("FFI enabled\n")?; + } + + let mut sess = Session::builder().with_stderr_emitter().build(); + sess.dcx = sess.dcx.set_flags(|flags| flags.track_diagnostics = false); + let unsafe_cheatcodes = &[ + "ffi".to_string(), + "readFile".to_string(), + "readLine".to_string(), + "writeFile".to_string(), + "writeLine".to_string(), + "removeFile".to_string(), + "closeFile".to_string(), + "setEnv".to_string(), + "deriveKey".to_string(), + ]; + Ok(sess + .enter(|| sources.iter().map(|file| lint_file(&sess, unsafe_cheatcodes, file)).sum())) + } +} + +fn lint_file(sess: &Session, unsafe_cheatcodes: &[String], path: &Path) -> usize { + try_lint_file(sess, unsafe_cheatcodes, path).unwrap_or(0) +} + +fn try_lint_file( + sess: &Session, + unsafe_cheatcodes: &[String], + path: &Path, +) -> solar_parse::interface::Result { + let arena = solar_parse::ast::Arena::new(); + let mut parser = solar_parse::Parser::from_file(sess, &arena, path)?; + let ast = parser.parse_file().map_err(|e| e.emit())?; + let mut visitor = Visitor::new(sess, unsafe_cheatcodes); + visitor.visit_source_unit(&ast); + Ok(visitor.count) +} + +struct Visitor<'a> { + sess: &'a Session, + count: usize, + unsafe_cheatcodes: &'a [String], +} + +impl<'a> Visitor<'a> { + fn new(sess: &'a Session, unsafe_cheatcodes: &'a [String]) -> Self { + Self { sess, count: 0, unsafe_cheatcodes } + } +} + +impl<'ast> Visit<'ast> for Visitor<'_> { + type BreakValue = solar_parse::interface::data_structures::Never; + + fn visit_expr(&mut self, expr: &'ast ast::Expr<'ast>) -> ControlFlow { + if let ast::ExprKind::Call(lhs, _args) = &expr.kind { + if let ast::ExprKind::Member(_lhs, member) = &lhs.kind { + if self.unsafe_cheatcodes.iter().any(|c| c.as_str() == member.as_str()) { + let msg = format!("usage of unsafe cheatcode `vm.{member}`"); + self.sess.dcx.err(msg).span(member.span).emit(); + self.count += 1; + } + } + } + self.walk_expr(expr) + } +} diff --git a/crates/forge/bin/cmd/geiger/error.rs b/crates/forge/bin/cmd/geiger/error.rs deleted file mode 100644 index 77c6374ea7a76..0000000000000 --- a/crates/forge/bin/cmd/geiger/error.rs +++ /dev/null @@ -1,12 +0,0 @@ -use foundry_common::errors::FsPathError; -use solang_parser::diagnostics::Diagnostic; -use std::path::PathBuf; - -/// Possible errors when scanning a solidity file -#[derive(Debug, thiserror::Error)] -pub enum ScanFileError { - #[error(transparent)] - Io(#[from] FsPathError), - #[error("Failed to parse {1:?}: {0:?}")] - ParseSol(Vec, PathBuf), -} diff --git a/crates/forge/bin/cmd/geiger/find.rs b/crates/forge/bin/cmd/geiger/find.rs deleted file mode 100644 index 80a31c395dac8..0000000000000 --- a/crates/forge/bin/cmd/geiger/find.rs +++ /dev/null @@ -1,168 +0,0 @@ -use super::{error::ScanFileError, visitor::CheatcodeVisitor}; -use eyre::Result; -use forge_fmt::{offset_to_line_column, parse, Visitable}; -use foundry_common::fs; -use solang_parser::{diagnostics::Diagnostic, pt::Loc}; -use std::{ - fmt, - path::{Path, PathBuf}, -}; -use yansi::Paint; - -/// Scan a single file for `unsafe` cheatcode usage. -pub fn find_cheatcodes_in_file(path: &Path) -> Result { - let contents = fs::read_to_string(path)?; - let cheatcodes = find_cheatcodes_in_string(&contents) - .map_err(|diagnostic| ScanFileError::ParseSol(diagnostic, path.to_path_buf()))?; - Ok(SolFileMetrics { contents, cheatcodes, file: path.to_path_buf() }) -} - -/// Scan a string for unsafe cheatcodes. -pub fn find_cheatcodes_in_string(src: &str) -> Result> { - let mut parsed = parse(src)?; - let mut visitor = CheatcodeVisitor::default(); - parsed.pt.visit(&mut visitor).unwrap(); - Ok(visitor.cheatcodes) -} - -/// Scan result for a single Solidity file. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct SolFileMetrics { - /// The Solidity file - pub file: PathBuf, - - /// The file's contents. - pub contents: String, - - /// The unsafe cheatcodes found. - pub cheatcodes: UnsafeCheatcodes, -} - -/// Formats the metrics for a single file using [`fmt::Display`]. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct SolFileMetricsPrinter<'a, 'b> { - pub metrics: &'a SolFileMetrics, - pub root: &'b Path, -} - -impl<'a, 'b> fmt::Display for SolFileMetricsPrinter<'a, 'b> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let SolFileMetricsPrinter { metrics, root } = *self; - - let file = metrics.file.strip_prefix(root).unwrap_or(&metrics.file); - - macro_rules! print_unsafe_fn { - ($($name:literal => $field:ident),*) => {$( - let $field = &metrics.cheatcodes.$field[..]; - if !$field.is_empty() { - writeln!(f, " {} {}", Paint::red(metrics.cheatcodes.$field.len()), Paint::red($name))?; - - for &loc in $field { - let content = &metrics.contents[loc.range()]; - let (line, col) = offset_to_line_column(&metrics.contents, loc.start()); - let pos = format!(" --> {}:{}:{}", file.display(), line, col); - writeln!(f,"{}", Paint::red(pos))?; - for line in content.lines() { - writeln!(f, " {}", Paint::red(line))?; - } - } - } - )*}; - } - - if !metrics.cheatcodes.is_empty() { - writeln!( - f, - "{} {}", - Paint::red(metrics.cheatcodes.len()), - Paint::red(file.display()) - )?; - print_unsafe_fn!( - "ffi" => ffi, - "readFile" => read_file, - "readLine" => read_line, - "writeFile" => write_file, - "writeLine" => write_line, - "removeFile" => remove_file, - "closeFile" => close_file, - "setEnv" => set_env, - "deriveKey" => derive_key - ); - } else { - writeln!(f, "0 {}", file.display())? - } - - Ok(()) - } -} - -/// Unsafe usage metrics collection. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct UnsafeCheatcodes { - pub ffi: Vec, - pub read_file: Vec, - pub read_line: Vec, - pub write_file: Vec, - pub write_line: Vec, - pub remove_file: Vec, - pub close_file: Vec, - pub set_env: Vec, - pub derive_key: Vec, -} - -impl UnsafeCheatcodes { - /// Whether there are any unsafe calls. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// The total number of unsafe calls. - pub fn len(&self) -> usize { - self.ffi.len() + - self.read_file.len() + - self.read_line.len() + - self.write_file.len() + - self.write_line.len() + - self.close_file.len() + - self.set_env.len() + - self.derive_key.len() + - self.remove_file.len() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_find_calls() { - let s = r#" - contract A is Test { - function do_ffi() public { - string[] memory inputs = new string[](1); - vm.ffi(inputs); - } - } - "#; - - let count = find_cheatcodes_in_string(s).unwrap(); - assert_eq!(count.ffi.len(), 1); - assert!(!count.is_empty()); - } - - #[test] - fn can_find_call_in_assignment() { - let s = r#" - contract A is Test { - function do_ffi() public { - string[] memory inputs = new string[](1); - bytes stuff = vm.ffi(inputs); - } - } - "#; - - let count = find_cheatcodes_in_string(s).unwrap(); - assert_eq!(count.ffi.len(), 1); - assert!(!count.is_empty()); - } -} diff --git a/crates/forge/bin/cmd/geiger/mod.rs b/crates/forge/bin/cmd/geiger/mod.rs deleted file mode 100644 index 6756a5921a939..0000000000000 --- a/crates/forge/bin/cmd/geiger/mod.rs +++ /dev/null @@ -1,119 +0,0 @@ -use clap::{Parser, ValueHint}; -use ethers::solc::Graph; -use eyre::{Result, WrapErr}; -use foundry_cli::utils::LoadConfig; -use foundry_config::{impl_figment_convert_basic, Config}; -use itertools::Itertools; -use rayon::prelude::*; -use std::path::PathBuf; -use yansi::Paint; - -mod error; - -mod find; -use find::{find_cheatcodes_in_file, SolFileMetricsPrinter}; - -mod visitor; - -/// CLI arguments for `forge geiger`. -#[derive(Debug, Clone, Parser)] -pub struct GeigerArgs { - /// Paths to files or directories to detect. - #[clap( - conflicts_with = "root", - value_hint = ValueHint::FilePath, - value_name = "PATH", - num_args(1..), - )] - paths: Vec, - - /// The project's root path. - /// - /// By default root of the Git repository, if in one, - /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] - root: Option, - - /// Run in "check" mode. - /// - /// The exit code of the program will be the number of unsafe cheatcodes found. - #[clap(long)] - pub check: bool, - - /// Globs to ignore. - #[clap( - long, - value_hint = ValueHint::FilePath, - value_name = "PATH", - num_args(1..), - )] - ignore: Vec, - - /// Print a report of all files, even if no unsafe functions are found. - #[clap(long)] - full: bool, -} - -impl_figment_convert_basic!(GeigerArgs); - -impl GeigerArgs { - pub fn sources(&self, config: &Config) -> Result> { - let cwd = std::env::current_dir()?; - - let mut sources: Vec = { - if self.paths.is_empty() { - Graph::resolve(&config.project_paths())?.files().keys().cloned().collect() - } else { - self.paths - .iter() - .flat_map(|path| foundry_common::fs::files_with_ext(path, "sol")) - .unique() - .collect() - } - }; - - sources.retain(|path| { - let abs_path = if path.is_absolute() { path.clone() } else { cwd.join(path) }; - !self.ignore.iter().any(|ignore| { - if ignore.is_absolute() { - abs_path.starts_with(ignore) - } else { - abs_path.starts_with(cwd.join(ignore)) - } - }) - }); - - Ok(sources) - } - - pub fn run(self) -> Result { - let config = self.try_load_config_emit_warnings()?; - let sources = self.sources(&config).wrap_err("Failed to resolve files")?; - - if config.ffi { - eprintln!("{}\n", Paint::red("ffi enabled")); - } - - let root = config.__root.0; - - let sum = sources - .par_iter() - .map(|file| match find_cheatcodes_in_file(file) { - Ok(metrics) => { - let len = metrics.cheatcodes.len(); - let printer = SolFileMetricsPrinter { metrics: &metrics, root: &root }; - if self.full || len == 0 { - eprint!("{printer}"); - } - len - } - Err(err) => { - eprintln!("{err}"); - 0 - } - }) - .sum(); - - Ok(sum) - } -} diff --git a/crates/forge/bin/cmd/geiger/visitor.rs b/crates/forge/bin/cmd/geiger/visitor.rs deleted file mode 100644 index 70313089019ca..0000000000000 --- a/crates/forge/bin/cmd/geiger/visitor.rs +++ /dev/null @@ -1,333 +0,0 @@ -use super::find::UnsafeCheatcodes; -use eyre::Result; -use forge_fmt::{Visitable, Visitor}; -use solang_parser::pt::{ - ContractDefinition, Expression, FunctionDefinition, IdentifierPath, Loc, Parameter, SourceUnit, - Statement, TypeDefinition, VariableDeclaration, VariableDefinition, -}; -use std::convert::Infallible; - -/// a [`forge_fmt::Visitor` that scans for invocations of cheatcodes -#[derive(Default)] -pub struct CheatcodeVisitor { - pub cheatcodes: UnsafeCheatcodes, -} - -impl Visitor for CheatcodeVisitor { - type Error = Infallible; - - fn visit_source_unit(&mut self, source_unit: &mut SourceUnit) -> Result<(), Self::Error> { - source_unit.0.visit(self) - } - - fn visit_contract(&mut self, contract: &mut ContractDefinition) -> Result<(), Self::Error> { - contract.base.visit(self)?; - contract.parts.visit(self) - } - - fn visit_block( - &mut self, - _loc: Loc, - _unchecked: bool, - statements: &mut Vec, - ) -> Result<(), Self::Error> { - statements.visit(self) - } - - fn visit_expr(&mut self, _loc: Loc, expr: &mut Expression) -> Result<(), Self::Error> { - match expr { - Expression::PostIncrement(_, expr) => { - expr.visit(self)?; - } - Expression::PostDecrement(_, expr) => { - expr.visit(self)?; - } - Expression::New(_, expr) => { - expr.visit(self)?; - } - Expression::ArraySubscript(_, expr1, expr2) => { - expr1.visit(self)?; - expr2.visit(self)?; - } - Expression::ArraySlice(_, expr1, expr2, expr3) => { - expr1.visit(self)?; - expr2.visit(self)?; - expr3.visit(self)?; - } - Expression::Parenthesis(_, expr) => { - expr.visit(self)?; - } - Expression::MemberAccess(_, expr, _) => { - expr.visit(self)?; - } - Expression::FunctionCall(loc, lhs, rhs) => { - // all cheatcodes are accessd via .cheatcode - if let Expression::MemberAccess(_, expr, identifier) = &**lhs { - if let Expression::Variable(_) = &**expr { - match identifier.name.as_str() { - "ffi" => self.cheatcodes.ffi.push(*loc), - "readFile" => self.cheatcodes.read_file.push(*loc), - "writeFile" => self.cheatcodes.write_file.push(*loc), - "readLine" => self.cheatcodes.read_line.push(*loc), - "writeLine" => self.cheatcodes.write_line.push(*loc), - "closeFile" => self.cheatcodes.close_file.push(*loc), - "removeFile" => self.cheatcodes.remove_file.push(*loc), - "setEnv" => self.cheatcodes.set_env.push(*loc), - "deriveKey" => self.cheatcodes.derive_key.push(*loc), - _ => {} - } - } - } - rhs.visit(self)?; - } - Expression::FunctionCallBlock(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::NamedFunctionCall(_, lhs, rhs) => { - lhs.visit(self)?; - for arg in rhs.iter_mut() { - arg.expr.visit(self)?; - } - } - Expression::Not(_, expr) => { - expr.visit(self)?; - } - Expression::BitwiseNot(_, expr) => { - expr.visit(self)?; - } - Expression::Delete(_, expr) => { - expr.visit(self)?; - } - Expression::PreIncrement(_, expr) => { - expr.visit(self)?; - } - Expression::PreDecrement(_, expr) => { - expr.visit(self)?; - } - Expression::UnaryPlus(_, expr) => { - expr.visit(self)?; - } - Expression::Negate(_, expr) => { - expr.visit(self)?; - } - Expression::Power(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Multiply(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Divide(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Modulo(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Add(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Subtract(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::ShiftLeft(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::ShiftRight(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::BitwiseAnd(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::BitwiseXor(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::BitwiseOr(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Less(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::More(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::LessEqual(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::MoreEqual(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Equal(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::NotEqual(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::And(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Or(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::ConditionalOperator(_, llhs, lhs, rhs) => { - llhs.visit(self)?; - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::Assign(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignOr(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignAnd(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignXor(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignShiftLeft(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignShiftRight(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignAdd(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignSubtract(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignMultiply(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignDivide(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::AssignModulo(_, lhs, rhs) => { - lhs.visit(self)?; - rhs.visit(self)?; - } - Expression::List(_, param) => { - for (_, param) in param.iter_mut() { - param.visit(self)?; - } - } - _ => {} - } - - Ok(()) - } - - fn visit_emit(&mut self, _: Loc, expr: &mut Expression) -> Result<(), Self::Error> { - expr.visit(self) - } - - fn visit_var_definition(&mut self, var: &mut VariableDefinition) -> Result<(), Self::Error> { - var.ty.visit(self)?; - var.initializer.visit(self) - } - - fn visit_var_definition_stmt( - &mut self, - _: Loc, - declaration: &mut VariableDeclaration, - expr: &mut Option, - ) -> Result<(), Self::Error> { - declaration.visit(self)?; - expr.visit(self) - } - - fn visit_var_declaration(&mut self, var: &mut VariableDeclaration) -> Result<(), Self::Error> { - var.ty.visit(self) - } - - fn visit_revert( - &mut self, - _: Loc, - _error: &mut Option, - args: &mut Vec, - ) -> Result<(), Self::Error> { - args.visit(self) - } - - fn visit_if( - &mut self, - _loc: Loc, - cond: &mut Expression, - if_branch: &mut Box, - else_branch: &mut Option>, - _is_frst_stmt: bool, - ) -> Result<(), Self::Error> { - cond.visit(self)?; - if_branch.visit(self)?; - else_branch.visit(self) - } - - fn visit_while( - &mut self, - _loc: Loc, - cond: &mut Expression, - body: &mut Statement, - ) -> Result<(), Self::Error> { - cond.visit(self)?; - body.visit(self) - } - - fn visit_for( - &mut self, - _loc: Loc, - init: &mut Option>, - cond: &mut Option>, - update: &mut Option>, - body: &mut Option>, - ) -> Result<(), Self::Error> { - init.visit(self)?; - cond.visit(self)?; - update.visit(self)?; - body.visit(self) - } - - fn visit_function(&mut self, func: &mut FunctionDefinition) -> Result<(), Self::Error> { - if let Some(ref mut body) = func.body { - body.visit(self)?; - } - Ok(()) - } - - fn visit_parameter(&mut self, parameter: &mut Parameter) -> Result<(), Self::Error> { - parameter.ty.visit(self) - } - - fn visit_type_definition(&mut self, def: &mut TypeDefinition) -> Result<(), Self::Error> { - def.ty.visit(self) - } -} diff --git a/crates/forge/bin/cmd/generate/mod.rs b/crates/forge/bin/cmd/generate/mod.rs index 9e25d6532a808..f1f69d8385618 100644 --- a/crates/forge/bin/cmd/generate/mod.rs +++ b/crates/forge/bin/cmd/generate/mod.rs @@ -7,7 +7,7 @@ use yansi::Paint; /// CLI arguments for `forge generate`. #[derive(Debug, Parser)] pub struct GenerateArgs { - #[clap(subcommand)] + #[command(subcommand)] pub sub: GenerateSubcommands, } @@ -20,7 +20,7 @@ pub enum GenerateSubcommands { #[derive(Debug, Parser)] pub struct GenerateTestArgs { /// Contract name for test generation. - #[clap(long, short, value_name = "CONTRACT_NAME")] + #[arg(long, short, value_name = "CONTRACT_NAME")] pub contract_name: String, } @@ -39,12 +39,12 @@ impl GenerateTestArgs { fs::create_dir_all("test")?; // Define the test file path - let test_file_path = Path::new("test").join(format!("{}.t.sol", contract_name)); + let test_file_path = Path::new("test").join(format!("{contract_name}.t.sol")); // Write the test content to the test file. fs::write(&test_file_path, test_content)?; - println!("{} test file: {}", Paint::green("Generated"), test_file_path.to_str().unwrap()); + sh_println!("{} test file: {}", "Generated".green(), test_file_path.to_str().unwrap())?; Ok(()) } } diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs index 2ae3c91844e0c..d0156ab750dee 100644 --- a/crates/forge/bin/cmd/init.rs +++ b/crates/forge/bin/cmd/init.rs @@ -1,93 +1,110 @@ use super::install::DependencyInstallOpts; use clap::{Parser, ValueHint}; -use ethers::solc::remappings::Remapping; use eyre::Result; -use foundry_cli::{p_println, utils::Git}; +use foundry_cli::utils::Git; use foundry_common::fs; +use foundry_compilers::artifacts::remappings::Remapping; use foundry_config::Config; use std::path::{Path, PathBuf}; use yansi::Paint; /// CLI arguments for `forge init`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Default, Parser)] pub struct InitArgs { /// The root directory of the new project. - #[clap(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] - root: PathBuf, + #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] + pub root: PathBuf, /// The template to start from. - #[clap(long, short)] - template: Option, + #[arg(long, short)] + pub template: Option, + + /// Branch argument that can only be used with template option. + /// If not specified, the default branch is used. + #[arg(long, short, requires = "template")] + pub branch: Option, /// Do not install dependencies from the network. - #[clap(long, conflicts_with = "template", visible_alias = "no-deps")] - offline: bool, + #[arg(long, conflicts_with = "template", visible_alias = "no-deps")] + pub offline: bool, /// Create the project even if the specified root directory is not empty. - #[clap(long, conflicts_with = "template")] - force: bool, + #[arg(long, conflicts_with = "template")] + pub force: bool, /// Create a .vscode/settings.json file with Solidity settings, and generate a remappings.txt /// file. - #[clap(long, conflicts_with = "template")] - vscode: bool, + #[arg(long, conflicts_with = "template")] + pub vscode: bool, - #[clap(flatten)] - opts: DependencyInstallOpts, + #[command(flatten)] + pub install: DependencyInstallOpts, } impl InitArgs { pub fn run(self) -> Result<()> { - let InitArgs { root, template, opts, offline, force, vscode } = self; - let DependencyInstallOpts { shallow, no_git, no_commit, quiet } = opts; + let Self { root, template, branch, install, offline, force, vscode } = self; + let DependencyInstallOpts { shallow, no_git, commit } = install; // create the root dir if it does not exist if !root.exists() { fs::create_dir_all(&root)?; } let root = dunce::canonicalize(root)?; - let git = Git::new(&root).quiet(quiet).shallow(shallow); + let git = Git::new(&root).shallow(shallow); - // if a template is provided, then this command clones the template repo, removes the .git - // folder, and initializes a new git repo—-this ensures there is no history from the - // template and the template is not set as a remote. + // if a template is provided, then this command initializes a git repo, + // fetches the template repo, and resets the git history to the head of the fetched + // repo with no other history if let Some(template) = template { let template = if template.contains("://") { template } else { "https://github.com/".to_string() + &template }; - p_println!(!quiet => "Initializing {} from {}...", root.display(), template); - - Git::clone(shallow, &template, Some(&root))?; + sh_println!("Initializing {} from {}...", root.display(), template)?; + // initialize the git repository + git.init()?; - // Modify the git history. - let commit_hash = git.commit_hash(true)?; - std::fs::remove_dir_all(".git")?; + // fetch the template - always fetch shallow for templates since git history will be + // collapsed. gitmodules will be initialized after the template is fetched + git.fetch(true, &template, branch)?; - git.init()?; - git.add(Some("--all"))?; + // reset git history to the head of the template + // first get the commit hash that was fetched + let commit_hash = git.commit_hash(true, "FETCH_HEAD")?; + // format a commit message for the new repo let commit_msg = format!("chore: init from {template} at {commit_hash}"); - git.commit(&commit_msg)?; + // get the hash of the FETCH_HEAD with the new commit message + let new_commit_hash = git.commit_tree("FETCH_HEAD^{tree}", Some(commit_msg))?; + // reset head of this repo to be the head of the template repo + git.reset(true, new_commit_hash)?; + + // if shallow, just initialize submodules + if shallow { + git.submodule_init()?; + } else { + // if not shallow, initialize and clone submodules (without fetching latest) + git.submodule_update(false, false, true, true, std::iter::empty::())?; + } } else { // if target is not empty - if root.read_dir().map_or(false, |mut i| i.next().is_some()) { + if root.read_dir().is_ok_and(|mut i| i.next().is_some()) { if !force { eyre::bail!( "Cannot run `init` on a non-empty directory.\n\ Run with the `--force` flag to initialize regardless." ); } - - p_println!(!quiet => "Target directory is not empty, but `--force` was specified"); + sh_warn!("Target directory is not empty, but `--force` was specified")?; } // ensure git status is clean before generating anything - if !no_git && !no_commit && !force && git.is_in_repo()? { + if !no_git && commit && !force && git.is_in_repo()? { git.ensure_clean()?; } - p_println!(!quiet => "Initializing {}...", root.display()); + sh_println!("Initializing {}...", root.display())?; // make the dirs let src = root.join("src"); @@ -114,25 +131,25 @@ impl InitArgs { // write foundry.toml, if it doesn't exist already let dest = root.join(Config::FILE_NAME); - let mut config = Config::load_with_root(&root); + let mut config = Config::load_with_root(&root)?; if !dest.exists() { fs::write(dest, config.clone().into_basic().to_string_pretty()?)?; } - let git = self.opts.git(&config); + let git = self.install.git(&config); // set up the repo if !no_git { - init_git_repo(git, no_commit)?; + init_git_repo(git, commit)?; } // install forge-std if !offline { if root.join("lib/forge-std").exists() { - p_println!(!quiet => "\"lib/forge-std\" already exists, skipping install...."); - self.opts.install(&mut config, vec![])?; + sh_warn!("\"lib/forge-std\" already exists, skipping install...")?; + self.install.install(&mut config, vec![])?; } else { let dep = "https://github.com/foundry-rs/forge-std".parse()?; - self.opts.install(&mut config, vec![dep])?; + self.install.install(&mut config, vec![dep])?; } } @@ -142,22 +159,17 @@ impl InitArgs { } } - p_println!(!quiet => " {} forge project", Paint::green("Initialized")); + sh_println!("{}", " Initialized forge project".green())?; Ok(()) } } -/// Returns the commit hash of the project if it exists -pub fn get_commit_hash(root: &Path) -> Option { - Git::new(root).commit_hash(true).ok() -} - /// Initialises `root` as a git repository, if it isn't one already. /// /// Creates `.gitignore` and `.github/workflows/test.yml`, if they don't exist already. /// -/// Commits everything in `root` if `no_commit` is false. -fn init_git_repo(git: Git<'_>, no_commit: bool) -> Result<()> { +/// Commits everything in `root` if `commit` is true. +fn init_git_repo(git: Git<'_>, commit: bool) -> Result<()> { // git init if !git.is_in_repo()? { git.init()?; @@ -177,7 +189,7 @@ fn init_git_repo(git: Git<'_>, no_commit: bool) -> Result<()> { } // commit everything - if !no_commit { + if commit { git.add(Some("--all"))?; git.commit("chore: forge init")?; } @@ -189,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::>(); @@ -206,7 +218,7 @@ fn init_vscode(root: &Path) -> Result<()> { fs::create_dir_all(&vscode_dir)?; serde_json::json!({}) } else if settings_file.exists() { - ethers::solc::utils::read_json_file(&settings_file)? + foundry_compilers::utils::read_json_file(&settings_file)? } else { serde_json::json!({}) }; diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index c4c955f6486f2..fe8ba67a7d509 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -1,201 +1,142 @@ +use alloy_json_abi::{EventParam, InternalType, JsonAbi, Param}; +use alloy_primitives::{hex, keccak256, Address}; use clap::Parser; -use comfy_table::{presets::ASCII_MARKDOWN, Table}; -use ethers::{ - abi::RawAbi, - prelude::{ - artifacts::output_selection::{ - BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, - EvmOutputSelection, EwasmOutputSelection, - }, - info::ContractInfo, - }, - solc::{ - artifacts::{LosslessAbi, StorageLayout}, - utils::canonicalize, +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Table}; +use eyre::{Context, Result}; +use forge::revm::primitives::Eof; +use foundry_cli::opts::{BuildOpts, CompilerOpts}; +use foundry_common::{ + compile::{PathOrContractInfo, ProjectCompiler}, + find_matching_contract_artifact, find_target_path, + fmt::pretty_eof, + shell, +}; +use foundry_compilers::artifacts::{ + output_selection::{ + BytecodeOutputSelection, ContractOutputSelection, DeployedBytecodeOutputSelection, + EvmOutputSelection, EwasmOutputSelection, }, + CompactBytecode, StorageLayout, }; -use eyre::Result; -use foundry_cli::opts::{CompilerArgs, CoreBuildArgs}; -use foundry_common::compile; -use serde_json::{to_value, Value}; -use std::fmt; -use tracing::trace; +use regex::Regex; +use serde_json::{Map, Value}; +use std::{collections::BTreeMap, fmt, str::FromStr, sync::LazyLock}; /// CLI arguments for `forge inspect`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct InspectArgs { /// The identifier of the contract to inspect in the form `(:)?`. - pub contract: ContractInfo, + #[arg(value_parser = PathOrContractInfo::from_str)] + pub contract: PathOrContractInfo, /// The contract artifact field to inspect. - #[clap(value_enum)] + #[arg(value_enum)] pub field: ContractArtifactField, - /// Pretty print the selected field, if supported. - #[clap(long)] - pub pretty: bool, - /// All build arguments are supported - #[clap(flatten)] - build: CoreBuildArgs, + #[command(flatten)] + build: BuildOpts, + + /// Whether to remove comments when inspecting `ir` and `irOptimized` artifact fields. + #[arg(long, short, help_heading = "Display options")] + pub strip_yul_comments: bool, } impl InspectArgs { pub fn run(self) -> Result<()> { - let InspectArgs { mut contract, field, build, pretty } = self; + let Self { contract, field, build, strip_yul_comments } = self; trace!(target: "forge", ?field, ?contract, "running forge inspect"); // Map field to ContractOutputSelection let mut cos = build.compiler.extra_output; - if !field.is_default() && !cos.iter().any(|selected| field.eq(selected)) { + if !field.is_default() && !cos.iter().any(|selected| field == *selected) { cos.push(field.into()); } // Run Optimized? - let optimized = if let ContractArtifactField::AssemblyOptimized = field { - true + let optimized = if field == ContractArtifactField::AssemblyOptimized { + Some(true) } else { build.compiler.optimize }; // Build modified Args - let modified_build_args = CoreBuildArgs { - compiler: CompilerArgs { extra_output: cos, optimize: optimized, ..build.compiler }, + let modified_build_args = BuildOpts { + compiler: CompilerOpts { extra_output: cos, optimize: optimized, ..build.compiler }, ..build }; // Build the project let project = modified_build_args.project()?; - let outcome = if let Some(ref mut contract_path) = contract.path { - let target_path = canonicalize(&*contract_path)?; - *contract_path = target_path.to_string_lossy().to_string(); - compile::compile_files(&project, vec![target_path], true) - } else { - compile::suppress_compile(&project) - }?; + let compiler = ProjectCompiler::new().quiet(true); + let target_path = find_target_path(&project, &contract)?; + let mut output = compiler.files([target_path.clone()]).compile(&project)?; // Find the artifact - let found_artifact = outcome.find_contract(&contract); - - trace!(target: "forge", artifact=?found_artifact, input=?contract, "Found contract"); + let artifact = find_matching_contract_artifact(&mut output, &target_path, contract.name())?; - // Unwrap the inner artifact - let artifact = found_artifact.ok_or_else(|| { - eyre::eyre!("Could not find artifact `{contract}` in the compiled artifacts") - })?; - - // Match on ContractArtifactFields and Pretty Print + // Match on ContractArtifactFields and pretty-print match field { ContractArtifactField::Abi => { let abi = artifact .abi .as_ref() .ok_or_else(|| eyre::eyre!("Failed to fetch lossless ABI"))?; - print_abi(abi, pretty)?; + print_abi(abi)?; } ContractArtifactField::Bytecode => { - let tval: Value = to_value(&artifact.bytecode)?; - println!( - "{}", - tval.get("object").unwrap_or(&tval).as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact bytecode as a string" - ))? - ); + print_json_str(&artifact.bytecode, Some("object"))?; } ContractArtifactField::DeployedBytecode => { - let tval: Value = to_value(&artifact.deployed_bytecode)?; - println!( - "{}", - tval.get("object").unwrap_or(&tval).as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact deployed bytecode as a string" - ))? - ); + print_json_str(&artifact.deployed_bytecode, Some("object"))?; } ContractArtifactField::Assembly | ContractArtifactField::AssemblyOptimized => { - println!( - "{}", - to_value(&artifact.assembly)?.as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact assembly as a string" - ))? - ); + print_json_str(&artifact.assembly, None)?; + } + ContractArtifactField::LegacyAssembly => { + print_json_str(&artifact.legacy_assembly, None)?; } ContractArtifactField::MethodIdentifiers => { - println!( - "{}", - serde_json::to_string_pretty(&to_value(&artifact.method_identifiers)?)? - ); + print_method_identifiers(&artifact.method_identifiers)?; } ContractArtifactField::GasEstimates => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.gas_estimates)?)?); + print_json(&artifact.gas_estimates)?; } ContractArtifactField::StorageLayout => { - print_storage_layout(&artifact.storage_layout, pretty)?; + print_storage_layout(artifact.storage_layout.as_ref())?; } ContractArtifactField::DevDoc => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.devdoc)?)?); + print_json(&artifact.devdoc)?; } ContractArtifactField::Ir => { - println!( - "{}", - to_value(&artifact.ir)? - .as_str() - .ok_or_else(|| eyre::eyre!("Failed to extract artifact ir as a string"))? - ); + print_yul(artifact.ir.as_deref(), strip_yul_comments)?; } ContractArtifactField::IrOptimized => { - println!( - "{}", - to_value(&artifact.ir_optimized)?.as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact optimized ir as a string" - ))? - ); + print_yul(artifact.ir_optimized.as_deref(), strip_yul_comments)?; } ContractArtifactField::Metadata => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.metadata)?)?); + print_json(&artifact.metadata)?; } ContractArtifactField::UserDoc => { - println!("{}", serde_json::to_string_pretty(&to_value(&artifact.userdoc)?)?); + print_json(&artifact.userdoc)?; } ContractArtifactField::Ewasm => { - println!( - "{}", - to_value(&artifact.ewasm)?.as_str().ok_or_else(|| eyre::eyre!( - "Failed to extract artifact ewasm as a string" - ))? - ); + print_json_str(&artifact.ewasm, None)?; } ContractArtifactField::Errors => { - let mut out = serde_json::Map::new(); - if let Some(LosslessAbi { abi, .. }) = &artifact.abi { - // Print the signature of all errors - for er in abi.errors.iter().flat_map(|(_, errors)| errors) { - let types = - er.inputs.iter().map(|p| p.kind.to_string()).collect::>(); - let sig = format!("{:x}", er.signature()); - let sig_trimmed = &sig[0..8]; - out.insert( - format!("{}({})", er.name, types.join(",")), - sig_trimmed.to_string().into(), - ); - } - } - println!("{}", serde_json::to_string_pretty(&out)?); + let out = artifact.abi.as_ref().map_or(Map::new(), parse_errors); + print_errors_events(&out, true)?; } ContractArtifactField::Events => { - let mut out = serde_json::Map::new(); - if let Some(LosslessAbi { abi, .. }) = &artifact.abi { - // print the signature of all events including anonymous - for ev in abi.events.iter().flat_map(|(_, events)| events) { - let types = - ev.inputs.iter().map(|p| p.kind.to_string()).collect::>(); - out.insert( - format!("{}({})", ev.name, types.join(",")), - format!("{:?}", ev.signature()).into(), - ); - } - } - println!("{}", serde_json::to_string_pretty(&out)?); + let out = artifact.abi.as_ref().map_or(Map::new(), parse_events); + print_errors_events(&out, false)?; + } + ContractArtifactField::Eof => { + print_eof(artifact.deployed_bytecode.and_then(|b| b.bytecode))?; + } + ContractArtifactField::EofInit => { + print_eof(artifact.bytecode)?; } }; @@ -203,50 +144,201 @@ impl InspectArgs { } } -pub fn print_abi(abi: &LosslessAbi, pretty: bool) -> Result<()> { - let abi_json = to_value(abi)?; - if !pretty { - println!("{}", serde_json::to_string_pretty(&abi_json)?); - return Ok(()) +fn parse_errors(abi: &JsonAbi) -> Map { + let mut out = serde_json::Map::new(); + for er in abi.errors.iter().flat_map(|(_, errors)| errors) { + let types = get_ty_sig(&er.inputs); + let sig = format!("{:x}", er.selector()); + let sig_trimmed = &sig[0..8]; + out.insert(format!("{}({})", er.name, types), sig_trimmed.to_string().into()); } + out +} - let abi_json: RawAbi = serde_json::from_value(abi_json)?; - let source = foundry_utils::abi::abi_to_solidity(&abi_json, "")?; - println!("{}", source); +fn parse_events(abi: &JsonAbi) -> Map { + let mut out = serde_json::Map::new(); + for ev in abi.events.iter().flat_map(|(_, events)| events) { + let types = parse_event_params(&ev.inputs); + let topic = hex::encode(keccak256(ev.signature())); + out.insert(format!("{}({})", ev.name, types), format!("0x{topic}").into()); + } + out +} - Ok(()) +fn parse_event_params(ev_params: &[EventParam]) -> String { + ev_params + .iter() + .map(|p| { + if let Some(ty) = p.internal_type() { + return internal_ty(ty) + } + p.ty.clone() + }) + .collect::>() + .join(",") } -pub fn print_storage_layout(storage_layout: &Option, pretty: bool) -> Result<()> { - if storage_layout.is_none() { - eyre::bail!("Could not get storage layout") +fn print_abi(abi: &JsonAbi) -> Result<()> { + if shell::is_json() { + return print_json(abi) } - let storage_layout = storage_layout.as_ref().unwrap(); + let headers = vec![Cell::new("Type"), Cell::new("Signature"), Cell::new("Selector")]; + print_table(headers, |table| { + // Print events + for ev in abi.events.iter().flat_map(|(_, events)| events) { + let types = parse_event_params(&ev.inputs); + let selector = ev.selector().to_string(); + table.add_row(["event", &format!("{}({})", ev.name, types), &selector]); + } + + // Print errors + for er in abi.errors.iter().flat_map(|(_, errors)| errors) { + let selector = er.selector().to_string(); + table.add_row([ + "error", + &format!("{}({})", er.name, get_ty_sig(&er.inputs)), + &selector, + ]); + } + + // Print functions + for func in abi.functions.iter().flat_map(|(_, f)| f) { + let selector = func.selector().to_string(); + let state_mut = func.state_mutability.as_json_str(); + let func_sig = if !func.outputs.is_empty() { + format!( + "{}({}) {state_mut} returns ({})", + func.name, + get_ty_sig(&func.inputs), + get_ty_sig(&func.outputs) + ) + } else { + format!("{}({}) {state_mut}", func.name, get_ty_sig(&func.inputs)) + }; + table.add_row(["function", &func_sig, &selector]); + } - if !pretty { - println!("{}", serde_json::to_string_pretty(&to_value(storage_layout)?)?); - return Ok(()) + if let Some(constructor) = abi.constructor() { + let state_mut = constructor.state_mutability.as_json_str(); + table.add_row([ + "constructor", + &format!("constructor({}) {state_mut}", get_ty_sig(&constructor.inputs)), + "", + ]); + } + + if let Some(fallback) = &abi.fallback { + let state_mut = fallback.state_mutability.as_json_str(); + table.add_row(["fallback", &format!("fallback() {state_mut}"), ""]); + } + + if let Some(receive) = &abi.receive { + let state_mut = receive.state_mutability.as_json_str(); + table.add_row(["receive", &format!("receive() {state_mut}"), ""]); + } + }) +} + +fn get_ty_sig(inputs: &[Param]) -> String { + inputs + .iter() + .map(|p| { + if let Some(ty) = p.internal_type() { + return internal_ty(ty); + } + p.ty.clone() + }) + .collect::>() + .join(",") +} + +fn internal_ty(ty: &InternalType) -> String { + let contract_ty = + |c: &Option, ty: &String| c.clone().map_or(ty.clone(), |c| format!("{c}.{ty}")); + match ty { + InternalType::AddressPayable(addr) => addr.clone(), + InternalType::Contract(contract) => contract.clone(), + InternalType::Enum { contract, ty } => contract_ty(contract, ty), + InternalType::Struct { contract, ty } => contract_ty(contract, ty), + InternalType::Other { contract, ty } => contract_ty(contract, ty), } +} - let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(vec!["Name", "Type", "Slot", "Offset", "Bytes", "Contract"]); - - for slot in &storage_layout.storage { - let storage_type = storage_layout.types.get(&slot.storage_type); - table.add_row(vec![ - slot.label.clone(), - storage_type.as_ref().map_or("?".to_string(), |t| t.label.clone()), - slot.slot.clone(), - slot.offset.to_string(), - storage_type.as_ref().map_or("?".to_string(), |t| t.number_of_bytes.clone()), - slot.contract.clone(), - ]); +pub fn print_storage_layout(storage_layout: Option<&StorageLayout>) -> Result<()> { + let Some(storage_layout) = storage_layout else { + eyre::bail!("Could not get storage layout"); + }; + + if shell::is_json() { + return print_json(&storage_layout) + } + + let headers = vec![ + Cell::new("Name"), + Cell::new("Type"), + Cell::new("Slot"), + Cell::new("Offset"), + Cell::new("Bytes"), + Cell::new("Contract"), + ]; + + print_table(headers, |table| { + for slot in &storage_layout.storage { + let storage_type = storage_layout.types.get(&slot.storage_type); + table.add_row([ + slot.label.as_str(), + storage_type.map_or("?", |t| &t.label), + &slot.slot, + &slot.offset.to_string(), + storage_type.map_or("?", |t| &t.number_of_bytes), + &slot.contract, + ]); + } + }) +} + +fn print_method_identifiers(method_identifiers: &Option>) -> Result<()> { + let Some(method_identifiers) = method_identifiers else { + eyre::bail!("Could not get method identifiers"); + }; + + if shell::is_json() { + return print_json(method_identifiers) } - println!("{table}"); + let headers = vec![Cell::new("Method"), Cell::new("Identifier")]; + print_table(headers, |table| { + for (method, identifier) in method_identifiers { + table.add_row([method, identifier]); + } + }) +} + +fn print_errors_events(map: &Map, is_err: bool) -> Result<()> { + if shell::is_json() { + return print_json(map); + } + + let headers = if is_err { + vec![Cell::new("Error"), Cell::new("Selector")] + } else { + vec![Cell::new("Event"), Cell::new("Topic")] + }; + print_table(headers, |table| { + for (method, selector) in map { + table.add_row([method, selector.as_str().unwrap()]); + } + }) +} + +fn print_table(headers: Vec, add_rows: impl FnOnce(&mut Table)) -> Result<()> { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + table.set_header(headers); + add_rows(&mut table); + sh_println!("\n{table}\n")?; Ok(()) } @@ -258,6 +350,7 @@ pub enum ContractArtifactField { DeployedBytecode, Assembly, AssemblyOptimized, + LegacyAssembly, MethodIdentifiers, GasEstimates, StorageLayout, @@ -269,13 +362,15 @@ pub enum ContractArtifactField { Ewasm, Errors, Events, + Eof, + EofInit, } macro_rules! impl_value_enum { (enum $name:ident { $($field:ident => $main:literal $(| $alias:literal)*),+ $(,)? }) => { impl $name { /// All the variants of this enum. - pub const ALL: &[Self] = &[$(Self::$field),+]; + pub const ALL: &'static [Self] = &[$(Self::$field),+]; /// Returns the string representation of `self`. #[inline] @@ -338,6 +433,7 @@ impl_value_enum! { DeployedBytecode => "deployedBytecode" | "deployed_bytecode" | "deployed-bytecode" | "deployed" | "deployedbytecode", Assembly => "assembly" | "asm", + LegacyAssembly => "legacyAssembly" | "legacyassembly" | "legacy_assembly", AssemblyOptimized => "assemblyOptimized" | "asmOptimized" | "assemblyoptimized" | "assembly_optimized" | "asmopt" | "assembly-optimized" | "asmo" | "asm-optimized" | "asmoptimized" | "asm_optimized", @@ -355,6 +451,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", } } @@ -368,6 +466,7 @@ impl From for ContractOutputSelection { DeployedBytecodeOutputSelection::All, )), Caf::Assembly | Caf::AssemblyOptimized => Self::Evm(EvmOutputSelection::Assembly), + Caf::LegacyAssembly => Self::Evm(EvmOutputSelection::LegacyAssembly), Caf::MethodIdentifiers => Self::Evm(EvmOutputSelection::MethodIdentifiers), Caf::GasEstimates => Self::Evm(EvmOutputSelection::GasEstimates), Caf::StorageLayout => Self::StorageLayout, @@ -379,6 +478,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)), } } } @@ -394,6 +497,7 @@ impl PartialEq for ContractArtifactField { (Self::Bytecode, Cos::Evm(Eos::ByteCode(_))) | (Self::DeployedBytecode, Cos::Evm(Eos::DeployedByteCode(_))) | (Self::Assembly | Self::AssemblyOptimized, Cos::Evm(Eos::Assembly)) | + (Self::LegacyAssembly, Cos::Evm(Eos::LegacyAssembly)) | (Self::MethodIdentifiers, Cos::Evm(Eos::MethodIdentifiers)) | (Self::GasEstimates, Cos::Evm(Eos::GasEstimates)) | (Self::StorageLayout, Cos::StorageLayout) | @@ -402,7 +506,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(_))) ) } } @@ -420,6 +526,72 @@ impl ContractArtifactField { } } +fn print_json(obj: &impl serde::Serialize) -> Result<()> { + sh_println!("{}", serde_json::to_string_pretty(obj)?)?; + Ok(()) +} + +fn print_json_str(obj: &impl serde::Serialize, key: Option<&str>) -> Result<()> { + sh_println!("{}", get_json_str(obj, key)?)?; + Ok(()) +} + +fn print_yul(yul: Option<&str>, strip_comments: bool) -> Result<()> { + let Some(yul) = yul else { + eyre::bail!("Could not get IR output"); + }; + + static YUL_COMMENTS: LazyLock = + LazyLock::new(|| Regex::new(r"(///.*\n\s*)|(\s*/\*\*.*?\*/)").unwrap()); + + if strip_comments { + sh_println!("{}", YUL_COMMENTS.replace_all(yul, ""))?; + } else { + sh_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 { + if let Some(value2) = value.get(key) { + value_ref = value2; + } + } + let s = match value_ref.as_str() { + Some(s) => s.to_string(), + None => format!("{value_ref:#}"), + }; + 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")?; + + sh_println!("{}", pretty_eof(&eof)?)?; + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/cmd/install.rs b/crates/forge/bin/cmd/install.rs index 5dcf3960adf17..5390f1f0ce6a9 100644 --- a/crates/forge/bin/cmd/install.rs +++ b/crates/forge/bin/cmd/install.rs @@ -2,30 +2,29 @@ use clap::{Parser, ValueHint}; use eyre::{Context, Result}; use foundry_cli::{ opts::Dependency, - p_println, prompt, utils::{CommandUtils, Git, LoadConfig}, }; use foundry_common::fs; use foundry_config::{impl_figment_convert_basic, Config}; -use once_cell::sync::Lazy; use regex::Regex; use semver::Version; use std::{ io::IsTerminal, path::{Path, PathBuf}, str, + sync::LazyLock, }; -use tracing::{trace, warn}; use yansi::Paint; -static DEPENDENCY_VERSION_TAG_REGEX: Lazy = - Lazy::new(|| Regex::new(r"^v?\d+(\.\d+)*$").unwrap()); +static DEPENDENCY_VERSION_TAG_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"^v?\d+(\.\d+)*$").unwrap()); /// CLI arguments for `forge install`. -#[derive(Debug, Clone, Parser)] -#[clap(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... +#[derive(Clone, Debug, Parser)] +#[command(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... forge install [OPTIONS] /@... forge install [OPTIONS] =/@... + forge install [OPTIONS] @git url>...)] forge install [OPTIONS] ...")] pub struct InstallArgs { /// The dependencies to install. @@ -39,6 +38,8 @@ pub struct InstallArgs { /// - A tag: v1.2.3 /// - A commit: 8e8128 /// + /// For exact match, a ref can be provided with `@tag=`, `@branch=` or `@rev=` prefix. + /// /// Target installation directory can be added via `=` suffix. /// The dependency will installed to `lib/`. dependencies: Vec, @@ -47,10 +48,10 @@ pub struct InstallArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, - #[clap(flatten)] + #[command(flatten)] opts: DependencyInstallOpts, } @@ -58,35 +59,31 @@ impl_figment_convert_basic!(InstallArgs); impl InstallArgs { pub fn run(self) -> Result<()> { - let mut config = self.try_load_config_emit_warnings()?; + let mut config = self.load_config()?; self.opts.install(&mut config, self.dependencies) } } -#[derive(Debug, Clone, Default, Copy, Parser)] +#[derive(Clone, Copy, Debug, Default, Parser)] pub struct DependencyInstallOpts { /// Perform shallow clones instead of deep ones. /// /// Improves performance and reduces disk usage, but prevents switching branches or tags. - #[clap(long)] + #[arg(long)] pub shallow: bool, /// Install without adding the dependency as a submodule. - #[clap(long)] + #[arg(long)] pub no_git: bool, - /// Do not create a commit. - #[clap(long)] - pub no_commit: bool, - - /// Do not print any messages. - #[clap(short, long)] - pub quiet: bool, + /// Create a commit after installing the dependencies. + #[arg(long)] + pub commit: bool, } impl DependencyInstallOpts { pub fn git(self, config: &Config) -> Git<'_> { - Git::from_config(config).quiet(self.quiet).shallow(self.shallow) + Git::from_config(config).shallow(self.shallow) } /// Installs all missing dependencies. @@ -94,20 +91,14 @@ impl DependencyInstallOpts { /// See also [`Self::install`]. /// /// Returns true if any dependency was installed. - pub fn install_missing_dependencies(mut self, config: &mut Config) -> bool { - let DependencyInstallOpts { quiet, .. } = self; + pub fn install_missing_dependencies(self, config: &mut Config) -> bool { let lib = config.install_lib_dir(); if self.git(config).has_missing_dependencies(Some(lib)).unwrap_or(false) { // The extra newline is needed, otherwise the compiler output will overwrite the message - p_println!(!quiet => "Missing dependencies found. Installing now...\n"); - self.no_commit = true; - if self.install(config, Vec::new()).is_err() && !quiet { - eprintln!( - "{}", - Paint::yellow( - "Your project has missing dependencies that could not be installed." - ) - ) + let _ = sh_println!("Missing dependencies found. Installing now...\n"); + if self.install(config, Vec::new()).is_err() { + let _ = + sh_warn!("Your project has missing dependencies that could not be installed."); } true } else { @@ -117,7 +108,7 @@ impl DependencyInstallOpts { /// Installs all dependencies pub fn install(self, config: &mut Config, dependencies: Vec) -> Result<()> { - let DependencyInstallOpts { no_git, no_commit, quiet, .. } = self; + let Self { no_git, commit, .. } = self; let git = self.git(config); @@ -125,25 +116,47 @@ impl DependencyInstallOpts { let libs = git.root.join(install_lib_dir); if dependencies.is_empty() && !self.no_git { - p_println!(!self.quiet => "Updating dependencies in {}", libs.display()); - git.submodule_update(false, false, Some(&libs))?; + // Use the root of the git repository to look for submodules. + let root = Git::root_of(git.root)?; + match git.has_submodules(Some(&root)) { + Ok(true) => { + sh_println!("Updating dependencies in {}", libs.display())?; + + // recursively fetch all submodules (without fetching latest) + git.submodule_update(false, false, false, true, Some(&libs))?; + } + + Err(err) => { + warn!(?err, "Failed to check for submodules"); + } + _ => { + // no submodules, nothing to do + } + } } + fs::create_dir_all(&libs)?; - let installer = Installer { git, no_commit }; + let installer = Installer { git, commit }; for dep in dependencies { let path = libs.join(dep.name()); let rel_path = path .strip_prefix(git.root) .wrap_err("Library directory is not relative to the repository root")?; - p_println!(!quiet => "Installing {} in {} (url: {:?}, tag: {:?})", dep.name, path.display(), dep.url, dep.tag); + sh_println!( + "Installing {} in {} (url: {:?}, tag: {:?})", + dep.name, + path.display(), + dep.url, + dep.tag + )?; // this tracks the actual installed tag let installed_tag; if no_git { installed_tag = installer.install_as_folder(&dep, &path)?; } else { - if !no_commit { + if commit { git.ensure_clean()?; } installed_tag = installer.install_as_submodule(&dep, &path)?; @@ -151,7 +164,7 @@ impl DependencyInstallOpts { // Pin branch to submodule if branch is used if let Some(branch) = &installed_tag { // First, check if this tag has a branch - if git.has_branch(branch)? { + if git.has_branch(branch, &path)? { // always work with relative paths when directly modifying submodules git.cmd() .args(["submodule", "set-branch", "-b", branch]) @@ -159,14 +172,16 @@ impl DependencyInstallOpts { .exec()?; } - // update .gitmodules which is at the root of the repo, - // not necessarily at the root of the current Foundry project - let root = Git::root_of(git.root)?; - git.root(&root).add(Some(".gitmodules"))?; + if commit { + // update .gitmodules which is at the root of the repo, + // not necessarily at the root of the current Foundry project + let root = Git::root_of(git.root)?; + git.root(&root).add(Some(".gitmodules"))?; + } } // commit the installation - if !no_commit { + if commit { let mut msg = String::with_capacity(128); msg.push_str("forge install: "); msg.push_str(dep.name()); @@ -178,14 +193,12 @@ impl DependencyInstallOpts { } } - if !quiet { - let mut msg = format!(" {} {}", Paint::green("Installed"), dep.name); - if let Some(tag) = dep.tag.or(installed_tag) { - msg.push(' '); - msg.push_str(tag.as_str()); - } - println!("{msg}"); + let mut msg = format!(" {} {}", "Installed".green(), dep.name); + if let Some(tag) = dep.tag.or(installed_tag) { + msg.push(' '); + msg.push_str(tag.as_str()); } + sh_println!("{msg}")?; } // update `libs` in config if not included yet @@ -197,14 +210,14 @@ impl DependencyInstallOpts { } } -pub fn install_missing_dependencies(config: &mut Config, quiet: bool) -> bool { - DependencyInstallOpts { quiet, ..Default::default() }.install_missing_dependencies(config) +pub fn install_missing_dependencies(config: &mut Config) -> bool { + DependencyInstallOpts::default().install_missing_dependencies(config) } #[derive(Clone, Copy, Debug)] struct Installer<'a> { git: Git<'a>, - no_commit: bool, + commit: bool, } impl Installer<'_> { @@ -222,6 +235,15 @@ impl Installer<'_> { // checkout the tag if necessary self.git_checkout(&dep, path, false)?; + trace!("updating dependency submodules recursively"); + self.git.root(path).submodule_update( + false, + false, + false, + true, + std::iter::empty::(), + )?; + // remove git artifacts fs::remove_dir_all(path.join(".git"))?; @@ -245,7 +267,16 @@ impl Installer<'_> { // checkout the tag if necessary self.git_checkout(&dep, path, true)?; - if !self.no_commit { + trace!("updating dependency submodules recursively"); + self.git.root(path).submodule_update( + false, + false, + false, + true, + std::iter::empty::(), + )?; + + if self.commit { self.git.add(Some(path))?; } @@ -301,10 +332,7 @@ impl Installer<'_> { let path = path.strip_prefix(self.git.root).unwrap(); trace!(?dep, url, ?path, "installing git submodule"); - self.git.submodule_add(true, url, path)?; - - trace!("updating submodule recursively"); - self.git.submodule_update(false, false, Some(path)) + self.git.submodule_add(true, url, path) } fn git_checkout(self, dep: &Dependency, path: &Path, recurse: bool) -> Result { @@ -384,9 +412,9 @@ impl Installer<'_> { // multiple candidates, ask the user to choose one or skip candidates.insert(0, String::from("SKIP AND USE ORIGINAL TAG")); - println!("There are multiple matching tags:"); + sh_println!("There are multiple matching tags:")?; for (i, candidate) in candidates.iter().enumerate() { - println!("[{i}] {candidate}"); + sh_println!("[{i}] {candidate}")?; } let n_candidates = candidates.len(); @@ -401,7 +429,7 @@ impl Installer<'_> { Ok(0) => return Ok(tag.into()), Ok(i) if (1..=n_candidates).contains(&i) => { let c = &candidates[i]; - println!("[{i}] {c} selected"); + sh_println!("[{i}] {c} selected")?; return Ok(c.clone()) } _ => continue, @@ -446,9 +474,9 @@ impl Installer<'_> { // multiple candidates, ask the user to choose one or skip candidates.insert(0, format!("{tag} (original branch)")); - println!("There are multiple matching branches:"); + sh_println!("There are multiple matching branches:")?; for (i, candidate) in candidates.iter().enumerate() { - println!("[{i}] {candidate}"); + sh_println!("[{i}] {candidate}")?; } let n_candidates = candidates.len(); @@ -460,7 +488,7 @@ impl Installer<'_> { // default selection, return None if input.is_empty() { - println!("Canceled branch matching"); + sh_println!("Canceled branch matching")?; return Ok(None) } @@ -469,7 +497,7 @@ impl Installer<'_> { Ok(0) => Ok(Some(tag.into())), Ok(i) if (1..=n_candidates).contains(&i) => { let c = &candidates[i]; - println!("[{i}] {c} selected"); + sh_println!("[{i}] {c} selected")?; Ok(Some(c.clone())) } _ => Ok(None), @@ -488,13 +516,14 @@ fn match_yn(input: String) -> bool { #[cfg(test)] mod tests { use super::*; - use foundry_test_utils::tempfile::tempdir; + use tempfile::tempdir; #[test] + #[ignore = "slow"] fn get_oz_tags() { let tmp = tempdir().unwrap(); let git = Git::new(tmp.path()); - let installer = Installer { git, no_commit: true }; + let installer = Installer { git, commit: false }; git.init().unwrap(); diff --git a/crates/forge/bin/cmd/mod.rs b/crates/forge/bin/cmd/mod.rs index 1c88f21ca29e9..d633564f69cf4 100644 --- a/crates/forge/bin/cmd/mod.rs +++ b/crates/forge/bin/cmd/mod.rs @@ -1,55 +1,23 @@ -//! Subcommands for forge +//! `forge` subcommands. //! //! All subcommands should respect the `foundry_config::Config`. //! If a subcommand accepts values that are supported by the `Config`, then the subcommand should //! implement `figment::Provider` which allows the subcommand to override the config's defaults, see //! [`foundry_config::Config`]. -//! -//! See [`BuildArgs`] for a reference implementation. -//! And [`DebugArgs`] for how to merge `Providers`. -//! -//! # Example -//! -//! create a `clap` subcommand into a `figment::Provider` and integrate it in the -//! `foundry_config::Config`: -//! -//! ``` -//! use clap::Parser; -//! use forge::executor::opts::EvmOpts; -//! use foundry_cli::cmd::forge::build::BuildArgs; -//! use foundry_common::evm::EvmArgs; -//! use foundry_config::{figment::Figment, *}; -//! -//! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` -//! #[derive(Debug, Clone, Parser)] -//! pub struct MyArgs { -//! #[clap(flatten)] -//! evm_opts: EvmArgs, -//! #[clap(flatten)] -//! opts: BuildArgs, -//! } -//! -//! // add `Figment` and `Config` converters -//! foundry_config::impl_figment_convert!(MyArgs, opts, evm_opts); -//! let args = MyArgs::parse_from(["build"]); -//! -//! let figment: Figment = From::from(&args); -//! let evm_opts = figment.extract::().unwrap(); -//! -//! let config: Config = From::from(&args); -//! ``` pub mod bind; +pub mod bind_json; pub mod build; pub mod cache; +pub mod clone; +pub mod compiler; pub mod config; pub mod coverage; pub mod create; -pub mod debug; pub mod doc; +pub mod eip712; pub mod flatten; pub mod fmt; -pub mod fourbyte; pub mod geiger; pub mod generate; pub mod init; @@ -57,12 +25,10 @@ pub mod inspect; pub mod install; pub mod remappings; pub mod remove; -pub mod retry; -pub mod script; pub mod selectors; pub mod snapshot; +pub mod soldeer; pub mod test; pub mod tree; pub mod update; -pub mod verify; pub mod watch; diff --git a/crates/forge/bin/cmd/remappings.rs b/crates/forge/bin/cmd/remappings.rs index c3d30cf73526e..c475e8199588e 100644 --- a/crates/forge/bin/cmd/remappings.rs +++ b/crates/forge/bin/cmd/remappings.rs @@ -1,55 +1,49 @@ use clap::{Parser, ValueHint}; -use ethers::solc::remappings::RelativeRemapping; use eyre::Result; use foundry_cli::utils::LoadConfig; use foundry_config::impl_figment_convert_basic; -use foundry_evm::HashMap; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; /// CLI arguments for `forge remappings`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RemappingArgs { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Pretty-print the remappings, grouping each of them by context. - #[clap(long)] + #[arg(long)] pretty: bool, } impl_figment_convert_basic!(RemappingArgs); impl RemappingArgs { - // TODO: Do people use `forge remappings >> file`? pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; if self.pretty { - let groups = config.remappings.into_iter().fold( - HashMap::new(), - |mut groups: HashMap, Vec>, remapping| { - groups.entry(remapping.context.clone()).or_default().push(remapping); - groups - }, - ); - for (group, remappings) in groups.into_iter() { + let mut groups = BTreeMap::<_, Vec<_>>::new(); + for remapping in config.remappings { + groups.entry(remapping.context.clone()).or_default().push(remapping); + } + for (group, remappings) in groups { if let Some(group) = group { - println!("Context: {group}"); + sh_println!("Context: {group}")?; } else { - println!("Global:"); + sh_println!("Global:")?; } - for mut remapping in remappings.into_iter() { + for mut remapping in remappings { remapping.context = None; // avoid writing context twice - println!("- {remapping}"); + sh_println!("- {remapping}")?; } - println!(); + sh_println!()?; } } else { - for remapping in config.remappings.into_iter() { - println!("{remapping}"); + for remapping in config.remappings { + sh_println!("{remapping}")?; } } diff --git a/crates/forge/bin/cmd/remove.rs b/crates/forge/bin/cmd/remove.rs index f5deb00b2e1fa..2033ad3a7c4ad 100644 --- a/crates/forge/bin/cmd/remove.rs +++ b/crates/forge/bin/cmd/remove.rs @@ -8,27 +8,28 @@ use foundry_config::impl_figment_convert_basic; use std::path::PathBuf; /// CLI arguments for `forge remove`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct RemoveArgs { /// The dependencies you want to remove. + #[arg(required = true)] dependencies: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Override the up-to-date check. - #[clap(short, long)] + #[arg(short, long)] force: bool, } impl_figment_convert_basic!(RemoveArgs); impl RemoveArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let (root, paths) = super::update::dependencies_paths(&self.dependencies, &config)?; let git_modules = root.join(".git/modules"); @@ -37,7 +38,7 @@ impl RemoveArgs { // remove all the dependencies from .git/modules for (Dependency { name, url, tag, .. }, path) in self.dependencies.iter().zip(&paths) { - println!("Removing '{name}' in {}, (url: {url:?}, tag: {tag:?})", path.display()); + sh_println!("Removing '{name}' in {}, (url: {url:?}, tag: {tag:?})", path.display())?; std::fs::remove_dir_all(git_modules.join(path))?; } diff --git a/crates/forge/bin/cmd/script/artifacts.rs b/crates/forge/bin/cmd/script/artifacts.rs deleted file mode 100644 index b546cef2e848e..0000000000000 --- a/crates/forge/bin/cmd/script/artifacts.rs +++ /dev/null @@ -1,9 +0,0 @@ -use ethers::abi::Abi; - -/// Bundles info of an artifact -pub struct ArtifactInfo<'a> { - pub contract_name: String, - pub contract_id: String, - pub abi: &'a Abi, - pub code: &'a Vec, -} diff --git a/crates/forge/bin/cmd/script/broadcast.rs b/crates/forge/bin/cmd/script/broadcast.rs deleted file mode 100644 index 5414c0d9284a5..0000000000000 --- a/crates/forge/bin/cmd/script/broadcast.rs +++ /dev/null @@ -1,662 +0,0 @@ -use super::{ - multi::MultiChainSequence, providers::ProvidersManager, receipts::clear_pendings, - sequence::ScriptSequence, transaction::TransactionWithMetadata, verify::VerifyBundle, *, -}; -use ethers::{ - prelude::{Provider, Signer, TxHash}, - providers::{JsonRpcClient, Middleware}, - utils::format_units, -}; -use eyre::{bail, ContextCompat, Result, WrapErr}; -use foundry_cli::{ - init_progress, - opts::WalletSigner, - update_progress, - utils::{has_batch_support, has_different_gas_calc}, -}; -use foundry_common::{estimate_eip1559_fees, shell, try_get_http_provider, RetryProvider}; -use futures::StreamExt; -use std::{cmp::min, collections::HashSet, ops::Mul, sync::Arc}; -use tracing::trace; - -impl ScriptArgs { - /// Sends the transactions which haven't been broadcasted yet. - pub async fn send_transactions( - &self, - deployment_sequence: &mut ScriptSequence, - fork_url: &str, - script_wallets: &[LocalWallet], - ) -> Result<()> { - let provider = Arc::new(try_get_http_provider(fork_url)?); - let already_broadcasted = deployment_sequence.receipts.len(); - - if already_broadcasted < deployment_sequence.transactions.len() { - let required_addresses = deployment_sequence - .typed_transactions() - .into_iter() - .skip(already_broadcasted) - .map(|(_, tx)| *tx.from().expect("No sender for onchain transaction!")) - .collect(); - - let (send_kind, chain) = if self.unlocked { - let chain = provider.get_chainid().await?; - let mut senders = HashSet::from([self - .evm_opts - .sender - .wrap_err("--sender must be set with --unlocked")?]); - // also take all additional senders that where set manually via broadcast - senders.extend( - deployment_sequence - .typed_transactions() - .iter() - .filter_map(|(_, tx)| tx.from().copied()), - ); - (SendTransactionsKind::Unlocked(senders), chain.as_u64()) - } else { - let local_wallets = self - .wallets - .find_all(provider.clone(), required_addresses, script_wallets) - .await?; - let chain = local_wallets.values().last().wrap_err("Error accessing local wallet when trying to send onchain transaction, did you set a private key, mnemonic or keystore?")?.chain_id(); - (SendTransactionsKind::Raw(local_wallets), chain) - }; - - // We only wait for a transaction receipt before sending the next transaction, if there - // is more than one signer. There would be no way of assuring their order - // otherwise. Or if the chain does not support batched transactions (eg. Arbitrum). - let sequential_broadcast = - send_kind.signers_count() != 1 || self.slow || !has_batch_support(chain); - - // Make a one-time gas price estimation - let (gas_price, eip1559_fees) = { - match deployment_sequence.transactions.front().unwrap().typed_tx() { - TypedTransaction::Legacy(_) | TypedTransaction::Eip2930(_) => { - (provider.get_gas_price().await.ok(), None) - } - TypedTransaction::Eip1559(_) => { - let fees = estimate_eip1559_fees(&provider, Some(chain)) - .await - .wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; - - (None, Some(fees)) - } - } - }; - - // Iterate through transactions, matching the `from` field with the associated - // wallet. Then send the transaction. Panics if we find a unknown `from` - let sequence = deployment_sequence - .transactions - .iter() - .skip(already_broadcasted) - .map(|tx_with_metadata| { - let tx = tx_with_metadata.typed_tx(); - let from = *tx.from().expect("No sender for onchain transaction!"); - - let kind = send_kind.for_sender(&from)?; - let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit; - - let mut tx = tx.clone(); - - tx.set_chain_id(chain); - - if let Some(gas_price) = self.with_gas_price { - tx.set_gas_price(gas_price); - } else { - // fill gas price - match tx { - TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => { - tx.set_gas_price(gas_price.expect("Could not get gas_price.")); - } - TypedTransaction::Eip1559(ref mut inner) => { - let eip1559_fees = - eip1559_fees.expect("Could not get eip1559 fee estimation."); - if let Some(priority_gas_price) = self.priority_gas_price { - inner.max_priority_fee_per_gas = Some(priority_gas_price); - } else { - inner.max_priority_fee_per_gas = Some(eip1559_fees.1); - } - inner.max_fee_per_gas = Some(eip1559_fees.0); - } - } - } - - Ok((tx, kind, is_fixed_gas_limit)) - }) - .collect::>>()?; - - let pb = init_progress!(deployment_sequence.transactions, "txes"); - - // We send transactions and wait for receipts in batches of 100, since some networks - // cannot handle more than that. - let batch_size = 100; - let mut index = 0; - - for (batch_number, batch) in sequence.chunks(batch_size).map(|f| f.to_vec()).enumerate() - { - let mut pending_transactions = vec![]; - - shell::println(format!( - "##\nSending transactions [{} - {}].", - batch_number * batch_size, - batch_number * batch_size + min(batch_size, batch.len()) - 1 - ))?; - for (tx, kind, is_fixed_gas_limit) in batch.into_iter() { - let tx_hash = self.send_transaction( - provider.clone(), - tx, - kind, - sequential_broadcast, - fork_url, - is_fixed_gas_limit, - ); - - if sequential_broadcast { - let tx_hash = tx_hash.await?; - deployment_sequence.add_pending(index, tx_hash); - - update_progress!(pb, (index + already_broadcasted)); - index += 1; - - clear_pendings(provider.clone(), deployment_sequence, Some(vec![tx_hash])) - .await?; - } else { - pending_transactions.push(tx_hash); - } - } - - if !pending_transactions.is_empty() { - let mut buffer = futures::stream::iter(pending_transactions).buffered(7); - - while let Some(tx_hash) = buffer.next().await { - let tx_hash = tx_hash?; - deployment_sequence.add_pending(index, tx_hash); - - update_progress!(pb, (index + already_broadcasted)); - index += 1; - } - - // Checkpoint save - deployment_sequence.save()?; - - if !sequential_broadcast { - shell::println("##\nWaiting for receipts.")?; - clear_pendings(provider.clone(), deployment_sequence, None).await?; - } - } - - // Checkpoint save - deployment_sequence.save()?; - } - } - - shell::println("\n\n==========================")?; - shell::println("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; - - let (total_gas, total_gas_price, total_paid) = deployment_sequence.receipts.iter().fold( - (U256::zero(), U256::zero(), U256::zero()), - |acc, receipt| { - let gas_used = receipt.gas_used.unwrap_or_default(); - let gas_price = receipt.effective_gas_price.unwrap_or_default(); - (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used.mul(gas_price)) - }, - ); - let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); - let avg_gas_price = format_units(total_gas_price / deployment_sequence.receipts.len(), 9) - .unwrap_or_else(|_| "N/A".to_string()); - shell::println(format!( - "Total Paid: {} ETH ({} gas * avg {} gwei)", - paid.trim_end_matches('0'), - total_gas, - avg_gas_price.trim_end_matches('0').trim_end_matches('.') - ))?; - - Ok(()) - } - - async fn send_transaction( - &self, - provider: Arc, - mut tx: TypedTransaction, - kind: SendTransactionKind<'_>, - sequential_broadcast: bool, - fork_url: &str, - is_fixed_gas_limit: bool, - ) -> Result { - let from = tx.from().expect("no sender"); - - if sequential_broadcast { - let nonce = foundry_utils::next_nonce(*from, fork_url, None) - .await - .map_err(|_| eyre::eyre!("Not able to query the EOA nonce."))?; - - let tx_nonce = tx.nonce().expect("no nonce"); - - if nonce != *tx_nonce { - bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.") - } - } - - match kind { - SendTransactionKind::Unlocked(addr) => { - tracing::debug!("sending transaction from unlocked account {:?}: {:?}", addr, tx); - - // Chains which use `eth_estimateGas` are being sent sequentially and require their - // gas to be re-estimated right before broadcasting. - if !is_fixed_gas_limit && - (has_different_gas_calc(provider.get_chainid().await?.as_u64()) || - self.skip_simulation) - { - self.estimate_gas(&mut tx, &provider).await?; - } - - // Submit the transaction - let pending = provider.send_transaction(tx, None).await?; - - Ok(pending.tx_hash()) - } - SendTransactionKind::Raw(signer) => self.broadcast(provider, signer, tx).await, - } - } - - /// Executes the created transactions, and if no error has occurred, broadcasts - /// them. - pub async fn handle_broadcastable_transactions( - &self, - mut result: ScriptResult, - libraries: Libraries, - decoder: &CallTraceDecoder, - mut script_config: ScriptConfig, - verify: VerifyBundle, - ) -> Result<()> { - if let Some(txs) = result.transactions.take() { - script_config.collect_rpcs(&txs); - script_config.check_multi_chain_constraints(&libraries)?; - script_config.check_shanghai_support().await?; - - if !script_config.missing_rpc { - trace!(target: "script", "creating deployments"); - - let mut deployments = self - .create_script_sequences( - txs, - &result, - &mut script_config, - decoder, - &verify.known_contracts, - ) - .await?; - - if script_config.has_multiple_rpcs() { - trace!(target: "script", "broadcasting multi chain deployment"); - - let multi = MultiChainSequence::new( - deployments.clone(), - &self.sig, - script_config.target_contract(), - &script_config.config.broadcast, - self.broadcast, - )?; - - if self.broadcast { - self.multi_chain_deployment( - multi, - libraries, - &script_config.config, - result.script_wallets, - verify, - ) - .await?; - } - } else if self.broadcast { - self.single_deployment( - deployments.first_mut().expect("to be set."), - script_config, - libraries, - result, - verify, - ) - .await?; - } - - if !self.broadcast { - shell::println("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; - } - } else { - shell::println("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; - } - } - Ok(()) - } - - /// Broadcasts a single chain script. - async fn single_deployment( - &self, - deployment_sequence: &mut ScriptSequence, - script_config: ScriptConfig, - libraries: Libraries, - result: ScriptResult, - verify: VerifyBundle, - ) -> Result<()> { - trace!(target: "script", "broadcasting single chain deployment"); - - let rpc = script_config.total_rpcs.into_iter().next().expect("exists; qed"); - - deployment_sequence.add_libraries(libraries); - - self.send_transactions(deployment_sequence, &rpc, &result.script_wallets).await?; - - if self.verify { - return deployment_sequence.verify_contracts(&script_config.config, verify).await - } - Ok(()) - } - - /// Given the collected transactions it creates a list of [`ScriptSequence`]. List length will - /// be higher than 1, if we're dealing with a multi chain deployment. - /// - /// If `--skip-simulation` is not passed, it will make an onchain simulation of the transactions - /// before adding them to [`ScriptSequence`]. - async fn create_script_sequences( - &self, - txs: BroadcastableTransactions, - script_result: &ScriptResult, - script_config: &mut ScriptConfig, - decoder: &CallTraceDecoder, - known_contracts: &ContractsByArtifact, - ) -> Result> { - if !txs.is_empty() { - let gas_filled_txs = self - .fills_transactions_with_gas(txs, script_config, decoder, known_contracts) - .await?; - - let returns = self.get_returns(&*script_config, &script_result.returned)?; - - return self - .bundle_transactions( - gas_filled_txs, - &script_config.target_contract().clone(), - &mut script_config.config, - returns, - ) - .await - } else if self.broadcast { - eyre::bail!("No onchain transactions generated in script"); - } - - Ok(vec![]) - } - - /// Takes the collected transactions and executes them locally before converting them to - /// [`TransactionWithMetadata`] with the appropriate gas execution estimation. If - /// `--skip-simulation` is passed, then it will skip the execution. - async fn fills_transactions_with_gas( - &self, - txs: BroadcastableTransactions, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - known_contracts: &ContractsByArtifact, - ) -> Result> { - let gas_filled_txs = if self.skip_simulation { - shell::println("\nSKIPPING ON CHAIN SIMULATION.")?; - txs.into_iter() - .map(|btx| { - let mut tx = TransactionWithMetadata::from_typed_transaction(btx.transaction); - tx.rpc = btx.rpc; - tx - }) - .collect() - } else { - self.onchain_simulation( - txs, - script_config, - decoder, - known_contracts, - ) - .await - .wrap_err("\nTransaction failed when running the on-chain simulation. Check the trace above for more information.")? - }; - Ok(gas_filled_txs) - } - - /// Returns all transactions of the [`TransactionWithMetadata`] type in a list of - /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi - /// chain deployment. - /// - /// Each transaction will be added with the correct transaction type and gas estimation. - async fn bundle_transactions( - &self, - transactions: VecDeque, - target: &ArtifactId, - config: &mut Config, - returns: HashMap, - ) -> Result> { - // User might be using both "in-code" forks and `--fork-url`. - let last_rpc = &transactions.back().expect("exists; qed").rpc; - let is_multi_deployment = transactions.iter().any(|tx| &tx.rpc != last_rpc); - - let mut total_gas_per_rpc: HashMap = HashMap::new(); - - // Batches sequence of transactions from different rpcs. - let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::default(); - let mut deployments = vec![]; - - // Config is used to initialize the sequence chain, so we need to change when handling a new - // sequence. This makes sure we don't lose the original value. - let original_config_chain = config.chain_id; - - // Peeking is used to check if the next rpc url is different. If so, it creates a - // [`ScriptSequence`] from all the collected transactions up to this point. - let mut txes_iter = transactions.into_iter().peekable(); - - while let Some(mut tx) = txes_iter.next() { - let tx_rpc = match tx.rpc.clone() { - Some(rpc) => rpc, - None => { - let rpc = self.evm_opts.ensure_fork_url()?.clone(); - // Fills the RPC inside the transaction, if missing one. - tx.rpc = Some(rpc.clone()); - rpc - } - }; - - let provider_info = manager.get_or_init_provider(&tx_rpc, self.legacy).await?; - - // Handles chain specific requirements. - tx.change_type(provider_info.is_legacy); - tx.transaction.set_chain_id(provider_info.chain); - - if !self.skip_simulation { - let typed_tx = tx.typed_tx_mut(); - - if has_different_gas_calc(provider_info.chain) { - trace!("estimating with different gas calculation"); - let gas = *typed_tx.gas().expect("gas is set by simulation."); - - // We are trying to show the user an estimation of the total gas usage. - // - // However, some transactions might depend on previous ones. For - // example, tx1 might deploy a contract that tx2 uses. That - // will result in the following `estimate_gas` call to fail, - // since tx1 hasn't been broadcasted yet. - // - // Not exiting here will not be a problem when actually broadcasting, because - // for chains where `has_different_gas_calc` returns true, - // we await each transaction before broadcasting the next - // one. - if let Err(err) = self.estimate_gas(typed_tx, &provider_info.provider).await { - trace!("gas estimation failed: {err}"); - - // Restore gas value, since `estimate_gas` will remove it. - typed_tx.set_gas(gas); - } - } - - let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(U256::zero()); - *total_gas += *typed_tx.gas().expect("gas is set"); - } - - new_sequence.push_back(tx); - // We only create a [`ScriptSequence`] object when we collect all the rpc related - // transactions. - if let Some(next_tx) = txes_iter.peek() { - if next_tx.rpc == Some(tx_rpc) { - continue - } - } - - config.chain_id = Some(provider_info.chain.into()); - let sequence = ScriptSequence::new( - new_sequence, - returns.clone(), - &self.sig, - target, - config, - self.broadcast, - is_multi_deployment, - )?; - - deployments.push(sequence); - - new_sequence = VecDeque::new(); - } - - // Restore previous config chain. - config.chain_id = original_config_chain; - - if !self.skip_simulation { - // Present gas information on a per RPC basis. - for (rpc, total_gas) in total_gas_per_rpc { - let provider_info = manager.get(&rpc).expect("provider is set."); - - // We don't store it in the transactions, since we want the most updated value. - // Right before broadcasting. - let per_gas = if let Some(gas_price) = self.with_gas_price { - gas_price - } else { - provider_info.gas_price()? - }; - - shell::println("\n==========================")?; - shell::println(format!("\nChain {}", provider_info.chain))?; - - shell::println(format!( - "\nEstimated gas price: {} gwei", - format_units(per_gas, 9) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - .trim_end_matches('.') - ))?; - shell::println(format!("\nEstimated total gas used for script: {total_gas}"))?; - shell::println(format!( - "\nEstimated amount required: {} ETH", - format_units(total_gas.saturating_mul(per_gas), 18) - .unwrap_or_else(|_| "[Could not calculate]".to_string()) - .trim_end_matches('0') - ))?; - shell::println("\n==========================")?; - } - } - Ok(deployments) - } - - /// Uses the signer to submit a transaction to the network. If it fails, it tries to retrieve - /// the transaction hash that can be used on a later run with `--resume`. - async fn broadcast( - &self, - provider: Arc, - signer: &WalletSigner, - mut legacy_or_1559: TypedTransaction, - ) -> Result { - tracing::debug!("sending transaction: {:?}", legacy_or_1559); - - // Chains which use `eth_estimateGas` are being sent sequentially and require their gas - // to be re-estimated right before broadcasting. - if has_different_gas_calc(signer.chain_id()) || self.skip_simulation { - // if already set, some RPC endpoints might simply return the gas value that is - // already set in the request and omit the estimate altogether, so - // we remove it here - let _ = legacy_or_1559.gas_mut().take(); - - self.estimate_gas(&mut legacy_or_1559, &provider).await?; - } - - // Signing manually so we skip `fill_transaction` and its `eth_createAccessList` - // request. - let signature = signer - .sign_transaction(&legacy_or_1559) - .await - .wrap_err("Failed to sign transaction")?; - - // Submit the raw transaction - let pending = provider.send_raw_transaction(legacy_or_1559.rlp_signed(&signature)).await?; - - Ok(pending.tx_hash()) - } - - async fn estimate_gas(&self, tx: &mut TypedTransaction, provider: &Provider) -> Result<()> - where - T: JsonRpcClient, - { - // if already set, some RPC endpoints might simply return the gas value that is already - // set in the request and omit the estimate altogether, so we remove it here - let _ = tx.gas_mut().take(); - - tx.set_gas( - provider - .estimate_gas(tx, None) - .await - .wrap_err_with(|| format!("Failed to estimate gas for tx: {:?}", tx.sighash()))? * - self.gas_estimate_multiplier / - 100, - ); - Ok(()) - } -} - -/// How to send a single transaction -#[derive(Clone)] -enum SendTransactionKind<'a> { - Unlocked(Address), - Raw(&'a WalletSigner), -} - -/// Represents how to send _all_ transactions -enum SendTransactionsKind { - /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. - Unlocked(HashSet

), - /// Send a signed transaction via `eth_sendRawTransaction` - Raw(HashMap), -} - -impl SendTransactionsKind { - /// Returns the [`SendTransactionKind`] for the given address - /// - /// Returns an error if no matching signer is found or the address is not unlocked - fn for_sender(&self, addr: &Address) -> Result> { - match self { - SendTransactionsKind::Unlocked(unlocked) => { - if !unlocked.contains(addr) { - bail!("Sender address {:?} is not unlocked", addr) - } - Ok(SendTransactionKind::Unlocked(*addr)) - } - SendTransactionsKind::Raw(wallets) => { - if let Some(wallet) = wallets.get(addr) { - Ok(SendTransactionKind::Raw(wallet)) - } else { - bail!("No matching signer for {:?} found", addr) - } - } - } - } - - /// How many signers are set - fn signers_count(&self) -> usize { - match self { - SendTransactionsKind::Unlocked(addr) => addr.len(), - SendTransactionsKind::Raw(signers) => signers.len(), - } - } -} diff --git a/crates/forge/bin/cmd/script/build.rs b/crates/forge/bin/cmd/script/build.rs deleted file mode 100644 index c905662af0b1a..0000000000000 --- a/crates/forge/bin/cmd/script/build.rs +++ /dev/null @@ -1,348 +0,0 @@ -use super::*; -use ethers::{ - prelude::{ - artifacts::Libraries, cache::SolFilesCache, ArtifactId, Graph, Project, - ProjectCompileOutput, - }, - solc::{ - artifacts::{CompactContractBytecode, ContractBytecode, ContractBytecodeSome}, - contracts::ArtifactContracts, - info::ContractInfo, - }, - types::{Address, U256}, -}; -use eyre::{Context, ContextCompat, Result}; -use foundry_cli::utils::get_cached_entry_by_name; -use foundry_common::compile; -use foundry_utils::{PostLinkInput, ResolvedDependency}; -use std::{collections::BTreeMap, fs, str::FromStr}; -use tracing::{trace, warn}; - -impl ScriptArgs { - /// Compiles the file or project and the verify metadata. - pub fn compile(&mut self, script_config: &mut ScriptConfig) -> Result { - trace!(target: "script", "compiling script"); - - self.build(script_config) - } - - /// Compiles the file with auto-detection and compiler params. - pub fn build(&mut self, script_config: &mut ScriptConfig) -> Result { - let (project, output) = self.get_project_and_output(script_config)?; - let output = output.with_stripped_file_prefixes(project.root()); - - let mut sources: BTreeMap = BTreeMap::new(); - - let contracts = output - .into_artifacts() - .map(|(id, artifact)| -> Result<_> { - // Sources are only required for the debugger, but it *might* mean that there's - // something wrong with the build and/or artifacts. - if let Some(source) = artifact.source_file() { - sources.insert( - source.id, - source - .ast - .ok_or(eyre::eyre!("Source from artifact has no AST."))? - .absolute_path, - ); - } else { - warn!("source not found for artifact={:?}", id); - } - Ok((id, artifact)) - }) - .collect::>()?; - - let mut output = self.link( - project, - contracts, - script_config.config.parsed_libraries()?, - script_config.evm_opts.sender, - script_config.sender_nonce, - )?; - - output.sources = sources; - script_config.target_contract = Some(output.target.clone()); - - Ok(output) - } - - pub fn link( - &self, - project: Project, - contracts: ArtifactContracts, - libraries_addresses: Libraries, - sender: Address, - nonce: U256, - ) -> Result { - let mut run_dependencies = vec![]; - let mut contract = CompactContractBytecode::default(); - let mut highlevel_known_contracts = BTreeMap::new(); - - let mut target_fname = dunce::canonicalize(&self.path) - .wrap_err("Couldn't convert contract path to absolute path.")? - .strip_prefix(project.root()) - .wrap_err("Couldn't strip project root from contract path.")? - .to_str() - .wrap_err("Bad path to string.")? - .to_string(); - - let no_target_name = if let Some(target_name) = &self.target_contract { - target_fname = target_fname + ":" + target_name; - false - } else { - true - }; - - let mut extra_info = ExtraLinkingInfo { - no_target_name, - target_fname: target_fname.clone(), - contract: &mut contract, - dependencies: &mut run_dependencies, - matched: false, - target_id: None, - }; - - // link_with_nonce_or_address expects absolute paths - let mut libs = libraries_addresses.clone(); - for (file, libraries) in libraries_addresses.libs.iter() { - if file.is_relative() { - let mut absolute_path = project.root().clone(); - absolute_path.push(file); - libs.libs.insert(absolute_path, libraries.clone()); - } - } - - foundry_utils::link_with_nonce_or_address( - contracts.clone(), - &mut highlevel_known_contracts, - libs, - sender, - nonce, - &mut extra_info, - |post_link_input| { - let PostLinkInput { - contract, - known_contracts: highlevel_known_contracts, - id, - extra, - dependencies, - } = post_link_input; - - fn unique_deps(deps: Vec) -> Vec<(String, Bytes)> { - let mut filtered = Vec::new(); - let mut seen = HashSet::new(); - for dep in deps { - if !seen.insert(dep.id.clone()) { - continue - } - filtered.push((dep.id, dep.bytecode)); - } - - filtered - } - - // if it's the target contract, grab the info - if extra.no_target_name { - if id.source == std::path::PathBuf::from(&extra.target_fname) { - if extra.matched { - eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`") - } - *extra.dependencies = unique_deps(dependencies); - *extra.contract = contract.clone(); - extra.matched = true; - extra.target_id = Some(id.clone()); - } - } else { - let (path, name) = extra - .target_fname - .rsplit_once(':') - .expect("The target specifier is malformed."); - let path = std::path::Path::new(path); - if path == id.source && name == id.name { - *extra.dependencies = unique_deps(dependencies); - *extra.contract = contract.clone(); - extra.matched = true; - extra.target_id = Some(id.clone()); - } - } - - let tc: ContractBytecode = contract.into(); - highlevel_known_contracts.insert(id, tc.unwrap()); - Ok(()) - }, - project.root(), - )?; - - let target = extra_info - .target_id - .ok_or_else(|| eyre::eyre!("Could not find target contract: {}", target_fname))?; - - let (new_libraries, predeploy_libraries): (Vec<_>, Vec<_>) = - run_dependencies.into_iter().unzip(); - - // Merge with user provided libraries - let mut new_libraries = Libraries::parse(&new_libraries)?; - for (file, libraries) in libraries_addresses.libs.into_iter() { - new_libraries.libs.entry(file).or_default().extend(libraries) - } - - Ok(BuildOutput { - target, - contract, - known_contracts: contracts, - highlevel_known_contracts: ArtifactContracts(highlevel_known_contracts), - predeploy_libraries, - sources: BTreeMap::new(), - project, - libraries: new_libraries, - }) - } - - pub fn get_project_and_output( - &mut self, - script_config: &ScriptConfig, - ) -> Result<(Project, ProjectCompileOutput)> { - let project = script_config.config.project()?; - - let filters = self.opts.skip.clone().unwrap_or_default(); - // We received a valid file path. - // If this file does not exist, `dunce::canonicalize` will - // result in an error and it will be handled below. - if let Ok(target_contract) = dunce::canonicalize(&self.path) { - let output = compile::compile_target_with_filter( - &target_contract, - &project, - self.opts.args.silent, - self.verify, - filters, - )?; - return Ok((project, output)) - } - - if !project.paths.has_input_files() { - eyre::bail!("The project doesn't have any input files. Make sure the `script` directory is configured properly in foundry.toml. Otherwise, provide the path to the file.") - } - - let contract = ContractInfo::from_str(&self.path)?; - self.target_contract = Some(contract.name.clone()); - - // We received `contract_path:contract_name` - if let Some(path) = contract.path { - let path = - dunce::canonicalize(path).wrap_err("Could not canonicalize the target path")?; - let output = compile::compile_target_with_filter( - &path, - &project, - self.opts.args.silent, - self.verify, - filters, - )?; - self.path = path.to_string_lossy().to_string(); - return Ok((project, output)) - } - - // We received `contract_name`, and need to find its file path. - let output = if self.opts.args.silent { - compile::suppress_compile(&project) - } else { - compile::compile(&project, false, false) - }?; - let cache = - SolFilesCache::read_joined(&project.paths).wrap_err("Could not open compiler cache")?; - - let (path, _) = get_cached_entry_by_name(&cache, &contract.name) - .wrap_err("Could not find target contract in cache")?; - self.path = path.to_string_lossy().to_string(); - - Ok((project, output)) - } -} - -/// Resolve the import tree of our target path, and get only the artifacts and -/// sources we need. If it's a standalone script, don't filter anything out. -pub fn filter_sources_and_artifacts( - target: &str, - sources: BTreeMap, - highlevel_known_contracts: ArtifactContracts, - project: Project, -) -> Result<(BTreeMap, HashMap)> { - // Find all imports - let graph = Graph::resolve(&project.paths)?; - let target_path = project.root().join(target); - let mut target_tree = BTreeMap::new(); - let mut is_standalone = false; - - if let Some(target_index) = graph.files().get(&target_path) { - target_tree.extend( - graph - .all_imported_nodes(*target_index) - .map(|index| graph.node(index).unpack()) - .collect::>(), - ); - - // Add our target into the tree as well. - let (target_path, target_source) = graph.node(*target_index).unpack(); - target_tree.insert(target_path, target_source); - } else { - is_standalone = true; - } - - let sources = sources - .into_iter() - .filter_map(|(id, path)| { - let mut resolved = project - .paths - .resolve_library_import(project.root(), &PathBuf::from(&path)) - .unwrap_or_else(|| PathBuf::from(&path)); - - if !resolved.is_absolute() { - resolved = project.root().join(&resolved); - } - - if !is_standalone { - target_tree.get(&resolved).map(|source| (id, source.content.as_str().to_string())) - } else { - Some(( - id, - fs::read_to_string(&resolved).unwrap_or_else(|_| { - panic!("Something went wrong reading the source file: {path:?}") - }), - )) - } - }) - .collect(); - - let artifacts = highlevel_known_contracts - .into_iter() - .filter_map(|(id, artifact)| { - if !is_standalone { - target_tree.get(&id.source).map(|_| (id.name, artifact)) - } else { - Some((id.name, artifact)) - } - }) - .collect(); - - Ok((sources, artifacts)) -} - -struct ExtraLinkingInfo<'a> { - no_target_name: bool, - target_fname: String, - contract: &'a mut CompactContractBytecode, - dependencies: &'a mut Vec<(String, ethers::types::Bytes)>, - matched: bool, - target_id: Option, -} - -pub struct BuildOutput { - pub project: Project, - pub target: ArtifactId, - pub contract: CompactContractBytecode, - pub known_contracts: ArtifactContracts, - pub highlevel_known_contracts: ArtifactContracts, - pub libraries: Libraries, - pub predeploy_libraries: Vec, - pub sources: BTreeMap, -} diff --git a/crates/forge/bin/cmd/script/cmd.rs b/crates/forge/bin/cmd/script/cmd.rs deleted file mode 100644 index 70c836877cdcb..0000000000000 --- a/crates/forge/bin/cmd/script/cmd.rs +++ /dev/null @@ -1,357 +0,0 @@ -use super::{multi::MultiChainSequence, sequence::ScriptSequence, verify::VerifyBundle, *}; -use ethers::{ - prelude::{Middleware, Signer}, - types::{transaction::eip2718::TypedTransaction, U256}, -}; -use eyre::Result; -use foundry_cli::utils::LoadConfig; -use foundry_common::{contracts::flatten_contracts, try_get_http_provider}; -use std::sync::Arc; -use tracing::trace; - -/// Helper alias type for the collection of data changed due to the new sender. -type NewSenderChanges = (CallTraceDecoder, Libraries, ArtifactContracts); - -impl ScriptArgs { - /// Executes the script - pub async fn run_script(mut self, breakpoints: Breakpoints) -> Result<()> { - trace!(target: "script", "executing script command"); - - let (config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - let mut script_config = ScriptConfig { - // dapptools compatibility - sender_nonce: U256::one(), - config, - evm_opts, - ..Default::default() - }; - - self.maybe_load_private_key(&mut script_config)?; - - if let Some(ref fork_url) = script_config.evm_opts.fork_url { - // when forking, override the sender's nonce to the onchain value - script_config.sender_nonce = - foundry_utils::next_nonce(script_config.evm_opts.sender, fork_url, None).await? - } else { - // if not forking, then ignore any pre-deployed library addresses - script_config.config.libraries = Default::default(); - } - - let build_output = self.compile(&mut script_config)?; - - let mut verify = VerifyBundle::new( - &build_output.project, - &script_config.config, - flatten_contracts(&build_output.highlevel_known_contracts, false), - self.retry, - self.verifier.clone(), - ); - - let BuildOutput { - project, - contract, - mut highlevel_known_contracts, - predeploy_libraries, - known_contracts: default_known_contracts, - sources, - mut libraries, - .. - } = build_output; - - // Execute once with default sender. - let sender = script_config.evm_opts.sender; - - // We need to execute the script even if just resuming, in case we need to collect private - // keys from the execution. - let mut result = - self.execute(&mut script_config, contract, sender, &predeploy_libraries).await?; - - if self.resume || (self.verify && !self.broadcast) { - return self - .resume_deployment( - script_config, - project, - default_known_contracts, - libraries, - result, - verify, - ) - .await - } - - let known_contracts = flatten_contracts(&highlevel_known_contracts, true); - let mut decoder = self.decode_traces(&script_config, &mut result, &known_contracts)?; - - if self.debug { - return self.run_debugger( - &decoder, - sources, - result, - project, - highlevel_known_contracts, - breakpoints, - ) - } - - if let Some((new_traces, updated_libraries, updated_contracts)) = self - .maybe_prepare_libraries( - &mut script_config, - project, - default_known_contracts, - predeploy_libraries, - &mut result, - ) - .await? - { - decoder = new_traces; - highlevel_known_contracts = updated_contracts; - libraries = updated_libraries; - } - - if self.json { - self.show_json(&script_config, &result)?; - } else { - self.show_traces(&script_config, &decoder, &mut result).await?; - } - - verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); - self.check_contract_sizes(&result, &highlevel_known_contracts)?; - - self.handle_broadcastable_transactions(result, libraries, &decoder, script_config, verify) - .await - } - - // In case there are libraries to be deployed, it makes sure that these are added to the list of - // broadcastable transactions with the appropriate sender. - async fn maybe_prepare_libraries( - &mut self, - script_config: &mut ScriptConfig, - project: Project, - default_known_contracts: ArtifactContracts, - predeploy_libraries: Vec, - result: &mut ScriptResult, - ) -> Result> { - if let Some(new_sender) = self.maybe_new_sender( - &script_config.evm_opts, - result.transactions.as_ref(), - &predeploy_libraries, - )? { - // We have a new sender, so we need to relink all the predeployed libraries. - let (libraries, highlevel_known_contracts) = self - .rerun_with_new_deployer( - project, - script_config, - new_sender, - result, - default_known_contracts, - ) - .await?; - - // redo traces for the new addresses - let new_traces = self.decode_traces( - &*script_config, - result, - &flatten_contracts(&highlevel_known_contracts, true), - )?; - - return Ok(Some((new_traces, libraries, highlevel_known_contracts))) - } - - // Add predeploy libraries to the list of broadcastable transactions. - let mut lib_deploy = self.create_deploy_transactions( - script_config.evm_opts.sender, - script_config.sender_nonce, - &predeploy_libraries, - &script_config.evm_opts.fork_url, - ); - - if let Some(txs) = &mut result.transactions { - for tx in txs.iter() { - lib_deploy.push_back(BroadcastableTransaction { - rpc: tx.rpc.clone(), - transaction: TypedTransaction::Legacy(tx.transaction.clone().into()), - }); - } - *txs = lib_deploy; - } - - Ok(None) - } - - /// Resumes the deployment and/or verification of the script. - async fn resume_deployment( - &mut self, - script_config: ScriptConfig, - project: Project, - default_known_contracts: ArtifactContracts, - libraries: Libraries, - result: ScriptResult, - verify: VerifyBundle, - ) -> Result<()> { - if self.multi { - return self - .multi_chain_deployment( - MultiChainSequence::load( - &script_config.config.broadcast, - &self.sig, - script_config.target_contract(), - )?, - libraries, - &script_config.config, - result.script_wallets, - verify, - ) - .await - } - self.resume_single_deployment( - script_config, - project, - default_known_contracts, - result, - verify, - ) - .await - .map_err(|err| { - eyre::eyre!("{err}\n\nIf you were trying to resume or verify a multi chain deployment, add `--multi` to your command invocation.") - }) - } - - /// Resumes the deployment and/or verification of a single RPC script. - async fn resume_single_deployment( - &mut self, - script_config: ScriptConfig, - project: Project, - default_known_contracts: ArtifactContracts, - result: ScriptResult, - mut verify: VerifyBundle, - ) -> Result<()> { - trace!(target: "script", "resuming single deployment"); - - let fork_url = script_config - .evm_opts - .fork_url - .as_deref() - .ok_or_else(|| eyre::eyre!("Missing `--fork-url` field."))?; - let provider = Arc::new(try_get_http_provider(fork_url)?); - - let chain = provider.get_chainid().await?.as_u64(); - verify.set_chain(&script_config.config, chain.into()); - - let broadcasted = self.broadcast || self.resume; - let mut deployment_sequence = match ScriptSequence::load( - &script_config.config, - &self.sig, - script_config.target_contract(), - chain, - broadcasted, - ) { - Ok(seq) => seq, - // If the script was simulated, but there was no attempt to broadcast yet, - // try to read the script sequence from the `dry-run/` folder - Err(_) if broadcasted => ScriptSequence::load( - &script_config.config, - &self.sig, - script_config.target_contract(), - chain, - false, - )?, - Err(err) => eyre::bail!(err), - }; - - receipts::wait_for_pending(provider, &mut deployment_sequence).await?; - - if self.resume { - self.send_transactions(&mut deployment_sequence, fork_url, &result.script_wallets) - .await?; - } - - if self.verify { - // We might have predeployed libraries from the broadcasting, so we need to - // relink the contracts with them, since their mapping is - // not included in the solc cache files. - let BuildOutput { highlevel_known_contracts, .. } = self.link( - project, - default_known_contracts, - Libraries::parse(&deployment_sequence.libraries)?, - script_config.config.sender, // irrelevant, since we're not creating any - U256::zero(), // irrelevant, since we're not creating any - )?; - - verify.known_contracts = flatten_contracts(&highlevel_known_contracts, false); - - deployment_sequence.verify_contracts(&script_config.config, verify).await?; - } - - Ok(()) - } - - /// Reruns the execution with a new sender and relinks the libraries accordingly - async fn rerun_with_new_deployer( - &mut self, - project: Project, - script_config: &mut ScriptConfig, - new_sender: Address, - first_run_result: &mut ScriptResult, - default_known_contracts: ArtifactContracts, - ) -> Result<(Libraries, ArtifactContracts)> { - // if we had a new sender that requires relinking, we need to - // get the nonce mainnet for accurate addresses for predeploy libs - let nonce = foundry_utils::next_nonce( - new_sender, - script_config.evm_opts.fork_url.as_ref().ok_or_else(|| { - eyre::eyre!("You must provide an RPC URL (see --fork-url) when broadcasting.") - })?, - None, - ) - .await?; - script_config.sender_nonce = nonce; - - let BuildOutput { - libraries, contract, highlevel_known_contracts, predeploy_libraries, .. - } = self.link( - project, - default_known_contracts, - script_config.config.parsed_libraries()?, - new_sender, - nonce, - )?; - - let mut txs = self.create_deploy_transactions( - new_sender, - nonce, - &predeploy_libraries, - &script_config.evm_opts.fork_url, - ); - - let result = - self.execute(script_config, contract, new_sender, &predeploy_libraries).await?; - - if let Some(new_txs) = &result.transactions { - for new_tx in new_txs.iter() { - txs.push_back(BroadcastableTransaction { - rpc: new_tx.rpc.clone(), - transaction: TypedTransaction::Legacy(new_tx.transaction.clone().into()), - }); - } - } - - *first_run_result = result; - first_run_result.transactions = Some(txs); - - Ok((libraries, highlevel_known_contracts)) - } - - /// In case the user has loaded *only* one private-key, we can assume that he's using it as the - /// `--sender` - fn maybe_load_private_key(&mut self, script_config: &mut ScriptConfig) -> Result<()> { - if let Some(ref private_key) = self.wallets.private_key { - self.wallets.private_keys = Some(vec![private_key.clone()]); - } - if let Some(wallets) = self.wallets.private_keys()? { - if wallets.len() == 1 { - script_config.evm_opts.sender = wallets.get(0).unwrap().address() - } - } - Ok(()) - } -} diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs deleted file mode 100644 index faeb3147d3ead..0000000000000 --- a/crates/forge/bin/cmd/script/executor.rs +++ /dev/null @@ -1,320 +0,0 @@ -use super::{ - artifacts::ArtifactInfo, - runner::SimulationStage, - transaction::{AdditionalContract, TransactionWithMetadata}, - *, -}; -use ethers::{ - solc::artifacts::CompactContractBytecode, - types::{transaction::eip2718::TypedTransaction, Address, U256}, -}; -use eyre::Result; -use forge::{ - executor::{ - inspector::{cheatcodes::util::BroadcastableTransactions, CheatsConfig}, - Backend, ExecutorBuilder, - }, - revm::primitives::U256 as rU256, - trace::{CallTraceDecoder, Traces}, - CallKind, -}; -use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; -use foundry_common::{shell, RpcUrl}; -use foundry_evm::utils::evm_spec; -use futures::future::join_all; -use parking_lot::RwLock; -use std::{collections::VecDeque, sync::Arc}; -use tracing::trace; - -/// Helper alias type for the processed result of a runner onchain simulation. -type RunnerResult = (Option, Traces); - -impl ScriptArgs { - /// Locally deploys and executes the contract method that will collect all broadcastable - /// transactions. - pub async fn execute( - &self, - script_config: &mut ScriptConfig, - contract: CompactContractBytecode, - sender: Address, - predeploy_libraries: &[ethers::types::Bytes], - ) -> Result { - trace!(target: "script", "start executing script"); - - let CompactContractBytecode { abi, bytecode, .. } = contract; - - let abi = abi.expect("no ABI for contract"); - let bytecode = bytecode.expect("no bytecode for contract").object.into_bytes().unwrap(); - - ensure_clean_constructor(&abi)?; - - let mut runner = self.prepare_runner(script_config, sender, SimulationStage::Local).await; - let (address, mut result) = runner.setup( - predeploy_libraries, - bytecode, - needs_setup(&abi), - script_config.sender_nonce, - self.broadcast, - script_config.evm_opts.fork_url.is_none(), - )?; - - let (func, calldata) = self.get_method_and_calldata(&abi)?; - script_config.called_function = Some(func); - - // Only call the method if `setUp()` succeeded. - if result.success { - let script_result = runner.script(address, calldata)?; - - result.success &= script_result.success; - result.gas_used = script_result.gas_used; - result.logs.extend(script_result.logs); - result.traces.extend(script_result.traces); - result.debug = script_result.debug; - result.labeled_addresses.extend(script_result.labeled_addresses); - result.returned = script_result.returned; - result.script_wallets.extend(script_result.script_wallets); - - match (&mut result.transactions, script_result.transactions) { - (Some(txs), Some(new_txs)) => { - txs.extend(new_txs); - } - (None, Some(new_txs)) => { - result.transactions = Some(new_txs); - } - _ => {} - } - } - - Ok(result) - } - - /// Simulates onchain state by executing a list of transactions locally and persisting their - /// state. Returns the transactions and any CREATE2 contract address created. - pub async fn onchain_simulation( - &self, - transactions: BroadcastableTransactions, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - contracts: &ContractsByArtifact, - ) -> Result> { - trace!(target: "script", "executing onchain simulation"); - - let runners = Arc::new( - self.build_runners(script_config) - .await - .into_iter() - .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner)))) - .collect::>(), - ); - - if script_config.evm_opts.verbosity > 3 { - println!("=========================="); - println!("Simulated On-chain Traces:\n"); - } - - let address_to_abi: BTreeMap = decoder - .contracts - .iter() - .filter_map(|(addr, contract_id)| { - let contract_name = get_contract_name(contract_id); - if let Ok(Some((_, (abi, code)))) = - contracts.find_by_name_or_identifier(contract_name) - { - let info = ArtifactInfo { - contract_name: contract_name.to_string(), - contract_id: contract_id.to_string(), - abi, - code, - }; - return Some((*addr, info)) - } - None - }) - .collect(); - - let mut final_txs = VecDeque::new(); - - // Executes all transactions from the different forks concurrently. - let futs = transactions - .into_iter() - .map(|transaction| async { - let mut runner = runners - .get(transaction.rpc.as_ref().expect("to have been filled already.")) - .expect("to have been built.") - .write(); - - if let TypedTransaction::Legacy(mut tx) = transaction.transaction { - let result = runner - .simulate( - tx.from.expect( - "Transaction doesn't have a `from` address at execution time", - ), - tx.to.clone(), - tx.data.clone(), - tx.value, - ) - .expect("Internal EVM error"); - - if !result.success || result.traces.is_empty() { - return Ok((None, result.traces)) - } - - let created_contracts = result - .traces - .iter() - .flat_map(|(_, traces)| { - traces.arena.iter().filter_map(|node| { - if matches!(node.kind(), CallKind::Create | CallKind::Create2) { - return Some(AdditionalContract { - opcode: node.kind(), - address: node.trace.address, - init_code: node.trace.data.to_raw(), - }) - } - None - }) - }) - .collect(); - - // Simulate mining the transaction if the user passes `--slow`. - if self.slow { - runner.executor.env_mut().block.number += rU256::from(1); - } - - let is_fixed_gas_limit = tx.gas.is_some(); - // If tx.gas is already set that means it was specified in script - if !is_fixed_gas_limit { - // We inflate the gas used by the user specified percentage - tx.gas = - Some(U256::from(result.gas_used * self.gas_estimate_multiplier / 100)); - } else { - println!("Gas limit was set in script to {:}", tx.gas.unwrap()); - } - - let tx = TransactionWithMetadata::new( - tx.into(), - transaction.rpc, - &result, - &address_to_abi, - decoder, - created_contracts, - is_fixed_gas_limit, - )?; - - Ok((Some(tx), result.traces)) - } else { - unreachable!() - } - }) - .collect::>(); - - let mut abort = false; - for res in join_all(futs).await { - // type hint - let res: Result = res; - - let (tx, mut traces) = res?; - - // Transaction will be `None`, if execution didn't pass. - if tx.is_none() || script_config.evm_opts.verbosity > 3 { - // Identify all contracts created during the call. - if traces.is_empty() { - eyre::bail!( - "Forge script requires tracing enabled to collect created contracts." - ) - } - - for (_kind, trace) in &mut traces { - decoder.decode(trace).await; - println!("{trace}"); - } - } - - if let Some(tx) = tx { - final_txs.push_back(tx); - } else { - abort = true; - } - } - - if abort { - eyre::bail!("Simulated execution failed.") - } - - Ok(final_txs) - } - - /// Build the multiple runners from different forks. - async fn build_runners(&self, script_config: &ScriptConfig) -> HashMap { - let sender = script_config.evm_opts.sender; - - if !shell::verbosity().is_silent() { - eprintln!("\n## Setting up ({}) EVMs.", script_config.total_rpcs.len()); - } - - let futs = script_config - .total_rpcs - .iter() - .map(|rpc| async { - let mut script_config = script_config.clone(); - script_config.evm_opts.fork_url = Some(rpc.clone()); - - ( - rpc.clone(), - self.prepare_runner(&mut script_config, sender, SimulationStage::OnChain).await, - ) - }) - .collect::>(); - - join_all(futs).await.into_iter().collect() - } - - /// Creates the Runner that drives script execution - async fn prepare_runner( - &self, - script_config: &mut ScriptConfig, - sender: Address, - stage: SimulationStage, - ) -> ScriptRunner { - trace!("preparing script runner"); - let env = - script_config.evm_opts.evm_env().await.expect("Could not instantiate fork environment"); - - // The db backend that serves all the data. - let db = match &script_config.evm_opts.fork_url { - Some(url) => match script_config.backends.get(url) { - Some(db) => db.clone(), - None => { - let backend = Backend::spawn( - script_config.evm_opts.get_fork(&script_config.config, env.clone()), - ) - .await; - script_config.backends.insert(url.clone(), backend); - script_config.backends.get(url).unwrap().clone() - } - }, - None => { - // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is - // no need to cache it, since there won't be any onchain simulation that we'd need - // to cache the backend for. - Backend::spawn(script_config.evm_opts.get_fork(&script_config.config, env.clone())) - .await - } - }; - - let mut builder = ExecutorBuilder::default() - .with_config(env) - .with_spec(evm_spec(&script_config.config.evm_version)) - .with_gas_limit(script_config.evm_opts.gas_limit()) - // We need it enabled to decode contract names: local or external. - .set_tracing(true); - - if let SimulationStage::Local = stage { - builder = builder - .set_debugger(self.debug) - .with_cheatcodes(CheatsConfig::new(&script_config.config, &script_config.evm_opts)); - } - - ScriptRunner::new(builder.build(db), script_config.evm_opts.initial_balance, sender) - } -} diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/forge/bin/cmd/script/mod.rs deleted file mode 100644 index 1825644c31295..0000000000000 --- a/crates/forge/bin/cmd/script/mod.rs +++ /dev/null @@ -1,1030 +0,0 @@ -use self::{ - build::{filter_sources_and_artifacts, BuildOutput}, - runner::ScriptRunner, -}; -use super::{build::BuildArgs, retry::RetryArgs}; -use clap::{Parser, ValueHint}; -use dialoguer::Confirm; -use ethers::{ - abi::{Abi, Function, HumanReadableParser}, - prelude::{ - artifacts::{ContractBytecodeSome, Libraries}, - ArtifactId, Bytes, Project, - }, - providers::{Http, Middleware}, - signers::LocalWallet, - solc::contracts::ArtifactContracts, - types::{ - transaction::eip2718::TypedTransaction, Address, Chain, Log, NameOrAddress, - TransactionRequest, U256, - }, -}; -use eyre::{ContextCompat, Result, WrapErr}; -use forge::{ - debug::DebugArena, - decode::decode_console_logs, - executor::{opts::EvmOpts, Backend}, - trace::{ - identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, - CallTraceDecoder, CallTraceDecoderBuilder, RawOrDecodedCall, RawOrDecodedReturnData, - TraceKind, Traces, - }, - CallKind, -}; -use foundry_cli::{opts::MultiWallet, utils::parse_ether_value}; -use foundry_common::{ - abi::{encode_args, format_token}, - contracts::get_contract_name, - errors::UnlinkedByteCode, - evm::{Breakpoints, EvmArgs}, - shell, ContractsByArtifact, RpcUrl, CONTRACT_MAX_SIZE, SELECTOR_LEN, -}; -use foundry_config::{ - figment, - figment::{ - value::{Dict, Map}, - Metadata, Profile, Provider, - }, - Config, -}; -use foundry_evm::{ - decode, - executor::inspector::{ - cheatcodes::{util::BroadcastableTransactions, BroadcastableTransaction}, - DEFAULT_CREATE2_DEPLOYER, - }, -}; -use futures::future; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashMap, HashSet, VecDeque}, - path::PathBuf, -}; -use tracing::log::trace; -use ui::{TUIExitReason, Tui, Ui}; -use yansi::Paint; - -mod artifacts; -mod broadcast; -mod build; -mod cmd; -mod executor; -mod multi; -mod providers; -mod receipts; -mod runner; -mod sequence; -pub mod transaction; -mod verify; - -pub use transaction::TransactionWithMetadata; - -/// List of Chains that support Shanghai. -static SHANGHAI_ENABLED_CHAINS: &[Chain] = &[ - // Ethereum Mainnet - Chain::Mainnet, - // Goerli - Chain::Goerli, - // Sepolia - Chain::Sepolia, -]; - -// Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(ScriptArgs, opts, evm_opts); - -/// CLI arguments for `forge script`. -#[derive(Debug, Clone, Parser, Default)] -pub struct ScriptArgs { - /// The contract you want to run. Either the file path or contract name. - /// - /// If multiple contracts exist in the same file you must specify the target contract with - /// --target-contract. - #[clap(value_hint = ValueHint::FilePath)] - pub path: String, - - /// Arguments to pass to the script function. - pub args: Vec, - - /// The name of the contract you want to run. - #[clap(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] - pub target_contract: Option, - - /// The signature of the function you want to call in the contract, or raw calldata. - #[clap( - long, - short, - default_value = "run()", - value_parser = foundry_common::clap_helpers::strip_0x_prefix - )] - pub sig: String, - - /// Max priority fee per gas for EIP1559 transactions. - #[clap( - long, - env = "ETH_PRIORITY_GAS_PRICE", - value_parser = parse_ether_value, - value_name = "PRICE" - )] - pub priority_gas_price: Option, - - /// Use legacy transactions instead of EIP1559 ones. - /// - /// This is auto-enabled for common networks without EIP1559. - #[clap(long)] - pub legacy: bool, - - /// Broadcasts the transactions. - #[clap(long)] - pub broadcast: bool, - - /// Skips on-chain simulation. - #[clap(long)] - pub skip_simulation: bool, - - /// Relative percentage to multiply gas estimates by. - #[clap(long, short, default_value = "130")] - pub gas_estimate_multiplier: u64, - - /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender - #[clap( - long, - requires = "sender", - conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], - )] - pub unlocked: bool, - - /// Resumes submitting transactions that failed or timed-out previously. - /// - /// It DOES NOT simulate the script again and it expects nonces to have remained the same. - /// - /// Example: If transaction N has a nonce of 22, then the account should have a nonce of 22, - /// otherwise it fails. - #[clap(long)] - pub resume: bool, - - /// If present, --resume or --verify will be assumed to be a multi chain deployment. - #[clap(long)] - pub multi: bool, - - /// Open the script in the debugger. - /// - /// Takes precedence over broadcast. - #[clap(long)] - pub debug: bool, - - /// Makes sure a transaction is sent, - /// only after its previous one has been confirmed and succeeded. - #[clap(long)] - pub slow: bool, - - /// Disables interactive prompts that might appear when deploying big contracts. - /// - /// For more info on the contract size limit, see EIP-170: - #[clap(long)] - pub non_interactive: bool, - - /// The Etherscan (or equivalent) API key - #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] - pub etherscan_api_key: Option, - - /// Verifies all the contracts found in the receipts of a script, if any. - #[clap(long)] - pub verify: bool, - - /// Output results in JSON format. - #[clap(long)] - pub json: bool, - - /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[clap( - long, - env = "ETH_GAS_PRICE", - value_parser = parse_ether_value, - value_name = "PRICE", - )] - pub with_gas_price: Option, - - #[clap(flatten)] - pub opts: BuildArgs, - - #[clap(flatten)] - pub wallets: MultiWallet, - - #[clap(flatten)] - pub evm_opts: EvmArgs, - - #[clap(flatten)] - pub verifier: super::verify::VerifierArgs, - - #[clap(flatten)] - pub retry: RetryArgs, -} - -// === impl ScriptArgs === - -impl ScriptArgs { - pub fn decode_traces( - &self, - script_config: &ScriptConfig, - result: &mut ScriptResult, - known_contracts: &ContractsByArtifact, - ) -> Result { - let verbosity = script_config.evm_opts.verbosity; - let mut etherscan_identifier = EtherscanIdentifier::new( - &script_config.config, - script_config.evm_opts.get_remote_chain_id(), - )?; - - let mut local_identifier = LocalTraceIdentifier::new(known_contracts); - let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) - .with_verbosity(verbosity) - .build(); - - decoder.add_signature_identifier(SignaturesIdentifier::new( - Config::foundry_cache_dir(), - script_config.config.offline, - )?); - - // Decoding traces using etherscan is costly as we run into rate limits, - // causing scripts to run for a very long time unnecesarily. - // Therefore, we only try and use etherscan if the user has provided an API key. - let should_use_etherscan_traces = script_config.config.etherscan_api_key.is_some(); - - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut local_identifier); - if should_use_etherscan_traces { - decoder.identify(trace, &mut etherscan_identifier); - } - } - Ok(decoder) - } - - pub fn get_returns( - &self, - script_config: &ScriptConfig, - returned: &bytes::Bytes, - ) -> Result> { - let func = script_config.called_function.as_ref().expect("There should be a function."); - let mut returns = HashMap::new(); - - match func.decode_output(returned) { - Ok(decoded) => { - for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { - let internal_type = output.internal_type.as_deref().unwrap_or("unknown"); - - let label = if !output.name.is_empty() { - output.name.to_string() - } else { - index.to_string() - }; - - returns.insert( - label, - NestedValue { - internal_type: internal_type.to_string(), - value: format_token(token), - }, - ); - } - } - Err(_) => { - shell::println(format!("{:x?}", (&returned)))?; - } - } - - Ok(returns) - } - - pub async fn show_traces( - &self, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - result: &mut ScriptResult, - ) -> Result<()> { - let verbosity = script_config.evm_opts.verbosity; - let func = script_config.called_function.as_ref().expect("There should be a function."); - - if !result.success || verbosity > 3 { - if result.traces.is_empty() { - eyre::bail!("Unexpected error: No traces despite verbosity level. Please report this as a bug: https://github.com/foundry-rs/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); - } - - shell::println("Traces:")?; - for (kind, trace) in &mut result.traces { - let should_include = match kind { - TraceKind::Setup => verbosity >= 5, - TraceKind::Execution => verbosity > 3, - _ => false, - } || !result.success; - - if should_include { - decoder.decode(trace).await; - shell::println(format!("{trace}"))?; - } - } - shell::println(String::new())?; - } - - if result.success { - shell::println(format!("{}", Paint::green("Script ran successfully.")))?; - } - - if script_config.evm_opts.fork_url.is_none() { - shell::println(format!("Gas used: {}", result.gas_used))?; - } - - if result.success && !result.returned.is_empty() { - shell::println("\n== Return ==")?; - match func.decode_output(&result.returned) { - Ok(decoded) => { - for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { - let internal_type = output.internal_type.as_deref().unwrap_or("unknown"); - - let label = if !output.name.is_empty() { - output.name.to_string() - } else { - index.to_string() - }; - shell::println(format!( - "{}: {internal_type} {}", - label.trim_end(), - format_token(token) - ))?; - } - } - Err(_) => { - shell::println(format!("{:x?}", (&result.returned)))?; - } - } - } - - let console_logs = decode_console_logs(&result.logs); - if !console_logs.is_empty() { - shell::println("\n== Logs ==")?; - for log in console_logs { - shell::println(format!(" {log}"))?; - } - } - - if !result.success { - let revert_msg = decode::decode_revert(&result.returned[..], None, None) - .map(|err| format!("{err}\n")) - .unwrap_or_else(|_| "Script failed.\n".to_string()); - - eyre::bail!("{}", Paint::red(revert_msg)); - } - - Ok(()) - } - - pub fn show_json(&self, script_config: &ScriptConfig, result: &ScriptResult) -> Result<()> { - let returns = self.get_returns(script_config, &result.returned)?; - - let console_logs = decode_console_logs(&result.logs); - let output = JsonResult { logs: console_logs, gas_used: result.gas_used, returns }; - let j = serde_json::to_string(&output)?; - shell::println(j)?; - - Ok(()) - } - - /// It finds the deployer from the running script and uses it to predeploy libraries. - /// - /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy - /// them instead. - fn maybe_new_sender( - &self, - evm_opts: &EvmOpts, - transactions: Option<&BroadcastableTransactions>, - predeploy_libraries: &[Bytes], - ) -> Result> { - let mut new_sender = None; - - if let Some(txs) = transactions { - // If the user passed a `--sender` don't check anything. - if !predeploy_libraries.is_empty() && self.evm_opts.sender.is_none() { - for tx in txs.iter() { - match &tx.transaction { - TypedTransaction::Legacy(tx) => { - if tx.to.is_none() { - let sender = tx.from.expect("no sender"); - if let Some(ns) = new_sender { - if sender != ns { - shell::println("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; - return Ok(None) - } - } else if sender != evm_opts.sender { - new_sender = Some(sender); - } - } - } - _ => unreachable!(), - } - } - } - } - Ok(new_sender) - } - - /// Helper for building the transactions for any libraries that need to be deployed ahead of - /// linking - fn create_deploy_transactions( - &self, - from: Address, - nonce: U256, - data: &[Bytes], - fork_url: &Option, - ) -> BroadcastableTransactions { - data.iter() - .enumerate() - .map(|(i, bytes)| BroadcastableTransaction { - rpc: fork_url.clone(), - transaction: TypedTransaction::Legacy(TransactionRequest { - from: Some(from), - data: Some(bytes.clone()), - nonce: Some(nonce + i), - ..Default::default() - }), - }) - .collect() - } - - fn run_debugger( - &self, - decoder: &CallTraceDecoder, - sources: BTreeMap, - result: ScriptResult, - project: Project, - highlevel_known_contracts: ArtifactContracts, - breakpoints: Breakpoints, - ) -> Result<()> { - trace!(target: "script", "debugging script"); - - let (sources, artifacts) = filter_sources_and_artifacts( - &self.path, - sources, - highlevel_known_contracts.clone(), - project, - )?; - let flattened = result - .debug - .and_then(|arena| arena.last().map(|arena| arena.flatten(0))) - .expect("We should have collected debug information"); - let identified_contracts = decoder - .contracts - .iter() - .map(|(addr, identifier)| (*addr, get_contract_name(identifier).to_string())) - .collect(); - - let tui = Tui::new( - flattened, - 0, - identified_contracts, - artifacts, - highlevel_known_contracts - .into_iter() - .map(|(id, _)| (id.name, sources.clone())) - .collect(), - breakpoints, - )?; - match tui.start().expect("Failed to start tui") { - TUIExitReason::CharExit => Ok(()), - } - } - - /// Returns the Function and calldata based on the signature - /// - /// If the `sig` is a valid human-readable function we find the corresponding function in the - /// `abi` If the `sig` is valid hex, we assume it's calldata and try to find the - /// corresponding function by matching the selector, first 4 bytes in the calldata. - /// - /// Note: We assume that the `sig` is already stripped of its prefix, See [`ScriptArgs`] - pub fn get_method_and_calldata(&self, abi: &Abi) -> Result<(Function, Bytes)> { - let (func, data) = if let Ok(func) = HumanReadableParser::parse_function(&self.sig) { - ( - abi.functions() - .find(|&abi_func| abi_func.short_signature() == func.short_signature()) - .wrap_err(format!( - "Function `{}` is not implemented in your script.", - self.sig - ))?, - encode_args(&func, &self.args)?.into(), - ) - } else { - let decoded = hex::decode(&self.sig).wrap_err("Invalid hex calldata")?; - let selector = &decoded[..SELECTOR_LEN]; - ( - abi.functions().find(|&func| selector == &func.short_signature()[..]).ok_or_else( - || { - eyre::eyre!( - "Function selector `{}` not found in the ABI", - hex::encode(selector) - ) - }, - )?, - decoded.into(), - ) - }; - - Ok((func.clone(), data)) - } - - /// Checks if the transaction is a deployment with either a size above the `CONTRACT_MAX_SIZE` - /// or specified `code_size_limit`. - /// - /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns - /// the user. - fn check_contract_sizes( - &self, - result: &ScriptResult, - known_contracts: &BTreeMap, - ) -> Result<()> { - // (name, &init, &deployed)[] - let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; - - // From artifacts - for (artifact, bytecode) in known_contracts.iter() { - if bytecode.bytecode.object.is_unlinked() { - return Err(UnlinkedByteCode::Bytecode(artifact.identifier()).into()) - } - let init_code = bytecode.bytecode.object.as_bytes().unwrap(); - // Ignore abstract contracts - if let Some(ref deployed_code) = bytecode.deployed_bytecode.bytecode { - if deployed_code.object.is_unlinked() { - return Err(UnlinkedByteCode::DeployedBytecode(artifact.identifier()).into()) - } - let deployed_code = deployed_code.object.as_bytes().unwrap(); - bytecodes.push((artifact.name.clone(), init_code, deployed_code)); - } - } - - // From traces - let create_nodes = result.traces.iter().flat_map(|(_, traces)| { - traces - .arena - .iter() - .filter(|node| matches!(node.kind(), CallKind::Create | CallKind::Create2)) - }); - let mut unknown_c = 0usize; - for node in create_nodes { - // Calldata == init code - if let RawOrDecodedCall::Raw(ref init_code) = node.trace.data { - // Output is the runtime code - if let RawOrDecodedReturnData::Raw(ref deployed_code) = node.trace.output { - // Only push if it was not present already - if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) { - bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); - unknown_c += 1; - } - continue - } - } - // Both should be raw and not decoded since it's just bytecode - eyre::bail!("Create node returned decoded data: {:?}", node); - } - - let mut prompt_user = false; - let max_size = match self.evm_opts.env.code_size_limit { - Some(size) => size, - None => CONTRACT_MAX_SIZE, - }; - - for (data, to) in result.transactions.iter().flat_map(|txes| { - txes.iter().filter_map(|tx| { - tx.transaction - .data() - .filter(|data| data.len() > max_size) - .map(|data| (data, tx.transaction.to())) - }) - }) { - let mut offset = 0; - - // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. - if let Some(NameOrAddress::Address(to)) = to { - if *to == DEFAULT_CREATE2_DEPLOYER { - // Size of the salt prefix. - offset = 32; - } - } else if to.is_some() { - continue - } - - // Find artifact with a deployment code same as the data. - if let Some((name, _, deployed_code)) = - bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..]) - { - let deployment_size = deployed_code.len(); - - if deployment_size > max_size { - prompt_user = self.broadcast; - shell::println(format!( - "{}", - Paint::red(format!( - "`{name}` is above the contract size limit ({deployment_size} > {max_size})." - )) - ))?; - } - } - } - - // Only prompt if we're broadcasting and we've not disabled interactivity. - if prompt_user && - !self.non_interactive && - !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? - { - eyre::bail!("User canceled the script."); - } - - Ok(()) - } -} - -impl Provider for ScriptArgs { - fn metadata(&self) -> Metadata { - Metadata::named("Script Args Provider") - } - - fn data(&self) -> Result, figment::Error> { - let mut dict = Dict::default(); - if let Some(ref etherscan_api_key) = self.etherscan_api_key { - dict.insert( - "etherscan_api_key".to_string(), - figment::value::Value::from(etherscan_api_key.to_string()), - ); - } - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -pub struct ScriptResult { - pub success: bool, - pub logs: Vec, - pub traces: Traces, - pub debug: Option>, - pub gas_used: u64, - pub labeled_addresses: BTreeMap, - pub transactions: Option, - pub returned: bytes::Bytes, - pub address: Option
, - pub script_wallets: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct JsonResult { - pub logs: Vec, - pub gas_used: u64, - pub returns: HashMap, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct NestedValue { - pub internal_type: String, - pub value: String, -} - -#[derive(Clone, Debug, Default)] -pub struct ScriptConfig { - pub config: Config, - pub evm_opts: EvmOpts, - pub sender_nonce: U256, - /// Maps a rpc url to a backend - pub backends: HashMap, - /// Script target contract - pub target_contract: Option, - /// Function called by the script - pub called_function: Option, - /// Unique list of rpc urls present - pub total_rpcs: HashSet, - /// If true, one of the transactions did not have a rpc - pub missing_rpc: bool, -} - -impl ScriptConfig { - fn collect_rpcs(&mut self, txs: &BroadcastableTransactions) { - self.missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); - - self.total_rpcs - .extend(txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>()); - - if let Some(rpc) = &self.evm_opts.fork_url { - self.total_rpcs.insert(rpc.clone()); - } - } - - fn has_multiple_rpcs(&self) -> bool { - self.total_rpcs.len() > 1 - } - - /// Certain features are disabled for multi chain deployments, and if tried, will return - /// error. [library support] - fn check_multi_chain_constraints(&self, libraries: &Libraries) -> Result<()> { - if self.has_multiple_rpcs() || (self.missing_rpc && !self.total_rpcs.is_empty()) { - shell::eprintln(format!( - "{}", - Paint::yellow( - "Multi chain deployment is still under development. Use with caution." - ) - ))?; - if !libraries.libs.is_empty() { - eyre::bail!( - "Multi chain deployment does not support library linking at the moment." - ) - } - } - Ok(()) - } - - /// Returns the script target contract - fn target_contract(&self) -> &ArtifactId { - self.target_contract.as_ref().expect("should exist after building") - } - - /// Checks if the RPCs used point to chains that support EIP-3855. - /// If not, warns the user. - async fn check_shanghai_support(&self) -> Result<()> { - let chain_ids = self.total_rpcs.iter().map(|rpc| async move { - if let Ok(provider) = ethers::providers::Provider::::try_from(rpc) { - match provider.get_chainid().await { - Ok(chain_id) => match TryInto::::try_into(chain_id) { - Ok(chain) => return Some((SHANGHAI_ENABLED_CHAINS.contains(&chain), chain)), - Err(_) => return None, - }, - Err(_) => return None, - } - } - None - }); - - let chain_ids: Vec<_> = future::join_all(chain_ids).await.into_iter().flatten().collect(); - - let chain_id_unsupported = chain_ids.iter().any(|(supported, _)| !supported); - - // At least one chain ID is unsupported, therefore we print the message. - if chain_id_unsupported { - let msg = format!( - r#" -EIP-3855 is not supported in one or more of the RPCs used. -Unsupported Chain IDs: {}. -Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly. -For more information, please see https://eips.ethereum.org/EIPS/eip-3855"#, - chain_ids - .iter() - .filter(|(supported, _)| !supported) - .map(|(_, chain)| format!("{}", *chain as u64)) - .collect::>() - .join(", ") - ); - shell::println(Paint::yellow(msg))?; - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use foundry_cli::utils::LoadConfig; - use foundry_config::UnresolvedEnvVarError; - use foundry_test_utils::tempfile::tempdir; - use std::fs; - - #[test] - fn can_parse_sig() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--sig", - "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266", - ]); - assert_eq!( - args.sig, - "522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266" - ); - } - - #[test] - fn can_parse_unlocked() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--sender", - "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "--unlocked", - ]); - assert!(args.unlocked); - - let key = U256::zero(); - let args = ScriptArgs::try_parse_from([ - "foundry-cli", - "Contract.sol", - "--sender", - "0x4e59b44847b379578588920ca78fbf26c0b4956c", - "--unlocked", - "--private-key", - key.to_string().as_str(), - ]); - assert!(args.is_err()); - } - - #[test] - fn can_merge_script_config() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--etherscan-api-key", - "goerli", - ]); - let config = args.load_config(); - assert_eq!(config.etherscan_api_key, Some("goerli".to_string())); - } - - #[test] - fn can_parse_verifier_url() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "script", - "script/Test.s.sol:TestScript", - "--fork-url", - "http://localhost:8545", - "--verifier-url", - "http://localhost:3000/api/verify", - "--etherscan-api-key", - "blacksmith", - "--broadcast", - "--verify", - "-vvvv", - ]); - assert_eq!( - args.verifier.verifier_url, - Some("http://localhost:3000/api/verify".to_string()) - ); - } - - #[test] - fn can_extract_code_size_limit() { - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "script", - "script/Test.s.sol:TestScript", - "--fork-url", - "http://localhost:8545", - "--broadcast", - "--code-size-limit", - "50000", - ]); - assert_eq!(args.evm_opts.env.code_size_limit, Some(50000)); - } - - #[test] - fn can_extract_script_etherscan_key() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - etherscan_api_key = "mumbai" - - [etherscan] - mumbai = { key = "https://etherscan-mumbai.com/" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "Contract.sol", - "--etherscan-api-key", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - - let config = args.load_config(); - let mumbai = config.get_etherscan_api_key(Some(ethers::types::Chain::PolygonMumbai)); - assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); - } - - #[test] - fn can_extract_script_rpc_alias() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [rpc_endpoints] - polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}" - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "DeployV1", - "--rpc-url", - "polygonMumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - - let err = args.load_config_and_evm_opts().unwrap_err(); - - assert!(err.downcast::().is_ok()); - - std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456"); - let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string())); - assert_eq!( - evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) - ); - } - - #[test] - fn can_extract_script_rpc_and_etherscan_alias() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}" - - [etherscan] - mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "DeployV1", - "--rpc-url", - "mumbai", - "--etherscan-api-key", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - let err = args.load_config_and_evm_opts().unwrap_err(); - - assert!(err.downcast::().is_ok()); - - std::env::set_var("_EXTRACT_RPC_ALIAS", "123456"); - std::env::set_var("_POLYSCAN_API_KEY", "polygonkey"); - let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!(config.eth_rpc_url, Some("mumbai".to_string())); - assert_eq!( - evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) - ); - let etherscan = config.get_etherscan_api_key(Some(80001u64)); - assert_eq!(etherscan, Some("polygonkey".to_string())); - let etherscan = config.get_etherscan_api_key(Option::::None); - assert_eq!(etherscan, Some("polygonkey".to_string())); - } - - #[test] - fn can_extract_script_rpc_and_sole_etherscan_alias() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [rpc_endpoints] - mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}" - - [etherscan] - mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - let args: ScriptArgs = ScriptArgs::parse_from([ - "foundry-cli", - "DeployV1", - "--rpc-url", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - let err = args.load_config_and_evm_opts().unwrap_err(); - - assert!(err.downcast::().is_ok()); - - std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456"); - std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey"); - let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); - assert_eq!( - evm_opts.fork_url, - Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) - ); - let etherscan = config.get_etherscan_api_key(Some(80001u64)); - assert_eq!(etherscan, Some("polygonkey".to_string())); - let etherscan = config.get_etherscan_api_key(Option::::None); - assert_eq!(etherscan, Some("polygonkey".to_string())); - } -} diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs deleted file mode 100644 index 04862b8389481..0000000000000 --- a/crates/forge/bin/cmd/script/multi.rs +++ /dev/null @@ -1,182 +0,0 @@ -use super::{ - receipts, - sequence::{ScriptSequence, DRY_RUN_DIR}, - verify::VerifyBundle, - ScriptArgs, -}; -use ethers::{ - prelude::{artifacts::Libraries, ArtifactId}, - signers::LocalWallet, -}; -use eyre::{ContextCompat, Result, WrapErr}; -use foundry_cli::utils::now; -use foundry_common::{fs, get_http_provider}; -use foundry_config::Config; -use futures::future::join_all; -use serde::{Deserialize, Serialize}; -use std::{ - io::{BufWriter, Write}, - path::{Path, PathBuf}, - sync::Arc, -}; -use tracing::log::trace; - -/// Holds the sequences of multiple chain deployments. -#[derive(Deserialize, Serialize, Clone, Default)] -pub struct MultiChainSequence { - pub deployments: Vec, - pub path: PathBuf, - pub timestamp: u64, -} - -impl Drop for MultiChainSequence { - fn drop(&mut self) { - self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); - self.save().expect("could not save multi deployment sequence"); - } -} - -impl MultiChainSequence { - pub fn new( - deployments: Vec, - sig: &str, - target: &ArtifactId, - log_folder: &Path, - broadcasted: bool, - ) -> Result { - let path = - MultiChainSequence::get_path(&log_folder.join("multi"), sig, target, broadcasted)?; - - Ok(MultiChainSequence { deployments, path, timestamp: now().as_secs() }) - } - - /// Saves to ./broadcast/multi/contract_filename[-timestamp]/sig.json - pub fn get_path( - out: &Path, - sig: &str, - target: &ArtifactId, - broadcasted: bool, - ) -> Result { - let mut out = out.to_path_buf(); - - if !broadcasted { - out.push(DRY_RUN_DIR); - } - - let target_fname = target - .source - .file_name() - .wrap_err_with(|| format!("No filename for {:?}", target.source))? - .to_string_lossy(); - out.push(format!("{target_fname}-latest")); - - fs::create_dir_all(&out)?; - - let filename = sig - .split_once('(') - .wrap_err_with(|| format!("Failed to compute file name: Signature {sig} is invalid."))? - .0; - out.push(format!("{filename}.json")); - - Ok(out) - } - - /// Loads the sequences for the multi chain deployment. - pub fn load(log_folder: &Path, sig: &str, target: &ArtifactId) -> Result { - let path = MultiChainSequence::get_path(&log_folder.join("multi"), sig, target, true)?; - ethers::solc::utils::read_json_file(path).wrap_err("Multi-chain deployment not found.") - } - - /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self) -> Result<()> { - self.timestamp = now().as_secs(); - - //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; - - //../Contract-[timestamp]/run.json - let path = self.path.to_string_lossy(); - let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); - fs::create_dir_all(file.parent().unwrap())?; - fs::copy(&self.path, &file)?; - - println!("\nTransactions saved to: {}\n", self.path.display()); - - Ok(()) - } -} - -impl ScriptArgs { - /// Given a [`MultiChainSequence`] with multiple sequences of different chains, it executes them - /// all in parallel. Supports `--resume` and `--verify`. - pub async fn multi_chain_deployment( - &self, - mut deployments: MultiChainSequence, - libraries: Libraries, - config: &Config, - script_wallets: Vec, - verify: VerifyBundle, - ) -> Result<()> { - if !libraries.is_empty() { - eyre::bail!("Libraries are currently not supported on multi deployment setups."); - } - - if self.resume { - trace!(target: "script", "resuming multi chain deployment"); - - let futs = deployments - .deployments - .iter_mut() - .map(|sequence| async move { - let provider = Arc::new(get_http_provider( - sequence.typed_transactions().first().unwrap().0.clone(), - )); - receipts::wait_for_pending(provider, sequence).await - }) - .collect::>(); - - let errors = - join_all(futs).await.into_iter().filter(|res| res.is_err()).collect::>(); - - if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")) - } - } - - trace!(target: "script", "broadcasting multi chain deployments"); - - let futs = deployments - .deployments - .iter_mut() - .map(|sequence| async { - match self - .send_transactions( - sequence, - &sequence.typed_transactions().first().unwrap().0.clone(), - &script_wallets, - ) - .await - { - Ok(_) => { - if self.verify { - return sequence.verify_contracts(config, verify.clone()).await - } - Ok(()) - } - Err(err) => Err(err), - } - }) - .collect::>(); - - let errors = - join_all(futs).await.into_iter().filter(|res| res.is_err()).collect::>(); - - if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")) - } - - Ok(()) - } -} diff --git a/crates/forge/bin/cmd/script/receipts.rs b/crates/forge/bin/cmd/script/receipts.rs deleted file mode 100644 index 57b531caf9f94..0000000000000 --- a/crates/forge/bin/cmd/script/receipts.rs +++ /dev/null @@ -1,155 +0,0 @@ -use super::sequence::ScriptSequence; -use ethers::{ - prelude::{PendingTransaction, TxHash}, - providers::Middleware, - types::TransactionReceipt, -}; -use eyre::Result; -use foundry_cli::{init_progress, update_progress, utils::print_receipt}; -use foundry_common::RetryProvider; -use futures::StreamExt; -use std::sync::Arc; -use tracing::{trace, warn}; - -/// Convenience enum for internal signalling of transaction status -enum TxStatus { - Dropped, - Success(TransactionReceipt), - Revert(TransactionReceipt), -} - -impl From for TxStatus { - fn from(receipt: TransactionReceipt) -> Self { - let status = receipt.status.expect("receipt is from an ancient, pre-EIP658 block"); - if status.is_zero() { - TxStatus::Revert(receipt) - } else { - TxStatus::Success(receipt) - } - } -} - -/// Gets the receipts of previously pending transactions, or removes them from -/// the deploy sequence's pending vector -pub async fn wait_for_pending( - provider: Arc, - deployment_sequence: &mut ScriptSequence, -) -> Result<()> { - if deployment_sequence.pending.is_empty() { - return Ok(()) - } - println!("##\nChecking previously pending transactions."); - clear_pendings(provider, deployment_sequence, None).await -} - -/// Traverses a set of pendings and either finds receipts, or clears them from -/// the deployment sequnce. -/// -/// If no `tx_hashes` are provided, then `deployment_sequence.pending` will be -/// used. For each `tx_hash`, we check if it has confirmed. If it has -/// confirmed, we push the receipt (if successful) or push an error (if -/// revert). If the transaction has not confirmed, but can be found in the -/// node's mempool, we wait for its receipt to be available. If the transaction -/// has not confirmed, and cannot be found in the mempool, we remove it from -/// the `deploy_sequence.pending` vector so that it will be rebroadcast in -/// later steps. -pub async fn clear_pendings( - provider: Arc, - deployment_sequence: &mut ScriptSequence, - tx_hashes: Option>, -) -> Result<()> { - let to_query = tx_hashes.unwrap_or_else(|| deployment_sequence.pending.clone()); - - let count = deployment_sequence.pending.len(); - - trace!("Checking status of {count} pending transactions"); - - let futs = to_query.iter().copied().map(|tx| check_tx_status(&provider, tx)); - let mut tasks = futures::stream::iter(futs).buffer_unordered(10); - - let mut errors: Vec = vec![]; - let mut receipts = Vec::::with_capacity(count); - - // set up progress bar - let mut pos = 0; - let pb = init_progress!(deployment_sequence.pending, "receipts"); - pb.set_position(pos); - - while let Some((tx_hash, result)) = tasks.next().await { - match result { - Err(err) => { - errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}")) - } - Ok(TxStatus::Dropped) => { - // We want to remove it from pending so it will be re-broadcast. - deployment_sequence.remove_pending(tx_hash); - errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}")); - } - Ok(TxStatus::Success(receipt)) => { - trace!(tx_hash = ?tx_hash, "received tx receipt"); - deployment_sequence.remove_pending(receipt.transaction_hash); - receipts.push(receipt); - } - Ok(TxStatus::Revert(receipt)) => { - // consider: - // if this is not removed from pending, then the script becomes - // un-resumable. Is this desirable on reverts? - warn!(tx_hash = ?tx_hash, "Transaction Failure"); - deployment_sequence.remove_pending(receipt.transaction_hash); - errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); - } - } - // update the progress bar - update_progress!(pb, pos); - pos += 1; - } - - // sort receipts by blocks asc and index - receipts.sort_unstable(); - - // print all receipts - for receipt in receipts { - print_receipt(deployment_sequence.chain.into(), &receipt); - deployment_sequence.add_receipt(receipt); - } - - // print any erros - if !errors.is_empty() { - let mut error_msg = errors.join("\n"); - if !deployment_sequence.pending.is_empty() { - error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting - the transactions." - } - eyre::bail!(error_msg); - } - - Ok(()) -} - -/// Checks the status of a txhash by first polling for a receipt, then for -/// mempool inclusion. Returns the tx hash, and a status -async fn check_tx_status( - provider: &RetryProvider, - hash: TxHash, -) -> (TxHash, Result) { - // We use the inner future so that we can use ? operator in the future, but - // still neatly return the tuple - let result = async move { - // First check if there's a receipt - let receipt_opt = provider.get_transaction_receipt(hash).await?; - if let Some(receipt) = receipt_opt { - return Ok(receipt.into()) - } - - // If the tx is present in the mempool, run the pending tx future, and - // assume the next drop is really really real - let pending_res = PendingTransaction::new(hash, provider).await?; - match pending_res { - Some(receipt) => Ok(receipt.into()), - None => Ok(TxStatus::Dropped), - } - } - .await; - - (hash, result) -} diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/forge/bin/cmd/script/runner.rs deleted file mode 100644 index 338befa271c0d..0000000000000 --- a/crates/forge/bin/cmd/script/runner.rs +++ /dev/null @@ -1,367 +0,0 @@ -use super::*; -use ethers::types::{Address, Bytes, NameOrAddress, U256}; -use eyre::Result; -use forge::{ - executor::{CallResult, DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, - revm::interpreter::{return_ok, InstructionResult}, - trace::{TraceKind, Traces}, - CALLER, -}; -use tracing::log::trace; - -/// Represents which simulation stage is the script execution at. -pub enum SimulationStage { - Local, - OnChain, -} - -/// Drives script execution -#[derive(Debug)] -pub struct ScriptRunner { - pub executor: Executor, - pub initial_balance: U256, - pub sender: Address, -} - -impl ScriptRunner { - pub fn new(executor: Executor, initial_balance: U256, sender: Address) -> Self { - Self { executor, initial_balance, sender } - } - - /// Deploys the libraries and broadcast contract. Calls setUp method if requested. - pub fn setup( - &mut self, - libraries: &[Bytes], - code: Bytes, - setup: bool, - sender_nonce: U256, - is_broadcast: bool, - need_create2_deployer: bool, - ) -> Result<(Address, ScriptResult)> { - trace!(target: "script", "executing setUP()"); - - if !is_broadcast { - if self.sender == Config::DEFAULT_SENDER { - // We max out their balance so that they can deploy and make calls. - self.executor.set_balance(self.sender, U256::MAX)?; - } - - if need_create2_deployer { - self.executor.deploy_create2_deployer()?; - } - } - - self.executor.set_nonce(self.sender, sender_nonce.as_u64())?; - - // We max out their balance so that they can deploy and make calls. - self.executor.set_balance(CALLER, U256::MAX)?; - - // Deploy libraries - let mut traces: Traces = libraries - .iter() - .filter_map(|code| { - let DeployResult { traces, .. } = self - .executor - .deploy(self.sender, code.0.clone(), 0u32.into(), None) - .expect("couldn't deploy library"); - - traces - }) - .map(|traces| (TraceKind::Deployment, traces)) - .collect(); - - // Deploy an instance of the contract - let DeployResult { - address, - mut logs, - traces: constructor_traces, - debug: constructor_debug, - .. - } = self - .executor - .deploy(CALLER, code.0, 0u32.into(), None) - .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?; - - traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); - self.executor.set_balance(address, self.initial_balance)?; - - // Optionally call the `setUp` function - let (success, gas_used, labeled_addresses, transactions, debug, script_wallets) = if !setup - { - self.executor.backend_mut().set_test_contract(address); - ( - true, - 0, - Default::default(), - None, - vec![constructor_debug].into_iter().collect(), - vec![], - ) - } else { - match self.executor.setup(Some(self.sender), address) { - Ok(CallResult { - reverted, - traces: setup_traces, - labels, - logs: setup_logs, - debug, - gas_used, - transactions, - script_wallets, - .. - }) => { - traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); - logs.extend_from_slice(&setup_logs); - - self.maybe_correct_nonce(sender_nonce, libraries.len())?; - - ( - !reverted, - gas_used, - labels, - transactions, - vec![constructor_debug, debug].into_iter().collect(), - script_wallets, - ) - } - Err(EvmError::Execution(err)) => { - let ExecutionErr { - reverted, - traces: setup_traces, - labels, - logs: setup_logs, - debug, - gas_used, - transactions, - script_wallets, - .. - } = *err; - traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); - logs.extend_from_slice(&setup_logs); - - self.maybe_correct_nonce(sender_nonce, libraries.len())?; - - ( - !reverted, - gas_used, - labels, - transactions, - vec![constructor_debug, debug].into_iter().collect(), - script_wallets, - ) - } - Err(e) => return Err(e.into()), - } - }; - - Ok(( - address, - ScriptResult { - returned: bytes::Bytes::new(), - success, - gas_used, - labeled_addresses, - transactions, - logs, - traces, - debug, - address: None, - script_wallets, - }, - )) - } - - /// We call the `setUp()` function with self.sender, and if there haven't been - /// any broadcasts, then the EVM cheatcode module hasn't corrected the nonce. - /// So we have to. - fn maybe_correct_nonce( - &mut self, - sender_initial_nonce: U256, - libraries_len: usize, - ) -> Result<()> { - if let Some(ref cheatcodes) = self.executor.inspector_config().cheatcodes { - if !cheatcodes.corrected_nonce { - self.executor - .set_nonce(self.sender, sender_initial_nonce.as_u64() + libraries_len as u64)?; - } - self.executor - .inspector_config_mut() - .cheatcodes - .as_mut() - .expect("exists") - .corrected_nonce = false; - } - Ok(()) - } - - /// Executes the method that will collect all broadcastable transactions. - pub fn script(&mut self, address: Address, calldata: Bytes) -> Result { - self.call(self.sender, address, calldata, U256::zero(), false) - } - - /// Runs a broadcastable transaction locally and persists its state. - pub fn simulate( - &mut self, - from: Address, - to: Option, - calldata: Option, - value: Option, - ) -> Result { - if let Some(NameOrAddress::Address(to)) = to { - self.call(from, to, calldata.unwrap_or_default(), value.unwrap_or(U256::zero()), true) - } else if to.is_none() { - let (address, gas_used, logs, traces, debug) = match self.executor.deploy( - from, - calldata.expect("No data for create transaction").0, - value.unwrap_or(U256::zero()), - None, - ) { - Ok(DeployResult { address, gas_used, logs, traces, debug, .. }) => { - (address, gas_used, logs, traces, debug) - } - Err(EvmError::Execution(err)) => { - let ExecutionErr { reason, traces, gas_used, logs, debug, .. } = *err; - println!("{}", Paint::red(format!("\nFailed with `{reason}`:\n"))); - - (Address::zero(), gas_used, logs, traces, debug) - } - Err(e) => eyre::bail!("Failed deploying contract: {e:?}"), - }; - - Ok(ScriptResult { - returned: bytes::Bytes::new(), - success: address != Address::zero(), - gas_used, - logs, - traces: traces - .map(|mut traces| { - // Manually adjust gas for the trace to add back the stipend/real used gas - traces.arena[0].trace.gas_cost = gas_used; - vec![(TraceKind::Execution, traces)] - }) - .unwrap_or_default(), - debug: vec![debug].into_iter().collect(), - labeled_addresses: Default::default(), - transactions: Default::default(), - address: Some(address), - script_wallets: vec![], - }) - } else { - eyre::bail!("ENS not supported."); - } - } - - /// Executes the call - /// - /// This will commit the changes if `commit` is true. - /// - /// This will return _estimated_ gas instead of the precise gas the call would consume, so it - /// can be used as `gas_limit`. - fn call( - &mut self, - from: Address, - to: Address, - calldata: Bytes, - value: U256, - commit: bool, - ) -> Result { - let mut res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; - let mut gas_used = res.gas_used; - - // We should only need to calculate realistic gas costs when preparing to broadcast - // something. This happens during the onchain simulation stage, where we commit each - // collected transactions. - // - // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921 - if commit { - gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?; - res = self.executor.call_raw_committing(from, to, calldata.0, value)?; - } - - let RawCallResult { - result, - reverted, - logs, - traces, - labels, - debug, - transactions, - script_wallets, - .. - } = res; - - Ok(ScriptResult { - returned: result, - success: !reverted, - gas_used, - logs, - traces: traces - .map(|mut traces| { - // Manually adjust gas for the trace to add back the stipend/real used gas - traces.arena[0].trace.gas_cost = gas_used; - vec![(TraceKind::Execution, traces)] - }) - .unwrap_or_default(), - debug: vec![debug].into_iter().collect(), - labeled_addresses: labels, - transactions, - address: None, - script_wallets, - }) - } - - /// The executor will return the _exact_ gas value this transaction consumed, setting this value - /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a - /// possible range we pick a higher gas limit 3x of a succeeded call should be safe. - /// - /// This might result in executing the same script multiple times. Depending on the user's goal, - /// it might be problematic when using `ffi`. - fn search_optimal_gas_usage( - &mut self, - res: &RawCallResult, - from: Address, - to: Address, - calldata: &Bytes, - value: U256, - ) -> Result { - let mut gas_used = res.gas_used; - if matches!(res.exit_reason, return_ok!()) { - // store the current gas limit and reset it later - let init_gas_limit = self.executor.env_mut().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_mut().tx.gas_limit = mid_gas_limit; - let res = self.executor.call_raw(from, to, calldata.0.clone(), value)?; - match res.exit_reason { - InstructionResult::Revert | - InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { - lowest_gas_limit = mid_gas_limit; - } - _ => { - highest_gas_limit = mid_gas_limit; - // if last two successful estimations only vary by 10%, we consider this to - // sufficiently accurate - const ACCURACY: u64 = 10; - if (last_highest_gas_limit - highest_gas_limit) * ACCURACY / - last_highest_gas_limit < - 1 - { - // update the gas - gas_used = highest_gas_limit; - break - } - last_highest_gas_limit = highest_gas_limit; - } - } - } - // reset gas limit in the - self.executor.env_mut().tx.gas_limit = init_gas_limit; - } - Ok(gas_used) - } -} diff --git a/crates/forge/bin/cmd/script/sequence.rs b/crates/forge/bin/cmd/script/sequence.rs deleted file mode 100644 index 0c9d0ad20bde3..0000000000000 --- a/crates/forge/bin/cmd/script/sequence.rs +++ /dev/null @@ -1,397 +0,0 @@ -use super::NestedValue; -use crate::cmd::{ - init::get_commit_hash, - script::{ - transaction::{wrapper, AdditionalContract, TransactionWithMetadata}, - verify::VerifyBundle, - }, - verify::provider::VerificationProviderType, -}; -use ethers::{ - abi::Address, - prelude::{artifacts::Libraries, ArtifactId, TransactionReceipt, TxHash}, - types::transaction::eip2718::TypedTransaction, -}; -use eyre::{ContextCompat, Result, WrapErr}; -use foundry_cli::utils::now; -use foundry_common::{fs, shell, SELECTOR_LEN}; -use foundry_config::Config; -use serde::{Deserialize, Serialize}; -use std::{ - collections::{HashMap, VecDeque}, - io::{BufWriter, Write}, - path::{Path, PathBuf}, -}; -use tracing::trace; -use yansi::Paint; - -pub const DRY_RUN_DIR: &str = "dry-run"; - -/// Helper that saves the transactions sequence and its state on which transactions have been -/// broadcasted -#[derive(Deserialize, Serialize, Clone, Default)] -pub struct ScriptSequence { - pub transactions: VecDeque, - #[serde(serialize_with = "wrapper::serialize_receipts")] - pub receipts: Vec, - pub libraries: Vec, - pub pending: Vec, - #[serde(skip)] - pub path: PathBuf, - #[serde(skip)] - pub sensitive_path: PathBuf, - pub returns: HashMap, - pub timestamp: u64, - pub chain: u64, - /// If `True`, the sequence belongs to a `MultiChainSequence` and won't save to disk as usual. - pub multi: bool, - pub commit: Option, -} - -/// Sensitive values from the transactions in a script sequence -#[derive(Deserialize, Serialize, Clone, Default)] -pub struct SensitiveTransactionMetadata { - pub rpc: Option, -} - -/// Sensitive info from the script sequence which is saved into the cache folder -#[derive(Deserialize, Serialize, Clone, Default)] -pub struct SensitiveScriptSequence { - pub transactions: VecDeque, -} - -impl From<&mut ScriptSequence> for SensitiveScriptSequence { - fn from(sequence: &mut ScriptSequence) -> Self { - SensitiveScriptSequence { - transactions: sequence - .transactions - .iter() - .map(|tx| SensitiveTransactionMetadata { rpc: tx.rpc.clone() }) - .collect(), - } - } -} - -impl ScriptSequence { - pub fn new( - transactions: VecDeque, - returns: HashMap, - sig: &str, - target: &ArtifactId, - config: &Config, - broadcasted: bool, - is_multi: bool, - ) -> Result { - let chain = config.chain_id.unwrap_or_default().id(); - - let (path, sensitive_path) = ScriptSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - chain, - broadcasted && !is_multi, - )?; - - let commit = get_commit_hash(&config.__root.0); - - Ok(ScriptSequence { - transactions, - returns, - receipts: vec![], - pending: vec![], - path, - sensitive_path, - timestamp: now().as_secs(), - libraries: vec![], - chain, - multi: is_multi, - commit, - }) - } - - /// Loads The sequence for the corresponding json file - pub fn load( - config: &Config, - sig: &str, - target: &ArtifactId, - chain_id: u64, - broadcasted: bool, - ) -> Result { - let (path, sensitive_path) = ScriptSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - chain_id, - broadcasted, - )?; - - let mut script_sequence: Self = ethers::solc::utils::read_json_file(&path) - .wrap_err(format!("Deployment not found for chain `{chain_id}`."))?; - - let sensitive_script_sequence: SensitiveScriptSequence = - ethers::solc::utils::read_json_file(&sensitive_path).wrap_err(format!( - "Deployment's sensitive details not found for chain `{chain_id}`." - ))?; - - script_sequence - .transactions - .iter_mut() - .enumerate() - .for_each(|(i, tx)| tx.rpc = sensitive_script_sequence.transactions[i].rpc.clone()); - - script_sequence.path = path; - script_sequence.sensitive_path = sensitive_path; - - Ok(script_sequence) - } - - /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self) -> Result<()> { - if self.multi || self.transactions.is_empty() { - return Ok(()) - } - - self.timestamp = now().as_secs(); - let ts_name = format!("run-{}.json", self.timestamp); - - let sensitive_script_sequence: SensitiveScriptSequence = self.into(); - - // broadcast folder writes - //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; - //../run-[timestamp].json - fs::copy(&self.path, self.path.with_file_name(&ts_name))?; - - // cache folder writes - //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); - serde_json::to_writer_pretty(&mut writer, &sensitive_script_sequence)?; - writer.flush()?; - //../run-[timestamp].json - fs::copy(&self.sensitive_path, self.sensitive_path.with_file_name(&ts_name))?; - - shell::println(format!("\nTransactions saved to: {}\n", self.path.display()))?; - shell::println(format!("Sensitive values saved to: {}\n", self.sensitive_path.display()))?; - - Ok(()) - } - - pub fn add_receipt(&mut self, receipt: TransactionReceipt) { - self.receipts.push(receipt); - } - - /// Sorts all receipts with ascending transaction index - pub fn sort_receipts(&mut self) { - self.receipts.sort_unstable() - } - - pub fn add_pending(&mut self, index: usize, tx_hash: TxHash) { - if !self.pending.contains(&tx_hash) { - self.transactions[index].hash = Some(tx_hash); - self.pending.push(tx_hash); - } - } - - pub fn remove_pending(&mut self, tx_hash: TxHash) { - self.pending.retain(|element| element != &tx_hash); - } - - pub fn add_libraries(&mut self, libraries: Libraries) { - self.libraries = libraries - .libs - .iter() - .flat_map(|(file, libs)| { - libs.iter() - .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy())) - }) - .collect(); - } - - /// Gets paths in the formats - /// ./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json and - /// ./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json - pub fn get_paths( - broadcast: &Path, - cache: &Path, - sig: &str, - target: &ArtifactId, - chain_id: u64, - broadcasted: bool, - ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = broadcast.to_path_buf(); - let mut cache = cache.to_path_buf(); - let mut common = PathBuf::new(); - - let target_fname = target.source.file_name().wrap_err("No filename.")?; - common.push(target_fname); - common.push(chain_id.to_string()); - if !broadcasted { - common.push(DRY_RUN_DIR); - } - - broadcast.push(common.clone()); - cache.push(common); - - fs::create_dir_all(&broadcast)?; - fs::create_dir_all(&cache)?; - - // TODO: ideally we want the name of the function here if sig is calldata - let filename = sig_to_file_name(sig); - - broadcast.push(format!("{filename}-latest.json")); - cache.push(format!("{filename}-latest.json")); - - Ok((broadcast, cache)) - } - - /// Given the broadcast log, it matches transactions with receipts, and tries to verify any - /// created contract on etherscan. - pub async fn verify_contracts( - &mut self, - config: &Config, - mut verify: VerifyBundle, - ) -> Result<()> { - trace!(target: "script", "verifying {} contracts [{}]", verify.known_contracts.len(), self.chain); - - verify.set_chain(config, self.chain.into()); - - if verify.etherscan.key.is_some() || - verify.verifier.verifier != VerificationProviderType::Etherscan - { - trace!(target: "script", "prepare future verifications"); - - let mut future_verifications = Vec::with_capacity(self.receipts.len()); - let mut unverifiable_contracts = vec![]; - - // Make sure the receipts have the right order first. - self.sort_receipts(); - - for (receipt, tx) in self.receipts.iter_mut().zip(self.transactions.iter()) { - // create2 hash offset - let mut offset = 0; - - if tx.is_create2() { - receipt.contract_address = tx.contract_address; - offset = 32; - } - - // Verify contract created directly from the transaction - if let (Some(address), Some(data)) = - (receipt.contract_address, tx.typed_tx().data()) - { - match verify.get_verify_args(address, offset, &data.0, &self.libraries) { - Some(verify) => future_verifications.push(verify.run()), - None => unverifiable_contracts.push(address), - }; - } - - // Verify potential contracts created during the transaction execution - for AdditionalContract { address, init_code, .. } in &tx.additional_contracts { - match verify.get_verify_args(*address, 0, init_code, &self.libraries) { - Some(verify) => future_verifications.push(verify.run()), - None => unverifiable_contracts.push(*address), - }; - } - } - - trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", future_verifications.len(), unverifiable_contracts.len()); - - self.check_unverified(unverifiable_contracts, verify); - - let num_verifications = future_verifications.len(); - println!("##\nStart verification for ({num_verifications}) contracts",); - for verification in future_verifications { - verification.await?; - } - - println!("All ({num_verifications}) contracts were verified!"); - } - - Ok(()) - } - - /// Let the user know if there are any contracts which can not be verified. Also, present some - /// hints on potential causes. - fn check_unverified(&self, unverifiable_contracts: Vec
, verify: VerifyBundle) { - if !unverifiable_contracts.is_empty() { - println!( - "\n{}", - Paint::yellow(format!( - "We haven't found any matching bytecode for the following contracts: {:?}.\n\n{}", - unverifiable_contracts, - "This may occur when resuming a verification, but the underlying source code or compiler version has changed." - )) - .bold(), - ); - - if let Some(commit) = &self.commit { - let current_commit = verify - .project_paths - .root - .map(|root| get_commit_hash(&root).unwrap_or_default()) - .unwrap_or_default(); - - if ¤t_commit != commit { - println!("\tScript was broadcasted on commit `{commit}`, but we are at `{current_commit}`."); - } - } - } - } - - /// Returns the list of the transactions without the metadata. - pub fn typed_transactions(&self) -> Vec<(String, &TypedTransaction)> { - self.transactions - .iter() - .map(|tx| { - (tx.rpc.clone().expect("to have been filled with a proper rpc"), tx.typed_tx()) - }) - .collect() - } -} - -impl Drop for ScriptSequence { - fn drop(&mut self) { - self.sort_receipts(); - self.save().expect("not able to save deployment sequence"); - } -} - -/// Converts the `sig` argument into the corresponding file path. -/// -/// This accepts either the signature of the function or the raw calldata - -fn sig_to_file_name(sig: &str) -> String { - if let Some((name, _)) = sig.split_once('(') { - // strip until call argument parenthesis - return name.to_string() - } - // assume calldata if `sig` is hex - if let Ok(calldata) = hex::decode(sig) { - // in which case we return the function signature - return hex::encode(&calldata[..SELECTOR_LEN]) - } - - // return sig as is - sig.to_string() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_convert_sig() { - assert_eq!(sig_to_file_name("run()").as_str(), "run"); - assert_eq!( - sig_to_file_name( - "522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266" - ) - .as_str(), - "522bb704" - ); - } -} diff --git a/crates/forge/bin/cmd/script/transaction.rs b/crates/forge/bin/cmd/script/transaction.rs deleted file mode 100644 index 73d466f3a460b..0000000000000 --- a/crates/forge/bin/cmd/script/transaction.rs +++ /dev/null @@ -1,480 +0,0 @@ -use super::{artifacts::ArtifactInfo, ScriptResult}; -use ethers::{ - abi, - abi::Address, - prelude::{NameOrAddress, H256 as TxHash}, - types::transaction::eip2718::TypedTransaction, -}; -use eyre::{ContextCompat, Result, WrapErr}; -use foundry_common::{abi::format_token_raw, RpcUrl, SELECTOR_LEN}; -use foundry_evm::{ - executor::inspector::DEFAULT_CREATE2_DEPLOYER, trace::CallTraceDecoder, CallKind, -}; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; -use tracing::error; - -#[derive(Debug, Deserialize, Serialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct AdditionalContract { - #[serde(rename = "transactionType")] - pub opcode: CallKind, - #[serde(serialize_with = "wrapper::serialize_addr")] - pub address: Address, - #[serde(with = "hex")] - pub init_code: Vec, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[serde(rename_all = "camelCase")] -pub struct TransactionWithMetadata { - pub hash: Option, - #[serde(rename = "transactionType")] - pub opcode: CallKind, - #[serde(default = "default_string")] - pub contract_name: Option, - #[serde(default = "default_address", serialize_with = "wrapper::serialize_opt_addr")] - pub contract_address: Option
, - #[serde(default = "default_string")] - pub function: Option, - #[serde(default = "default_vec_of_strings")] - pub arguments: Option>, - #[serde(skip)] - pub rpc: Option, - pub transaction: TypedTransaction, - pub additional_contracts: Vec, - pub is_fixed_gas_limit: bool, -} - -fn default_string() -> Option { - Some("".to_string()) -} - -fn default_address() -> Option
{ - Some(Address::zero()) -} - -fn default_vec_of_strings() -> Option> { - Some(vec![]) -} - -impl TransactionWithMetadata { - pub fn from_typed_transaction(transaction: TypedTransaction) -> Self { - Self { transaction, ..Default::default() } - } - - pub fn new( - transaction: TypedTransaction, - rpc: Option, - result: &ScriptResult, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - additional_contracts: Vec, - is_fixed_gas_limit: bool, - ) -> Result { - let mut metadata = Self { transaction, rpc, is_fixed_gas_limit, ..Default::default() }; - - // Specify if any contract was directly created with this transaction - if let Some(NameOrAddress::Address(to)) = metadata.transaction.to().cloned() { - if to == DEFAULT_CREATE2_DEPLOYER { - metadata.set_create( - true, - Address::from_slice(&result.returned), - local_contracts, - decoder, - )?; - } else { - metadata - .set_call(to, local_contracts, decoder) - .wrap_err("Could not decode transaction type.")?; - } - } else if metadata.transaction.to().is_none() { - metadata.set_create( - false, - result.address.wrap_err("There should be a contract address from CREATE.")?, - local_contracts, - decoder, - )?; - } - - // Add the additional contracts created in this transaction, so we can verify them later. - if let Some(tx_address) = metadata.contract_address { - metadata.additional_contracts = additional_contracts - .into_iter() - .filter_map(|contract| { - // Filter out the transaction contract repeated init_code. - if contract.address != tx_address { - Some(contract) - } else { - None - } - }) - .collect(); - } - - Ok(metadata) - } - - /// Populate the transaction as CREATE tx - /// - /// If this is a CREATE2 transaction this attempt to decode the arguments from the CREATE2 - /// deployer's function - fn set_create( - &mut self, - is_create2: bool, - address: Address, - contracts: &BTreeMap, - decoder: &CallTraceDecoder, - ) -> Result<()> { - if is_create2 { - self.opcode = CallKind::Create2; - } else { - self.opcode = CallKind::Create; - } - - self.contract_name = contracts.get(&address).map(|info| info.contract_name.clone()); - self.contract_address = Some(address); - - if let Some(data) = self.transaction.data() { - // a CREATE2 transaction is a CALL to the CREATE2 deployer function, so we need to - // decode the arguments from the function call - if is_create2 { - // this will be the `deploy()` function of the CREATE2 call, we assume the contract - // is already deployed and will try to fetch it via its signature - if let Some(Some(function)) = decoder - .functions - .get(&data.0[..SELECTOR_LEN]) - .map(|functions| functions.first()) - { - self.function = Some(function.signature()); - self.arguments = Some( - function - .decode_input(&data.0[SELECTOR_LEN..]) - .map(|tokens| tokens.iter().map(format_token_raw).collect()) - .map_err(|_| eyre::eyre!("Failed to decode CREATE2 call arguments"))?, - ); - } - } else { - // This is a regular CREATE via the constructor, in which case we expect the - // contract has a constructor and if so decode its input params - if let Some(info) = contracts.get(&address) { - // set constructor arguments - if data.len() > info.code.len() { - if let Some(constructor) = info.abi.constructor() { - let on_err = || { - let inputs = constructor - .inputs - .iter() - .map(|p| p.kind.to_string()) - .collect::>() - .join(","); - let signature = format!("constructor({inputs})"); - let bytecode = hex::encode(&data.0); - (signature, bytecode) - }; - - let params = constructor - .inputs - .iter() - .map(|p| p.kind.clone()) - .collect::>(); - - // the constructor args start after bytecode - let constructor_args = &data.0[info.code.len()..]; - - if let Ok(arguments) = abi::decode(¶ms, constructor_args) { - self.arguments = - Some(arguments.iter().map(format_token_raw).collect()); - } else { - let (signature, bytecode) = on_err(); - error!(constructor=?signature, contract=?self.contract_name, bytecode, "Failed to decode constructor arguments") - }; - } - } - } - } - } - Ok(()) - } - - /// Populate the transaction as CALL tx - fn set_call( - &mut self, - target: Address, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - ) -> Result<()> { - self.opcode = CallKind::Call; - - if let Some(data) = self.transaction.data() { - if data.0.len() >= SELECTOR_LEN { - if let Some(info) = local_contracts.get(&target) { - // This CALL is made to a local contract. - - self.contract_name = Some(info.contract_name.clone()); - if let Some(function) = info - .abi - .functions() - .find(|function| function.short_signature() == data.0[..SELECTOR_LEN]) - { - self.function = Some(function.signature()); - self.arguments = Some( - function - .decode_input(&data.0[SELECTOR_LEN..]) - .map(|tokens| tokens.iter().map(format_token_raw).collect())?, - ); - } - } else { - // This CALL is made to an external contract. We can only decode it, if it has - // been verified and identified by etherscan. - - if let Some(Some(function)) = decoder - .functions - .get(&data.0[..SELECTOR_LEN]) - .map(|functions| functions.first()) - { - self.contract_name = decoder.contracts.get(&target).cloned(); - - self.function = Some(function.signature()); - self.arguments = Some( - function - .decode_input(&data.0[SELECTOR_LEN..]) - .map(|tokens| tokens.iter().map(format_token_raw).collect())?, - ); - } - } - self.contract_address = Some(target); - } - } - Ok(()) - } - - pub fn set_tx(&mut self, tx: TypedTransaction) { - self.transaction = tx; - } - - pub fn change_type(&mut self, is_legacy: bool) { - self.transaction = if is_legacy { - TypedTransaction::Legacy(self.transaction.clone().into()) - } else { - TypedTransaction::Eip1559(self.transaction.clone().into()) - }; - } - - pub fn typed_tx(&self) -> &TypedTransaction { - &self.transaction - } - - pub fn typed_tx_mut(&mut self) -> &mut TypedTransaction { - &mut self.transaction - } - - pub fn is_create2(&self) -> bool { - self.opcode == CallKind::Create2 - } -} - -// wrapper for modifying ethers-rs type serialization -pub mod wrapper { - pub use super::*; - - use ethers::{ - types::{Bloom, Bytes, Log, TransactionReceipt, H256, U256, U64}, - utils::to_checksum, - }; - - pub fn serialize_addr(addr: &Address, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&to_checksum(addr, None)) - } - - pub fn serialize_opt_addr(opt: &Option
, serializer: S) -> Result - where - S: serde::Serializer, - { - match opt { - Some(addr) => serialize_addr(addr, serializer), - None => serializer.serialize_none(), - } - } - - pub fn serialize_vec_with_wrapped( - vec: &[T], - serializer: S, - ) -> Result - where - S: serde::Serializer, - T: Clone, - WrappedType: serde::Serialize + From, - { - serializer.collect_seq(vec.iter().cloned().map(WrappedType::from)) - } - - // copied from https://github.com/gakonst/ethers-rs - #[derive(Serialize, Deserialize)] - struct WrappedLog { - /// H160. the contract that emitted the log - #[serde(serialize_with = "serialize_addr")] - pub address: Address, - - /// topics: Array of 0 to 4 32 Bytes of indexed log arguments. - /// (In solidity: The first topic is the hash of the signature of the event - /// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event - /// with the anonymous specifier.) - pub topics: Vec, - - /// Data - pub data: Bytes, - - /// Block Hash - #[serde(rename = "blockHash")] - #[serde(skip_serializing_if = "Option::is_none")] - pub block_hash: Option, - - /// Block Number - #[serde(rename = "blockNumber")] - #[serde(skip_serializing_if = "Option::is_none")] - pub block_number: Option, - - /// Transaction Hash - #[serde(rename = "transactionHash")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_hash: Option, - - /// Transaction Index - #[serde(rename = "transactionIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_index: Option, - - /// Integer of the log index position in the block. None if it's a pending log. - #[serde(rename = "logIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub log_index: Option, - - /// Integer of the transactions index position log was created from. - /// None when it's a pending log. - #[serde(rename = "transactionLogIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_log_index: Option, - - /// Log Type - #[serde(rename = "logType")] - #[serde(skip_serializing_if = "Option::is_none")] - pub log_type: Option, - - /// True when the log was removed, due to a chain reorganization. - /// false if it's a valid log. - #[serde(skip_serializing_if = "Option::is_none")] - pub removed: Option, - } - impl From for WrappedLog { - fn from(log: Log) -> Self { - Self { - address: log.address, - topics: log.topics, - data: log.data, - block_hash: log.block_hash, - block_number: log.block_number, - transaction_hash: log.transaction_hash, - transaction_index: log.transaction_index, - log_index: log.log_index, - transaction_log_index: log.transaction_log_index, - log_type: log.log_type, - removed: log.removed, - } - } - } - - fn serialize_logs( - logs: &[Log], - serializer: S, - ) -> Result { - serialize_vec_with_wrapped::(logs, serializer) - } - - // "Receipt" of an executed transaction: details of its execution. - // copied from https://github.com/gakonst/ethers-rs - #[derive(Default, Clone, Serialize, Deserialize)] - pub struct WrappedTransactionReceipt { - /// Transaction hash. - #[serde(rename = "transactionHash")] - pub transaction_hash: H256, - /// Index within the block. - #[serde(rename = "transactionIndex")] - pub transaction_index: U64, - /// Hash of the block this transaction was included within. - #[serde(rename = "blockHash")] - pub block_hash: Option, - /// Number of the block this transaction was included within. - #[serde(rename = "blockNumber")] - pub block_number: Option, - /// The address of the sender. - #[serde(serialize_with = "serialize_addr")] - pub from: Address, - // The address of the receiver. `None` when its a contract creation transaction. - #[serde(serialize_with = "serialize_opt_addr")] - pub to: Option
, - /// Cumulative gas used within the block after this was executed. - #[serde(rename = "cumulativeGasUsed")] - pub cumulative_gas_used: U256, - /// Gas used by this transaction alone. - /// - /// Gas used is `None` if the the client is running in light client mode. - #[serde(rename = "gasUsed")] - pub gas_used: Option, - /// Contract address created, or `None` if not a deployment. - #[serde(rename = "contractAddress", serialize_with = "serialize_opt_addr")] - pub contract_address: Option
, - /// Logs generated within this transaction. - #[serde(serialize_with = "serialize_logs")] - pub logs: Vec, - /// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) - pub status: Option, - /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub root: Option, - /// Logs bloom - #[serde(rename = "logsBloom")] - pub logs_bloom: Bloom, - /// Transaction type, Some(1) for AccessList transaction, None for Legacy - #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] - pub transaction_type: Option, - /// The price paid post-execution by the transaction (i.e. base fee + priority fee). - /// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the - /// amount that's actually paid by users can only be determined post-execution - #[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")] - pub effective_gas_price: Option, - } - impl From for WrappedTransactionReceipt { - fn from(receipt: TransactionReceipt) -> Self { - Self { - transaction_hash: receipt.transaction_hash, - transaction_index: receipt.transaction_index, - block_hash: receipt.block_hash, - block_number: receipt.block_number, - from: receipt.from, - to: receipt.to, - cumulative_gas_used: receipt.cumulative_gas_used, - gas_used: receipt.gas_used, - contract_address: receipt.contract_address, - logs: receipt.logs, - status: receipt.status, - root: receipt.root, - logs_bloom: receipt.logs_bloom, - transaction_type: receipt.transaction_type, - effective_gas_price: receipt.effective_gas_price, - } - } - } - - pub fn serialize_receipts( - receipts: &[TransactionReceipt], - serializer: S, - ) -> Result { - serialize_vec_with_wrapped::( - receipts, serializer, - ) - } -} diff --git a/crates/forge/bin/cmd/script/verify.rs b/crates/forge/bin/cmd/script/verify.rs deleted file mode 100644 index 1ec2a050d0458..0000000000000 --- a/crates/forge/bin/cmd/script/verify.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::cmd::{ - retry::RetryArgs, - verify::{VerifierArgs, VerifyArgs}, -}; -use ethers::{ - abi::Address, - solc::{info::ContractInfo, Project}, -}; -use foundry_cli::opts::{EtherscanOpts, ProjectPathsArgs}; -use foundry_common::ContractsByArtifact; -use foundry_config::{Chain, Config}; -use semver::Version; - -/// Data struct to help `ScriptSequence` verify contracts on `etherscan`. -#[derive(Clone)] -pub struct VerifyBundle { - pub num_of_optimizations: Option, - pub known_contracts: ContractsByArtifact, - pub project_paths: ProjectPathsArgs, - pub etherscan: EtherscanOpts, - pub retry: RetryArgs, - pub verifier: VerifierArgs, -} - -impl VerifyBundle { - pub fn new( - project: &Project, - config: &Config, - known_contracts: ContractsByArtifact, - retry: RetryArgs, - verifier: VerifierArgs, - ) -> Self { - let num_of_optimizations = - if config.optimizer { Some(config.optimizer_runs) } else { None }; - - let config_path = config.get_config_path(); - - let project_paths = ProjectPathsArgs { - root: Some(project.paths.root.clone()), - contracts: Some(project.paths.sources.clone()), - remappings: project.paths.remappings.clone(), - remappings_env: None, - cache_path: Some(project.paths.cache.clone()), - lib_paths: project.paths.libraries.clone(), - hardhat: config.profile == Config::HARDHAT_PROFILE, - config_path: if config_path.exists() { Some(config_path) } else { None }, - }; - - VerifyBundle { - num_of_optimizations, - known_contracts, - etherscan: Default::default(), - project_paths, - retry, - verifier, - } - } - - /// Configures the chain and sets the etherscan key, if available - pub fn set_chain(&mut self, config: &Config, chain: Chain) { - // If dealing with multiple chains, we need to be able to change inbetween the config - // chain_id. - self.etherscan.key = config.get_etherscan_api_key(Some(chain)); - self.etherscan.chain = Some(chain); - } - - /// Given a `VerifyBundle` and contract details, it tries to generate a valid `VerifyArgs` to - /// use against the `contract_address`. - pub fn get_verify_args( - &self, - contract_address: Address, - create2_offset: usize, - data: &[u8], - libraries: &[String], - ) -> Option { - for (artifact, (_contract, bytecode)) in self.known_contracts.iter() { - // If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning - // of the transaction - if data.split_at(create2_offset).1.starts_with(bytecode) { - let constructor_args = data.split_at(create2_offset + bytecode.len()).1.to_vec(); - - let contract = ContractInfo { - path: Some( - artifact.source.to_str().expect("There should be an artifact.").to_string(), - ), - name: artifact.name.clone(), - }; - - // We strip the build metadadata information, since it can lead to - // etherscan not identifying it correctly. eg: - // `v0.8.10+commit.fc410830.Linux.gcc` != `v0.8.10+commit.fc410830` - let version = Version::new( - artifact.version.major, - artifact.version.minor, - artifact.version.patch, - ); - - let verify = VerifyArgs { - address: contract_address, - contract, - compiler_version: Some(version.to_string()), - constructor_args: Some(hex::encode(constructor_args)), - constructor_args_path: None, - num_of_optimizations: self.num_of_optimizations, - etherscan: self.etherscan.clone(), - flatten: false, - force: false, - watch: true, - retry: self.retry, - libraries: libraries.to_vec(), - root: None, - verifier: self.verifier.clone(), - show_standard_json_input: false, - }; - - return Some(verify) - } - } - None - } -} diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index b594081a72d11..56c25cc003cd6 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -1,65 +1,105 @@ +use alloy_primitives::hex; use clap::Parser; -use comfy_table::Table; -use ethers::prelude::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Table}; use eyre::Result; use foundry_cli::{ - opts::{CompilerArgs, CoreBuildArgs, ProjectPathsArgs}, - utils::FoundryPathExt, + opts::{BuildOpts, CompilerOpts, ProjectPathOpts}, + utils::{cache_local_signatures, FoundryPathExt}, }; use foundry_common::{ - compile, + compile::{compile_target, ProjectCompiler}, selectors::{import_selectors, SelectorImportData}, }; +use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; +use foundry_config::Config; use std::fs::canonicalize; /// CLI arguments for `forge selectors`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub enum SelectorsSubcommands { /// Check for selector collisions between contracts - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Collision { - /// First contract - #[clap( - help = "The first of the two contracts for which to look selector collisions for, in the form `(:)?`", - value_name = "FIRST_CONTRACT" - )] + /// The first of the two contracts for which to look selector collisions for, in the form + /// `(:)?`. first_contract: ContractInfo, - /// Second contract - #[clap( - help = "The second of the two contracts for which to look selector collisions for, in the form `(:)?`", - value_name = "SECOND_CONTRACT" - )] + /// The second of the two contracts for which to look selector collisions for, in the form + /// `(:)?`. second_contract: ContractInfo, - /// Support build args - #[clap(flatten)] - build: Box, + #[command(flatten)] + build: Box, }, /// Upload selectors to registry - #[clap(visible_alias = "up")] + #[command(visible_alias = "up")] Upload { /// The name of the contract to upload selectors for. - #[clap(required_unless_present = "all")] + #[arg(required_unless_present = "all")] contract: Option, /// Upload selectors for all contracts in the project. - #[clap(long, required_unless_present = "contract")] + #[arg(long, required_unless_present = "contract")] all: bool, - #[clap(flatten)] - project_paths: ProjectPathsArgs, + #[command(flatten)] + project_paths: ProjectPathOpts, + }, + + /// List selectors from current workspace + #[command(visible_alias = "ls")] + List { + /// The name of the contract to list selectors for. + #[arg(help = "The name of the contract to list selectors for.")] + contract: Option, + + #[command(flatten)] + project_paths: ProjectPathOpts, + }, + + /// Find if a selector is present in the project + #[command(visible_alias = "f")] + Find { + /// The selector to search for + #[arg(help = "The selector to search for (with or without 0x prefix)")] + selector: String, + + #[command(flatten)] + project_paths: ProjectPathOpts, + }, + + /// Cache project selectors (enables trace with local contracts functions and events). + #[command(visible_alias = "c")] + Cache { + #[command(flatten)] + project_paths: ProjectPathOpts, }, } impl SelectorsSubcommands { pub async fn run(self) -> Result<()> { match self { - SelectorsSubcommands::Upload { contract, all, project_paths } => { - let build_args = CoreBuildArgs { + Self::Cache { project_paths } => { + sh_println!("Caching selectors for contracts in the project...")?; + let build_args = BuildOpts { + project_paths, + compiler: CompilerOpts { + extra_output: vec![ContractOutputSelection::Abi], + ..Default::default() + }, + ..Default::default() + }; + + // compile the project to get the artifacts/abis + let project = build_args.project()?; + let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; + cache_local_signatures(&outcome, Config::foundry_cache_dir().unwrap())? + } + Self::Upload { contract, all, project_paths } => { + let build_args = BuildOpts { project_paths: project_paths.clone(), - compiler: CompilerArgs { + compiler: CompilerOpts { extra_output: vec![ContractOutputSelection::Abi], ..Default::default() }, @@ -67,13 +107,17 @@ impl SelectorsSubcommands { }; let project = build_args.project()?; - let outcome = compile::suppress_compile(&project)?; + let output = if let Some(name) = &contract { + let target_path = project.find_contract_path(name)?; + compile_target(&target_path, &project, false)? + } else { + ProjectCompiler::new().compile(&project)? + }; let artifacts = if all { - outcome + output .into_artifacts_with_files() .filter(|(file, _, _)| { - let is_sources_path = file - .starts_with(&project.paths.sources.to_string_lossy().to_string()); + let is_sources_path = file.starts_with(&project.paths.sources); let is_test = file.is_sol_test(); is_sources_path && !is_test @@ -82,7 +126,7 @@ impl SelectorsSubcommands { .collect() } else { let contract = contract.unwrap(); - let found_artifact = outcome.find_first(&contract); + let found_artifact = output.find_first(&contract); let artifact = found_artifact .ok_or_else(|| { eyre::eyre!( @@ -95,60 +139,50 @@ impl SelectorsSubcommands { let mut artifacts = artifacts.into_iter().peekable(); while let Some((contract, artifact)) = artifacts.next() { - let abi = artifact.abi.ok_or(eyre::eyre!("Unable to fetch abi"))?; - if abi.abi.functions.is_empty() && - abi.abi.events.is_empty() && - abi.abi.errors.is_empty() - { + let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; + if abi.functions.is_empty() && abi.events.is_empty() && abi.errors.is_empty() { continue } - println!("Uploading selectors for {contract}..."); + sh_println!("Uploading selectors for {contract}...")?; // upload abi to selector database import_selectors(SelectorImportData::Abi(vec![abi])).await?.describe(); if artifacts.peek().is_some() { - println!() + sh_println!()? } } } - SelectorsSubcommands::Collision { mut first_contract, mut second_contract, build } => { - // Build first project - let first_project = build.project()?; - let first_outcome = if let Some(ref mut contract_path) = first_contract.path { + Self::Collision { mut first_contract, mut second_contract, build } => { + // Compile the project with the two contracts included + let project = build.project()?; + let mut compiler = ProjectCompiler::new().quiet(true); + + if let Some(contract_path) = &mut first_contract.path { let target_path = canonicalize(&*contract_path)?; *contract_path = target_path.to_string_lossy().to_string(); - compile::compile_files(&first_project, vec![target_path], true) - } else { - compile::suppress_compile(&first_project) - }?; - - // Build second project - let second_project = build.project()?; - let second_outcome = if let Some(ref mut contract_path) = second_contract.path { + compiler = compiler.files([target_path]); + } + if let Some(contract_path) = &mut second_contract.path { let target_path = canonicalize(&*contract_path)?; *contract_path = target_path.to_string_lossy().to_string(); - compile::compile_files(&second_project, vec![target_path], true) - } else { - compile::suppress_compile(&second_project) - }?; - - // Find the artifacts - let first_found_artifact = first_outcome.find_contract(&first_contract); - let second_found_artifact = second_outcome.find_contract(&second_contract); + compiler = compiler.files([target_path]); + } - // Unwrap inner artifacts - let first_artifact = first_found_artifact.ok_or_else(|| { - eyre::eyre!("Failed to extract first artifact bytecode as a string") - })?; - let second_artifact = second_found_artifact.ok_or_else(|| { - eyre::eyre!("Failed to extract second artifact bytecode as a string") - })?; + let output = compiler.compile(&project)?; // Check method selectors for collisions - let first_method_map = first_artifact.method_identifiers.as_ref().unwrap(); - let second_method_map = second_artifact.method_identifiers.as_ref().unwrap(); + let methods = |contract: &ContractInfo| -> eyre::Result<_> { + let artifact = output + .find_contract(contract) + .ok_or_else(|| eyre::eyre!("Could not find artifact for {contract}"))?; + artifact.method_identifiers.as_ref().ok_or_else(|| { + eyre::eyre!("Could not find method identifiers for {contract}") + }) + }; + let first_method_map = methods(&first_contract)?; + let second_method_map = methods(&second_contract)?; let colliding_methods: Vec<(&String, &String, &String)> = first_method_map .iter() @@ -161,19 +195,182 @@ impl SelectorsSubcommands { .collect(); if colliding_methods.is_empty() { - println!("No colliding method selectors between the two contracts."); + sh_println!("No colliding method selectors between the two contracts.")?; } else { let mut table = Table::new(); - table.set_header(vec![ + table.apply_modifier(UTF8_ROUND_CORNERS); + table.set_header([ String::from("Selector"), first_contract.name, second_contract.name, ]); - colliding_methods.iter().for_each(|t| { - table.add_row(vec![t.0, t.1, t.2]); - }); - println!("{} collisions found:", colliding_methods.len()); - println!("{table}"); + for method in colliding_methods.iter() { + table.add_row([method.0, method.1, method.2]); + } + sh_println!("{} collisions found:", colliding_methods.len())?; + sh_println!("\n{table}\n")?; + } + } + Self::List { contract, project_paths } => { + sh_println!("Listing selectors for contracts in the project...")?; + let build_args = BuildOpts { + project_paths, + compiler: CompilerOpts { + extra_output: vec![ContractOutputSelection::Abi], + ..Default::default() + }, + ..Default::default() + }; + + // compile the project to get the artifacts/abis + let project = build_args.project()?; + let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; + let artifacts = if let Some(contract) = contract { + let found_artifact = outcome.find_first(&contract); + let artifact = found_artifact + .ok_or_else(|| { + let candidates = outcome + .artifacts() + .map(|(name, _,)| name) + .collect::>(); + let suggestion = if let Some(suggestion) = foundry_cli::utils::did_you_mean(&contract, candidates).pop() { + format!("\nDid you mean `{suggestion}`?") + } else { + String::new() + }; + eyre::eyre!( + "Could not find artifact `{contract}` in the compiled artifacts{suggestion}", + ) + })? + .clone(); + vec![(contract, artifact)] + } else { + outcome + .into_artifacts_with_files() + .filter(|(file, _, _)| { + let is_sources_path = file.starts_with(&project.paths.sources); + let is_test = file.is_sol_test(); + + is_sources_path && !is_test + }) + .map(|(_, contract, artifact)| (contract, artifact)) + .collect() + }; + + let mut artifacts = artifacts.into_iter().peekable(); + + while let Some((contract, artifact)) = artifacts.next() { + let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; + if abi.functions.is_empty() && abi.events.is_empty() && abi.errors.is_empty() { + continue + } + + sh_println!("{contract}")?; + + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(["Type", "Signature", "Selector"]); + + for func in abi.functions() { + let sig = func.signature(); + let selector = func.selector(); + table.add_row(["Function", &sig, &hex::encode_prefixed(selector)]); + } + + for event in abi.events() { + let sig = event.signature(); + let selector = event.selector(); + table.add_row(["Event", &sig, &hex::encode_prefixed(selector)]); + } + + for error in abi.errors() { + let sig = error.signature(); + let selector = error.selector(); + table.add_row(["Error", &sig, &hex::encode_prefixed(selector)]); + } + + sh_println!("\n{table}\n")?; + + if artifacts.peek().is_some() { + sh_println!()? + } + } + } + + Self::Find { selector, project_paths } => { + sh_println!("Searching for selector {selector:?} in the project...")?; + + let build_args = BuildOpts { + project_paths, + compiler: CompilerOpts { + extra_output: vec![ContractOutputSelection::Abi], + ..Default::default() + }, + ..Default::default() + }; + + let project = build_args.project()?; + let outcome = ProjectCompiler::new().quiet(true).compile(&project)?; + let artifacts = outcome + .into_artifacts_with_files() + .filter(|(file, _, _)| { + let is_sources_path = file.starts_with(&project.paths.sources); + let is_test = file.is_sol_test(); + is_sources_path && !is_test + }) + .collect::>(); + + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(["Type", "Signature", "Selector", "Contract"]); + + for (_file, contract, artifact) in artifacts { + let abi = artifact.abi.ok_or_else(|| eyre::eyre!("Unable to fetch abi"))?; + + let selector_bytes = + hex::decode(selector.strip_prefix("0x").unwrap_or(&selector))?; + + for func in abi.functions() { + if func.selector().as_slice().starts_with(selector_bytes.as_slice()) { + table.add_row([ + "Function", + &func.signature(), + &hex::encode_prefixed(func.selector()), + contract.as_str(), + ]); + } + } + + for event in abi.events() { + if event.selector().as_slice().starts_with(selector_bytes.as_slice()) { + table.add_row([ + "Event", + &event.signature(), + &hex::encode_prefixed(event.selector()), + contract.as_str(), + ]); + } + } + + for error in abi.errors() { + if error.selector().as_slice().starts_with(selector_bytes.as_slice()) { + table.add_row([ + "Error", + &error.signature(), + &hex::encode_prefixed(error.selector()), + contract.as_str(), + ]); + } + } + } + + if table.row_count() > 0 { + sh_println!("\nFound {} instance(s)...", table.row_count())?; + sh_println!("\n{table}\n")?; + } else { + return Err(eyre::eyre!("\nSelector not found in the project.")); } } } diff --git a/crates/forge/bin/cmd/snapshot.rs b/crates/forge/bin/cmd/snapshot.rs index ebc1f242cec74..872a53138c8ee 100644 --- a/crates/forge/bin/cmd/snapshot.rs +++ b/crates/forge/bin/cmd/snapshot.rs @@ -1,38 +1,33 @@ -use super::{ - test, - test::{Test, TestOutcome}, -}; +use super::test; +use alloy_primitives::{map::HashMap, U256}; use clap::{builder::RangedU64ValueParser, Parser, ValueHint}; -use ethers::types::U256; use eyre::{Context, Result}; -use forge::result::TestKindReport; +use forge::result::{SuiteTestResult, TestKindReport, TestOutcome}; use foundry_cli::utils::STATIC_FUZZ_SEED; -use once_cell::sync::Lazy; use regex::Regex; use std::{ cmp::Ordering, - collections::HashMap, fs, io::{self, BufRead}, path::{Path, PathBuf}, str::FromStr, + sync::LazyLock, }; -use watchexec::config::{InitConfig, RuntimeConfig}; use yansi::Paint; /// A regex that matches a basic snapshot entry like /// `Test:testDeposit() (gas: 58804)` -pub static RE_BASIC_SNAPSHOT_ENTRY: Lazy = Lazy::new(|| { +pub static RE_BASIC_SNAPSHOT_ENTRY: LazyLock = LazyLock::new(|| { Regex::new(r"(?P(.*?)):(?P(\w+)\s*\((.*?)\))\s*\(((gas:)?\s*(?P\d+)|(runs:\s*(?P\d+),\s*μ:\s*(?P\d+),\s*~:\s*(?P\d+))|(runs:\s*(?P\d+),\s*calls:\s*(?P\d+),\s*reverts:\s*(?P\d+)))\)").unwrap() }); /// CLI arguments for `forge snapshot`. -#[derive(Debug, Clone, Parser)] -pub struct SnapshotArgs { - /// Output a diff against a pre-existing snapshot. +#[derive(Clone, Debug, Parser)] +pub struct GasSnapshotArgs { + /// Output a diff against a pre-existing gas snapshot. /// /// By default, the comparison is done with .gas-snapshot. - #[clap( + #[arg( conflicts_with = "snap", long, value_hint = ValueHint::FilePath, @@ -40,12 +35,12 @@ pub struct SnapshotArgs { )] diff: Option>, - /// Compare against a pre-existing snapshot, exiting with code 1 if they do not match. + /// Compare against a pre-existing gas snapshot, exiting with code 1 if they do not match. /// - /// Outputs a diff if the snapshots do not match. + /// Outputs a diff if the gas snapshots do not match. /// /// By default, the comparison is done with .gas-snapshot. - #[clap( + #[arg( conflicts_with = "diff", long, value_hint = ValueHint::FilePath, @@ -55,11 +50,11 @@ pub struct SnapshotArgs { // Hidden because there is only one option /// How to format the output. - #[clap(long, hide(true))] + #[arg(long, hide(true))] format: Option, - /// Output file for the snapshot. - #[clap( + /// Output file for the gas snapshot. + #[arg( long, default_value = ".gas-snapshot", value_hint = ValueHint::FilePath, @@ -68,7 +63,7 @@ pub struct SnapshotArgs { snap: PathBuf, /// Tolerates gas deviations up to the specified percentage. - #[clap( + #[arg( long, value_parser = RangedU64ValueParser::::new().range(0..100), value_name = "SNAPSHOT_THRESHOLD" @@ -76,55 +71,55 @@ pub struct SnapshotArgs { tolerance: Option, /// All test arguments are supported - #[clap(flatten)] + #[command(flatten)] pub(crate) test: test::TestArgs, /// Additional configs for test results - #[clap(flatten)] - config: SnapshotConfig, + #[command(flatten)] + config: GasSnapshotConfig, } -impl SnapshotArgs { - /// Returns whether `SnapshotArgs` was configured with `--watch` +impl GasSnapshotArgs { + /// Returns whether `GasSnapshotArgs` was configured with `--watch` pub fn is_watch(&self) -> bool { self.test.is_watch() } /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { + pub(crate) fn watchexec_config(&self) -> Result { self.test.watchexec_config() } pub async fn run(mut self) -> Result<()> { // Set fuzz seed so gas snapshots are deterministic - self.test.fuzz_seed = Some(U256::from_big_endian(&STATIC_FUZZ_SEED)); + self.test.fuzz_seed = Some(U256::from_be_bytes(STATIC_FUZZ_SEED)); let outcome = self.test.execute_tests().await?; - outcome.ensure_ok()?; + outcome.ensure_ok(false)?; let tests = self.config.apply(outcome); if let Some(path) = self.diff { let snap = path.as_ref().unwrap_or(&self.snap); - let snaps = read_snapshot(snap)?; + let snaps = read_gas_snapshot(snap)?; diff(tests, snaps)?; } else if let Some(path) = self.check { let snap = path.as_ref().unwrap_or(&self.snap); - let snaps = read_snapshot(snap)?; + let snaps = read_gas_snapshot(snap)?; if check(tests, snaps, self.tolerance) { std::process::exit(0) } else { std::process::exit(1) } } else { - write_to_snapshot_file(&tests, self.snap, self.format)?; + write_to_gas_snapshot_file(&tests, self.snap, self.format)?; } Ok(()) } } // TODO implement pretty tables -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub enum Format { Table, } @@ -134,33 +129,33 @@ impl FromStr for Format { fn from_str(s: &str) -> Result { match s { - "t" | "table" => Ok(Format::Table), + "t" | "table" => Ok(Self::Table), _ => Err(format!("Unrecognized format `{s}`")), } } } /// Additional filters that can be applied on the test results -#[derive(Debug, Clone, Parser, Default)] -struct SnapshotConfig { +#[derive(Clone, Debug, Default, Parser)] +struct GasSnapshotConfig { /// Sort results by gas used (ascending). - #[clap(long)] + #[arg(long)] asc: bool, /// Sort results by gas used (descending). - #[clap(conflicts_with = "asc", long)] + #[arg(conflicts_with = "asc", long)] desc: bool, /// Only include tests that used more gas that the given amount. - #[clap(long, value_name = "MIN_GAS")] + #[arg(long, value_name = "MIN_GAS")] min: Option, /// Only include tests that used less gas that the given amount. - #[clap(long, value_name = "MAX_GAS")] + #[arg(long, value_name = "MAX_GAS")] max: Option, } -impl SnapshotConfig { +impl GasSnapshotConfig { fn is_in_gas_range(&self, gas_used: u64) -> bool { if let Some(min) = self.min { if gas_used < min { @@ -175,7 +170,7 @@ impl SnapshotConfig { true } - fn apply(&self, outcome: TestOutcome) -> Vec { + fn apply(&self, outcome: TestOutcome) -> Vec { let mut tests = outcome .into_tests() .filter(|test| self.is_in_gas_range(test.gas_used())) @@ -191,20 +186,20 @@ impl SnapshotConfig { } } -/// A general entry in a snapshot file +/// A general entry in a gas snapshot file /// /// Has the form: /// `(gas:? 40181)` for normal tests /// `(runs: 256, μ: 40181, ~: 40181)` for fuzz tests /// `(runs: 256, calls: 40181, reverts: 40181)` for invariant tests -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SnapshotEntry { +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GasSnapshotEntry { pub contract_name: String, pub signature: String, pub gas_used: TestKindReport, } -impl FromStr for SnapshotEntry { +impl FromStr for GasSnapshotEntry { type Err = String; fn from_str(s: &str) -> Result { @@ -214,17 +209,17 @@ impl FromStr for SnapshotEntry { cap.name("file").and_then(|file| { cap.name("sig").and_then(|sig| { if let Some(gas) = cap.name("gas") { - Some(SnapshotEntry { + Some(Self { contract_name: file.as_str().to_string(), signature: sig.as_str().to_string(), - gas_used: TestKindReport::Standard { + gas_used: TestKindReport::Unit { gas: gas.as_str().parse().unwrap(), }, }) } else if let Some(runs) = cap.name("runs") { cap.name("avg") .and_then(|avg| cap.name("med").map(|med| (runs, avg, med))) - .map(|(runs, avg, med)| SnapshotEntry { + .map(|(runs, avg, med)| Self { contract_name: file.as_str().to_string(), signature: sig.as_str().to_string(), gas_used: TestKindReport::Fuzz { @@ -240,13 +235,14 @@ impl FromStr for SnapshotEntry { cap.name("reverts").map(|med| (runs, avg, med)) }) }) - .map(|(runs, calls, reverts)| SnapshotEntry { + .map(|(runs, calls, reverts)| Self { contract_name: file.as_str().to_string(), signature: sig.as_str().to_string(), gas_used: TestKindReport::Invariant { runs: runs.as_str().parse().unwrap(), calls: calls.as_str().parse().unwrap(), reverts: reverts.as_str().parse().unwrap(), + metrics: HashMap::default(), }, }) } @@ -257,8 +253,8 @@ impl FromStr for SnapshotEntry { } } -/// Reads a list of snapshot entries from a snapshot file -fn read_snapshot(path: impl AsRef) -> Result> { +/// Reads a list of gas snapshot entries from a gas snapshot file. +fn read_gas_snapshot(path: impl AsRef) -> Result> { let path = path.as_ref(); let mut entries = Vec::new(); for line in io::BufReader::new( @@ -267,14 +263,15 @@ fn read_snapshot(path: impl AsRef) -> Result> { ) .lines() { - entries.push(SnapshotEntry::from_str(line?.as_str()).map_err(|err| eyre::eyre!("{err}"))?); + entries + .push(GasSnapshotEntry::from_str(line?.as_str()).map_err(|err| eyre::eyre!("{err}"))?); } Ok(entries) } -/// Writes a series of tests to a snapshot file after sorting them -fn write_to_snapshot_file( - tests: &[Test], +/// Writes a series of tests to a gas snapshot file after sorting them. +fn write_to_gas_snapshot_file( + tests: &[SuiteTestResult], path: impl AsRef, _format: Option, ) -> Result<()> { @@ -292,15 +289,15 @@ fn write_to_snapshot_file( Ok(fs::write(path, content)?) } -/// A Snapshot entry diff -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct SnapshotDiff { +/// A Gas snapshot entry diff. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GasSnapshotDiff { pub signature: String, pub source_gas_used: TestKindReport, pub target_gas_used: TestKindReport, } -impl SnapshotDiff { +impl GasSnapshotDiff { /// Returns the gas diff /// /// `> 0` if the source used more gas @@ -315,10 +312,14 @@ impl SnapshotDiff { } } -/// Compares the set of tests with an existing snapshot +/// Compares the set of tests with an existing gas snapshot. /// /// Returns true all tests match -fn check(tests: Vec, snaps: Vec, tolerance: Option) -> bool { +fn check( + tests: Vec, + snaps: Vec, + tolerance: Option, +) -> bool { let snaps = snaps .into_iter() .map(|s| ((s.contract_name, s.signature), s.gas_used)) @@ -330,7 +331,7 @@ fn check(tests: Vec, snaps: Vec, tolerance: Option) -> { let source_gas = test.result.kind.report(); if !within_tolerance(source_gas.gas(), target_gas.gas(), tolerance) { - eprintln!( + let _ = sh_println!( "Diff in \"{}::{}\": consumed \"{}\" gas, expected \"{}\" gas ", test.contract_name(), test.signature, @@ -340,7 +341,7 @@ fn check(tests: Vec, snaps: Vec, tolerance: Option) -> has_diff = true; } } else { - eprintln!( + let _ = sh_println!( "No matching snapshot entry found for \"{}::{}\" in snapshot file", test.contract_name(), test.signature @@ -351,8 +352,8 @@ fn check(tests: Vec, snaps: Vec, tolerance: Option) -> !has_diff } -/// Compare the set of tests with an existing snapshot -fn diff(tests: Vec, snaps: Vec) -> Result<()> { +/// Compare the set of tests with an existing gas snapshot. +fn diff(tests: Vec, snaps: Vec) -> Result<()> { let snaps = snaps .into_iter() .map(|s| ((s.contract_name, s.signature), s.gas_used)) @@ -362,7 +363,7 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> { if let Some(target_gas_used) = snaps.get(&(test.contract_name().to_string(), test.signature.clone())).cloned() { - diffs.push(SnapshotDiff { + diffs.push(GasSnapshotDiff { source_gas_used: test.result.kind.report(), signature: test.signature, target_gas_used, @@ -381,41 +382,41 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> { overall_gas_change += gas_change; overall_gas_used += diff.target_gas_used.gas() as i128; let gas_diff = diff.gas_diff(); - println!( + sh_println!( "{} (gas: {} ({})) ", diff.signature, fmt_change(gas_change), fmt_pct_change(gas_diff) - ); + )?; } let overall_gas_diff = overall_gas_change as f64 / overall_gas_used as f64; - println!( + sh_println!( "Overall gas change: {} ({})", fmt_change(overall_gas_change), fmt_pct_change(overall_gas_diff) - ); + )?; Ok(()) } fn fmt_pct_change(change: f64) -> String { let change_pct = change * 100.0; match change.partial_cmp(&0.0).unwrap_or(Ordering::Equal) { - Ordering::Less => Paint::green(format!("{change_pct:.3}%")).to_string(), + Ordering::Less => format!("{change_pct:.3}%").green().to_string(), Ordering::Equal => { format!("{change_pct:.3}%") } - Ordering::Greater => Paint::red(format!("{change_pct:.3}%")).to_string(), + Ordering::Greater => format!("{change_pct:.3}%").red().to_string(), } } fn fmt_change(change: i128) -> String { match change.cmp(&0) { - Ordering::Less => Paint::green(format!("{change}")).to_string(), + Ordering::Less => format!("{change}").green().to_string(), Ordering::Equal => { format!("{change}") } - Ordering::Greater => Paint::red(format!("{change}")).to_string(), + Ordering::Greater => format!("{change}").red().to_string(), } } @@ -450,26 +451,26 @@ mod tests { } #[test] - fn can_parse_basic_snapshot_entry() { + fn can_parse_basic_gas_snapshot_entry() { let s = "Test:deposit() (gas: 7222)"; - let entry = SnapshotEntry::from_str(s).unwrap(); + let entry = GasSnapshotEntry::from_str(s).unwrap(); assert_eq!( entry, - SnapshotEntry { + GasSnapshotEntry { contract_name: "Test".to_string(), signature: "deposit()".to_string(), - gas_used: TestKindReport::Standard { gas: 7222 } + gas_used: TestKindReport::Unit { gas: 7222 } } ); } #[test] - fn can_parse_fuzz_snapshot_entry() { + fn can_parse_fuzz_gas_snapshot_entry() { let s = "Test:deposit() (runs: 256, μ: 100, ~:200)"; - let entry = SnapshotEntry::from_str(s).unwrap(); + let entry = GasSnapshotEntry::from_str(s).unwrap(); assert_eq!( entry, - SnapshotEntry { + GasSnapshotEntry { contract_name: "Test".to_string(), signature: "deposit()".to_string(), gas_used: TestKindReport::Fuzz { runs: 256, median_gas: 200, mean_gas: 100 } @@ -478,29 +479,39 @@ mod tests { } #[test] - fn can_parse_invariant_snapshot_entry() { + fn can_parse_invariant_gas_snapshot_entry() { let s = "Test:deposit() (runs: 256, calls: 100, reverts: 200)"; - let entry = SnapshotEntry::from_str(s).unwrap(); + let entry = GasSnapshotEntry::from_str(s).unwrap(); assert_eq!( entry, - SnapshotEntry { + GasSnapshotEntry { contract_name: "Test".to_string(), signature: "deposit()".to_string(), - gas_used: TestKindReport::Invariant { runs: 256, calls: 100, reverts: 200 } + gas_used: TestKindReport::Invariant { + runs: 256, + calls: 100, + reverts: 200, + metrics: HashMap::default() + } } ); } #[test] - fn can_parse_invariant_snapshot_entry2() { + fn can_parse_invariant_gas_snapshot_entry2() { let s = "ERC20Invariants:invariantBalanceSum() (runs: 256, calls: 3840, reverts: 2388)"; - let entry = SnapshotEntry::from_str(s).unwrap(); + let entry = GasSnapshotEntry::from_str(s).unwrap(); assert_eq!( entry, - SnapshotEntry { + GasSnapshotEntry { contract_name: "ERC20Invariants".to_string(), signature: "invariantBalanceSum()".to_string(), - gas_used: TestKindReport::Invariant { runs: 256, calls: 3840, reverts: 2388 } + gas_used: TestKindReport::Invariant { + runs: 256, + calls: 3840, + reverts: 2388, + metrics: HashMap::default() + } } ); } diff --git a/crates/forge/bin/cmd/soldeer.rs b/crates/forge/bin/cmd/soldeer.rs new file mode 100644 index 0000000000000..a43600cf10406 --- /dev/null +++ b/crates/forge/bin/cmd/soldeer.rs @@ -0,0 +1,47 @@ +use clap::Parser; +use eyre::Result; +use soldeer_commands::Command; + +// CLI arguments for `forge soldeer`. +// The following list of commands and their actions: +// +// forge soldeer install: looks up the config file and install all the dependencies that are present +// there forge soldeer install package~version: looks up on https://soldeer.xyz and if the package>version is there then add to config+lockfile and install new dependency. Replaces existing entry if version is different. +// forge soldeer install package~version url: same behavior as install but instead of looking at https://soldeer.xyz it choses the URL, which can be git or custom zip url +// forge soldeer update: same behavior as install looks up the config file and install all the +// dependencies that are present there. This will change in the future forge soldeer login: logs in into https://soldeer.xyz account +// forge soldeer push package~version: pushes files to the central repository +// forge soldeer version: checks soldeer version +// forge soldeer init: initializes a new project with minimal dependency for foundry setup, install +// latest forge-std version forge soldeer uninstall dependency: uninstalls a dependency, removes +// artifacts and configs + +#[derive(Clone, Debug, Parser)] +#[clap( + override_usage = "Native Solidity Package Manager, `run forge soldeer [COMMAND] --help` for more details" +)] +pub struct SoldeerArgs { + /// Command must be one of the following init/install/login/push/uninstall/update/version. + #[command(subcommand)] + command: Command, +} + +impl SoldeerArgs { + pub async fn run(self) -> Result<()> { + match soldeer_commands::run(self.command).await { + Ok(_) => Ok(()), + Err(err) => Err(eyre::eyre!("Failed to run soldeer {}", err)), + } + } +} + +#[cfg(test)] +mod tests { + use soldeer_commands::{commands::Version, Command}; + + #[tokio::test] + async fn test_soldeer_version() { + let command = Command::Version(Version::default()); + assert!(soldeer_commands::run(command).await.is_ok()); + } +} diff --git a/crates/forge/bin/cmd/test/filter.rs b/crates/forge/bin/cmd/test/filter.rs index 572ad327700e7..ec2e9b01b50e8 100644 --- a/crates/forge/bin/cmd/test/filter.rs +++ b/crates/forge/bin/cmd/test/filter.rs @@ -1,71 +1,84 @@ use clap::Parser; -use ethers::solc::{FileFilter, ProjectPathsConfig}; -use forge::TestFilter; -use foundry_cli::utils::FoundryPathExt; -use foundry_common::glob::GlobMatcher; -use foundry_config::Config; +use foundry_common::TestFilter; +use foundry_compilers::{FileFilter, ProjectPathsConfig}; +use foundry_config::{filter::GlobMatcher, Config}; use std::{fmt, path::Path}; /// The filter to use during testing. /// /// See also `FileFilter`. #[derive(Clone, Parser)] -#[clap(next_help_heading = "Test filtering")] +#[command(next_help_heading = "Test filtering")] pub struct FilterArgs { /// Only run test functions matching the specified regex pattern. - #[clap(long = "match-test", visible_alias = "mt", value_name = "REGEX")] + #[arg(long = "match-test", visible_alias = "mt", value_name = "REGEX")] pub test_pattern: Option, /// Only run test functions that do not match the specified regex pattern. - #[clap(long = "no-match-test", visible_alias = "nmt", value_name = "REGEX")] + #[arg(long = "no-match-test", visible_alias = "nmt", value_name = "REGEX")] pub test_pattern_inverse: Option, /// Only run tests in contracts matching the specified regex pattern. - #[clap(long = "match-contract", visible_alias = "mc", value_name = "REGEX")] + #[arg(long = "match-contract", visible_alias = "mc", value_name = "REGEX")] pub contract_pattern: Option, /// Only run tests in contracts that do not match the specified regex pattern. - #[clap(long = "no-match-contract", visible_alias = "nmc", value_name = "REGEX")] + #[arg(long = "no-match-contract", visible_alias = "nmc", value_name = "REGEX")] pub contract_pattern_inverse: Option, /// Only run tests in source files matching the specified glob pattern. - #[clap(long = "match-path", visible_alias = "mp", value_name = "GLOB")] + #[arg(long = "match-path", visible_alias = "mp", value_name = "GLOB")] pub path_pattern: Option, /// Only run tests in source files that do not match the specified glob pattern. - #[clap( - name = "no-match-path", + #[arg( + id = "no-match-path", long = "no-match-path", visible_alias = "nmp", 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 { + /// Returns true if the filter is empty. + pub fn is_empty(&self) -> bool { + self.test_pattern.is_none() && + self.test_pattern_inverse.is_none() && + self.contract_pattern.is_none() && + self.contract_pattern_inverse.is_none() && + self.path_pattern.is_none() && + self.path_pattern_inverse.is_none() + } + /// Merges the set filter globs with the config's values - pub fn merge_with_config(&self, config: &Config) -> ProjectPathsAwareFilter { - let mut filter = self.clone(); - if filter.test_pattern.is_none() { - filter.test_pattern = config.test_pattern.clone().map(|p| p.into()); + pub fn merge_with_config(mut self, config: &Config) -> ProjectPathsAwareFilter { + if self.test_pattern.is_none() { + self.test_pattern = config.test_pattern.clone().map(Into::into); + } + if self.test_pattern_inverse.is_none() { + self.test_pattern_inverse = config.test_pattern_inverse.clone().map(Into::into); } - if filter.test_pattern_inverse.is_none() { - filter.test_pattern_inverse = config.test_pattern_inverse.clone().map(|p| p.into()); + if self.contract_pattern.is_none() { + self.contract_pattern = config.contract_pattern.clone().map(Into::into); } - if filter.contract_pattern.is_none() { - filter.contract_pattern = config.contract_pattern.clone().map(|p| p.into()); + if self.contract_pattern_inverse.is_none() { + self.contract_pattern_inverse = config.contract_pattern_inverse.clone().map(Into::into); } - if filter.contract_pattern_inverse.is_none() { - filter.contract_pattern_inverse = - config.contract_pattern_inverse.clone().map(|p| p.into()); + if self.path_pattern.is_none() { + self.path_pattern = config.path_pattern.clone().map(Into::into); } - if filter.path_pattern.is_none() { - filter.path_pattern = config.path_pattern.clone().map(Into::into); + if self.path_pattern_inverse.is_none() { + self.path_pattern_inverse = config.path_pattern_inverse.clone().map(Into::into); } - if filter.path_pattern_inverse.is_none() { - filter.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: filter, paths: config.project_paths() } + ProjectPathsAwareFilter { args_filter: self, paths: config.project_paths() } } } @@ -78,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() } } @@ -85,133 +99,131 @@ impl fmt::Debug for FilterArgs { impl FileFilter for FilterArgs { /// Returns true if the file regex pattern match the `file` /// - /// If no file regex is set this returns true if the file ends with `.t.sol`, see - /// [FoundryPathExr::is_sol_test()] + /// If no file regex is set this returns true by default fn is_match(&self, file: &Path) -> bool { - if let Some(file) = file.as_os_str().to_str() { - if let Some(ref glob) = self.path_pattern { - return glob.is_match(file) - } - if let Some(ref glob) = self.path_pattern_inverse { - return !glob.is_match(file) - } - } - file.is_sol_test() + self.matches_path(file) } } impl TestFilter for FilterArgs { - fn matches_test(&self, test_name: impl AsRef) -> bool { + fn matches_test(&self, test_name: &str) -> bool { let mut ok = true; - let test_name = test_name.as_ref(); if let Some(re) = &self.test_pattern { - ok &= re.is_match(test_name); + ok = ok && re.is_match(test_name); } if let Some(re) = &self.test_pattern_inverse { - ok &= !re.is_match(test_name); + ok = ok && !re.is_match(test_name); } ok } - fn matches_contract(&self, contract_name: impl AsRef) -> bool { + fn matches_contract(&self, contract_name: &str) -> bool { let mut ok = true; - let contract_name = contract_name.as_ref(); if let Some(re) = &self.contract_pattern { - ok &= re.is_match(contract_name); + ok = ok && re.is_match(contract_name); } if let Some(re) = &self.contract_pattern_inverse { - ok &= !re.is_match(contract_name); + ok = ok && !re.is_match(contract_name); } ok } - fn matches_path(&self, path: impl AsRef) -> bool { + fn matches_path(&self, path: &Path) -> bool { let mut ok = true; - let path = path.as_ref(); - if let Some(ref glob) = self.path_pattern { - ok &= glob.is_match(path); + if let Some(re) = &self.path_pattern { + ok = ok && re.is_match(path); } - if let Some(ref glob) = self.path_pattern_inverse { - ok &= !glob.is_match(path); + if let Some(re) = &self.path_pattern_inverse { + ok = ok && !re.is_match(path); } ok } } impl fmt::Display for FilterArgs { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut patterns = Vec::new(); - if let Some(ref p) = self.test_pattern { - patterns.push(format!("\tmatch-test: `{}`", p.as_str())); + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(p) = &self.test_pattern { + writeln!(f, "\tmatch-test: `{}`", p.as_str())?; + } + if let Some(p) = &self.test_pattern_inverse { + writeln!(f, "\tno-match-test: `{}`", p.as_str())?; } - if let Some(ref p) = self.test_pattern_inverse { - patterns.push(format!("\tno-match-test: `{}`", p.as_str())); + if let Some(p) = &self.contract_pattern { + writeln!(f, "\tmatch-contract: `{}`", p.as_str())?; } - if let Some(ref p) = self.contract_pattern { - patterns.push(format!("\tmatch-contract: `{}`", p.as_str())); + if let Some(p) = &self.contract_pattern_inverse { + writeln!(f, "\tno-match-contract: `{}`", p.as_str())?; } - if let Some(ref p) = self.contract_pattern_inverse { - patterns.push(format!("\tno-match-contract: `{}`", p.as_str())); + if let Some(p) = &self.path_pattern { + writeln!(f, "\tmatch-path: `{}`", p.as_str())?; } - if let Some(ref p) = self.path_pattern { - patterns.push(format!("\tmatch-path: `{}`", p.as_str())); + if let Some(p) = &self.path_pattern_inverse { + writeln!(f, "\tno-match-path: `{}`", p.as_str())?; } - if let Some(ref p) = self.path_pattern_inverse { - patterns.push(format!("\tno-match-path: `{}`", p.as_str())); + if let Some(p) = &self.coverage_pattern_inverse { + writeln!(f, "\tno-match-coverage: `{}`", p.as_str())?; } - write!(f, "{}", patterns.join("\n")) + Ok(()) } } /// A filter that combines all command line arguments and the paths of the current projects -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct ProjectPathsAwareFilter { args_filter: FilterArgs, paths: ProjectPathsConfig, } -// === impl ProjectPathsAwareFilter === - impl ProjectPathsAwareFilter { - /// Returns the CLI arguments + /// Returns true if the filter is empty. + pub fn is_empty(&self) -> bool { + self.args_filter.is_empty() + } + + /// Returns the CLI arguments. pub fn args(&self) -> &FilterArgs { &self.args_filter } - /// Returns the CLI arguments mutably + /// Returns the CLI arguments mutably. 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 { /// Returns true if the file regex pattern match the `file` /// - /// If no file regex is set this returns true if the file ends with `.t.sol`, see - /// [FoundryPathExr::is_sol_test()] - fn is_match(&self, file: &Path) -> bool { + /// If no file regex is set this returns true by default + fn is_match(&self, mut file: &Path) -> bool { + file = file.strip_prefix(&self.paths.root).unwrap_or(file); self.args_filter.is_match(file) } } impl TestFilter for ProjectPathsAwareFilter { - fn matches_test(&self, test_name: impl AsRef) -> bool { + fn matches_test(&self, test_name: &str) -> bool { self.args_filter.matches_test(test_name) } - fn matches_contract(&self, contract_name: impl AsRef) -> bool { + fn matches_contract(&self, contract_name: &str) -> bool { self.args_filter.matches_contract(contract_name) } - fn matches_path(&self, path: impl AsRef) -> bool { - let path = path.as_ref(); + fn matches_path(&self, mut path: &Path) -> bool { // we don't want to test files that belong to a library - self.args_filter.matches_path(path) && !self.paths.has_library_ancestor(Path::new(path)) + path = path.strip_prefix(&self.paths.root).unwrap_or(path); + self.args_filter.matches_path(path) && !self.paths.has_library_ancestor(path) } } impl fmt::Display for ProjectPathsAwareFilter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.args_filter.fmt(f) } } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index f5648b850b899..709908d6698fd 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -1,26 +1,34 @@ -use super::{debug::DebugArgs, install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; -use clap::Parser; -use ethers::types::U256; -use eyre::Result; +use super::{install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; +use alloy_primitives::U256; +use chrono::Utc; +use clap::{Parser, ValueHint}; +use eyre::{Context, OptionExt, Result}; use forge::{ decode::decode_console_logs, - executor::inspector::CheatsConfig, gas_report::GasReport, - result::{SuiteResult, TestKind, TestResult, TestStatus}, - trace::{ - identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, - CallTraceDecoderBuilder, TraceKind, + multi_runner::matches_contract, + result::{SuiteResult, TestOutcome, TestStatus}, + traces::{ + debug::{ContractSources, DebugTraceIdentifier}, + decode_trace_arena, folded_stack_trace, + identifier::SignaturesIdentifier, + CallTraceDecoderBuilder, InternalTraceMode, TraceKind, }, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, + MultiContractRunner, MultiContractRunnerBuilder, TestFilter, }; use foundry_cli::{ - opts::CoreBuildArgs, + opts::{BuildOpts, GlobalArgs}, utils::{self, LoadConfig}, }; -use foundry_common::{ - compile::{self, ProjectCompiler}, - evm::EvmArgs, - get_contract_name, get_file_name, shell, +use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell, TestFunctionExt}; +use foundry_compilers::{ + artifacts::output_selection::OutputSelection, + compilers::{ + multi::{MultiCompiler, MultiCompilerLanguage}, + Language, + }, + utils::source_files_iter, + ProjectCompileOutput, }; use foundry_config::{ figment, @@ -28,237 +36,798 @@ use foundry_config::{ value::{Dict, Map}, Metadata, Profile, Provider, }, - get_available_profiles, Config, + filter::GlobMatcher, + Config, }; -use foundry_evm::{fuzz::CounterExample, utils::evm_spec}; +use foundry_debugger::Debugger; +use foundry_evm::traces::identifier::TraceIdentifiers; use regex::Regex; -use std::{collections::BTreeMap, path::PathBuf, sync::mpsc::channel, time::Duration}; -use tracing::trace; -use watchexec::config::{InitConfig, RuntimeConfig}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Write, + path::PathBuf, + sync::{mpsc::channel, Arc}, + time::{Duration, Instant}, +}; use yansi::Paint; mod filter; +mod summary; pub use filter::FilterArgs; +use forge::{result::TestKind, traces::render_trace_arena_inner}; +use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite}; +use summary::{format_invariant_metrics_table, TestSummaryReport}; // Loads project's figment and merges the build cli arguments into it -foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); +foundry_config::merge_impl_figment_convert!(TestArgs, build, evm); /// CLI arguments for `forge test`. -#[derive(Debug, Clone, Parser)] -#[clap(next_help_heading = "Test options")] +#[derive(Clone, Debug, Parser)] +#[command(next_help_heading = "Test options")] pub struct TestArgs { - /// Run a test in the debugger. - /// - /// The argument passed to this flag is the name of the test function you want to run, and it - /// works the same as --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). + // Include global options for users of this struct. + #[command(flatten)] + pub global: GlobalArgs, + + /// The contract file you want to test, it's a shortcut for --match-path. + #[arg(value_hint = ValueHint::FilePath)] + pub path: Option, + + /// Run a single test in the debugger. /// /// The matching test will be opened in the debugger regardless of the outcome of the test. /// /// If the matching test is a fuzz test, then it will open the debugger on the first failure - /// case. - /// If the fuzz test does not fail, it will open the debugger on the last fuzz case. + /// case. If the fuzz test does not fail, it will open the debugger on the last fuzz case. + #[arg(long, conflicts_with_all = ["flamegraph", "flamechart", "decode_internal", "rerun"])] + debug: bool, + + /// Generate a flamegraph for a single test. Implies `--decode-internal`. + /// + /// A flame graph is used to visualize which functions or operations within the smart contract + /// are consuming the most gas overall in a sorted manner. + #[arg(long)] + flamegraph: bool, + + /// Generate a flamechart for a single test. Implies `--decode-internal`. + /// + /// A flame chart shows the gas usage over time, illustrating when each function is + /// called (execution order) and how much gas it consumes at each point in the timeline. + #[arg(long, conflicts_with = "flamegraph")] + flamechart: bool, + + /// Identify internal functions in traces. + /// + /// This will trace internal functions and decode stack parameters. /// - /// For more fine-grained control of which fuzz case is run, see forge run. - #[clap(long, value_name = "TEST_FUNCTION")] - debug: Option, + /// Parameters stored in memory (such as bytes or arrays) are currently decoded only when a + /// single function is matched, similarly to `--debug`, for performance reasons. + #[arg(long)] + decode_internal: bool, + + /// Dumps all debugger steps to file. + #[arg( + long, + requires = "debug", + value_hint = ValueHint::FilePath, + value_name = "PATH" + )] + dump: Option, /// Print a gas report. - #[clap(long, env = "FORGE_GAS_REPORT")] + #[arg(long, env = "FORGE_GAS_REPORT")] gas_report: bool, + /// Check gas snapshots against previous runs. + #[arg(long, env = "FORGE_SNAPSHOT_CHECK")] + gas_snapshot_check: Option, + + /// Enable/disable recording of gas snapshot results. + #[arg(long, env = "FORGE_SNAPSHOT_EMIT")] + gas_snapshot_emit: Option, + /// Exit with code 0 even if a test fails. - #[clap(long, env = "FORGE_ALLOW_FAILURE")] + #[arg(long, env = "FORGE_ALLOW_FAILURE")] allow_failure: bool, - /// Output test results in JSON format. - #[clap(long, short, help_heading = "Display options")] - json: bool, + /// Suppress successful test traces and show only traces for failures. + #[arg(long, short, env = "FORGE_SUPPRESS_SUCCESSFUL_TRACES", help_heading = "Display options")] + suppress_successful_traces: bool, - /// Stop running tests after the first failure - #[clap(long)] + /// Output test results as JUnit XML report. + #[arg(long, conflicts_with_all = ["quiet", "json", "gas_report", "summary", "list", "show_progress"], help_heading = "Display options")] + pub junit: bool, + + /// Stop running tests after the first failure. + #[arg(long)] pub fail_fast: bool, - /// The Etherscan (or equivalent) API key - #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + /// The Etherscan (or equivalent) API key. + #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] etherscan_api_key: Option, - /// List tests instead of running them - #[clap(long, short, help_heading = "Display options")] + /// List tests instead of running them. + #[arg(long, short, conflicts_with_all = ["show_progress", "decode_internal", "summary"], help_heading = "Display options")] list: bool, /// Set seed used to generate randomness during your fuzz runs. - #[clap(long, value_parser = utils::parse_u256)] + #[arg(long)] pub fuzz_seed: Option, - #[clap(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] + #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, - #[clap(flatten)] + /// Timeout for each fuzz run in seconds. + #[arg(long, env = "FOUNDRY_FUZZ_TIMEOUT", value_name = "TIMEOUT")] + pub fuzz_timeout: Option, + + /// File to rerun fuzz failures from. + #[arg(long)] + pub fuzz_input_file: Option, + + /// Show test execution progress. + #[arg(long, conflicts_with_all = ["quiet", "json"], help_heading = "Display options")] + pub show_progress: bool, + + /// Re-run recorded test failures from last run. + /// If no failure recorded then regular test run is performed. + #[arg(long)] + pub rerun: bool, + + /// Print test summary table. + #[arg(long, help_heading = "Display options")] + pub summary: bool, + + /// Print detailed test summary table. + #[arg(long, help_heading = "Display options", requires = "summary")] + pub detailed: bool, + + #[command(flatten)] filter: FilterArgs, - #[clap(flatten)] - evm_opts: EvmArgs, + #[command(flatten)] + evm: EvmArgs, - #[clap(flatten)] - opts: CoreBuildArgs, + #[command(flatten)] + pub build: BuildOpts, - #[clap(flatten)] + #[command(flatten)] pub watch: WatchArgs, } impl TestArgs { - /// Returns the flattened [`CoreBuildArgs`]. - pub fn build_args(&self) -> &CoreBuildArgs { - &self.opts - } - pub async fn run(self) -> Result { trace!(target: "forge::test", "executing test command"); - shell::set_shell(shell::Shell::from_args(self.opts.silent, self.json))?; self.execute_tests().await } + /// Returns sources which include any tests to be executed. + /// If no filters are provided, sources are filtered by existence of test/invariant methods in + /// them, If filters are provided, sources are additionally filtered by them. + pub fn get_sources_to_compile( + &self, + config: &Config, + filter: &ProjectPathsAwareFilter, + ) -> Result> { + let mut project = config.create_project(true, true)?; + project.update_output_selection(|selection| { + *selection = OutputSelection::common_output_selection(["abi".to_string()]); + }); + + let output = project.compile()?; + + if output.has_compiler_errors() { + sh_println!("{output}")?; + eyre::bail!("Compilation failed"); + } + + // ABIs of all sources + let abis = output + .into_artifacts() + .filter_map(|(id, artifact)| artifact.abi.map(|abi| (id, abi))) + .collect::>(); + + // Filter sources by their abis and contract names. + let mut test_sources = abis + .iter() + .filter(|(id, abi)| matches_contract(id, abi, filter)) + .map(|(id, _)| id.source.clone()) + .collect::>(); + + if test_sources.is_empty() { + if filter.is_empty() { + sh_println!( + "No tests found in project! \ + Forge looks for functions that starts with `test`." + )?; + } else { + sh_println!("No tests match the provided pattern:")?; + sh_print!("{filter}")?; + + // Try to suggest a test when there's no match + if let Some(test_pattern) = &filter.args().test_pattern { + let test_name = test_pattern.as_str(); + let candidates = abis + .into_iter() + .filter(|(id, _)| { + filter.matches_path(&id.source) && filter.matches_contract(&id.name) + }) + .flat_map(|(_, abi)| abi.functions.into_keys()) + .collect::>(); + if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { + sh_println!("\nDid you mean `{suggestion}`?")?; + } + } + } + + eyre::bail!("No tests to run"); + } + + // Always recompile all sources to ensure that `getCode` cheatcode can use any artifact. + test_sources.extend(source_files_iter( + &project.paths.sources, + MultiCompilerLanguage::FILE_EXTENSIONS, + )); + + Ok(test_sources) + } + /// Executes all the tests in the project. /// /// This will trigger the build process first. On success all test contracts that match the /// configured filter will be executed /// /// 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()?; + pub async fn execute_tests(mut self) -> Result { + // Merge all configs. + let (mut config, mut evm_opts) = self.load_config_and_evm_opts()?; + + // Explicitly enable isolation for gas reports for more correct gas accounting. + if self.gas_report { + evm_opts.isolate = true; + } else { + // Do not collect gas report traces if gas report is not enabled. + config.fuzz.gas_report_samples = 0; + config.invariant.gas_report_samples = 0; + } + + // Install missing dependencies. + if install::install_missing_dependencies(&mut config) && config.auto_detect_remappings { + // need to re-configure here to also catch additional remappings + config = self.load_config()?; + } - let mut filter = self.filter(&config); + // Set up the project. + let project = config.project()?; + let filter = self.filter(&config); trace!(target: "forge::test", ?filter, "using filter"); - // Set up the project - let mut project = config.project()?; + let sources_to_compile = self.get_sources_to_compile(&config, &filter)?; - // install missing dependencies - if install::install_missing_dependencies(&mut config, self.build_args().silent) && - config.auto_detect_remappings - { - // need to re-configure here to also catch additional remappings - config = self.load_config(); - project = config.project()?; - } + let compiler = + ProjectCompiler::new().quiet(shell::is_json() || self.junit).files(sources_to_compile); - let compiler = ProjectCompiler::default(); - let output = if config.sparse_mode { - compiler.compile_sparse(&project, filter.clone()) - } else if self.opts.silent { - compile::suppress_compile(&project) - } else { - compiler.compile(&project) - }?; + let output = compiler.compile(&project)?; - // Create test options from general project settings - // and compiler output + // Create test options from general project settings and compiler output. let project_root = &project.paths.root; - let toml = config.get_config_path(); - let profiles = get_available_profiles(toml)?; - let test_options: TestOptions = TestOptionsBuilder::default() - .fuzz(config.fuzz) - .invariant(config.invariant) - .compile_output(&output) - .profiles(profiles) - .build(project_root)?; + let should_debug = self.debug; + let should_draw = self.flamegraph || self.flamechart; - // 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 { + if (self.gas_report && evm_opts.verbosity < 3) || self.flamegraph || self.flamechart { evm_opts.verbosity = 3; } let env = evm_opts.evm_env().await?; - // Prepare the test builder - let evm_spec = evm_spec(&config.evm_version); + // Enable internal tracing for more informative flamegraph. + if should_draw && !self.decode_internal { + self.decode_internal = true; + } - let mut runner = MultiContractRunnerBuilder::default() + // Choose the internal function tracing mode, if --decode-internal is provided. + let decode_internal = if self.decode_internal { + // If more than one function matched, we enable simple tracing. + // If only one function matched, we enable full tracing. This is done in `run_tests`. + InternalTraceMode::Simple + } else { + InternalTraceMode::None + }; + + // Prepare the test builder. + let config = Arc::new(config); + let runner = MultiContractRunnerBuilder::new(config.clone()) + .set_debug(should_debug) + .set_decode_internal(decode_internal) .initial_balance(evm_opts.initial_balance) - .evm_spec(evm_spec) + .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new(&config, &evm_opts)) - .with_test_options(test_options.clone()) - .build(project_root, output, env, evm_opts)?; - - if self.debug.is_some() { - filter.args_mut().test_pattern = self.debug; - - match runner.count_filtered_tests(&filter) { - 1 => { - // Run the test - let results = runner.test(&filter, None, test_options).await; - - // Get the result of the single test - let (id, sig, test_kind, counterexample, breakpoints) = results.iter().map(|(id, SuiteResult{ test_results, .. })| { - let (sig, result) = test_results.iter().next().unwrap(); - - (id.clone(), sig.clone(), result.kind.clone(), result.counterexample.clone(), result.breakpoints.clone()) - }).next().unwrap(); - - // Build debugger args if this is a fuzz test - let sig = match test_kind { - TestKind::Fuzz { first_case, .. } => { - if let Some(CounterExample::Single(counterexample)) = counterexample { - counterexample.calldata.to_string() - } else { - first_case.calldata.to_string() + .enable_isolation(evm_opts.isolate) + .odyssey(evm_opts.odyssey) + .build::(project_root, &output, env, evm_opts)?; + + let libraries = runner.libraries.clone(); + let mut outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; + + if should_draw { + let (suite_name, test_name, mut test_result) = + outcome.remove_first().ok_or_eyre("no tests were executed")?; + + let (_, arena) = test_result + .traces + .iter_mut() + .find(|(kind, _)| *kind == TraceKind::Execution) + .unwrap(); + + // Decode traces. + let decoder = outcome.last_run_decoder.as_ref().unwrap(); + decode_trace_arena(arena, decoder).await?; + let mut fst = folded_stack_trace::build(arena); + + let label = if self.flamegraph { "flamegraph" } else { "flamechart" }; + let contract = suite_name.split(':').next_back().unwrap(); + let test_name = test_name.trim_end_matches("()"); + let file_name = format!("cache/{label}_{contract}_{test_name}.svg"); + let file = std::fs::File::create(&file_name).wrap_err("failed to create file")?; + let file = std::io::BufWriter::new(file); + + let mut options = inferno::flamegraph::Options::default(); + options.title = format!("{label} {contract}::{test_name}"); + options.count_name = "gas".to_string(); + if self.flamechart { + options.flame_chart = true; + fst.reverse(); + } + + // Generate SVG. + inferno::flamegraph::from_lines(&mut options, fst.iter().map(String::as_str), file) + .wrap_err("failed to write svg")?; + sh_println!("Saved to {file_name}")?; + + // Open SVG in default program. + if let Err(e) = opener::open(&file_name) { + sh_err!("Failed to open {file_name}; please open it manually: {e}")?; + } + } + + if should_debug { + // Get first non-empty suite result. We will have only one such entry. + let (_, _, test_result) = + outcome.remove_first().ok_or_eyre("no tests were executed")?; + + let sources = + ContractSources::from_project_output(&output, project.root(), Some(&libraries))?; + + // Run the debugger. + let mut builder = Debugger::builder() + .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(); + if let Some(dump_path) = self.dump { + debugger.dump_to_file(&dump_path)?; + } else { + debugger.try_run_tui()?; + } + } + + Ok(outcome) + } + + /// Run all tests that matches the filter predicate from a test runner + pub async fn run_tests( + &self, + mut runner: MultiContractRunner, + config: Arc, + verbosity: u8, + filter: &ProjectPathsAwareFilter, + output: &ProjectCompileOutput, + ) -> eyre::Result { + if self.list { + return list(runner, filter); + } + + trace!(target: "forge::test", "running all tests"); + + // If we need to render to a serialized format, we should not print anything else to stdout. + let silent = self.gas_report && shell::is_json() || self.summary && shell::is_json(); + + let num_filtered = runner.matching_test_functions(filter).count(); + if num_filtered != 1 && (self.debug || self.flamegraph || self.flamechart) { + let action = if self.flamegraph { + "generate a flamegraph" + } else if self.flamechart { + "generate a flamechart" + } else { + "run the debugger" + }; + let filter = if filter.is_empty() { + String::new() + } else { + format!("\n\nFilter used:\n{filter}") + }; + eyre::bail!( + "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to {action}.\n\n\ + Use --match-contract and --match-path to further limit the search.{filter}", + ); + } + + // If exactly one test matched, we enable full tracing. + if num_filtered == 1 && self.decode_internal { + runner.decode_internal = InternalTraceMode::Full; + } + + // Run tests in a non-streaming fashion and collect results for serialization. + if !self.gas_report && !self.summary && shell::is_json() { + let mut results = runner.test_collect(filter); + results.values_mut().for_each(|suite_result| { + for test_result in suite_result.test_results.values_mut() { + if verbosity >= 2 { + // Decode logs at level 2 and above. + test_result.decoded_logs = decode_console_logs(&test_result.logs); + } else { + // Empty logs for non verbose runs. + test_result.logs = vec![]; + } + } + }); + sh_println!("{}", serde_json::to_string(&results)?)?; + return Ok(TestOutcome::new(results, self.allow_failure)); + } + + if self.junit { + let results = runner.test_collect(filter); + sh_println!("{}", junit_xml_report(&results, verbosity).to_string()?)?; + return Ok(TestOutcome::new(results, self.allow_failure)); + } + + let 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 in a streaming fashion. + let (tx, rx) = channel::<(String, SuiteResult)>(); + let timer = Instant::now(); + let show_progress = config.show_progress; + let handle = tokio::task::spawn_blocking({ + let filter = filter.clone(); + move || runner.test(&filter, tx, show_progress) + }); + + // Set up trace identifiers. + let mut identifier = TraceIdentifiers::new().with_local(&known_contracts); + + // Avoid using etherscan for gas report as we decode more traces and this will be + // expensive. + if !self.gas_report { + identifier = identifier.with_etherscan(&config, remote_chain_id)?; + } + + // Build the trace decoder. + let mut builder = CallTraceDecoderBuilder::new() + .with_known_contracts(&known_contracts) + .with_verbosity(verbosity); + // Signatures are of no value for gas reports. + if !self.gas_report { + builder = builder.with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + config.offline, + )?); + } + + if self.decode_internal { + 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.gas_report.then(|| { + GasReport::new( + config.gas_reports.clone(), + config.gas_reports_ignore.clone(), + config.gas_reports_include_tests, + ) + }); + + let mut gas_snapshots = BTreeMap::>::new(); + + let mut outcome = TestOutcome::empty(self.allow_failure); + + let mut any_test_failed = false; + for (contract_name, suite_result) in rx { + let tests = &suite_result.test_results; + + // Clear the addresses and labels from previous test. + decoder.clear_addresses(); + + // We identify addresses if we're going to print *any* trace or gas report. + let identify_addresses = verbosity >= 3 || + self.gas_report || + self.debug || + self.flamegraph || + self.flamechart; + + // Print suite header. + if !silent { + sh_println!()?; + for warning in suite_result.warnings.iter() { + sh_warn!("{warning}")?; + } + if !tests.is_empty() { + let len = tests.len(); + let tests = if len > 1 { "tests" } else { "test" }; + sh_println!("Ran {len} {tests} for {contract_name}")?; + } + } + + // Process individual test results, printing logs and traces when necessary. + for (name, result) in tests { + let show_traces = + !self.suppress_successful_traces || result.status == TestStatus::Failure; + if !silent { + sh_println!("{}", result.short_result(name))?; + + // Display invariant metrics if invariant kind. + if let TestKind::Invariant { metrics, .. } = &result.kind { + if !metrics.is_empty() { + let _ = sh_println!("\n{}\n", format_invariant_metrics_table(metrics)); + } + } + + // We only display logs at level 2 and above + if verbosity >= 2 && show_traces { + // We only decode logs from Hardhat and DS-style console events + let console_logs = decode_console_logs(&result.logs); + if !console_logs.is_empty() { + sh_println!("Logs:")?; + for log in console_logs { + sh_println!(" {log}")?; } - }, - _ => sig, - }; + sh_println!()?; + } + } + } - // Run the debugger - let mut opts = self.opts.clone(); - opts.silent = true; - let debugger = DebugArgs { - path: PathBuf::from(runner.source_paths.get(&id).unwrap()), - target_contract: Some(get_contract_name(&id).to_string()), - sig, - args: Vec::new(), - debug: true, - opts, - evm_opts: self.evm_opts, + // We shouldn't break out of the outer loop directly here so that we finish + // processing the remaining tests and print the suite summary. + any_test_failed |= result.status == TestStatus::Failure; + + // Clear the addresses and labels from previous runs. + decoder.clear_addresses(); + decoder + .labels + .extend(result.labeled_addresses.iter().map(|(k, v)| (*k, v.clone()))); + + // Identify addresses and decode traces. + let mut decoded_traces = Vec::with_capacity(result.traces.len()); + for (kind, arena) in &mut result.traces.clone() { + if identify_addresses { + decoder.identify(arena, &mut identifier); + } + + // verbosity: + // - 0..3: nothing + // - 3: only display traces for failed tests + // - 4: also display the setup trace for failed tests + // - 5..: display all traces for all tests, including storage changes + let should_include = match kind { + TraceKind::Execution => { + (verbosity == 3 && result.status.is_failure()) || verbosity >= 4 + } + TraceKind::Setup => { + (verbosity == 4 && result.status.is_failure()) || verbosity >= 5 + } + TraceKind::Deployment => false, }; - debugger.debug(breakpoints).await?; - Ok(TestOutcome::new(results, self.allow_failure)) + if should_include { + decode_trace_arena(arena, &decoder).await?; + decoded_traces.push(render_trace_arena_inner(arena, false, verbosity > 4)); + } + } + + if !silent && show_traces && !decoded_traces.is_empty() { + sh_println!("Traces:")?; + for trace in &decoded_traces { + sh_println!("{trace}")?; + } + } + + if let Some(gas_report) = &mut gas_report { + gas_report.analyze(result.traces.iter().map(|(_, a)| &a.arena), &decoder).await; + + for trace in result.gas_report_traces.iter() { + decoder.clear_addresses(); + + // Re-execute setup and deployment traces to collect identities created in + // setUp and constructor. + for (kind, arena) in &result.traces { + if !matches!(kind, TraceKind::Execution) { + decoder.identify(arena, &mut identifier); + } + } + + for arena in trace { + decoder.identify(arena, &mut identifier); + gas_report.analyze([arena], &decoder).await; + } + } + } + + // Collect and merge gas snapshots. + for (group, new_snapshots) in result.gas_snapshots.iter() { + gas_snapshots.entry(group.clone()).or_default().extend(new_snapshots.clone()); } - n => - Err( - eyre::eyre!("{n} 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.")) } - } else if self.list { - list(runner, filter, self.json) - } else { - test( - config, - runner, - verbosity, - filter, - self.json, - self.allow_failure, - test_options, - self.gas_report, - self.fail_fast, - ) - .await + + // Write gas snapshots to disk if any were collected. + if !gas_snapshots.is_empty() { + // By default `gas_snapshot_check` is set to `false` in the config. + // + // The user can either: + // - Set `FORGE_SNAPSHOT_CHECK=true` in the environment. + // - Pass `--gas-snapshot-check=true` as a CLI argument. + // - Set `gas_snapshot_check = true` in the config. + // + // If the user passes `--gas-snapshot-check=` then it will override the config + // and the environment variable, disabling the check if `false` is passed. + // + // Exiting early with code 1 if differences are found. + if self.gas_snapshot_check.unwrap_or(config.gas_snapshot_check) { + let differences_found = gas_snapshots.clone().into_iter().fold( + false, + |mut found, (group, snapshots)| { + // If the snapshot file doesn't exist, we can't compare so we skip. + if !&config.snapshots.join(format!("{group}.json")).exists() { + return false; + } + + let previous_snapshots: BTreeMap = + fs::read_json_file(&config.snapshots.join(format!("{group}.json"))) + .expect("Failed to read snapshots from disk"); + + let diff: BTreeMap<_, _> = snapshots + .iter() + .filter_map(|(k, v)| { + previous_snapshots.get(k).and_then(|previous_snapshot| { + if previous_snapshot != v { + Some(( + k.clone(), + (previous_snapshot.clone(), v.clone()), + )) + } else { + None + } + }) + }) + .collect(); + + if !diff.is_empty() { + let _ = sh_eprintln!( + "{}", + format!("\n[{group}] Failed to match snapshots:").red().bold() + ); + + for (key, (previous_snapshot, snapshot)) in &diff { + let _ = sh_eprintln!( + "{}", + format!("- [{key}] {previous_snapshot} → {snapshot}").red() + ); + } + + found = true; + } + + found + }, + ); + + if differences_found { + sh_eprintln!()?; + eyre::bail!("Snapshots differ from previous run"); + } + } + + // By default `gas_snapshot_emit` is set to `true` in the config. + // + // The user can either: + // - Set `FORGE_SNAPSHOT_EMIT=false` in the environment. + // - Pass `--gas-snapshot-emit=false` as a CLI argument. + // - Set `gas_snapshot_emit = false` in the config. + // + // If the user passes `--gas-snapshot-emit=` then it will override the config + // and the environment variable, enabling the check if `true` is passed. + if self.gas_snapshot_emit.unwrap_or(config.gas_snapshot_emit) { + // Create `snapshots` directory if it doesn't exist. + fs::create_dir_all(&config.snapshots)?; + + // Write gas snapshots to disk per group. + gas_snapshots.clone().into_iter().for_each(|(group, snapshots)| { + fs::write_pretty_json_file( + &config.snapshots.join(format!("{group}.json")), + &snapshots, + ) + .expect("Failed to write gas snapshots to disk"); + }); + } + } + + // Print suite summary. + if !silent { + sh_println!("{}", suite_result.summary())?; + } + + // Add the suite result to the outcome. + outcome.results.insert(contract_name, suite_result); + + // Stop processing the remaining suites if any test failed and `fail_fast` is set. + if self.fail_fast && any_test_failed { + break; + } + } + outcome.last_run_decoder = Some(decoder); + let duration = timer.elapsed(); + + trace!(target: "forge::test", len=outcome.results.len(), %any_test_failed, "done with results"); + + if let Some(gas_report) = gas_report { + let finalized = gas_report.finalize(); + sh_println!("{}", &finalized)?; + outcome.gas_report = Some(finalized); + } + + if !self.summary && !shell::is_json() { + sh_println!("{}", outcome.summary(duration))?; + } + + if self.summary && !outcome.results.is_empty() { + let summary_report = TestSummaryReport::new(self.detailed, outcome.clone()); + sh_println!("{}", &summary_report)?; + } + + // Reattach the task. + if let Err(e) = handle.await { + match e.try_into_panic() { + Ok(payload) => std::panic::resume_unwind(payload), + Err(e) => return Err(e.into()), + } } + + // Persist test run failures to enable replaying. + 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.merge_with_config(config) + let mut filter = self.filter.clone(); + if self.rerun { + filter.test_pattern = last_run_failures(config); + } + if filter.path_pattern.is_some() { + if self.path.is_some() { + panic!("Can not supply both --match-path and |path|"); + } + } else { + filter.path_pattern = self.path.clone(); + } + filter.merge_with_config(config) } /// Returns whether `BuildArgs` was configured with `--watch` @@ -268,10 +837,10 @@ impl TestArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { + pub(crate) fn watchexec_config(&self) -> Result { self.watch.watchexec_config(|| { - let config = Config::from(self); - vec![config.src, config.test] + let config = self.load_config()?; + Ok([config.src, config.test]) }) } } @@ -291,397 +860,151 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } - dict.insert("fuzz".to_string(), fuzz_dict.into()); - - if let Some(ref etherscan_api_key) = self.etherscan_api_key { - dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); + if let Some(fuzz_timeout) = self.fuzz_timeout { + fuzz_dict.insert("timeout".to_string(), fuzz_timeout.into()); } - - Ok(Map::from([(Config::selected_profile(), dict)])) - } -} - -/// The result of a single test -#[derive(Debug, Clone)] -pub struct Test { - /// The identifier of the artifact/contract in the form of `:` - pub artifact_id: String, - /// The signature of the solidity test - pub signature: String, - /// Result of the executed solidity test - pub result: TestResult, -} - -impl Test { - pub fn gas_used(&self) -> u64 { - self.result.kind.report().gas() - } - - /// Returns the contract name of the artifact id - pub fn contract_name(&self) -> &str { - get_contract_name(&self.artifact_id) - } - - /// Returns the file name of the artifact id - pub fn file_name(&self) -> &str { - get_file_name(&self.artifact_id) - } -} - -/// Represents the bundled results of all tests -pub struct TestOutcome { - /// Whether failures are allowed - pub allow_failure: bool, - /// Results for each suite of tests `contract -> SuiteResult` - pub results: BTreeMap, -} - -impl TestOutcome { - fn new(results: BTreeMap, allow_failure: bool) -> Self { - Self { results, allow_failure } - } - - /// Iterator over all succeeding tests and their names - pub fn successes(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Success) - } - - /// Iterator over all failing tests and their names - pub fn failures(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Failure) - } - - pub fn skips(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Skipped) - } - - /// Iterator over all tests and their names - pub fn tests(&self) -> impl Iterator { - self.results.values().flat_map(|suite| suite.tests()) - } - - /// Returns an iterator over all `Test` - pub fn into_tests(self) -> impl Iterator { - self.results - .into_iter() - .flat_map(|(file, SuiteResult { test_results, .. })| { - test_results.into_iter().map(move |t| (file.clone(), t)) - }) - .map(|(artifact_id, (signature, result))| Test { artifact_id, signature, result }) - } - - /// Checks if there are any failures and failures are disallowed - pub fn ensure_ok(&self) -> Result<()> { - let failures = self.failures().count(); - if self.allow_failure || failures == 0 { - return Ok(()) + if let Some(fuzz_input_file) = self.fuzz_input_file.clone() { + fuzz_dict.insert("failure_persist_file".to_string(), fuzz_input_file.into()); } + dict.insert("fuzz".to_string(), fuzz_dict.into()); - if !shell::verbosity().is_normal() { - // skip printing and exit early - std::process::exit(1); + if let Some(etherscan_api_key) = + self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) + { + dict.insert("etherscan_api_key".to_string(), etherscan_api_key.to_string().into()); } - println!(); - println!("Failing tests:"); - for (suite_name, suite) in self.results.iter() { - let failures = suite.failures().count(); - if failures == 0 { - continue - } - - let term = if failures > 1 { "tests" } else { "test" }; - println!("Encountered {failures} failing {term} in {suite_name}"); - for (name, result) in suite.failures() { - short_test_result(name, result); - } - println!(); + if self.show_progress { + dict.insert("show_progress".to_string(), true.into()); } - let successes = self.successes().count(); - println!( - "Encountered a total of {} failing tests, {} tests succeeded", - Paint::red(failures.to_string()), - Paint::green(successes.to_string()) - ); - std::process::exit(1); - } - - pub fn duration(&self) -> Duration { - self.results - .values() - .fold(Duration::ZERO, |acc, SuiteResult { duration, .. }| acc + *duration) - } - - pub fn summary(&self) -> String { - let failed = self.failures().count(); - let result = if failed == 0 { Paint::green("ok") } else { Paint::red("FAILED") }; - format!( - "Test result: {}. {} passed; {} failed; {} skipped; finished in {:.2?}", - result, - self.successes().count(), - failed, - self.skips().count(), - self.duration() - ) + Ok(Map::from([(Config::selected_profile(), dict)])) } } -fn short_test_result(name: &str, result: &TestResult) { - let status = if result.status == TestStatus::Success { - Paint::green("[PASS]".to_string()) - } else if result.status == TestStatus::Skipped { - Paint::yellow("[SKIP]".to_string()) - } else { - let reason = result - .reason - .as_ref() - .map(|reason| format!("Reason: {reason}")) - .unwrap_or_else(|| "Reason: Assertion failed.".to_string()); - - let counterexample = result - .counterexample - .as_ref() - .map(|example| match example { - CounterExample::Single(eg) => format!(" Counterexample: {eg}]"), - CounterExample::Sequence(sequence) => { - let mut inner_txt = String::new(); - - for checkpoint in sequence { - inner_txt += format!("\t\t{checkpoint}\n").as_str(); - } - format!("]\n\t[Sequence]\n{inner_txt}\n") - } - }) - .unwrap_or_else(|| "]".to_string()); - - Paint::red(format!("[FAIL. {reason}{counterexample}")) - }; - - println!("{status} {name} {}", result.kind.report()); -} - -/** - * Formats the aggregated summary of all test suites into a string (for printing) - */ -fn format_aggregated_summary( - num_test_suites: usize, - total_passed: usize, - total_failed: usize, - total_skipped: usize, -) -> String { - let total_tests = total_passed + total_failed + total_skipped; - format!( - "Ran {} test suites: {} tests passed, {} failed, {} skipped ({} total tests)", - num_test_suites, total_passed, total_failed, total_skipped, total_tests - ) -} - /// Lists all matching tests -fn list( - runner: MultiContractRunner, - filter: ProjectPathsAwareFilter, - json: bool, -) -> Result { - let results = runner.list(&filter); - - if json { - println!("{}", serde_json::to_string(&results)?); +fn list(runner: MultiContractRunner, filter: &ProjectPathsAwareFilter) -> Result { + let results = runner.list(filter); + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&results)?)?; } else { for (file, contracts) in results.iter() { - println!("{file}"); + sh_println!("{file}")?; for (contract, tests) in contracts.iter() { - println!(" {contract}"); - println!(" {}\n", tests.join("\n ")); + sh_println!(" {contract}")?; + sh_println!(" {}\n", tests.join("\n "))?; } } } - Ok(TestOutcome::new(BTreeMap::new(), false)) + Ok(TestOutcome::empty(false)) } -/// Runs all the tests -#[allow(clippy::too_many_arguments)] -async fn test( - config: Config, - mut runner: MultiContractRunner, - verbosity: u8, - filter: ProjectPathsAwareFilter, - json: bool, - allow_failure: bool, - test_options: TestOptions, - gas_reporting: bool, - fail_fast: bool, -) -> Result { - trace!(target: "forge::test", "running all tests"); - if runner.count_filtered_tests(&filter) == 0 { - let filter_str = filter.to_string(); - if filter_str.is_empty() { - println!( - "\nNo tests found in project! Forge looks for functions that starts with `test`." - ); - } else { - println!("\nNo tests match the provided pattern:"); - println!("{filter_str}"); - // Try to suggest a test when there's no match - if let Some(ref test_pattern) = filter.args().test_pattern { - let test_name = test_pattern.as_str(); - let candidates = runner.get_tests(&filter); - if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { - println!("\nDid you mean `{suggestion}`?"); +/// 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 test_name.is_any_test() { + if let Some(test_match) = test_name.split("(").next() { + filter.push_str(test_match); + if failures.peek().is_some() { + filter.push('|'); + } } } } + let _ = fs::write(&config.test_failures_file, filter); } +} - if json { - let results = runner.test(filter, None, test_options).await; - println!("{}", serde_json::to_string(&results)?); - Ok(TestOutcome::new(results, allow_failure)) - } else { - // Set up identifiers - let mut local_identifier = LocalTraceIdentifier::new(&runner.known_contracts); - let remote_chain_id = runner.evm_opts.get_remote_chain_id(); - // Do not re-query etherscan for contracts that you've already queried today. - let mut etherscan_identifier = EtherscanIdentifier::new(&config, remote_chain_id)?; - - // Set up test reporter channel - let (tx, rx) = channel::<(String, SuiteResult)>(); - - // Run tests - let handle = - tokio::task::spawn(async move { runner.test(filter, Some(tx), test_options).await }); - - let mut results: BTreeMap = BTreeMap::new(); - let mut gas_report = GasReport::new(config.gas_reports, config.gas_reports_ignore); - let sig_identifier = - SignaturesIdentifier::new(Config::foundry_cache_dir(), config.offline)?; - - let mut total_passed = 0; - let mut total_failed = 0; - let mut total_skipped = 0; - - 'outer: for (contract_name, suite_result) in rx { - results.insert(contract_name.clone(), suite_result.clone()); - - let mut tests = suite_result.test_results.clone(); - println!(); - for warning in suite_result.warnings.iter() { - eprintln!("{} {warning}", Paint::yellow("Warning:").bold()); +/// Generate test report in JUnit XML report format. +fn junit_xml_report(results: &BTreeMap, verbosity: u8) -> Report { + let mut total_duration = Duration::default(); + let mut junit_report = Report::new("Test run"); + junit_report.set_timestamp(Utc::now()); + for (suite_name, suite_result) in results { + let mut test_suite = TestSuite::new(suite_name); + total_duration += suite_result.duration; + test_suite.set_time(suite_result.duration); + test_suite.set_system_out(suite_result.summary()); + for (test_name, test_result) in &suite_result.test_results { + let mut test_status = match test_result.status { + TestStatus::Success => TestCaseStatus::success(), + TestStatus::Failure => TestCaseStatus::non_success(NonSuccessKind::Failure), + TestStatus::Skipped => TestCaseStatus::skipped(), + }; + if let Some(reason) = &test_result.reason { + test_status.set_message(reason); } - if !tests.is_empty() { - let term = if tests.len() > 1 { "tests" } else { "test" }; - println!("Running {} {term} for {contract_name}", tests.len()); - } - for (name, result) in &mut tests { - short_test_result(name, result); - - // If the test failed, we want to stop processing the rest of the tests - if fail_fast && result.status == TestStatus::Failure { - break 'outer - } - - // We only display logs at level 2 and above - if verbosity >= 2 { - // We only decode logs from Hardhat and DS-style console events - let console_logs = decode_console_logs(&result.logs); - if !console_logs.is_empty() { - println!("Logs:"); - for log in console_logs { - println!(" {log}"); - } - println!(); - } - } - - if !result.traces.is_empty() { - // Identify addresses in each trace - let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) - .with_events(local_identifier.events().cloned()) - .with_verbosity(verbosity) - .build(); - - // Signatures are of no value for gas reports - if !gas_reporting { - decoder.add_signature_identifier(sig_identifier.clone()); - } - - // Decode the traces - let mut decoded_traces = Vec::new(); - for (kind, trace) in &mut result.traces { - decoder.identify(trace, &mut local_identifier); - decoder.identify(trace, &mut etherscan_identifier); - - let should_include = match kind { - // At verbosity level 3, we only display traces for failed tests - // At verbosity level 4, we also display the setup trace for failed - // tests At verbosity level 5, we display - // all traces for all tests - TraceKind::Setup => { - (verbosity >= 5) || - (verbosity == 4 && result.status == TestStatus::Failure) - } - TraceKind::Execution => { - verbosity > 3 || - (verbosity == 3 && result.status == TestStatus::Failure) - } - _ => false, - }; - - // We decode the trace if we either need to build a gas report or we need - // to print it - if should_include || gas_reporting { - decoder.decode(trace).await; - } - if should_include { - decoded_traces.push(trace.to_string()); - } - } - - if !decoded_traces.is_empty() { - println!("Traces:"); - decoded_traces.into_iter().for_each(|trace| println!("{trace}")); - } - - if gas_reporting { - gas_report.analyze(&result.traces); - } + let mut test_case = TestCase::new(test_name, test_status); + test_case.set_time(test_result.duration); + + let mut sys_out = String::new(); + let result_report = test_result.kind.report(); + write!(sys_out, "{test_result} {test_name} {result_report}").unwrap(); + if verbosity >= 2 && !test_result.logs.is_empty() { + write!(sys_out, "\\nLogs:\\n").unwrap(); + let console_logs = decode_console_logs(&test_result.logs); + for log in console_logs { + write!(sys_out, " {log}\\n").unwrap(); } } - let block_outcome = - TestOutcome::new([(contract_name, suite_result)].into(), allow_failure); - total_passed += block_outcome.successes().count(); - total_failed += block_outcome.failures().count(); - total_skipped += block_outcome.skips().count(); - - println!("{}", block_outcome.summary()); + test_case.set_system_out(sys_out); + test_suite.add_test_case(test_case); } + junit_report.add_test_suite(test_suite); + } + junit_report.set_time(total_duration); + junit_report +} - if gas_reporting { - println!("{}", gas_report.finalize()); - } +#[cfg(test)] +mod tests { + use super::*; + use foundry_config::Chain; - let num_test_suites = results.len(); + #[test] + fn watch_parse() { + let args: TestArgs = TestArgs::parse_from(["foundry-cli", "-vw"]); + assert!(args.watch.watch.is_some()); + } - if num_test_suites > 0 { - println!( - "{}", - format_aggregated_summary( - num_test_suites, - total_passed, - total_failed, - total_skipped - ) - ); - } + #[test] + fn fuzz_seed() { + let args: TestArgs = TestArgs::parse_from(["foundry-cli", "--fuzz-seed", "0x10"]); + assert!(args.fuzz_seed.is_some()); + } - // reattach the thread - let _results = handle.await?; + // + #[test] + fn fuzz_seed_exists() { + let args: TestArgs = + TestArgs::parse_from(["foundry-cli", "-vvv", "--gas-report", "--fuzz-seed", "0x10"]); + assert!(args.fuzz_seed.is_some()); + } - trace!(target: "forge::test", "received {} results", results.len()); - Ok(TestOutcome::new(results, allow_failure)) + #[test] + fn extract_chain() { + let test = |arg: &str, expected: Chain| { + let args = TestArgs::parse_from(["foundry-cli", arg]); + assert_eq!(args.evm.env.chain, Some(expected)); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!(config.chain, Some(expected)); + assert_eq!(evm_opts.env.chain_id, Some(expected.id())); + }; + test("--chain-id=1", Chain::mainnet()); + test("--chain-id=42", Chain::from_id(42)); } } diff --git a/crates/forge/bin/cmd/test/summary.rs b/crates/forge/bin/cmd/test/summary.rs new file mode 100644 index 0000000000000..68ab3f4590b05 --- /dev/null +++ b/crates/forge/bin/cmd/test/summary.rs @@ -0,0 +1,226 @@ +use crate::cmd::test::TestOutcome; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Row, Table}; +use foundry_common::reports::{report_kind, ReportKind}; +use foundry_evm::executors::invariant::InvariantMetrics; +use itertools::Itertools; +use serde_json::json; +use std::{collections::HashMap, fmt::Display}; + +/// Represents a test summary report. +pub struct TestSummaryReport { + /// The kind of report to generate. + report_kind: ReportKind, + /// Whether the report should be detailed. + is_detailed: bool, + /// The test outcome to report. + outcome: TestOutcome, +} + +impl TestSummaryReport { + pub fn new(is_detailed: bool, outcome: TestOutcome) -> Self { + Self { report_kind: report_kind(), is_detailed, outcome } + } +} + +impl Display for TestSummaryReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self.report_kind { + ReportKind::Text => { + writeln!(f, "\n{}", &self.format_table_output(&self.is_detailed, &self.outcome))?; + } + ReportKind::JSON => { + writeln!(f, "{}", &self.format_json_output(&self.is_detailed, &self.outcome))?; + } + } + + Ok(()) + } +} + +impl TestSummaryReport { + // Helper function to format the JSON output. + fn format_json_output(&self, is_detailed: &bool, outcome: &TestOutcome) -> String { + let output = json!({ + "results": outcome.results.iter().map(|(contract, suite)| { + let (suite_path, suite_name) = contract.split_once(':').unwrap(); + let passed = suite.successes().count(); + let failed = suite.failures().count(); + let skipped = suite.skips().count(); + let mut result = json!({ + "suite": suite_name, + "passed": passed, + "failed": failed, + "skipped": skipped, + }); + + if *is_detailed { + result["file_path"] = serde_json::Value::String(suite_path.to_string()); + result["duration"] = serde_json::Value::String(format!("{:.2?}", suite.duration)); + } + + result + }).collect::>(), + }); + + serde_json::to_string_pretty(&output).unwrap() + } + + fn format_table_output(&self, is_detailed: &bool, outcome: &TestOutcome) -> Table { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + let mut row = Row::from(vec![ + Cell::new("Test Suite"), + Cell::new("Passed").fg(Color::Green), + Cell::new("Failed").fg(Color::Red), + Cell::new("Skipped").fg(Color::Yellow), + ]); + if *is_detailed { + row.add_cell(Cell::new("File Path").fg(Color::Cyan)); + row.add_cell(Cell::new("Duration").fg(Color::Cyan)); + } + table.set_header(row); + + // Traverse the test_results vector and build the table + for (contract, suite) in &outcome.results { + let mut row = Row::new(); + let (suite_path, suite_name) = contract.split_once(':').unwrap(); + + let passed = suite.successes().count(); + let mut passed_cell = Cell::new(passed); + + let failed = suite.failures().count(); + let mut failed_cell = Cell::new(failed); + + let skipped = suite.skips().count(); + let mut skipped_cell = Cell::new(skipped); + + row.add_cell(Cell::new(suite_name)); + + if passed > 0 { + passed_cell = passed_cell.fg(Color::Green); + } + row.add_cell(passed_cell); + + if failed > 0 { + failed_cell = failed_cell.fg(Color::Red); + } + row.add_cell(failed_cell); + + if skipped > 0 { + skipped_cell = skipped_cell.fg(Color::Yellow); + } + row.add_cell(skipped_cell); + + if self.is_detailed { + row.add_cell(Cell::new(suite_path)); + row.add_cell(Cell::new(format!("{:.2?}", suite.duration).to_string())); + } + + table.add_row(row); + } + + table + } +} + +/// Helper function to create the invariant metrics table. +/// +/// ╭-----------------------+----------------+-------+---------+----------╮ +/// | Contract | Selector | Calls | Reverts | Discards | +/// +=====================================================================+ +/// | AnotherCounterHandler | doWork | 7451 | 123 | 4941 | +/// |-----------------------+----------------+-------+---------+----------| +/// | AnotherCounterHandler | doWorkThing | 7279 | 137 | 4849 | +/// |-----------------------+----------------+-------+---------+----------| +/// | CounterHandler | doAnotherThing | 7302 | 150 | 4794 | +/// |-----------------------+----------------+-------+---------+----------| +/// | CounterHandler | doSomething | 7382 | 160 |4794 | +/// ╰-----------------------+----------------+-------+---------+----------╯ +pub(crate) fn format_invariant_metrics_table( + test_metrics: &HashMap, +) -> Table { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("Contract"), + Cell::new("Selector"), + Cell::new("Calls").fg(Color::Green), + Cell::new("Reverts").fg(Color::Red), + Cell::new("Discards").fg(Color::Yellow), + ]); + + for name in test_metrics.keys().sorted() { + if let Some((contract, selector)) = + name.split_once(':').map_or(name.as_str(), |(_, contract)| contract).split_once('.') + { + let mut row = Row::new(); + row.add_cell(Cell::new(contract)); + row.add_cell(Cell::new(selector)); + + if let Some(metrics) = test_metrics.get(name) { + let calls_cell = Cell::new(metrics.calls).fg(if metrics.calls > 0 { + Color::Green + } else { + Color::White + }); + + let reverts_cell = Cell::new(metrics.reverts).fg(if metrics.reverts > 0 { + Color::Red + } else { + Color::White + }); + + let discards_cell = Cell::new(metrics.discards).fg(if metrics.discards > 0 { + Color::Yellow + } else { + Color::White + }); + + row.add_cell(calls_cell); + row.add_cell(reverts_cell); + row.add_cell(discards_cell); + } + + table.add_row(row); + } + } + table +} + +#[cfg(test)] +mod tests { + use crate::cmd::test::summary::format_invariant_metrics_table; + use foundry_evm::executors::invariant::InvariantMetrics; + use std::collections::HashMap; + + #[test] + fn test_invariant_metrics_table() { + let mut test_metrics = HashMap::new(); + test_metrics.insert( + "SystemConfig.setGasLimit".to_string(), + InvariantMetrics { calls: 10, reverts: 1, discards: 1 }, + ); + test_metrics.insert( + "src/universal/Proxy.sol:Proxy.changeAdmin".to_string(), + InvariantMetrics { calls: 20, reverts: 2, discards: 2 }, + ); + let table = format_invariant_metrics_table(&test_metrics); + assert_eq!(table.row_count(), 2); + + let mut first_row_content = table.row(0).unwrap().cell_iter(); + assert_eq!(first_row_content.next().unwrap().content(), "SystemConfig"); + assert_eq!(first_row_content.next().unwrap().content(), "setGasLimit"); + assert_eq!(first_row_content.next().unwrap().content(), "10"); + assert_eq!(first_row_content.next().unwrap().content(), "1"); + assert_eq!(first_row_content.next().unwrap().content(), "1"); + + let mut second_row_content = table.row(1).unwrap().cell_iter(); + assert_eq!(second_row_content.next().unwrap().content(), "Proxy"); + assert_eq!(second_row_content.next().unwrap().content(), "changeAdmin"); + assert_eq!(second_row_content.next().unwrap().content(), "20"); + assert_eq!(second_row_content.next().unwrap().content(), "2"); + assert_eq!(second_row_content.next().unwrap().content(), "2"); + } +} diff --git a/crates/forge/bin/cmd/tree.rs b/crates/forge/bin/cmd/tree.rs index b7e7669510007..b97d7c8d98654 100644 --- a/crates/forge/bin/cmd/tree.rs +++ b/crates/forge/bin/cmd/tree.rs @@ -1,34 +1,34 @@ use clap::Parser; -use ethers::solc::{ - resolver::{Charset, TreeOptions}, +use eyre::Result; +use foundry_cli::{opts::ProjectPathOpts, utils::LoadConfig}; +use foundry_compilers::{ + resolver::{parse::SolData, Charset, TreeOptions}, Graph, }; -use eyre::Result; -use foundry_cli::{opts::ProjectPathsArgs, utils::LoadConfig}; /// CLI arguments for `forge tree`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct TreeArgs { /// Do not de-duplicate (repeats all shared dependencies) - #[clap(long)] + #[arg(long)] no_dedupe: bool, /// Character set to use in output. /// /// [possible values: utf8, ascii] - #[clap(long, default_value = "utf8")] + #[arg(long, default_value = "utf8")] charset: Charset, - #[clap(flatten)] - opts: ProjectPathsArgs, + #[command(flatten)] + project_paths: ProjectPathOpts, } -foundry_config::impl_figment_convert!(TreeArgs, opts); +foundry_config::impl_figment_convert!(TreeArgs, project_paths); impl TreeArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; - let graph = Graph::resolve(&config.project_paths())?; + let config = self.load_config()?; + let graph = Graph::::resolve(&config.project_paths())?; let opts = TreeOptions { charset: self.charset, no_dedupe: self.no_dedupe }; graph.print_with_options(opts); diff --git a/crates/forge/bin/cmd/update.rs b/crates/forge/bin/cmd/update.rs index 9f1f2969a77d3..5e965c34a9999 100644 --- a/crates/forge/bin/cmd/update.rs +++ b/crates/forge/bin/cmd/update.rs @@ -8,7 +8,7 @@ use foundry_config::{impl_figment_convert_basic, Config}; use std::path::PathBuf; /// CLI arguments for `forge update`. -#[derive(Debug, Clone, Parser)] +#[derive(Clone, Debug, Parser)] pub struct UpdateArgs { /// The dependencies you want to update. dependencies: Vec, @@ -17,27 +17,42 @@ pub struct UpdateArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Override the up-to-date check. - #[clap(short, long)] + #[arg(short, long)] force: bool, + + /// Recursively update submodules. + #[arg(short, long)] + recursive: bool, } impl_figment_convert_basic!(UpdateArgs); impl UpdateArgs { pub fn run(self) -> Result<()> { - let config = self.try_load_config_emit_warnings()?; + let config = self.load_config()?; let (root, paths) = dependencies_paths(&self.dependencies, &config)?; - Git::new(&root).submodule_update(self.force, true, paths) + // fetch the latest changes for each submodule (recursively if flag is set) + let git = Git::new(&root); + if self.recursive { + // update submodules recursively + git.submodule_update(self.force, true, false, true, paths) + } else { + // update root submodules + git.submodule_update(self.force, true, false, false, paths)?; + // initialize submodules of each submodule recursively (otherwise direct submodule + // dependencies will revert to last commit) + git.submodule_foreach(false, "git submodule update --init --progress --recursive") + } } } /// Returns `(root, paths)` where `root` is the root of the Git repository and `paths` are the /// relative paths of the dependencies. pub fn dependencies_paths(deps: &[Dependency], config: &Config) -> Result<(PathBuf, Vec)> { - let git_root = Git::root_of(&config.__root.0)?; + let git_root = Git::root_of(&config.root)?; let libs = config.install_lib_dir(); let mut paths = Vec::with_capacity(deps.len()); diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/forge/bin/cmd/verify/etherscan/mod.rs deleted file mode 100644 index b0d5edbe4e16e..0000000000000 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ /dev/null @@ -1,597 +0,0 @@ -use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; -use crate::cmd::retry::RETRY_CHECK_ON_VERIFY; -use ethers::{ - abi::Function, - etherscan::{ - utils::lookup_compiler_version, - verify::{CodeFormat, VerifyContract}, - Client, - }, - prelude::errors::EtherscanError, - solc::{artifacts::CompactContract, cache::CacheEntry, Project, Solc}, - utils::to_checksum, -}; -use eyre::{eyre, Context, Result}; -use foundry_cli::utils::{get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; -use foundry_common::abi::encode_args; -use foundry_config::{Chain, Config, SolcReq}; -use foundry_utils::Retry; -use futures::FutureExt; -use once_cell::sync::Lazy; -use regex::Regex; -use semver::{BuildMetadata, Version}; -use std::{ - fmt::Debug, - path::{Path, PathBuf}, -}; -use tracing::{error, trace, warn}; - -mod flatten; -mod standard_json; - -pub static RE_BUILD_COMMIT: Lazy = - Lazy::new(|| Regex::new(r"(?Pcommit\.[0-9,a-f]{8})").unwrap()); - -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct EtherscanVerificationProvider { - /// Memoized cached entry of the target contract - cached_entry: Option<(PathBuf, CacheEntry, CompactContract)>, -} - -/// The contract source provider for [EtherscanVerificationProvider] -/// -/// Returns source, contract_name and the source [CodeFormat] -trait EtherscanSourceProvider: Send + Sync + Debug { - fn source( - &self, - args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, - ) -> Result<(String, String, CodeFormat)>; -} - -#[async_trait::async_trait] -impl VerificationProvider for EtherscanVerificationProvider { - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()> { - let _ = self.prepare_request(&args).await?; - Ok(()) - } - - async fn verify(&mut self, args: VerifyArgs) -> Result<()> { - let (etherscan, verify_args) = self.prepare_request(&args).await?; - - if self.is_contract_verified(ðerscan, &verify_args).await? { - println!( - "\nContract [{}] {:?} is already verified. Skipping verification.", - verify_args.contract_name, - to_checksum(&verify_args.address, None) - ); - - return Ok(()) - } - - trace!(target : "forge::verify", ?verify_args, "submitting verification request"); - - let retry: Retry = args.retry.into(); - let resp = retry.run_async(|| { - async { - println!("\nSubmitting verification for [{}] {:?}.", verify_args.contract_name, to_checksum(&verify_args.address, None)); - let resp = etherscan - .submit_contract_verification(&verify_args) - .await - .wrap_err_with(|| { - // valid json - let args = serde_json::to_string(&verify_args).unwrap(); - error!(target : "forge::verify", ?args, "Failed to submit verification"); - format!("Failed to submit contract verification, payload:\n{args}") - })?; - - trace!(target : "forge::verify", ?resp, "Received verification response"); - - if resp.status == "0" { - if resp.result == "Contract source code already verified" { - return Ok(None) - } - - if resp.result.starts_with("Unable to locate ContractCode at") { - warn!("{}", resp.result); - return Err(eyre!("Etherscan could not detect the deployment.")) - } - - warn!("Failed verify submission: {:?}", resp); - eprintln!( - "Encountered an error verifying this contract:\nResponse: `{}`\nDetails: `{}`", - resp.message, resp.result - ); - std::process::exit(1); - } - - Ok(Some(resp)) - } - .boxed() - }).await?; - - if let Some(resp) = resp { - println!( - "Submitted contract for verification:\n\tResponse: `{}`\n\tGUID: `{}`\n\tURL: - {}", - resp.message, - resp.result, - etherscan.address_url(args.address) - ); - - if args.watch { - let check_args = VerifyCheckArgs { - id: resp.result, - etherscan: args.etherscan, - retry: RETRY_CHECK_ON_VERIFY, - verifier: args.verifier, - }; - // return check_args.run().await - return self.check(check_args).await - } - } else { - println!("Contract source code already verified"); - } - - Ok(()) - } - - /// Executes the command to check verification status on Etherscan - async fn check(&self, args: VerifyCheckArgs) -> Result<()> { - let config = args.try_load_config_emit_warnings()?; - let etherscan = self.client( - args.etherscan.chain.unwrap_or_default(), - args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), - &config, - )?; - let retry: Retry = args.retry.into(); - retry - .run_async(|| { - async { - let resp = etherscan - .check_contract_verification_status(args.id.clone()) - .await - .wrap_err("Failed to request verification status")?; - - trace!(target : "forge::verify", ?resp, "Received verification response"); - - eprintln!( - "Contract verification status:\nResponse: `{}`\nDetails: `{}`", - resp.message, resp.result - ); - - if resp.result == "Pending in queue" { - return Err(eyre!("Verification is still pending...",)) - } - - if resp.result == "Unable to verify" { - return Err(eyre!("Unable to verify.",)) - } - - if resp.result == "Already Verified" { - println!("Contract source code already verified"); - return Ok(()) - } - - if resp.status == "0" { - println!("Contract failed to verify."); - std::process::exit(1); - } - - if resp.result == "Pass - Verified" { - println!("Contract successfully verified"); - } - - Ok(()) - } - .boxed() - }) - .await - .wrap_err("Checking verification result failed:") - } -} - -impl EtherscanVerificationProvider { - /// Create a source provider - fn source_provider(&self, args: &VerifyArgs) -> Box { - if args.flatten { - Box::new(flatten::EtherscanFlattenedSource) - } else { - Box::new(standard_json::EtherscanStandardJsonSource) - } - } - - /// Return the memoized cache entry for the target contract. - /// Read the artifact from cache on first access. - fn cache_entry( - &mut self, - project: &Project, - contract_name: &str, - ) -> Result<&(PathBuf, CacheEntry, CompactContract)> { - if let Some(ref entry) = self.cached_entry { - return Ok(entry) - } - - let cache = project.read_cache_file()?; - let (path, entry) = get_cached_entry_by_name(&cache, contract_name)?; - let contract: CompactContract = cache.read_artifact(path.clone(), contract_name)?; - Ok(self.cached_entry.insert((path, entry, contract))) - } - - /// Configures the API request to the etherscan API using the given [`VerifyArgs`]. - async fn prepare_request(&mut self, args: &VerifyArgs) -> Result<(Client, VerifyContract)> { - let config = args.try_load_config_emit_warnings()?; - let etherscan = self.client( - args.etherscan.chain.unwrap_or_default(), - args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), - &config, - )?; - let verify_args = self.create_verify_request(args, Some(config)).await?; - - Ok((etherscan, verify_args)) - } - - /// Queries the etherscan API to verify if the contract is already verified. - async fn is_contract_verified( - &self, - etherscan: &Client, - verify_contract: &VerifyContract, - ) -> Result { - let check = etherscan.contract_abi(verify_contract.address).await; - - if let Err(err) = check { - match err { - EtherscanError::ContractCodeNotVerified(_) => return Ok(false), - error => return Err(error.into()), - } - } - - Ok(true) - } - - /// Create an etherscan client - pub(crate) fn client( - &self, - chain: Chain, - verifier_url: Option<&str>, - etherscan_key: Option<&str>, - config: &Config, - ) -> Result { - let etherscan_config = config.get_etherscan_config_with_chain(Some(chain))?; - - let api_url = - verifier_url.or_else(|| etherscan_config.as_ref().map(|c| c.api_url.as_str())); - let base_url = etherscan_config - .as_ref() - .and_then(|c| c.browser_url.as_deref()) - .or_else(|| chain.etherscan_urls().map(|urls| urls.1)); - - let etherscan_key = - etherscan_key.or_else(|| etherscan_config.as_ref().map(|c| c.key.as_str())); - - let mut builder = Client::builder(); - - builder = if let Some(api_url) = api_url { - builder.with_api_url(api_url)?.with_url(base_url.unwrap_or(api_url))? - } else { - builder.chain(chain.to_owned().try_into()?)? - }; - - builder - .with_api_key(etherscan_key.unwrap_or_default()) - .build() - .wrap_err("Failed to create etherscan client") - } - - /// Creates the `VerifyContract` etherscan request in order to verify the contract - /// - /// If `--flatten` is set to `true` then this will send with [`CodeFormat::SingleFile`] - /// otherwise this will use the [`CodeFormat::StandardJsonInput`] - pub async fn create_verify_request( - &mut self, - args: &VerifyArgs, - config: Option, - ) -> Result { - let mut config = - if let Some(config) = config { config } else { args.try_load_config_emit_warnings()? }; - - config.libraries.extend(args.libraries.clone()); - - let project = config.project()?; - - let contract_path = self.contract_path(args, &project)?; - let compiler_version = self.compiler_version(args, &config, &project)?; - let (source, contract_name, code_format) = - self.source_provider(args).source(args, &project, &contract_path, &compiler_version)?; - - let compiler_version = format!("v{}", ensure_solc_build_metadata(compiler_version).await?); - let constructor_args = self.constructor_args(args, &project)?; - let mut verify_args = - VerifyContract::new(args.address, contract_name, source, compiler_version) - .constructor_arguments(constructor_args) - .code_format(code_format); - - if code_format == CodeFormat::SingleFile { - verify_args = if let Some(optimizations) = args.num_of_optimizations { - verify_args.optimized().runs(optimizations as u32) - } else if config.optimizer { - verify_args.optimized().runs(config.optimizer_runs.try_into()?) - } else { - verify_args.not_optimized() - }; - } - - Ok(verify_args) - } - - /// Get the target contract path. If it wasn't provided, attempt a lookup - /// in cache. Validate the path indeed exists on disk. - fn contract_path(&mut self, args: &VerifyArgs, project: &Project) -> Result { - let path = match args.contract.path.as_ref() { - Some(path) => project.root().join(path), - None => { - let (path, _, _) = self.cache_entry(project, &args.contract.name).wrap_err( - "If cache is disabled, contract info must be provided in the format :", - )?; - path.to_owned() - } - }; - - // check that the provided contract is part of the source dir - if !path.exists() { - eyre::bail!("Contract {:?} does not exist.", path); - } - - Ok(path) - } - - /// Parse the compiler version. - /// The priority desc: - /// 1. Through CLI arg `--compiler-version` - /// 2. `solc` defined in foundry.toml - /// 3. The version contract was last compiled with. - fn compiler_version( - &mut self, - args: &VerifyArgs, - config: &Config, - project: &Project, - ) -> Result { - if let Some(ref version) = args.compiler_version { - return Ok(version.trim_start_matches('v').parse()?) - } - - if let Some(ref solc) = config.solc { - match solc { - SolcReq::Version(version) => return Ok(version.to_owned()), - SolcReq::Local(solc) => { - if solc.is_file() { - return Ok(Solc::new(solc).version()?) - } - } - } - } - - let (_, entry, _) = self.cache_entry(project, &args.contract.name).wrap_err( - "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" - )?; - let artifacts = entry.artifacts_versions().collect::>(); - if artifacts.len() == 1 { - let mut version = artifacts[0].0.to_owned(); - version.build = match RE_BUILD_COMMIT.captures(version.build.as_str()) { - Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, - _ => BuildMetadata::EMPTY, - }; - return Ok(version) - } - - if artifacts.is_empty() { - warn!("No artifacts detected") - } else { - let versions = artifacts.iter().map(|a| a.0.to_string()).collect::>(); - warn!("Ambiguous compiler versions found in cache: {}", versions.join(", ")); - } - - eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") - } - - /// Return the optional encoded constructor arguments. If the path to - /// constructor arguments was provided, read them and encode. Otherwise, - /// return whatever was set in the [VerifyArgs] args. - fn constructor_args(&mut self, args: &VerifyArgs, project: &Project) -> Result> { - if let Some(ref constructor_args_path) = args.constructor_args_path { - let (_, _, contract) = self.cache_entry(project, &args.contract.name).wrap_err( - "Cache must be enabled in order to use the `--constructor-args-path` option", - )?; - let abi = contract.abi.as_ref().ok_or(eyre!("Can't find ABI in cached artifact."))?; - let constructor = abi - .constructor() - .ok_or(eyre!("Can't retrieve constructor info from artifact ABI."))?; - #[allow(deprecated)] - let func = Function { - name: "constructor".to_string(), - inputs: constructor.inputs.clone(), - outputs: vec![], - constant: None, - state_mutability: Default::default(), - }; - let encoded_args = encode_args( - &func, - &read_constructor_args_file(constructor_args_path.to_path_buf())?, - )?; - let encoded_args = hex::encode(encoded_args); - return Ok(Some(encoded_args[8..].into())) - } - - Ok(args.constructor_args.clone()) - } -} - -/// Given any solc [Version] return a [Version] with build metadata -/// -/// # Example -/// -/// ```ignore -/// use semver::{BuildMetadata, Version}; -/// let version = Version::new(1, 2, 3); -/// let version = ensure_solc_build_metadata(version).await?; -/// assert_ne!(version.build, BuildMetadata::EMPTY); -/// ``` -async fn ensure_solc_build_metadata(version: Version) -> Result { - if version.build != BuildMetadata::EMPTY { - Ok(version) - } else { - Ok(lookup_compiler_version(&version).await?) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use clap::Parser; - use foundry_cli::utils::LoadConfig; - use foundry_common::fs; - use foundry_test_utils::tempfile::tempdir; - - #[test] - fn can_extract_etherscan_verify_config() { - let temp = tempdir().unwrap(); - let root = temp.path(); - - let config = r#" - [profile.default] - - [etherscan] - mumbai = { key = "dummykey", chain = 80001, url = "https://api-testnet.polygonscan.com/" } - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - - let args: VerifyArgs = VerifyArgs::parse_from([ - "foundry-cli", - "0xd8509bee9c9bf012282ad33aba0d87241baf5064", - "src/Counter.sol:Counter", - "--chain", - "mumbai", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - - let config = args.load_config(); - - let etherscan = EtherscanVerificationProvider::default(); - let client = etherscan - .client( - args.etherscan.chain.unwrap_or_default(), - args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), - &config, - ) - .unwrap(); - assert_eq!(client.etherscan_api_url().as_str(), "https://api-testnet.polygonscan.com/"); - - assert!(format!("{client:?}").contains("dummykey")); - - let args: VerifyArgs = VerifyArgs::parse_from([ - "foundry-cli", - "0xd8509bee9c9bf012282ad33aba0d87241baf5064", - "src/Counter.sol:Counter", - "--chain", - "mumbai", - "--verifier-url", - "https://verifier-url.com/", - "--root", - root.as_os_str().to_str().unwrap(), - ]); - - let config = args.load_config(); - - let etherscan = EtherscanVerificationProvider::default(); - let client = etherscan - .client( - args.etherscan.chain.unwrap_or_default(), - args.verifier.verifier_url.as_deref(), - args.etherscan.key.as_deref(), - &config, - ) - .unwrap(); - assert_eq!(client.etherscan_api_url().as_str(), "https://verifier-url.com/"); - assert!(format!("{client:?}").contains("dummykey")); - } - - #[tokio::test(flavor = "multi_thread")] - async fn fails_on_disabled_cache_and_missing_info() { - let temp = tempdir().unwrap(); - let root = temp.path(); - let root_path = root.as_os_str().to_str().unwrap(); - - let config = r#" - [profile.default] - cache = false - "#; - - let toml_file = root.join(Config::FILE_NAME); - fs::write(toml_file, config).unwrap(); - - let address = "0xd8509bee9c9bf012282ad33aba0d87241baf5064"; - let contract_name = "Counter"; - let src_dir = "src"; - fs::create_dir_all(root.join(src_dir)).unwrap(); - let contract_path = format!("{src_dir}/Counter.sol"); - fs::write(root.join(&contract_path), "").unwrap(); - - let mut etherscan = EtherscanVerificationProvider::default(); - - // No compiler argument - let args = VerifyArgs::parse_from([ - "foundry-cli", - address, - &format!("{contract_path}:{contract_name}"), - "--root", - root_path, - ]); - - let result = etherscan.preflight_check(args).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" - ); - - // No contract path - let args = - VerifyArgs::parse_from(["foundry-cli", address, contract_name, "--root", root_path]); - - let result = etherscan.preflight_check(args).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "If cache is disabled, contract info must be provided in the format :" - ); - - // Constructor args path - let args = VerifyArgs::parse_from([ - "foundry-cli", - address, - &format!("{contract_path}:{contract_name}"), - "--constructor-args-path", - ".", - "--compiler-version", - "0.8.15", - "--root", - root_path, - ]); - - let result = etherscan.preflight_check(args).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Cache must be enabled in order to use the `--constructor-args-path` option", - ); - } -} diff --git a/crates/forge/bin/cmd/verify/etherscan/standard_json.rs b/crates/forge/bin/cmd/verify/etherscan/standard_json.rs deleted file mode 100644 index f79c76461abfd..0000000000000 --- a/crates/forge/bin/cmd/verify/etherscan/standard_json.rs +++ /dev/null @@ -1,48 +0,0 @@ -use super::{EtherscanSourceProvider, VerifyArgs}; -use ethers::{ - etherscan::verify::CodeFormat, prelude::artifacts::StandardJsonCompilerInput, solc::Project, -}; -use eyre::{Context, Result}; -use semver::Version; -use std::path::Path; -use tracing::trace; - -#[derive(Debug)] -pub struct EtherscanStandardJsonSource; -impl EtherscanSourceProvider for EtherscanStandardJsonSource { - fn source( - &self, - args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, - ) -> Result<(String, String, CodeFormat)> { - let mut input: StandardJsonCompilerInput = project - .standard_json_input(target) - .wrap_err("Failed to get standard json input")? - .normalize_evm_version(version); - - input.settings.libraries.libs = input - .settings - .libraries - .libs - .into_iter() - .map(|(f, libs)| (f.strip_prefix(project.root()).unwrap_or(&f).to_path_buf(), libs)) - .collect(); - - // remove all incompatible settings - input.settings.sanitize(version); - - let source = - serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; - - trace!(target : "forge::verify", standard_json = source, "determined standard json input"); - - let name = format!( - "{}:{}", - target.strip_prefix(project.root()).unwrap_or(target).display(), - args.contract.name.clone() - ); - Ok((source, name, CodeFormat::StandardJsonInput)) - } -} diff --git a/crates/forge/bin/cmd/verify/mod.rs b/crates/forge/bin/cmd/verify/mod.rs deleted file mode 100644 index bd17d1418b9b7..0000000000000 --- a/crates/forge/bin/cmd/verify/mod.rs +++ /dev/null @@ -1,231 +0,0 @@ -use super::retry::RetryArgs; -use clap::{Parser, ValueHint}; -use ethers::{abi::Address, solc::info::ContractInfo}; -use eyre::Result; -use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; -use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config}; -use provider::VerificationProviderType; -use reqwest::Url; -use std::path::PathBuf; - -mod etherscan; -use etherscan::EtherscanVerificationProvider; - -pub mod provider; -use provider::VerificationProvider; - -mod sourcify; - -/// Verification provider arguments -#[derive(Debug, Clone, Parser)] -pub struct VerifierArgs { - /// The contract verification provider to use. - #[clap(long, help_heading = "Verifier options", default_value = "etherscan", value_enum)] - pub verifier: VerificationProviderType, - - /// The verifier URL, if using a custom provider - #[clap(long, help_heading = "Verifier options", env = "VERIFIER_URL")] - pub verifier_url: Option, -} - -impl Default for VerifierArgs { - fn default() -> Self { - VerifierArgs { verifier: VerificationProviderType::Etherscan, verifier_url: None } - } -} - -/// CLI arguments for `forge verify`. -#[derive(Debug, Clone, Parser)] -pub struct VerifyArgs { - /// The address of the contract to verify. - pub address: Address, - - /// The contract identifier in the form `:`. - pub contract: ContractInfo, - - /// The ABI-encoded constructor arguments. - #[clap(long, conflicts_with = "constructor_args_path", value_name = "ARGS")] - pub constructor_args: Option, - - /// The path to a file containing the constructor arguments. - #[clap(long, value_hint = ValueHint::FilePath, value_name = "PATH")] - pub constructor_args_path: Option, - - /// The `solc` version to use to build the smart contract. - #[clap(long, value_name = "VERSION")] - pub compiler_version: Option, - - /// The number of optimization runs used to build the smart contract. - #[clap(long, visible_alias = "optimizer-runs", value_name = "NUM")] - pub num_of_optimizations: Option, - - /// Flatten the source code before verifying. - #[clap(long)] - pub flatten: bool, - - /// Do not compile the flattened smart contract before verifying (if --flatten is passed). - #[clap(short, long)] - pub force: bool, - - /// Wait for verification result after submission. - #[clap(long)] - pub watch: bool, - - /// Set pre-linked libraries. - #[clap(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] - pub libraries: Vec, - - /// The project's root path. - /// - /// By default root of the Git repository, if in one, - /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] - pub root: Option, - - /// Prints the standard json compiler input. - /// - /// The standard json compiler input can be used to manually submit contract verification in - /// the browser. - #[clap(long, conflicts_with = "flatten")] - pub show_standard_json_input: bool, - - #[clap(flatten)] - pub etherscan: EtherscanOpts, - - #[clap(flatten)] - pub retry: RetryArgs, - - #[clap(flatten)] - pub verifier: VerifierArgs, -} - -impl_figment_convert!(VerifyArgs); - -impl figment::Provider for VerifyArgs { - fn metadata(&self) -> figment::Metadata { - figment::Metadata::named("Verify Provider") - } - fn data( - &self, - ) -> Result, figment::Error> { - let mut dict = self.etherscan.dict(); - if let Some(root) = self.root.as_ref() { - dict.insert("root".to_string(), figment::value::Value::serialize(root)?); - } - if let Some(optimizer_runs) = self.num_of_optimizations { - dict.insert("optimizer".to_string(), figment::value::Value::serialize(true)?); - dict.insert( - "optimizer_runs".to_string(), - figment::value::Value::serialize(optimizer_runs)?, - ); - } - Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) - } -} - -impl VerifyArgs { - /// Run the verify command to submit the contract's source code for verification on etherscan - pub async fn run(mut self) -> Result<()> { - let config = self.load_config_emit_warnings(); - let chain = config.chain_id.unwrap_or_default(); - self.etherscan.chain = Some(chain); - self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); - - if self.show_standard_json_input { - let args = - EtherscanVerificationProvider::default().create_verify_request(&self, None).await?; - println!("{}", args.source); - return Ok(()) - } - - let verifier_url = self.verifier.verifier_url.clone(); - println!("Start verifying contract `{:?}` deployed on {chain}", self.address); - self.verifier.verifier.client(&self.etherscan.key)?.verify(self).await.map_err(|err| { - if let Some(verifier_url) = verifier_url { - match Url::parse(&verifier_url) { - Ok(url) => { - if is_host_only(&url) { - return err.wrap_err(format!( - "Provided URL `{verifier_url}` is host only.\n Did you mean to use the API endpoint`{verifier_url}/api` ?" - )) - } - } - Err(url_err) => { - return err.wrap_err(format!( - "Invalid URL {verifier_url} provided: {url_err}" - )) - } - } - } - - err - }) - } - - /// Returns the configured verification provider - pub fn verification_provider(&self) -> Result> { - self.verifier.verifier.client(&self.etherscan.key) - } -} - -/// Check verification status arguments -#[derive(Debug, Clone, Parser)] -pub struct VerifyCheckArgs { - /// The verification ID. - /// - /// For Etherscan - Submission GUID. - /// - /// For Sourcify - Contract Address. - id: String, - - #[clap(flatten)] - retry: RetryArgs, - - #[clap(flatten)] - etherscan: EtherscanOpts, - - #[clap(flatten)] - verifier: VerifierArgs, -} - -impl_figment_convert_cast!(VerifyCheckArgs); - -impl VerifyCheckArgs { - /// Run the verify command to submit the contract's source code for verification on etherscan - pub async fn run(self) -> Result<()> { - println!("Checking verification status on {}", self.etherscan.chain.unwrap_or_default()); - self.verifier.verifier.client(&self.etherscan.key)?.check(self).await - } -} - -impl figment::Provider for VerifyCheckArgs { - fn metadata(&self) -> figment::Metadata { - figment::Metadata::named("Verify Check Provider") - } - - fn data( - &self, - ) -> Result, figment::Error> { - self.etherscan.data() - } -} - -/// Returns `true` if the URL only consists of host. -/// -/// This is used to check user input url for missing /api path -#[inline] -fn is_host_only(url: &Url) -> bool { - matches!(url.path(), "/" | "") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_host_only() { - assert!(!is_host_only(&Url::parse("https://blockscout.net/api").unwrap())); - assert!(is_host_only(&Url::parse("https://blockscout.net/").unwrap())); - assert!(is_host_only(&Url::parse("https://blockscout.net").unwrap())); - } -} diff --git a/crates/forge/bin/cmd/verify/provider.rs b/crates/forge/bin/cmd/verify/provider.rs deleted file mode 100644 index 08f4a65e2146e..0000000000000 --- a/crates/forge/bin/cmd/verify/provider.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::{ - etherscan::EtherscanVerificationProvider, sourcify::SourcifyVerificationProvider, VerifyArgs, - VerifyCheckArgs, -}; -use async_trait::async_trait; -use eyre::Result; -use std::{fmt, str::FromStr}; - -/// An abstraction for various verification providers such as etherscan, sourcify, blockscout -#[async_trait] -pub trait VerificationProvider { - /// This should ensure the verify request can be prepared successfully. - /// - /// Caution: Implementers must ensure that this _never_ sends the actual verify request - /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given - /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a - /// contract deployment that's executed before the verify request and the subsequent verify task - /// fails due to misconfiguration. - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()>; - - /// Sends the actual verify request for the targeted contract. - async fn verify(&mut self, args: VerifyArgs) -> Result<()>; - - /// Checks whether the contract is verified. - async fn check(&self, args: VerifyCheckArgs) -> Result<()>; -} - -impl FromStr for VerificationProviderType { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "e" | "etherscan" => Ok(VerificationProviderType::Etherscan), - "s" | "sourcify" => Ok(VerificationProviderType::Sourcify), - "b" | "blockscout" => Ok(VerificationProviderType::Blockscout), - _ => Err(format!("Unknown provider: {s}")), - } - } -} - -impl fmt::Display for VerificationProviderType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - VerificationProviderType::Etherscan => { - write!(f, "etherscan")?; - } - VerificationProviderType::Sourcify => { - write!(f, "sourcify")?; - } - VerificationProviderType::Blockscout => { - write!(f, "blockscout")?; - } - }; - Ok(()) - } -} - -#[derive(clap::ValueEnum, Debug, Default, Clone, PartialEq, Eq)] -pub enum VerificationProviderType { - #[default] - Etherscan, - Sourcify, - Blockscout, -} - -impl VerificationProviderType { - /// Returns the corresponding `VerificationProvider` for the key - pub fn client(&self, key: &Option) -> Result> { - match self { - VerificationProviderType::Etherscan => { - if key.as_ref().map_or(true, |key| key.is_empty()) { - eyre::bail!("ETHERSCAN_API_KEY must be set") - } - Ok(Box::::default()) - } - VerificationProviderType::Sourcify => { - Ok(Box::::default()) - } - VerificationProviderType::Blockscout => { - Ok(Box::::default()) - } - } - } -} diff --git a/crates/forge/bin/cmd/verify/sourcify.rs b/crates/forge/bin/cmd/verify/sourcify.rs deleted file mode 100644 index 30a5139ba61ad..0000000000000 --- a/crates/forge/bin/cmd/verify/sourcify.rs +++ /dev/null @@ -1,208 +0,0 @@ -use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; -use async_trait::async_trait; -use ethers::{solc::ConfigurableContractArtifact, utils::to_checksum}; -use eyre::Result; -use foundry_cli::utils::{get_cached_entry_by_name, LoadConfig}; -use foundry_common::fs; -use foundry_utils::Retry; -use futures::FutureExt; -use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf}; -use tracing::{trace, warn}; - -pub static SOURCIFY_URL: &str = "https://sourcify.dev/server/"; - -/// The type that can verify a contract on `sourcify` -#[derive(Debug, Clone, Default)] -#[non_exhaustive] -pub struct SourcifyVerificationProvider; - -#[async_trait] -impl VerificationProvider for SourcifyVerificationProvider { - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()> { - let _ = self.prepare_request(&args)?; - Ok(()) - } - - async fn verify(&mut self, args: VerifyArgs) -> Result<()> { - let body = self.prepare_request(&args)?; - - trace!("submitting verification request {:?}", body); - - let client = reqwest::Client::new(); - - let retry: Retry = args.retry.into(); - let resp = retry - .run_async(|| { - async { - println!( - "\nSubmitting verification for [{}] {:?}.", - args.contract.name, - to_checksum(&args.address, None) - ); - let response = client - .post(args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL)) - .header("Content-Type", "application/json") - .body(serde_json::to_string(&body)?) - .send() - .await?; - - let status = response.status(); - if !status.is_success() { - let error: serde_json::Value = response.json().await?; - eprintln!( - "Sourcify verification request for address ({}) failed with status code {}\nDetails: {:#}", - format_args!("{:?}", args.address), - status, - error - ); - warn!("Failed verify submission: {:?}", error); - std::process::exit(1); - } - - let text = response.text().await?; - Ok(Some(serde_json::from_str::(&text)?)) - } - .boxed() - }) - .await?; - - self.process_sourcify_response(resp.map(|r| r.result)); - Ok(()) - } - - async fn check(&self, args: VerifyCheckArgs) -> Result<()> { - let retry: Retry = args.retry.into(); - let resp = retry - .run_async(|| { - async { - let url = format!( - "{}check-by-addresses?addresses={}&chainIds={}", - args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL), - args.id, - args.etherscan.chain.unwrap_or_default().id(), - ); - - let response = reqwest::get(url).await?; - if !response.status().is_success() { - eprintln!( - "Failed to request verification status with status code {}", - response.status() - ); - std::process::exit(1); - }; - - Ok(Some(response.json::>().await?)) - } - .boxed() - }) - .await?; - - self.process_sourcify_response(resp); - Ok(()) - } -} - -impl SourcifyVerificationProvider { - /// Configures the API request to the sourcify API using the given [`VerifyArgs`]. - fn prepare_request(&self, args: &VerifyArgs) -> Result { - let mut config = args.try_load_config_emit_warnings()?; - config.libraries.extend(args.libraries.clone()); - - let project = config.project()?; - - if !config.cache { - eyre::bail!("Cache is required for sourcify verification.") - } - - let cache = project.read_cache_file()?; - let (path, entry) = get_cached_entry_by_name(&cache, &args.contract.name)?; - - if entry.solc_config.settings.metadata.is_none() { - eyre::bail!( - r#"Contract {} was compiled without the solc `metadata` setting. -Sourcify requires contract metadata for verification. -metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.toml`"#, - args.contract.name - ) - } - - let mut files = HashMap::with_capacity(2 + entry.imports.len()); - - // the metadata is included in the contract's artifact file - let artifact_path = entry - .find_artifact_path(&args.contract.name) - .ok_or_else(|| eyre::eyre!("No artifact found for contract {}", args.contract.name))?; - - let artifact: ConfigurableContractArtifact = fs::read_json_file(artifact_path)?; - if let Some(metadata) = artifact.metadata { - let metadata = serde_json::to_string_pretty(&metadata)?; - files.insert("metadata.json".to_string(), metadata); - } else { - eyre::bail!( - r#"No metadata found in artifact `{}` for contract {}. -Sourcify requires contract metadata for verification. -metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.toml`"#, - artifact_path.display(), - args.contract.name - ) - } - - let contract_path = args.contract.path.clone().map_or(path, PathBuf::from); - let filename = contract_path.file_name().unwrap().to_string_lossy().to_string(); - files.insert(filename, fs::read_to_string(&contract_path)?); - - for import in entry.imports { - let import_entry = format!("{}", import.display()); - files.insert(import_entry, fs::read_to_string(&import)?); - } - - let req = SourcifyVerifyRequest { - address: format!("{:?}", args.address), - chain: args.etherscan.chain.unwrap_or_default().id().to_string(), - files, - chosen_contract: None, - }; - - Ok(req) - } - - fn process_sourcify_response(&self, response: Option>) { - let response = response.unwrap().remove(0); - if response.status == "perfect" { - if let Some(ts) = response.storage_timestamp { - println!("Contract source code already verified. Storage Timestamp: {ts}"); - } else { - println!("Contract successfully verified") - } - } else if response.status == "partial" { - println!("The recompiled contract partially matches the deployed version") - } else if response.status == "false" { - println!("Contract source code is not verified") - } else { - eprintln!("Unknown status from sourcify. Status: {}", response.status); - std::process::exit(1); - } - } -} - -#[derive(Serialize, Debug)] -pub struct SourcifyVerifyRequest { - address: String, - chain: String, - files: HashMap, - #[serde(rename = "chosenContract", skip_serializing_if = "Option::is_none")] - chosen_contract: Option, -} - -#[derive(Deserialize, Debug)] -pub struct SourcifyVerificationResponse { - result: Vec, -} - -#[derive(Deserialize, Debug)] -pub struct SourcifyResponseElement { - status: String, - #[serde(rename = "storageTimestamp")] - storage_timestamp: Option, -} diff --git a/crates/forge/bin/cmd/watch.rs b/crates/forge/bin/cmd/watch.rs index bdee1e3d0456c..62bb4ebac6a8d 100644 --- a/crates/forge/bin/cmd/watch.rs +++ b/crates/forge/bin/cmd/watch.rs @@ -1,43 +1,52 @@ -use super::{build::BuildArgs, snapshot::SnapshotArgs, test::TestArgs}; +use super::{ + build::BuildArgs, coverage::CoverageArgs, doc::DocArgs, fmt::FmtArgs, + snapshot::GasSnapshotArgs, test::TestArgs, +}; +use alloy_primitives::map::HashSet; use clap::Parser; use eyre::Result; -use foundry_cli::utils::{self, FoundryPathExt}; +use foundry_cli::utils::{self, FoundryPathExt, LoadConfig}; use foundry_config::Config; -use std::{collections::HashSet, convert::Infallible, path::PathBuf, sync::Arc}; -use tracing::trace; +use parking_lot::Mutex; +use std::{ + path::PathBuf, + sync::{ + atomic::{AtomicU8, Ordering}, + Arc, + }, + time::Duration, +}; +use tokio::process::Command as TokioCommand; use watchexec::{ - action::{Action, Outcome, PreSpawn}, - command::Command, - config::{InitConfig, RuntimeConfig}, - event::{Event, Priority, ProcessEnd}, - handler::SyncFnHandler, + action::ActionHandler, + command::{Command, Program}, + job::{CommandState, Job}, paths::summarise_events_to_env, - signal::source::MainSignal, Watchexec, }; +use watchexec_events::{Event, Priority, ProcessEnd}; +use watchexec_signals::Signal; +use yansi::{Color, Paint}; -#[derive(Debug, Clone, Parser, Default)] -#[clap(next_help_heading = "Watch options")] +type SpawnHook = Arc; + +#[derive(Clone, Debug, Default, Parser)] +#[command(next_help_heading = "Watch options")] pub struct WatchArgs { /// Watch the given files or directories for changes. /// /// If no paths are provided, the source and test directories of the project are watched. - #[clap( - long, - short, - num_args(0..), - value_name = "PATH", - )] + #[arg(long, short, num_args(0..), value_name = "PATH")] pub watch: Option>, /// Do not restart the command while it's still running. - #[clap(long)] + #[arg(long)] pub no_restart: bool, /// Explicitly re-run all tests when a change is made. /// /// By default, only the tests of the last modified test file are executed. - #[clap(long)] + #[arg(long)] pub run_all: bool, /// File update debounce delay. @@ -53,363 +62,346 @@ pub struct WatchArgs { /// /// When using --poll mode, you'll want a larger duration, or risk /// overloading disk I/O. - #[clap(long, value_name = "DELAY")] + #[arg(long, value_name = "DELAY")] pub watch_delay: Option, } impl WatchArgs { - /// Returns new [InitConfig] and [RuntimeConfig] based on the [WatchArgs] + /// Creates a new [`watchexec::Config`]. /// /// If paths were provided as arguments the these will be used as the watcher's pathset, - /// otherwise the path the closure returns will be used - pub fn watchexec_config( + /// otherwise the path the closure returns will be used. + pub fn watchexec_config, P: Into>( &self, - f: impl FnOnce() -> Vec, - ) -> Result<(InitConfig, RuntimeConfig)> { - let init = init()?; - let mut runtime = runtime(self)?; + default_paths: impl FnOnce() -> Result, + ) -> Result { + self.watchexec_config_generic(default_paths, None) + } - // contains all the arguments `--watch p1, p2, p3` - let has_paths = self.watch.as_ref().map(|paths| !paths.is_empty()).unwrap_or_default(); + /// Creates a new [`watchexec::Config`] with a custom command spawn hook. + /// + /// If paths were provided as arguments the these will be used as the watcher's pathset, + /// otherwise the path the closure returns will be used. + pub fn watchexec_config_with_override, P: Into>( + &self, + default_paths: impl FnOnce() -> Result, + spawn_hook: impl Fn(&[Event], &mut TokioCommand) + Send + Sync + 'static, + ) -> Result { + self.watchexec_config_generic(default_paths, Some(Arc::new(spawn_hook))) + } - if !has_paths { - // use alternative pathset, but only those that exists - runtime.pathset(f().into_iter().filter(|p| p.exists())); + fn watchexec_config_generic, P: Into>( + &self, + default_paths: impl FnOnce() -> Result, + spawn_hook: Option, + ) -> Result { + let mut paths = self.watch.as_deref().unwrap_or_default(); + let storage: Vec<_>; + if paths.is_empty() { + storage = default_paths()?.into_iter().map(Into::into).filter(|p| p.exists()).collect(); + paths = &storage; } - Ok((init, runtime)) + self.watchexec_config_inner(paths, spawn_hook) } -} -/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge -/// build` -pub async fn watch_build(args: BuildArgs) -> Result<()> { - let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); + fn watchexec_config_inner( + &self, + paths: &[PathBuf], + spawn_hook: Option, + ) -> Result { + let config = watchexec::Config::default(); - trace!("watch build cmd={:?}", cmd); - runtime.command(watch_command(cmd.clone())); + config.on_error(|err| { + let _ = sh_eprintln!("[[{err:?}]]"); + }); - let wx = Watchexec::new(init, runtime.clone())?; - on_action(args.watch, runtime, Arc::clone(&wx), cmd, (), |_| {}); + if let Some(delay) = &self.watch_delay { + config.throttle(utils::parse_delay(delay)?); + } - // start executing the command immediately - wx.send_event(Event::default(), Priority::default()).await?; - wx.main().await??; + config.pathset(paths.iter().map(|p| p.as_path())); + + let n_path_args = self.watch.as_deref().unwrap_or_default().len(); + let base_command = Arc::new(watch_command(cmd_args(n_path_args))); + + let id = watchexec::Id::default(); + let quit_again = Arc::new(AtomicU8::new(0)); + let stop_timeout = Duration::from_secs(5); + let no_restart = self.no_restart; + let stop_signal = Signal::Terminate; + config.on_action(move |mut action| { + let base_command = base_command.clone(); + let job = action.get_or_create_job(id, move || base_command.clone()); + + let events = action.events.clone(); + let spawn_hook = spawn_hook.clone(); + job.set_spawn_hook(move |command, _| { + // https://github.com/watchexec/watchexec/blob/72f069a8477c679e45f845219276b0bfe22fed79/crates/cli/src/emits.rs#L9 + let env = summarise_events_to_env(events.iter()); + for (k, v) in env { + command.command_mut().env(format!("WATCHEXEC_{k}_PATH"), v); + } - Ok(()) + if let Some(spawn_hook) = &spawn_hook { + spawn_hook(&events, command.command_mut()); + } + }); + + let clear_screen = || { + let _ = clearscreen::clear(); + }; + + let quit = |mut action: ActionHandler| { + match quit_again.fetch_add(1, Ordering::Relaxed) { + 0 => { + let _ = sh_eprintln!( + "[Waiting {stop_timeout:?} for processes to exit before stopping... \ + Ctrl-C again to exit faster]" + ); + action.quit_gracefully(stop_signal, stop_timeout); + } + 1 => action.quit_gracefully(Signal::ForceStop, Duration::ZERO), + _ => action.quit(), + } + + action + }; + + let signals = action.signals().collect::>(); + + if signals.contains(&Signal::Terminate) || signals.contains(&Signal::Interrupt) { + return quit(action); + } + + // Only filesystem events below here (or empty synthetic events). + if action.paths().next().is_none() && !action.events.iter().any(|e| e.is_empty()) { + debug!("no filesystem or synthetic events, skip without doing more"); + return action; + } + + job.run({ + let job = job.clone(); + move |context| { + if context.current.is_running() && no_restart { + return; + } + job.restart_with_signal(stop_signal, stop_timeout); + job.run({ + let job = job.clone(); + move |context| { + clear_screen(); + setup_process(job, &context.command) + } + }); + } + }); + + action + }); + + Ok(config) + } } -/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge -/// snapshot` -pub async fn watch_snapshot(args: SnapshotArgs) -> Result<()> { - let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args(args.test.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); +fn setup_process(job: Job, _command: &Command) { + tokio::spawn(async move { + job.to_wait().await; + job.run(move |context| end_of_process(context.current)); + }); +} - trace!("watch snapshot cmd={:?}", cmd); - runtime.command(watch_command(cmd.clone())); - let wx = Watchexec::new(init, runtime.clone())?; +fn end_of_process(state: &CommandState) { + let CommandState::Finished { status, started, finished } = state else { + return; + }; - on_action(args.test.watch.clone(), runtime, Arc::clone(&wx), cmd, (), |_| {}); + let duration = *finished - *started; + let timings = true; + let timing = if timings { format!(", lasted {duration:?}") } else { String::new() }; + let (msg, fg) = match status { + ProcessEnd::ExitError(code) => (format!("Command exited with {code}{timing}"), Color::Red), + ProcessEnd::ExitSignal(sig) => { + (format!("Command killed by {sig:?}{timing}"), Color::Magenta) + } + ProcessEnd::ExitStop(sig) => (format!("Command stopped by {sig:?}{timing}"), Color::Blue), + ProcessEnd::Continued => (format!("Command continued{timing}"), Color::Cyan), + ProcessEnd::Exception(ex) => { + (format!("Command ended by exception {ex:#x}{timing}"), Color::Yellow) + } + ProcessEnd::Success => (format!("Command was successful{timing}"), Color::Green), + }; - // start executing the command immediately - wx.send_event(Event::default(), Priority::default()).await?; - wx.main().await??; + let quiet = false; + if !quiet { + let _ = sh_eprintln!("{}", format!("[{msg}]").paint(fg.foreground())); + } +} +/// Runs the given [`watchexec::Config`]. +pub async fn run(config: watchexec::Config) -> Result<()> { + let wx = Watchexec::with_config(config)?; + wx.send_event(Event::default(), Priority::Urgent).await?; + wx.main().await??; Ok(()) } /// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge -/// test` -pub async fn watch_test(args: TestArgs) -> Result<()> { - let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); - trace!("watch test cmd={:?}", cmd); - runtime.command(watch_command(cmd.clone())); - let wx = Watchexec::new(init, runtime.clone())?; +/// build` +pub async fn watch_build(args: BuildArgs) -> Result<()> { + let config = args.watchexec_config()?; + run(config).await +} - let config: Config = args.build_args().into(); +/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge +/// snapshot` +pub async fn watch_gas_snapshot(args: GasSnapshotArgs) -> Result<()> { + let config = args.watchexec_config()?; + run(config).await +} +/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge +/// test` +pub async fn watch_test(args: TestArgs) -> Result<()> { + let config: Config = args.build.load_config()?; let filter = args.filter(&config); - - // marker to check whether to override the command + // Marker to check whether to override the command. let no_reconfigure = filter.args().test_pattern.is_some() || filter.args().path_pattern.is_some() || filter.args().contract_pattern.is_some() || args.watch.run_all; - let state = WatchTestState { - project_root: config.__root.0, - no_reconfigure, - last_test_files: Default::default(), - }; - on_action(args.watch.clone(), runtime, Arc::clone(&wx), cmd, state, on_test); - - // start executing the command immediately - wx.send_event(Event::default(), Priority::default()).await?; - wx.main().await??; - - Ok(()) -} - -#[derive(Debug, Clone)] -struct WatchTestState { - /// the root directory of the project - project_root: PathBuf, - /// marks whether we can reconfigure the watcher command with the `--match-path` arg - no_reconfigure: bool, - /// Tracks the last changed test files, if any so that if a non-test file was modified we run - /// this file instead *Note:* this is a vec, so we can also watch out for changes - /// introduced by `forge fmt` - last_test_files: HashSet, -} + let last_test_files = Mutex::new(HashSet::::default()); + let project_root = config.root.to_string_lossy().into_owned(); + let config = args.watch.watchexec_config_with_override( + || Ok([&config.test, &config.src]), + move |events, command| { + let mut changed_sol_test_files: HashSet<_> = events + .iter() + .flat_map(|e| e.paths()) + .filter(|(path, _)| path.is_sol_test()) + .filter_map(|(path, _)| path.to_str()) + .map(str::to_string) + .collect(); + + if changed_sol_test_files.len() > 1 { + // Run all tests if multiple files were changed at once, for example when running + // `forge fmt`. + return; + } -/// The `on_action` hook for `forge test --watch` -fn on_test(action: OnActionState) { - let OnActionState { args, runtime, action, wx, cmd, other } = action; - let WatchTestState { project_root, no_reconfigure, last_test_files } = other; + if changed_sol_test_files.is_empty() { + // Reuse the old test files if a non-test file was changed. + let last = last_test_files.lock(); + if last.is_empty() { + return; + } + changed_sol_test_files = last.clone(); + } - if no_reconfigure { - // nothing to reconfigure - return - } + // append `--match-path` glob + let mut file = changed_sol_test_files.iter().next().expect("test file present").clone(); - let mut cmd = cmd.clone(); - - let mut changed_sol_test_files: HashSet<_> = action - .events - .iter() - .flat_map(|e| e.paths()) - .filter(|(path, _)| path.is_sol_test()) - .filter_map(|(path, _)| path.to_str()) - .map(str::to_string) - .collect(); - - // replace `--match-path` | `-mp` argument - if let Some(pos) = cmd.iter().position(|arg| arg == "--match-path" || arg == "-mp") { - // --match-path requires 1 argument - cmd.drain(pos..=(pos + 1)); - } + // remove the project root dir from the detected file + if let Some(f) = file.strip_prefix(&project_root) { + file = f.trim_start_matches('/').to_string(); + } - if changed_sol_test_files.len() > 1 || - (changed_sol_test_files.is_empty() && last_test_files.is_empty()) - { - // this could happen if multiple files were changed at once, for example `forge fmt` was - // run, or if no test files were changed and no previous test files were modified in which - // case we simply run all - let mut config = runtime.clone(); - config.command(watch_command(cmd.clone())); - // re-register the action - on_action( - args.clone(), - config, - wx, - cmd, - WatchTestState { - project_root, - no_reconfigure, - last_test_files: changed_sol_test_files, - }, - on_test, - ); - return - } + trace!(?file, "reconfigure test command"); - if changed_sol_test_files.is_empty() { - // reuse the old test files if a non-test file was changed - changed_sol_test_files = last_test_files; - } + // Before appending `--match-path`, check if it already exists + if !no_reconfigure { + command.arg("--match-path").arg(file); + } + }, + )?; + run(config).await +} - // append `--match-path` glob - let mut file = changed_sol_test_files.clone().into_iter().next().expect("test file present"); +pub async fn watch_coverage(args: CoverageArgs) -> Result<()> { + let config = args.watch().watchexec_config(|| { + let config = args.load_config()?; + Ok([config.test, config.src]) + })?; + run(config).await +} - // remove the project root dir from the detected file - if let Some(root) = project_root.as_os_str().to_str() { - if let Some(f) = file.strip_prefix(root) { - file = f.trim_start_matches('/').to_string(); - } - } +pub async fn watch_fmt(args: FmtArgs) -> Result<()> { + let config = args.watch.watchexec_config(|| { + let config = args.load_config()?; + Ok([config.src, config.test, config.script]) + })?; + run(config).await +} - let mut new_cmd = cmd.clone(); - new_cmd.push("--match-path".to_string()); - new_cmd.push(file); - trace!("reconfigure test command {:?}", new_cmd); - - // reconfigure the executor with a new runtime - let mut config = runtime.clone(); - config.command(watch_command(new_cmd)); - - // re-register the action - on_action( - args.clone(), - config, - wx, - cmd, - WatchTestState { project_root, no_reconfigure, last_test_files: changed_sol_test_files }, - on_test, - ); +/// Executes a [`Watchexec`] that listens for changes in the project's sources directory +pub async fn watch_doc(args: DocArgs) -> Result<()> { + let config = args.watch.watchexec_config(|| { + let config = args.config()?; + Ok([config.src]) + })?; + run(config).await } -/// Converts a list of arguments to a `watchexec::Command` +/// Converts a list of arguments to a `watchexec::Command`. /// -/// The first index in `args`, is expected to be the path to the executable, See `cmd_args` +/// The first index in `args` is the path to the executable. /// /// # Panics -/// if `args` is empty +/// +/// Panics if `args` is empty. fn watch_command(mut args: Vec) -> Command { debug_assert!(!args.is_empty()); let prog = args.remove(0); - Command::Exec { prog, args } + Command { program: Program::Exec { prog: prog.into(), args }, options: Default::default() } } /// Returns the env args without the `--watch` flag from the args for the Watchexec command fn cmd_args(num: usize) -> Vec { - // all the forge arguments including path to forge bin - let mut cmd_args: Vec<_> = std::env::args().collect(); + clean_cmd_args(num, std::env::args().collect()) +} + +#[instrument(level = "debug", ret)] +fn clean_cmd_args(num: usize, mut cmd_args: Vec) -> Vec { if let Some(pos) = cmd_args.iter().position(|arg| arg == "--watch" || arg == "-w") { cmd_args.drain(pos..=(pos + num)); } - cmd_args -} - -/// Returns the Initialisation configuration for [`Watchexec`]. -pub fn init() -> Result { - let mut config = InitConfig::default(); - config.on_error(SyncFnHandler::from(|data| -> std::result::Result<(), Infallible> { - trace!("[[{:?}]]", data); - Ok(()) - })); - - Ok(config) -} - -/// Contains all necessary context to reconfigure a [`Watchexec`] on the fly -struct OnActionState<'a, T: Clone> { - args: &'a WatchArgs, - runtime: &'a RuntimeConfig, - action: &'a Action, - cmd: &'a Vec, - wx: Arc, - // additional context to inject - other: T, -} - -/// Registers the `on_action` hook on the `RuntimeConfig` currently in use in the `Watchexec` -/// -/// **Note** this is a bit weird since we're installing the hook on the config that's already used -/// in `Watchexec` but necessary if we want to have access to it in order to -/// [`Watchexec::reconfigure`] -fn on_action( - args: WatchArgs, - mut config: RuntimeConfig, - wx: Arc, - cmd: Vec, - other: T, - f: F, -) where - F: for<'a> Fn(OnActionState<'a, T>) + Send + 'static, - T: Clone + Send + 'static, -{ - let on_busy = if args.no_restart { "do-nothing" } else { "restart" }; - let runtime = config.clone(); - let w = Arc::clone(&wx); - config.on_action(move |action: Action| { - let fut = async { Ok::<(), Infallible>(()) }; - let signals: Vec = action.events.iter().flat_map(|e| e.signals()).collect(); - let has_paths = action.events.iter().flat_map(|e| e.paths()).next().is_some(); - - if signals.contains(&MainSignal::Terminate) || signals.contains(&MainSignal::Interrupt) { - action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); - return fut - } - - if !has_paths { - if !signals.is_empty() { - let mut out = Outcome::DoNothing; - for sig in signals { - out = Outcome::both(out, Outcome::Signal(sig)); - } - - action.outcome(out); - return fut + // There's another edge case where short flags are combined into one which is supported by clap, + // like `-vw` for verbosity and watch + // this removes any `w` from concatenated short flags + if let Some(pos) = cmd_args.iter().position(|arg| { + fn contains_w_in_short(arg: &str) -> Option { + let mut iter = arg.chars().peekable(); + if *iter.peek()? != '-' { + return None } - - let completion = action.events.iter().flat_map(|e| e.completions()).next(); - if let Some(status) = completion { - match status { - Some(ProcessEnd::ExitError(code)) => { - tracing::trace!("Command exited with {code}") - } - Some(ProcessEnd::ExitSignal(sig)) => { - tracing::trace!("Command killed by {:?}", sig) - } - Some(ProcessEnd::ExitStop(sig)) => { - tracing::trace!("Command stopped by {:?}", sig) - } - Some(ProcessEnd::Continued) => tracing::trace!("Command continued"), - Some(ProcessEnd::Exception(ex)) => { - tracing::trace!("Command ended by exception {:#x}", ex) - } - Some(ProcessEnd::Success) => tracing::trace!("Command was successful"), - None => tracing::trace!("Command completed"), - }; - - action.outcome(Outcome::DoNothing); - return fut + iter.next(); + if *iter.peek()? == '-' { + return None } + Some(iter.any(|c| c == 'w')) } + contains_w_in_short(arg).unwrap_or(false) + }) { + let clean_arg = cmd_args[pos].replace('w', ""); + if clean_arg == "-" { + cmd_args.remove(pos); + } else { + cmd_args[pos] = clean_arg; + } + } - f(OnActionState { - args: &args, - runtime: &runtime, - action: &action, - wx: w.clone(), - cmd: &cmd, - other: other.clone(), - }); - - // mattsse: could be made into flag to never clear the shell - let clear = false; - let when_running = match (clear, on_busy) { - (_, "do-nothing") => Outcome::DoNothing, - (true, "restart") => { - Outcome::both(Outcome::Stop, Outcome::both(Outcome::Clear, Outcome::Start)) - } - (false, "restart") => Outcome::both(Outcome::Stop, Outcome::Start), - _ => Outcome::DoNothing, - }; - - let when_idle = - if clear { Outcome::both(Outcome::Clear, Outcome::Start) } else { Outcome::Start }; - - action.outcome(Outcome::if_running(when_running, when_idle)); - - fut - }); - - let _ = wx.reconfigure(config); + cmd_args } -/// Returns the Runtime configuration for [`Watchexec`]. -pub fn runtime(args: &WatchArgs) -> Result { - let mut config = RuntimeConfig::default(); +#[cfg(test)] +mod tests { + use super::*; - config.pathset(args.watch.clone().unwrap_or_default()); - - if let Some(delay) = &args.watch_delay { - config.action_throttle(utils::parse_delay(delay)?); + #[test] + fn parse_cmd_args() { + let args = vec!["-vw".to_string()]; + let cleaned = clean_cmd_args(0, args); + assert_eq!(cleaned, vec!["-v".to_string()]); } - - config.on_pre_spawn(move |prespawn: PreSpawn| async move { - let envs = summarise_events_to_env(prespawn.events.iter()); - if let Some(mut command) = prespawn.command().await { - for (k, v) in envs { - command.env(format!("CARGO_WATCH_{k}_PATH"), v); - } - } - - Ok::<(), Infallible>(()) - }); - - Ok(config) } diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index 633b7b8002ef0..82463d64e72a1 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -2,102 +2,163 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; use foundry_cli::{handler, utils}; +use foundry_common::shell; +use foundry_evm::inspectors::cheatcodes::{set_execution_context, ForgeContext}; mod cmd; +use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}; + mod opts; +use opts::{Forge, ForgeSubcommand}; -use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}; -use opts::{Opts, Subcommands}; +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; -fn main() -> Result<()> { +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + +fn main() { + if let Err(err) = run() { + let _ = foundry_common::sh_err!("{err:?}"); + std::process::exit(1); + } +} + +fn run() -> Result<()> { + handler::install(); utils::load_dotenv(); - handler::install()?; utils::subscriber(); utils::enable_paint(); - let opts = Opts::parse(); - match opts.sub { - Subcommands::Test(cmd) => { + let args = Forge::parse(); + args.global.init()?; + init_execution_context(&args.cmd); + + match args.cmd { + ForgeSubcommand::Test(cmd) => { if cmd.is_watch() { utils::block_on(watch::watch_test(cmd)) } else { + let silent = cmd.junit || shell::is_json(); let outcome = utils::block_on(cmd.run())?; - outcome.ensure_ok() + outcome.ensure_ok(silent) } } - Subcommands::Script(cmd) => { - // install the shell before executing the command - foundry_common::shell::set_shell(foundry_common::shell::Shell::from_args( - cmd.opts.args.silent, - cmd.json, - ))?; - utils::block_on(cmd.run_script(Default::default())) + ForgeSubcommand::Script(cmd) => utils::block_on(cmd.run_script()), + ForgeSubcommand::Coverage(cmd) => { + if cmd.is_watch() { + utils::block_on(watch::watch_coverage(cmd)) + } else { + utils::block_on(cmd.run()) + } } - Subcommands::Coverage(cmd) => utils::block_on(cmd.run()), - Subcommands::Bind(cmd) => cmd.run(), - Subcommands::Build(cmd) => { + ForgeSubcommand::Bind(cmd) => cmd.run(), + ForgeSubcommand::Build(cmd) => { if cmd.is_watch() { utils::block_on(watch::watch_build(cmd)) } else { - cmd.run().map(|_| ()) + cmd.run().map(drop) } } - Subcommands::Debug(cmd) => utils::block_on(cmd.debug(Default::default())), - Subcommands::VerifyContract(args) => utils::block_on(args.run()), - Subcommands::VerifyCheck(args) => utils::block_on(args.run()), - Subcommands::Cache(cmd) => match cmd.sub { + ForgeSubcommand::VerifyContract(args) => utils::block_on(args.run()), + ForgeSubcommand::VerifyCheck(args) => utils::block_on(args.run()), + ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Clone(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Cache(cmd) => match cmd.sub { CacheSubcommands::Clean(cmd) => cmd.run(), CacheSubcommands::Ls(cmd) => cmd.run(), }, - Subcommands::Create(cmd) => utils::block_on(cmd.run()), - Subcommands::Update(cmd) => cmd.run(), - Subcommands::Install(cmd) => cmd.run(), - Subcommands::Remove(cmd) => cmd.run(), - Subcommands::Remappings(cmd) => cmd.run(), - Subcommands::Init(cmd) => cmd.run(), - Subcommands::Completions { shell } => { - generate(shell, &mut Opts::command(), "forge", &mut std::io::stdout()); + ForgeSubcommand::Create(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Update(cmd) => cmd.run(), + ForgeSubcommand::Install(cmd) => cmd.run(), + ForgeSubcommand::Remove(cmd) => cmd.run(), + ForgeSubcommand::Remappings(cmd) => cmd.run(), + ForgeSubcommand::Init(cmd) => cmd.run(), + ForgeSubcommand::Completions { shell } => { + generate(shell, &mut Forge::command(), "forge", &mut std::io::stdout()); Ok(()) } - Subcommands::GenerateFigSpec => { + ForgeSubcommand::GenerateFigSpec => { clap_complete::generate( clap_complete_fig::Fig, - &mut Opts::command(), + &mut Forge::command(), "forge", &mut std::io::stdout(), ); Ok(()) } - Subcommands::Clean { root } => { - let config = utils::load_config_with_root(root); - config.project()?.cleanup()?; + ForgeSubcommand::Clean { root } => { + let config = utils::load_config_with_root(root.as_deref())?; + let project = config.project()?; + config.cleanup(&project)?; Ok(()) } - Subcommands::Snapshot(cmd) => { + ForgeSubcommand::Snapshot(cmd) => { if cmd.is_watch() { - utils::block_on(watch::watch_snapshot(cmd)) + utils::block_on(watch::watch_gas_snapshot(cmd)) } else { utils::block_on(cmd.run()) } } - Subcommands::Fmt(cmd) => cmd.run(), - Subcommands::Config(cmd) => cmd.run(), - Subcommands::Flatten(cmd) => cmd.run(), - Subcommands::Inspect(cmd) => cmd.run(), - Subcommands::UploadSelectors(args) => utils::block_on(args.run()), - Subcommands::Tree(cmd) => cmd.run(), - Subcommands::Geiger(cmd) => { - let check = cmd.check; + ForgeSubcommand::Fmt(cmd) => { + if cmd.is_watch() { + utils::block_on(watch::watch_fmt(cmd)) + } else { + cmd.run() + } + } + ForgeSubcommand::Config(cmd) => cmd.run(), + ForgeSubcommand::Flatten(cmd) => cmd.run(), + ForgeSubcommand::Inspect(cmd) => cmd.run(), + ForgeSubcommand::Tree(cmd) => cmd.run(), + ForgeSubcommand::Geiger(cmd) => { let n = cmd.run()?; - if check && n > 0 { + if n > 0 { std::process::exit(n as i32); } Ok(()) } - Subcommands::Doc(cmd) => cmd.run(), - Subcommands::Selectors { command } => utils::block_on(command.run()), - Subcommands::Generate(cmd) => match cmd.sub { + ForgeSubcommand::Doc(cmd) => { + if cmd.is_watch() { + utils::block_on(watch::watch_doc(cmd)) + } else { + utils::block_on(cmd.run())?; + Ok(()) + } + } + ForgeSubcommand::Selectors { command } => utils::block_on(command.run()), + ForgeSubcommand::Generate(cmd) => match cmd.sub { GenerateSubcommands::Test(cmd) => cmd.run(), }, + ForgeSubcommand::Compiler(cmd) => cmd.run(), + ForgeSubcommand::Soldeer(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Eip712(cmd) => cmd.run(), + ForgeSubcommand::BindJson(cmd) => cmd.run(), } } + +/// Set the program execution context based on `forge` subcommand used. +/// The execution context can be set only once per program, and it can be checked by using +/// cheatcodes. +fn init_execution_context(subcommand: &ForgeSubcommand) { + let context = match subcommand { + ForgeSubcommand::Test(_) => ForgeContext::Test, + ForgeSubcommand::Coverage(_) => ForgeContext::Coverage, + ForgeSubcommand::Snapshot(_) => ForgeContext::Snapshot, + ForgeSubcommand::Script(cmd) => { + if cmd.broadcast { + ForgeContext::ScriptBroadcast + } else if cmd.resume { + ForgeContext::ScriptResume + } else { + ForgeContext::ScriptDryRun + } + } + _ => ForgeContext::Unknown, + }; + set_execution_context(context); +} diff --git a/crates/forge/bin/opts.rs b/crates/forge/bin/opts.rs index 68c2683ca14e2..86a23d7408bc2 100644 --- a/crates/forge/bin/opts.rs +++ b/crates/forge/bin/opts.rs @@ -1,54 +1,40 @@ use crate::cmd::{ - bind::BindArgs, - build::BuildArgs, - cache::CacheArgs, - config, coverage, - create::CreateArgs, - debug::DebugArgs, - doc::DocArgs, - flatten, - fmt::FmtArgs, - fourbyte::UploadSelectorsArgs, - geiger, generate, - init::InitArgs, - inspect, - install::InstallArgs, - remappings::RemappingArgs, - remove::RemoveArgs, - script::ScriptArgs, - selectors::SelectorsSubcommands, - snapshot, test, tree, update, - verify::{VerifyArgs, VerifyCheckArgs}, + bind::BindArgs, bind_json, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, + compiler::CompilerArgs, config, coverage, create::CreateArgs, 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; +use forge_verify::{VerifyArgs, VerifyBytecodeArgs, VerifyCheckArgs}; +use foundry_cli::opts::GlobalArgs; +use foundry_common::version::{LONG_VERSION, SHORT_VERSION}; use std::path::PathBuf; -const VERSION_MESSAGE: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("VERGEN_GIT_SHA"), - " ", - env!("VERGEN_BUILD_TIMESTAMP"), - ")" -); - -#[derive(Debug, Parser)] -#[clap(name = "forge", version = VERSION_MESSAGE)] -pub struct Opts { - #[clap(subcommand)] - pub sub: Subcommands, -} - -#[derive(Debug, Subcommand)] -#[clap( - about = "Build, test, fuzz, debug and deploy Solidity contracts.", +/// Build, test, fuzz, debug and deploy Solidity contracts. +#[derive(Parser)] +#[command( + name = "forge", + version = SHORT_VERSION, + long_version = LONG_VERSION, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html", - next_display_order = None + next_display_order = None, )] +pub struct Forge { + /// Include the global arguments. + #[command(flatten)] + pub global: GlobalArgs, + + #[command(subcommand)] + pub cmd: ForgeSubcommand, +} + +#[derive(Subcommand)] #[allow(clippy::large_enum_variant)] -pub enum Subcommands { +pub enum ForgeSubcommand { /// Run the project's tests. - #[clap(visible_alias = "t")] + #[command(visible_alias = "t")] Test(test::TestArgs), /// Run a smart contract as a script, building transactions that can be sent onchain. @@ -58,103 +44,101 @@ pub enum Subcommands { Coverage(coverage::CoverageArgs), /// Generate Rust bindings for smart contracts. - #[clap(alias = "bi")] + #[command(alias = "bi")] Bind(BindArgs), /// Build the project's smart contracts. - #[clap(visible_aliases = ["b", "compile"])] + #[command(visible_aliases = ["b", "compile"])] Build(BuildArgs), - /// Debugs a single smart contract as a script. - #[clap(visible_alias = "d")] - Debug(DebugArgs), + /// Clone a contract from Etherscan. + Clone(CloneArgs), /// Update one or multiple dependencies. /// /// If no arguments are provided, then all dependencies are updated. - #[clap(visible_alias = "u")] + #[command(visible_alias = "u")] Update(update::UpdateArgs), /// Install one or multiple dependencies. /// /// If no arguments are provided, then existing dependencies will be installed. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Install(InstallArgs), /// Remove one or multiple dependencies. - #[clap(visible_alias = "rm")] + #[command(visible_alias = "rm")] Remove(RemoveArgs), /// Get the automatically inferred remappings for the project. - #[clap(visible_alias = "re")] + #[command(visible_alias = "re")] Remappings(RemappingArgs), /// Verify smart contracts on Etherscan. - #[clap(visible_alias = "v")] + #[command(visible_alias = "v")] VerifyContract(VerifyArgs), /// Check verification status on Etherscan. - #[clap(visible_alias = "vc")] + #[command(visible_alias = "vc")] VerifyCheck(VerifyCheckArgs), + /// Verify the deployed bytecode against its source on Etherscan. + #[clap(visible_alias = "vb")] + VerifyBytecode(VerifyBytecodeArgs), + /// Deploy a smart contract. - #[clap(visible_alias = "c")] + #[command(visible_alias = "c")] Create(CreateArgs), /// Create a new Forge project. Init(InitArgs), /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, /// Remove the build artifacts and cache directories. - #[clap(visible_alias = "cl")] + #[command(visible_alias = "cl")] Clean { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, }, /// Manage the Foundry cache. Cache(CacheArgs), - /// Create a snapshot of each test's gas usage. - #[clap(visible_alias = "s")] - Snapshot(snapshot::SnapshotArgs), + /// Create a gas snapshot of each test's gas usage. + #[command(visible_alias = "s")] + Snapshot(snapshot::GasSnapshotArgs), /// Display the current config. - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Config(config::ConfigArgs), /// Flatten a source file and all of its imports into one file. - #[clap(visible_alias = "f")] + #[command(visible_alias = "f")] Flatten(flatten::FlattenArgs), /// Format Solidity source files. Fmt(FmtArgs), /// Get specialized information about a smart contract. - #[clap(visible_alias = "in")] + #[command(visible_alias = "in")] Inspect(inspect::InspectArgs), - /// Uploads abi of given contract to the https://api.openchain.xyz - /// function selector database. - #[clap(visible_alias = "up")] - UploadSelectors(UploadSelectorsArgs), - /// Display a tree visualization of the project's dependency graph. - #[clap(visible_alias = "tr")] + #[command(visible_alias = "tr")] Tree(tree::TreeArgs), /// Detects usage of unsafe cheat codes in a project and its dependencies. @@ -163,13 +147,36 @@ pub enum Subcommands { /// Generate documentation for the project. Doc(DocArgs), - /// Function selector utilities - #[clap(visible_alias = "se")] + /// Function selector utilities. + #[command(visible_alias = "se")] Selectors { - #[clap(subcommand)] + #[command(subcommand)] command: SelectorsSubcommands, }, /// Generate scaffold files. Generate(generate::GenerateArgs), + + /// Compiler utilities. + Compiler(CompilerArgs), + + /// 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)] +mod tests { + use super::*; + use clap::CommandFactory; + + #[test] + fn verify_cli() { + Forge::command().debug_assert(); + } } diff --git a/crates/forge/build.rs b/crates/forge/build.rs deleted file mode 100644 index c2f550fb6f829..0000000000000 --- a/crates/forge/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - vergen::EmitBuilder::builder().build_timestamp().git_sha(true).emit().unwrap(); -} diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index d9cfabaff900b..22c1bbb0701d1 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -1,31 +1,55 @@ -use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Row, Table}; +//! Coverage reports. + +use alloy_primitives::map::HashMap; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Attribute, Cell, Color, Row, Table}; +use evm_disassembler::disassemble_bytes; +use foundry_common::fs; +use semver::Version; +use std::{ + collections::hash_map, + io::Write, + path::{Path, PathBuf}, +}; + pub use foundry_evm::coverage::*; -use std::io::Write; /// A coverage reporter. pub trait CoverageReporter { - fn report(self, report: &CoverageReport) -> eyre::Result<()>; + /// Returns `true` if the reporter needs source maps for the final report. + fn needs_source_maps(&self) -> bool { + false + } + + /// Runs the reporter. + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()>; } /// A simple summary reporter that prints the coverage results in a table. -pub struct SummaryReporter { +pub struct CoverageSummaryReporter { /// The summary table. table: Table, /// The total coverage of the entire project. total: CoverageSummary, } -impl Default for SummaryReporter { +impl Default for CoverageSummaryReporter { fn default() -> Self { let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(["File", "% Lines", "% Statements", "% Branches", "% Funcs"]); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![ + Cell::new("File"), + Cell::new("% Lines"), + Cell::new("% Statements"), + Cell::new("% Branches"), + Cell::new("% Funcs"), + ]); Self { table, total: CoverageSummary::default() } } } -impl SummaryReporter { +impl CoverageSummaryReporter { fn add_row(&mut self, name: impl Into, summary: CoverageSummary) { let mut row = Row::new(); row.add_cell(name.into()) @@ -37,15 +61,15 @@ impl SummaryReporter { } } -impl CoverageReporter for SummaryReporter { - fn report(mut self, report: &CoverageReport) -> eyre::Result<()> { +impl CoverageReporter for CoverageSummaryReporter { + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, summary) in report.summary_by_file() { - self.total += &summary; - self.add_row(path, summary); + self.total.merge(&summary); + self.add_row(path.display(), summary); } self.add_row("Total", self.total.clone()); - println!("{}", self.table); + sh_println!("\n{}", self.table)?; Ok(()) } } @@ -67,68 +91,88 @@ fn format_cell(hits: usize, total: usize) -> Cell { cell } -pub struct LcovReporter<'a> { - /// Destination buffer - destination: &'a mut (dyn Write + 'a), +/// Writes the coverage report in [LCOV]'s [tracefile format]. +/// +/// [LCOV]: https://github.com/linux-test-project/lcov +/// [tracefile format]: https://man.archlinux.org/man/geninfo.1.en#TRACEFILE_FORMAT +pub struct LcovReporter { + path: PathBuf, + version: Version, } -impl<'a> LcovReporter<'a> { - pub fn new(destination: &'a mut (dyn Write + 'a)) -> LcovReporter<'a> { - Self { destination } +impl LcovReporter { + /// Create a new LCOV reporter. + pub fn new(path: PathBuf, version: Version) -> Self { + Self { path, version } } } -impl<'a> CoverageReporter for LcovReporter<'a> { - fn report(self, report: &CoverageReport) -> eyre::Result<()> { - for (file, items) in report.items_by_source() { - let summary = items.iter().fold(CoverageSummary::default(), |mut summary, item| { - summary += item; - summary - }); +impl CoverageReporter for LcovReporter { + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { + let mut out = std::io::BufWriter::new(fs::create_file(&self.path)?); + + let mut fn_index = 0usize; + for (path, items) in report.items_by_file() { + let summary = CoverageSummary::from_items(items.iter().copied()); - writeln!(self.destination, "TN:")?; - writeln!(self.destination, "SF:{file}")?; + writeln!(out, "TN:")?; + writeln!(out, "SF:{}", path.display())?; for item in items { - let line = item.loc.line; + let line = item.loc.lines.start; + // `lines` is half-open, so we need to subtract 1 to get the last included line. + let end_line = item.loc.lines.end - 1; let hits = item.hits; match item.kind { - CoverageItemKind::Function { name } => { + CoverageItemKind::Function { ref name } => { let name = format!("{}.{name}", item.loc.contract_name); - writeln!(self.destination, "FN:{line},{name}")?; - writeln!(self.destination, "FNDA:{hits},{name}")?; + if self.version >= Version::new(2, 2, 0) { + // v2.2 changed the FN format. + writeln!(out, "FNL:{fn_index},{line},{end_line}")?; + writeln!(out, "FNA:{fn_index},{hits},{name}")?; + fn_index += 1; + } else if self.version >= Version::new(2, 0, 0) { + // v2.0 added end_line to FN. + writeln!(out, "FN:{line},{end_line},{name}")?; + writeln!(out, "FNDA:{hits},{name}")?; + } else { + writeln!(out, "FN:{line},{name}")?; + writeln!(out, "FNDA:{hits},{name}")?; + } } CoverageItemKind::Line => { - writeln!(self.destination, "DA:{line},{hits}")?; + writeln!(out, "DA:{line},{hits}")?; } - CoverageItemKind::Branch { branch_id, path_id } => { + CoverageItemKind::Branch { branch_id, path_id, .. } => { writeln!( - self.destination, + out, "BRDA:{line},{branch_id},{path_id},{}", if hits == 0 { "-".to_string() } else { hits.to_string() } )?; } - // Statements are not in the LCOV format - CoverageItemKind::Statement => (), + // Statements are not in the LCOV format. + // We don't add them in order to avoid doubling line hits. + CoverageItemKind::Statement => {} } } // Function summary - writeln!(self.destination, "FNF:{}", summary.function_count)?; - writeln!(self.destination, "FNH:{}", summary.function_hits)?; + writeln!(out, "FNF:{}", summary.function_count)?; + writeln!(out, "FNH:{}", summary.function_hits)?; // Line summary - writeln!(self.destination, "LF:{}", summary.line_count)?; - writeln!(self.destination, "LH:{}", summary.line_hits)?; + writeln!(out, "LF:{}", summary.line_count)?; + writeln!(out, "LH:{}", summary.line_hits)?; // Branch summary - writeln!(self.destination, "BRF:{}", summary.branch_count)?; - writeln!(self.destination, "BRH:{}", summary.branch_hits)?; + writeln!(out, "BRF:{}", summary.branch_count)?; + writeln!(out, "BRH:{}", summary.branch_hits)?; - writeln!(self.destination, "end_of_record")?; + writeln!(out, "end_of_record")?; } - println!("Wrote LCOV report."); + out.flush()?; + sh_println!("Wrote LCOV report.")?; Ok(()) } @@ -138,33 +182,153 @@ impl<'a> CoverageReporter for LcovReporter<'a> { pub struct DebugReporter; impl CoverageReporter for DebugReporter { - fn report(self, report: &CoverageReport) -> eyre::Result<()> { - for (path, items) in report.items_by_source() { - println!("Uncovered for {path}:"); - items.iter().for_each(|item| { + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { + for (path, items) in report.items_by_file() { + sh_println!("Uncovered for {}:", path.display())?; + for item in items { if item.hits == 0 { - println!("- {item}"); + sh_println!("- {item}")?; } - }); - println!(); + } + sh_println!()?; } for (contract_id, anchors) in &report.anchors { - println!("Anchors for {contract_id}:"); - anchors.iter().for_each(|anchor| { - println!("- {anchor}"); - println!( + sh_println!("Anchors for {contract_id}:")?; + let anchors = anchors + .0 + .iter() + .map(|anchor| (false, anchor)) + .chain(anchors.1.iter().map(|anchor| (true, anchor))); + for (is_deployed, anchor) in anchors { + sh_println!("- {anchor}")?; + if is_deployed { + sh_println!("- Creation code")?; + } else { + sh_println!("- Runtime code")?; + } + sh_println!( " - Refers to item: {}", report - .items + .analyses .get(&contract_id.version) .and_then(|items| items.get(anchor.item_id)) - .map_or("None".to_owned(), |item| item.to_string()) - ); - }); - println!(); + .map_or_else(|| "None".to_owned(), |item| item.to_string()) + )?; + } + sh_println!()?; } Ok(()) } } + +pub struct BytecodeReporter { + root: PathBuf, + destdir: PathBuf, +} + +impl BytecodeReporter { + pub fn new(root: PathBuf, destdir: PathBuf) -> Self { + Self { root, destdir } + } +} + +impl CoverageReporter for BytecodeReporter { + fn needs_source_maps(&self) -> bool { + true + } + + fn report(&mut self, report: &CoverageReport) -> eyre::Result<()> { + use std::fmt::Write; + + fs::create_dir_all(&self.destdir)?; + + let no_source_elements = Vec::new(); + let mut line_number_cache = LineNumberCache::new(self.root.clone()); + + for (contract_id, hits) in &report.bytecode_hits { + let ops = disassemble_bytes(hits.bytecode().to_vec())?; + let mut formatted = String::new(); + + let source_elements = + report.source_maps.get(contract_id).map(|sm| &sm.1).unwrap_or(&no_source_elements); + + for (code, source_element) in std::iter::zip(ops.iter(), source_elements) { + let hits = hits + .get(code.offset) + .map(|h| format!("[{h:03}]")) + .unwrap_or(" ".to_owned()); + let source_id = source_element.index(); + let source_path = source_id.and_then(|i| { + report.source_paths.get(&(contract_id.version.clone(), i as usize)) + }); + + let code = format!("{code:?}"); + let start = source_element.offset() as usize; + let end = (source_element.offset() + source_element.length()) as usize; + + if let Some(source_path) = source_path { + let (sline, spos) = line_number_cache.get_position(source_path, start)?; + let (eline, epos) = line_number_cache.get_position(source_path, end)?; + writeln!( + formatted, + "{} {:40} // {}: {}:{}-{}:{} ({}-{})", + hits, + code, + source_path.display(), + sline, + spos, + eline, + epos, + start, + end + )?; + } else if let Some(source_id) = source_id { + writeln!(formatted, "{hits} {code:40} // SRCID{source_id}: ({start}-{end})")?; + } else { + writeln!(formatted, "{hits} {code:40}")?; + } + } + fs::write( + self.destdir.join(&*contract_id.contract_name).with_extension("asm"), + formatted, + )?; + } + + Ok(()) + } +} + +/// Cache line number offsets for source files +struct LineNumberCache { + root: PathBuf, + line_offsets: HashMap>, +} + +impl LineNumberCache { + pub fn new(root: PathBuf) -> Self { + Self { root, line_offsets: HashMap::default() } + } + + pub fn get_position(&mut self, path: &Path, offset: usize) -> eyre::Result<(usize, usize)> { + let line_offsets = match self.line_offsets.entry(path.to_path_buf()) { + hash_map::Entry::Occupied(o) => o.into_mut(), + hash_map::Entry::Vacant(v) => { + let text = fs::read_to_string(self.root.join(path))?; + let mut line_offsets = vec![0]; + for line in text.lines() { + let line_offset = line.as_ptr() as usize - text.as_ptr() as usize; + line_offsets.push(line_offset); + } + v.insert(line_offsets) + } + }; + let lo = match line_offsets.binary_search(&offset) { + Ok(lo) => lo, + Err(lo) => lo - 1, + }; + let pos = offset - line_offsets.get(lo).unwrap() + 1; + Ok((lo, pos)) + } +} diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index d610a19190a68..dc8527d5e3565 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -1,165 +1,283 @@ +//! Gas reports. + use crate::{ - executor::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, - trace::{CallTraceArena, RawOrDecodedCall, TraceKind}, + constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, + traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, +}; +use alloy_primitives::map::HashSet; +use comfy_table::{modifiers::UTF8_ROUND_CORNERS, Cell, Color, Table}; +use foundry_common::{ + calc, + reports::{report_kind, ReportKind}, + TestFunctionExt, }; -use comfy_table::{presets::ASCII_MARKDOWN, *}; -use ethers::types::U256; -use foundry_common::{calc, TestFunctionExt}; +use foundry_evm::traces::CallKind; + use serde::{Deserialize, Serialize}; +use serde_json::json; use std::{collections::BTreeMap, fmt::Display}; -#[derive(Default, Debug, Serialize, Deserialize)] +/// Represents the gas report for a set of contracts. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasReport { - pub report_for: Vec, - pub ignore: Vec, + /// Whether to report any contracts. + report_any: bool, + /// What kind of report to generate. + report_kind: ReportKind, + /// Contracts to generate the report for. + report_for: HashSet, + /// Contracts to ignore when generating the report. + ignore: HashSet, + /// Whether to include gas reports for tests. + include_tests: bool, + /// All contracts that were analyzed grouped by their identifier + /// ``test/Counter.t.sol:CounterTest pub contracts: BTreeMap, } -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct ContractInfo { - pub gas: U256, - pub size: U256, - pub functions: BTreeMap>, -} - -#[derive(Debug, Serialize, Deserialize, Default)] -pub struct GasInfo { - pub calls: Vec, - pub min: U256, - pub mean: U256, - pub median: U256, - pub max: U256, -} - impl GasReport { - pub fn new(report_for: Vec, ignore: Vec) -> Self { - Self { report_for, ignore, ..Default::default() } + pub fn new( + report_for: impl IntoIterator, + ignore: impl IntoIterator, + include_tests: bool, + ) -> Self { + let report_for = report_for.into_iter().collect::>(); + let ignore = ignore.into_iter().collect::>(); + let report_any = report_for.is_empty() || report_for.contains("*"); + Self { + report_any, + report_kind: report_kind(), + report_for, + ignore, + include_tests, + ..Default::default() + } } - pub fn analyze(&mut self, traces: &[(TraceKind, CallTraceArena)]) { - traces.iter().for_each(|(_, trace)| { - self.analyze_node(0, trace); - }); + /// Whether the given contract should be reported. + #[instrument(level = "trace", skip(self), ret)] + fn should_report(&self, contract_name: &str) -> bool { + if self.ignore.contains(contract_name) { + let contains_anyway = self.report_for.contains(contract_name); + if contains_anyway { + // If the user listed the contract in 'gas_reports' (the foundry.toml field) a + // report for the contract is generated even if it's listed in the ignore + // list. This is addressed this way because getting a report you don't expect is + // preferable than not getting one you expect. A warning is printed to stderr + // indicating the "double listing". + let _ = sh_warn!( + "{contract_name} is listed in both 'gas_reports' and 'gas_reports_ignore'." + ); + } + return contains_anyway; + } + self.report_any || self.report_for.contains(contract_name) + } + + /// Analyzes the given traces and generates a gas report. + pub async fn analyze( + &mut self, + arenas: impl IntoIterator, + decoder: &CallTraceDecoder, + ) { + for node in arenas.into_iter().flat_map(|arena| arena.nodes()) { + self.analyze_node(node, decoder).await; + } } - fn analyze_node(&mut self, node_index: usize, arena: &CallTraceArena) { - let node = &arena.arena[node_index]; + async fn analyze_node(&mut self, node: &CallTraceNode, decoder: &CallTraceDecoder) { let trace = &node.trace; if trace.address == CHEATCODE_ADDRESS || trace.address == HARDHAT_CONSOLE_ADDRESS { - return + return; } - if let Some(name) = &trace.contract { - let contract_name = name.rsplit(':').next().unwrap_or(name.as_str()).to_string(); - // If the user listed the contract in 'gas_reports' (the foundry.toml field) a - // report for the contract is generated even if it's listed in the ignore - // list. This is addressed this way because getting a report you don't expect is - // preferable than not getting one you expect. A warning is printed to stderr - // indicating the "double listing". - if self.report_for.contains(&contract_name) && self.ignore.contains(&contract_name) { - eprintln!( - "{}: {} is listed in both 'gas_reports' and 'gas_reports_ignore'.", - yansi::Paint::yellow("warning").bold(), - contract_name - ); - } - let report_contract = (!self.ignore.contains(&contract_name) && - self.report_for.contains(&"*".to_string())) || - (!self.ignore.contains(&contract_name) && self.report_for.is_empty()) || - (self.report_for.contains(&contract_name)); - if report_contract { - let contract_report = self.contracts.entry(name.to_string()).or_default(); - - match &trace.data { - RawOrDecodedCall::Raw(bytes) if trace.created() => { - contract_report.gas = trace.gas_cost.into(); - contract_report.size = bytes.len().into(); - } - // TODO: More robust test contract filtering - RawOrDecodedCall::Decoded(func, sig, _) - if !func.is_test() && !func.is_setup() => - { - let function_report = contract_report - .functions - .entry(func.clone()) - .or_default() - .entry(sig.clone()) - .or_default(); - function_report.calls.push(trace.gas_cost.into()); - } - _ => (), - } - } + 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 contract_info = self.contracts.entry(name.to_string()).or_default(); + let is_create_call = trace.kind.is_any_create(); - node.children.iter().for_each(|index| { - self.analyze_node(*index, arena); - }); + // Record contract deployment size. + if is_create_call { + trace!(contract_name, "adding create size info"); + contract_info.size = trace.data.len(); + } + + // Only include top-level calls which account for calldata and base (21.000) cost. + // Only include Calls and Creates as only these calls are isolated in inspector. + if trace.depth > 1 && (trace.kind == CallKind::Call || is_create_call) { + return; + } + + let decoded = || decoder.decode_function(&node.trace); + + if is_create_call { + trace!(contract_name, "adding create gas info"); + contract_info.gas = trace.gas_used; + } else if let Some(DecodedCallData { signature, .. }) = decoded().await.call_data { + let name = signature.split('(').next().unwrap(); + // ignore any test/setup functions + if self.include_tests || !name.test_function_kind().is_known() { + trace!(contract_name, signature, "adding gas info"); + let gas_info = contract_info + .functions + .entry(name.to_string()) + .or_default() + .entry(signature.clone()) + .or_default(); + gas_info.frames.push(trace.gas_used); + } + } } + /// Finalizes the gas report by calculating the min, max, mean, and median for each function. #[must_use] pub fn finalize(mut self) -> Self { - self.contracts.iter_mut().for_each(|(_, contract)| { - contract.functions.iter_mut().for_each(|(_, sigs)| { - sigs.iter_mut().for_each(|(_, func)| { - func.calls.sort_unstable(); - func.min = func.calls.first().copied().unwrap_or_default(); - func.max = func.calls.last().copied().unwrap_or_default(); - func.mean = calc::mean(&func.calls); - func.median = calc::median_sorted(&func.calls); - }); - }); - }); + trace!("finalizing gas report"); + for contract in self.contracts.values_mut() { + for sigs in contract.functions.values_mut() { + for func in sigs.values_mut() { + func.frames.sort_unstable(); + func.min = func.frames.first().copied().unwrap_or_default(); + func.max = func.frames.last().copied().unwrap_or_default(); + func.mean = calc::mean(&func.frames); + func.median = calc::median_sorted(&func.frames); + func.calls = func.frames.len() as u64; + } + } + } self } } impl Display for GasReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - for (name, contract) in self.contracts.iter() { - if contract.functions.is_empty() { - continue - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self.report_kind { + ReportKind::Text => { + for (name, contract) in &self.contracts { + if contract.functions.is_empty() { + trace!(name, "gas report contract without functions"); + continue; + } - let mut table = Table::new(); - table.load_preset(ASCII_MARKDOWN); - table.set_header(vec![Cell::new(format!("{name} contract")) - .add_attribute(Attribute::Bold) - .fg(Color::Green)]); - table.add_row(vec![ - Cell::new("Deployment Cost").add_attribute(Attribute::Bold).fg(Color::Cyan), - Cell::new("Deployment Size").add_attribute(Attribute::Bold).fg(Color::Cyan), - ]); - table.add_row(vec![contract.gas.to_string(), contract.size.to_string()]); - - table.add_row(vec![ - Cell::new("Function Name").add_attribute(Attribute::Bold).fg(Color::Magenta), - Cell::new("min").add_attribute(Attribute::Bold).fg(Color::Green), - Cell::new("avg").add_attribute(Attribute::Bold).fg(Color::Yellow), - Cell::new("median").add_attribute(Attribute::Bold).fg(Color::Yellow), - Cell::new("max").add_attribute(Attribute::Bold).fg(Color::Red), - Cell::new("# calls").add_attribute(Attribute::Bold), - ]); - contract.functions.iter().for_each(|(fname, sigs)| { - sigs.iter().for_each(|(sig, function)| { - // show function signature if overloaded else name - let fn_display = - if sigs.len() == 1 { fname.clone() } else { sig.replace(':', "") }; - - table.add_row(vec![ - Cell::new(fn_display).add_attribute(Attribute::Bold), - Cell::new(function.min.to_string()).fg(Color::Green), - Cell::new(function.mean.to_string()).fg(Color::Yellow), - Cell::new(function.median.to_string()).fg(Color::Yellow), - Cell::new(function.max.to_string()).fg(Color::Red), - Cell::new(function.calls.len().to_string()), - ]); - }) - }); - writeln!(f, "{table}")?; - writeln!(f, "\n")?; + let table = self.format_table_output(contract, name); + writeln!(f, "\n{table}")?; + } + } + ReportKind::JSON => { + writeln!(f, "{}", &self.format_json_output())?; + } } + Ok(()) } } + +impl GasReport { + fn format_json_output(&self) -> String { + serde_json::to_string( + &self + .contracts + .iter() + .filter_map(|(name, contract)| { + if contract.functions.is_empty() { + trace!(name, "gas report contract without functions"); + return None; + } + + let functions = contract + .functions + .iter() + .flat_map(|(_, sigs)| { + sigs.iter().map(|(sig, gas_info)| { + let display_name = sig.replace(':', ""); + (display_name, gas_info) + }) + }) + .collect::>(); + + Some(json!({ + "contract": name, + "deployment": { + "gas": contract.gas, + "size": contract.size, + }, + "functions": functions, + })) + }) + .collect::>(), + ) + .unwrap() + } + + fn format_table_output(&self, contract: &ContractInfo, name: &str) -> Table { + let mut table = Table::new(); + table.apply_modifier(UTF8_ROUND_CORNERS); + + table.set_header(vec![Cell::new(format!("{name} Contract")).fg(Color::Magenta)]); + + table.add_row(vec![ + Cell::new("Deployment Cost").fg(Color::Cyan), + Cell::new("Deployment Size").fg(Color::Cyan), + ]); + table.add_row(vec![ + Cell::new(contract.gas.to_string()), + Cell::new(contract.size.to_string()), + ]); + + // Add a blank row to separate deployment info from function info. + table.add_row(vec![Cell::new("")]); + + table.add_row(vec![ + Cell::new("Function Name"), + Cell::new("Min").fg(Color::Green), + Cell::new("Avg").fg(Color::Yellow), + Cell::new("Median").fg(Color::Yellow), + Cell::new("Max").fg(Color::Red), + Cell::new("# Calls").fg(Color::Cyan), + ]); + + contract.functions.iter().for_each(|(fname, sigs)| { + sigs.iter().for_each(|(sig, gas_info)| { + // Show function signature if overloaded else display function name. + let display_name = + if sigs.len() == 1 { fname.to_string() } else { sig.replace(':', "") }; + + table.add_row(vec![ + Cell::new(display_name), + Cell::new(gas_info.min.to_string()).fg(Color::Green), + Cell::new(gas_info.mean.to_string()).fg(Color::Yellow), + Cell::new(gas_info.median.to_string()).fg(Color::Yellow), + Cell::new(gas_info.max.to_string()).fg(Color::Red), + Cell::new(gas_info.calls.to_string()), + ]); + }) + }); + + table + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct ContractInfo { + pub gas: u64, + pub size: usize, + /// Function name -> Function signature -> GasInfo + pub functions: BTreeMap>, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +pub struct GasInfo { + pub calls: u64, + pub min: u64, + pub mean: u64, + pub median: u64, + pub max: u64, + + #[serde(skip)] + pub frames: Vec, +} diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 35537bde685a2..27dd63738f2ee 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -1,255 +1,26 @@ -use ethers::solc::ProjectCompileOutput; -use foundry_config::{ - validate_profiles, Config, FuzzConfig, InlineConfig, InlineConfigError, InlineConfigParser, - InvariantConfig, NatSpec, -}; -use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; -use std::path::Path; +//! Forge is a fast and flexible Ethereum testing framework. + +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] -extern crate tracing; +extern crate foundry_common; -/// Gas reports -pub mod gas_report; +#[macro_use] +extern crate tracing; -/// Coverage reports pub mod coverage; -/// The Forge test runner -mod runner; -pub use runner::ContractRunner; +pub mod gas_report; -/// Forge test runners for multiple contracts -mod multi_runner; +pub mod multi_runner; pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder}; -/// reexport -pub use foundry_common::traits::TestFilter; +mod runner; +pub use runner::ContractRunner; +mod progress; pub mod result; -/// The Forge EVM backend +// TODO: remove +pub use foundry_common::traits::TestFilter; pub use foundry_evm::*; - -/// Metadata on how to run fuzz/invariant tests -#[derive(Debug, Clone, Default)] -pub struct TestOptions { - /// The base "fuzz" test configuration. To be used as a fallback in case - /// no more specific configs are found for a given run. - pub fuzz: FuzzConfig, - /// The base "invariant" test configuration. To be used as a fallback in case - /// no more specific configs are found for a given run. - pub invariant: InvariantConfig, - /// Contains per-test specific "fuzz" configurations. - pub inline_fuzz: InlineConfig, - /// Contains per-test specific "invariant" configurations. - pub inline_invariant: InlineConfig, -} - -impl TestOptions { - /// Returns a "fuzz" test runner instance. Parameters are used to select tight scoped fuzz - /// configs that apply for a contract-function pair. A fallback configuration is applied - /// if no specific setup is found for a given input. - /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_runner(&self, contract_id: S, test_fn: S) -> TestRunner - where - S: Into, - { - let fuzz = self.fuzz_config(contract_id, test_fn); - self.fuzzer_with_cases(fuzz.runs) - } - - /// Returns an "invariant" test runner instance. Parameters are used to select tight scoped fuzz - /// configs that apply for a contract-function pair. A fallback configuration is applied - /// if no specific setup is found for a given input. - /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_runner(&self, contract_id: S, test_fn: S) -> TestRunner - where - S: Into, - { - let invariant = self.invariant_config(contract_id, test_fn); - self.fuzzer_with_cases(invariant.runs) - } - - /// Returns a "fuzz" configuration setup. Parameters are used to select tight scoped fuzz - /// configs that apply for a contract-function pair. A fallback configuration is applied - /// if no specific setup is found for a given input. - /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_config(&self, contract_id: S, test_fn: S) -> &FuzzConfig - where - S: Into, - { - self.inline_fuzz.get(contract_id, test_fn).unwrap_or(&self.fuzz) - } - - /// Returns an "invariant" configuration setup. Parameters are used to select tight scoped - /// invariant configs that apply for a contract-function pair. A fallback configuration is - /// applied if no specific setup is found for a given input. - /// - /// - `contract_id` is the id of the test contract, expressed as a relative path from the - /// project root. - /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_config(&self, contract_id: S, test_fn: S) -> &InvariantConfig - where - S: Into, - { - self.inline_invariant.get(contract_id, test_fn).unwrap_or(&self.invariant) - } - - pub fn fuzzer_with_cases(&self, cases: u32) -> TestRunner { - // TODO: Add Options to modify the persistence - let cfg = proptest::test_runner::Config { - failure_persistence: None, - cases, - max_global_rejects: self.fuzz.max_test_rejects, - ..Default::default() - }; - - if let Some(ref fuzz_seed) = self.fuzz.seed { - trace!(target: "forge::test", "building deterministic fuzzer with seed {}", fuzz_seed); - let mut bytes: [u8; 32] = [0; 32]; - fuzz_seed.to_big_endian(&mut bytes); - let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &bytes); - TestRunner::new_with_rng(cfg, rng) - } else { - trace!(target: "forge::test", "building stochastic fuzzer"); - TestRunner::new(cfg) - } - } -} - -impl<'a, P> TryFrom<(&'a ProjectCompileOutput, &'a P, Vec, FuzzConfig, InvariantConfig)> - for TestOptions -where - P: AsRef, -{ - type Error = InlineConfigError; - - /// Tries to create an instance of `Self`, detecting inline configurations from the project - /// compile output. - /// - /// Param is a tuple, whose elements are: - /// 1. Solidity compiler output, essential to extract natspec test configs. - /// 2. Root path to express contract base dirs. This is essential to match inline configs at - /// runtime. 3. List of available configuration profiles - /// 4. Reference to a fuzz base configuration. - /// 5. Reference to an invariant base configuration. - fn try_from( - value: (&'a ProjectCompileOutput, &'a P, Vec, FuzzConfig, InvariantConfig), - ) -> Result { - let output = value.0; - let root = value.1; - let profiles = &value.2; - let base_fuzz: FuzzConfig = value.3; - let base_invariant: InvariantConfig = value.4; - - let natspecs: Vec = NatSpec::parse(output, root); - 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)?; - - // Apply in-line configurations for the current profile - let configs: Vec = natspec.current_profile_configs(); - 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), - Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, - _ => { /* No inline config found, do nothing */ } - } - - match base_invariant.try_merge(&configs) { - Ok(Some(conf)) => inline_invariant.insert(c, f, conf), - Err(e) => Err(InlineConfigError { line: line.clone(), source: e })?, - _ => { /* No inline config found, do nothing */ } - } - } - - Ok(Self { fuzz: base_fuzz, invariant: base_invariant, inline_fuzz, inline_invariant }) - } -} - -/// Builder utility to create a [`TestOptions`] instance. -#[derive(Default)] -pub struct TestOptionsBuilder { - fuzz: Option, - invariant: Option, - profiles: Option>, - output: Option, -} - -impl TestOptionsBuilder { - /// Sets a [`FuzzConfig`] to be used as base "fuzz" configuration. - #[must_use = "A base 'fuzz' config must be provided"] - pub fn fuzz(mut self, conf: FuzzConfig) -> Self { - self.fuzz = Some(conf); - self - } - - /// Sets a [`InvariantConfig`] to be used as base "invariant" configuration. - #[must_use = "A base 'invariant' config must be provided"] - pub fn invariant(mut self, conf: InvariantConfig) -> Self { - self.invariant = Some(conf); - self - } - - /// Sets available configuration profiles. Profiles are useful to validate existing in-line - /// configurations. This argument is necessary in case a `compile_output`is provided. - pub fn profiles(mut self, p: Vec) -> Self { - self.profiles = Some(p); - self - } - - /// Sets a project compiler output instance. This is used to extract - /// inline test configurations that override `self.fuzz` and `self.invariant` - /// specs when necessary. - pub fn compile_output(mut self, output: &ProjectCompileOutput) -> Self { - self.output = Some(output.clone()); - self - } - - /// Creates an instance of [`TestOptions`]. This takes care of creating "fuzz" and - /// "invariant" fallbacks, and extracting all inline test configs, if available. - /// - /// `root` is a reference to the user's project root dir. This is essential - /// to determine the base path of generated contract identifiers. This is to provide correct - /// matchers for inline test configs. - pub fn build(self, root: impl AsRef) -> Result { - let default_profiles = vec![Config::selected_profile().into()]; - let profiles: Vec = self.profiles.unwrap_or(default_profiles); - let base_fuzz = self.fuzz.unwrap_or_default(); - let base_invariant = self.invariant.unwrap_or_default(); - - match self.output { - Some(compile_output) => Ok(TestOptions::try_from(( - &compile_output, - &root, - profiles, - base_fuzz, - base_invariant, - ))?), - None => Ok(TestOptions { - fuzz: base_fuzz, - invariant: base_invariant, - inline_fuzz: InlineConfig::default(), - inline_invariant: InlineConfig::default(), - }), - } - } -} diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 72094ac69cf65..8ecb0e63a6152 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -1,110 +1,128 @@ -use crate::{result::SuiteResult, ContractRunner, TestFilter, TestOptions}; -use ethers::{ - abi::Abi, - prelude::{artifacts::CompactContractBytecode, ArtifactId, ArtifactOutput}, - solc::{contracts::ArtifactContracts, Artifact, ProjectCompileOutput}, - types::{Address, Bytes, U256}, +//! Forge test runner for multiple contracts. + +use crate::{ + progress::TestsProgress, result::SuiteResult, runner::LIBRARY_DEPLOYER, ContractRunner, + TestFilter, }; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; -use foundry_common::{ContractsByArtifact, TestFunctionExt}; +use foundry_common::{get_contract_name, shell::verbosity, ContractsByArtifact, TestFunctionExt}; +use foundry_compilers::{ + artifacts::{Contract, Libraries}, + compilers::Compiler, + Artifact, ArtifactId, ProjectCompileOutput, +}; +use foundry_config::{Config, InlineConfig}; use foundry_evm::{ - executor::{ - backend::Backend, fork::CreateFork, inspector::CheatsConfig, opts::EvmOpts, Executor, - ExecutorBuilder, - }, + backend::Backend, + decode::RevertDecoder, + executors::{Executor, ExecutorBuilder}, + fork::CreateFork, + inspectors::CheatsConfig, + opts::EvmOpts, revm, + traces::{InternalTraceMode, TraceMode}, }; -use foundry_utils::{PostLinkInput, ResolvedDependency}; +use foundry_linking::{LinkOutput, Linker}; use rayon::prelude::*; use revm::primitives::SpecId; use std::{ - collections::{BTreeMap, HashSet}, + borrow::Borrow, + collections::BTreeMap, + fmt::Debug, path::Path, - sync::mpsc::Sender, + sync::{mpsc, Arc}, + time::Instant, }; -pub type DeployableContracts = BTreeMap)>; +#[derive(Debug, Clone)] +pub struct TestContract { + pub abi: JsonAbi, + pub bytecode: Bytes, +} + +pub type DeployableContracts = BTreeMap; /// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds /// to run all test functions in these contracts. pub struct MultiContractRunner { - /// Mapping of contract name to Abi, creation bytecode and library bytecode which + /// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which /// needs to be deployed & linked against pub contracts: DeployableContracts, - /// Compiled contracts by name that have an Abi and runtime bytecode + /// Known contracts linked with computed library addresses. pub known_contracts: ContractsByArtifact, - /// The EVM instance used in the test runner - pub evm_opts: EvmOpts, - /// The configured evm - pub env: revm::primitives::Env, - /// The EVM spec - pub evm_spec: SpecId, - /// All known errors, used for decoding reverts - pub errors: Option, - /// The address which will be used as the `from` field in all EVM calls - pub sender: Option
, - /// A map of contract names to absolute source file paths - pub source_paths: BTreeMap, + /// Revert decoder. Contains all known errors and their selectors. + pub revert_decoder: RevertDecoder, + /// Libraries to deploy. + pub libs_to_deploy: Vec, + /// Library addresses used to link contracts. + pub libraries: Libraries, + /// The fork to use at launch pub fork: Option, - /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: CheatsConfig, - /// Whether to collect coverage info - pub coverage: bool, - /// Settings related to fuzz and/or invariant tests - pub test_options: TestOptions, + + /// The base configuration for the test runner. + pub tcfg: TestRunnerConfig, +} + +impl std::ops::Deref for MultiContractRunner { + type Target = TestRunnerConfig; + + fn deref(&self) -> &Self::Target { + &self.tcfg + } +} + +impl std::ops::DerefMut for MultiContractRunner { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tcfg + } } impl MultiContractRunner { - /// Returns the number of matching tests - pub fn count_filtered_tests(&self, filter: &impl TestFilter) -> usize { - self.contracts - .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .flat_map(|(_, (abi, _, _))| { - abi.functions().filter(|func| filter.matches_test(func.signature())) - }) - .count() + /// Returns an iterator over all contracts that match the filter. + pub fn matching_contracts<'a: 'b, 'b>( + &'a self, + filter: &'b dyn TestFilter, + ) -> impl Iterator + 'b { + self.contracts.iter().filter(|&(id, c)| matches_contract(id, &c.abi, filter)) + } + + /// Returns an iterator over all test functions that match the filter. + pub fn matching_test_functions<'a: 'b, 'b>( + &'a self, + filter: &'b dyn TestFilter, + ) -> impl Iterator + 'b { + self.matching_contracts(filter) + .flat_map(|(_, c)| c.abi.functions()) + .filter(|func| is_matching_test(func, filter)) } - // Get all tests of matching path and contract - pub fn get_tests(&self, filter: &impl TestFilter) -> Vec { + /// Returns an iterator over all test functions in contracts that match the filter. + pub fn all_test_functions<'a: 'b, 'b>( + &'a self, + filter: &'b dyn TestFilter, + ) -> impl Iterator + 'b { self.contracts .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .flat_map(|(_, (abi, _, _))| abi.functions().map(|func| func.name.clone())) - .filter(|sig| sig.is_test()) - .collect() + .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) + .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: &impl TestFilter, - ) -> BTreeMap>> { - self.contracts - .iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) - .map(|(id, (abi, _, _))| { + pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap>> { + self.matching_contracts(filter) + .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| func.name.is_test()) - .filter(|func| filter.matches_test(func.signature())) + .filter(|func| is_matching_test(func, filter)) .map(|func| func.name.clone()) .collect::>(); - (source, name, tests) }) .fold(BTreeMap::new(), |mut acc, (source, name, tests)| { @@ -113,91 +131,251 @@ impl MultiContractRunner { }) } - /// Executes _all_ tests that match the given `filter` + /// Executes _all_ tests that match the given `filter`. + /// + /// The same as [`test`](Self::test), but returns the results instead of streaming them. + /// + /// Note that this method returns only when all tests have been executed. + pub fn test_collect(&mut self, filter: &dyn TestFilter) -> BTreeMap { + self.test_iter(filter).collect() + } + + /// Executes _all_ tests that match the given `filter`. + /// + /// The same as [`test`](Self::test), but returns the results instead of streaming them. + /// + /// Note that this method returns only when all tests have been executed. + pub fn test_iter( + &mut self, + filter: &dyn TestFilter, + ) -> impl Iterator { + let (tx, rx) = mpsc::channel(); + self.test(filter, tx, false); + rx.into_iter() + } + + /// Executes _all_ tests that match the given `filter`. /// /// This will create the runtime based on the configured `evm` ops and create the `Backend` /// before executing all contracts and their tests in _parallel_. /// /// Each Executor gets its own instance of the `Backend`. - pub async fn test( + pub fn test( &mut self, - filter: impl TestFilter, - stream_result: Option>, - test_options: TestOptions, - ) -> BTreeMap { + filter: &dyn TestFilter, + tx: mpsc::Sender<(String, SuiteResult)>, + show_progress: bool, + ) { + let tokio_handle = tokio::runtime::Handle::current(); trace!("running all tests"); - // the db backend that serves all the data, each contract gets its own instance - let db = Backend::spawn(self.fork.take()).await; - let filter = &filter; + // The DB backend that serves all the data. + let db = Backend::spawn(self.fork.take()); - self.contracts - .par_iter() - .filter(|(id, _)| { - filter.matches_path(id.source.to_string_lossy()) && - filter.matches_contract(&id.name) - }) - .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) - .map_with(stream_result, |stream_result, (id, (abi, deploy_code, libs))| { - let executor = ExecutorBuilder::default() - .with_cheatcodes(self.cheats_config.clone()) - .with_config(self.env.clone()) - .with_spec(self.evm_spec) - .with_gas_limit(self.evm_opts.gas_limit()) - .set_tracing(self.evm_opts.verbosity >= 3) - .set_coverage(self.coverage) - .build(db.clone()); - let identifier = id.identifier(); - trace!(contract=%identifier, "start executing all tests in contract"); - - let result = self.run_tests( - &identifier, - abi, - executor, - deploy_code.clone(), - libs, - filter, - test_options.clone(), - ); - trace!(contract= ?identifier, "executed all tests in contract"); - - if let Some(stream_result) = stream_result { - let _ = stream_result.send((identifier.clone(), result.clone())); - } - - (identifier, result) + let find_timer = Instant::now(); + let contracts = self.matching_contracts(filter).collect::>(); + let find_time = find_timer.elapsed(); + debug!( + "Found {} test contracts out of {} in {:?}", + contracts.len(), + self.contracts.len(), + find_time, + ); + + if show_progress { + let tests_progress = TestsProgress::new(contracts.len(), rayon::current_num_threads()); + // Collect test suite results to stream at the end of test run. + let results: Vec<(String, SuiteResult)> = contracts + .par_iter() + .map(|&(id, contract)| { + let _guard = tokio_handle.enter(); + tests_progress.inner.lock().start_suite_progress(&id.identifier()); + + let result = self.run_test_suite( + id, + contract, + &db, + filter, + &tokio_handle, + Some(&tests_progress), + ); + + tests_progress + .inner + .lock() + .end_suite_progress(&id.identifier(), result.summary()); + + (id.identifier(), result) + }) + .collect(); + + tests_progress.inner.lock().clear(); + + results.iter().for_each(|result| { + let _ = tx.send(result.to_owned()); + }); + } else { + contracts.par_iter().for_each(|&(id, contract)| { + let _guard = tokio_handle.enter(); + let result = self.run_test_suite(id, contract, &db, filter, &tokio_handle, None); + let _ = tx.send((id.identifier(), result)); }) - .collect() + } } - #[instrument(skip_all, fields(name = %name))] - #[allow(clippy::too_many_arguments)] - fn run_tests( + fn run_test_suite( &self, - name: &str, - contract: &Abi, - executor: Executor, - deploy_code: Bytes, - libs: &[Bytes], - filter: impl TestFilter, - test_options: TestOptions, + artifact_id: &ArtifactId, + contract: &TestContract, + db: &Backend, + filter: &dyn TestFilter, + tokio_handle: &tokio::runtime::Handle, + progress: Option<&TestsProgress>, ) -> SuiteResult { + let identifier = artifact_id.identifier(); + let mut span_name = identifier.as_str(); + + if !enabled!(tracing::Level::TRACE) { + span_name = get_contract_name(&identifier); + } + let span = 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( - name, - executor, + &identifier, contract, - deploy_code, - self.evm_opts.initial_balance, - self.sender, - self.errors.as_ref(), - libs, + self.tcfg.executor(self.known_contracts.clone(), artifact_id, db.clone()), + progress, + tokio_handle, + span, + self, ); - runner.run_tests(filter, test_options, Some(&self.known_contracts)) + let r = runner.run_tests(filter); + + debug!(duration=?r.duration, "executed all tests in contract"); + + r + } +} + +/// Configuration for the test runner. +/// +/// This is modified after instantiation through inline config. +#[derive(Clone)] +pub struct TestRunnerConfig { + /// Project config. + pub config: Arc, + /// Inline configuration. + pub inline_config: Arc, + + /// EVM configuration. + pub evm_opts: EvmOpts, + /// EVM environment. + pub env: revm::primitives::Env, + /// EVM version. + pub spec_id: SpecId, + /// The address which will be used to deploy the initial contracts and send all transactions. + pub sender: Address, + + /// Whether to collect coverage info + pub coverage: bool, + /// Whether to collect debug info + pub debug: bool, + /// Whether to enable steps tracking in the tracer. + pub decode_internal: InternalTraceMode, + /// Whether to enable call isolation. + pub isolation: bool, + /// Whether to enable Odyssey features. + pub odyssey: bool, +} + +impl TestRunnerConfig { + /// Reconfigures all fields using the given `config`. + /// This is for example used to override the configuration with inline config. + pub fn reconfigure_with(&mut self, config: Arc) { + debug_assert!(!Arc::ptr_eq(&self.config, &config)); + + self.spec_id = config.evm_spec_id(); + self.sender = config.sender; + self.odyssey = config.odyssey; + self.isolation = config.isolate; + + // Specific to Forge, not present in config. + // TODO: self.evm_opts + // TODO: self.env + // self.coverage = N/A; + // self.debug = N/A; + // self.decode_internal = N/A; + + self.config = config; + } + + /// Configures the given executor with this configuration. + pub fn configure_executor(&self, executor: &mut Executor) { + // TODO: See above + + let inspector = executor.inspector_mut(); + // inspector.set_env(&self.env); + if let Some(cheatcodes) = inspector.cheatcodes.as_mut() { + cheatcodes.config = + Arc::new(cheatcodes.config.clone_with(&self.config, self.evm_opts.clone())); + } + inspector.tracing(self.trace_mode()); + inspector.collect_coverage(self.coverage); + inspector.enable_isolation(self.isolation); + inspector.odyssey(self.odyssey); + // inspector.set_create2_deployer(self.evm_opts.create2_deployer); + + // executor.env_mut().clone_from(&self.env); + executor.set_spec_id(self.spec_id); + // executor.set_gas_limit(self.evm_opts.gas_limit()); + executor.set_legacy_assertions(self.config.legacy_assertions); + } + + /// Creates a new executor with this configuration. + pub fn executor( + &self, + known_contracts: ContractsByArtifact, + artifact_id: &ArtifactId, + db: Backend, + ) -> Executor { + let cheats_config = Arc::new(CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(known_contracts), + Some(artifact_id.clone()), + )); + ExecutorBuilder::new() + .inspectors(|stack| { + stack + .cheatcodes(cheats_config) + .trace_mode(self.trace_mode()) + .coverage(self.coverage) + .enable_isolation(self.isolation) + .odyssey(self.odyssey) + .create2_deployer(self.evm_opts.create2_deployer) + }) + .spec_id(self.spec_id) + .gas_limit(self.evm_opts.gas_limit()) + .legacy_assertions(self.config.legacy_assertions) + .build(self.env.clone(), db) + } + + fn trace_mode(&self) -> TraceMode { + TraceMode::default() + .with_debug(self.debug) + .with_decode_internal(self.decode_internal) + .with_verbosity(self.evm_opts.verbosity) + .with_state_changes(verbosity() > 4) } } /// Builder used for instantiating the multi-contract runner -#[derive(Debug, Default)] +#[derive(Clone, Debug)] +#[must_use = "builders do nothing unless you call `build` on them"] pub struct MultiContractRunnerBuilder { /// The address which will be used to deploy the initial contracts and send all /// transactions @@ -208,163 +386,169 @@ pub struct MultiContractRunnerBuilder { pub evm_spec: Option, /// The fork to use at launch pub fork: Option, - /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: Option, + /// Project config. + pub config: Arc, /// Whether or not to collect coverage info pub coverage: bool, - /// Settings related to fuzz and/or invariant tests - pub test_options: Option, + /// Whether or not to collect debug info + pub debug: bool, + /// Whether to enable steps tracking in the tracer. + pub decode_internal: InternalTraceMode, + /// Whether to enable call isolation + pub isolation: bool, + /// Whether to enable Odyssey features. + pub odyssey: bool, } impl MultiContractRunnerBuilder { - /// Given an EVM, proceeds to return a runner which is able to execute all tests - /// against that evm - pub fn build( - self, - root: impl AsRef, - output: ProjectCompileOutput, - env: revm::primitives::Env, - evm_opts: EvmOpts, - ) -> Result - where - A: ArtifactOutput, - { - // This is just the contracts compiled, but we need to merge this with the read cached - // artifacts - let contracts = output - .with_stripped_file_prefixes(&root) - .into_artifacts() - .map(|(i, c)| (i, c.into_contract_bytecode())) - .collect::>(); - - let mut known_contracts = ContractsByArtifact::default(); - let source_paths = contracts - .iter() - .map(|(i, _)| (i.identifier(), root.as_ref().join(&i.source).to_string_lossy().into())) - .collect::>(); - // create a mapping of name => (abi, deployment code, Vec) - let mut deployable_contracts = DeployableContracts::default(); - - fn unique_deps(deps: Vec) -> Vec { - let mut filtered = Vec::new(); - let mut seen = HashSet::new(); - for dep in deps { - if !seen.insert(dep.id.clone()) { - continue - } - filtered.push(dep); - } - - filtered + pub fn new(config: Arc) -> Self { + Self { + config, + sender: Default::default(), + initial_balance: Default::default(), + evm_spec: Default::default(), + fork: Default::default(), + coverage: Default::default(), + debug: Default::default(), + isolation: Default::default(), + decode_internal: Default::default(), + odyssey: Default::default(), } - - foundry_utils::link_with_nonce_or_address( - ArtifactContracts::from_iter(contracts), - &mut known_contracts, - Default::default(), - evm_opts.sender, - U256::one(), - &mut deployable_contracts, - |post_link_input| { - let PostLinkInput { - contract, - known_contracts, - id, - extra: deployable_contracts, - dependencies, - } = post_link_input; - let dependencies = unique_deps(dependencies); - - // get bytes - let bytecode = - if let Some(b) = contract.bytecode.expect("No bytecode").object.into_bytes() { - b - } else { - return Ok(()) - }; - - let abi = contract.abi.expect("We should have an abi by now"); - // if it's a test, add it 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()) - { - deployable_contracts.insert( - id.clone(), - ( - abi.clone(), - bytecode, - dependencies.into_iter().map(|dep| dep.bytecode).collect::>(), - ), - ); - } - - contract - .deployed_bytecode - .and_then(|d_bcode| d_bcode.bytecode) - .and_then(|bcode| bcode.object.into_bytes()) - .and_then(|bytes| known_contracts.insert(id.clone(), (abi, bytes.to_vec()))); - Ok(()) - }, - root, - )?; - - let execution_info = known_contracts.flatten(); - Ok(MultiContractRunner { - contracts: deployable_contracts, - known_contracts, - evm_opts, - env, - evm_spec: self.evm_spec.unwrap_or(SpecId::MERGE), - sender: self.sender, - errors: Some(execution_info.2), - source_paths, - fork: self.fork, - cheats_config: self.cheats_config.unwrap_or_default(), - coverage: self.coverage, - test_options: self.test_options.unwrap_or_default(), - }) } - #[must_use] pub fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self } - #[must_use] pub fn initial_balance(mut self, initial_balance: U256) -> Self { self.initial_balance = initial_balance; self } - #[must_use] pub fn evm_spec(mut self, spec: SpecId) -> Self { self.evm_spec = Some(spec); self } - #[must_use] pub fn with_fork(mut self, fork: Option) -> Self { self.fork = fork; self } - #[must_use] - pub fn with_cheats_config(mut self, cheats_config: CheatsConfig) -> Self { - self.cheats_config = Some(cheats_config); + pub fn set_coverage(mut self, enable: bool) -> Self { + self.coverage = enable; self } - #[must_use] - pub fn with_test_options(mut self, test_options: TestOptions) -> Self { - self.test_options = Some(test_options); + pub fn set_debug(mut self, enable: bool) -> Self { + self.debug = enable; self } - #[must_use] - pub fn set_coverage(mut self, enable: bool) -> Self { - self.coverage = enable; + 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 + } + + pub fn odyssey(mut self, enable: bool) -> Self { + self.odyssey = enable; self } + + /// Given an EVM, proceeds to return a runner which is able to execute all tests + /// against that evm + pub fn build>( + self, + root: &Path, + output: &ProjectCompileOutput, + env: revm::primitives::Env, + evm_opts: EvmOpts, + ) -> Result { + let contracts = output + .artifact_ids() + .map(|(id, v)| (id.with_stripped_file_prefixes(root), v)) + .collect(); + let linker = Linker::new(root, contracts); + + // Build revert decoder from ABIs of all artifacts. + let abis = linker + .contracts + .iter() + .filter_map(|(_, contract)| contract.abi.as_ref().map(|abi| abi.borrow())); + let revert_decoder = RevertDecoder::new().with_abis(abis); + + let LinkOutput { libraries, libs_to_deploy } = linker.link_with_nonce_or_address( + Default::default(), + LIBRARY_DEPLOYER, + 0, + linker.contracts.keys(), + )?; + + let linked_contracts = linker.get_linked_artifacts(&libraries)?; + + // Create a mapping of name => (abi, deployment code, Vec) + let mut deployable_contracts = DeployableContracts::default(); + + for (id, contract) in linked_contracts.iter() { + let Some(abi) = &contract.abi else { continue }; + + // if it's a test, link it and add to deployable contracts + if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && + abi.functions().any(|func| func.name.is_any_test()) + { + let Some(bytecode) = + contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty()) + else { + continue; + }; + + deployable_contracts + .insert(id.clone(), TestContract { abi: abi.clone(), bytecode }); + } + } + + let known_contracts = ContractsByArtifact::new(linked_contracts); + + Ok(MultiContractRunner { + contracts: deployable_contracts, + revert_decoder, + known_contracts, + libs_to_deploy, + libraries, + + fork: self.fork, + + tcfg: TestRunnerConfig { + evm_opts, + env, + spec_id: self.evm_spec.unwrap_or_else(|| self.config.evm_spec_id()), + sender: self.sender.unwrap_or(self.config.sender), + + coverage: self.coverage, + debug: self.debug, + decode_internal: self.decode_internal, + inline_config: Arc::new(InlineConfig::new_parsed(output, &self.config)?), + isolation: self.isolation, + odyssey: self.odyssey, + + config: self.config, + }, + }) + } +} + +pub fn matches_contract(id: &ArtifactId, abi: &JsonAbi, filter: &dyn TestFilter) -> bool { + (filter.matches_path(&id.source) && filter.matches_contract(&id.name)) && + abi.functions().any(|func| is_matching_test(func, filter)) +} + +/// Returns `true` if the function is a test function that matches the given filter. +pub(crate) fn is_matching_test(func: &Function, filter: &dyn TestFilter) -> bool { + func.is_any_test() && filter.matches_test(&func.signature()) } diff --git a/crates/forge/src/progress.rs b/crates/forge/src/progress.rs new file mode 100644 index 0000000000000..9ca182f769d50 --- /dev/null +++ b/crates/forge/src/progress.rs @@ -0,0 +1,116 @@ +use alloy_primitives::map::HashMap; +use indicatif::{MultiProgress, ProgressBar}; +use parking_lot::Mutex; +use std::{sync::Arc, time::Duration}; +/// State of [ProgressBar]s displayed for the given test run. +/// Shows progress of all test suites matching filter. +/// For each test within the test suite an individual progress bar is displayed. +/// When a test suite completes, their progress is removed from overall progress and result summary +/// is displayed. +#[derive(Debug)] +pub struct TestsProgressState { + /// Main [MultiProgress] instance showing progress for all test suites. + multi: MultiProgress, + /// Progress bar counting completed / remaining test suites. + overall_progress: ProgressBar, + /// Individual test suites progress. + suites_progress: HashMap, +} + +impl TestsProgressState { + // Creates overall tests progress state. + pub fn new(suites_len: usize, threads_no: usize) -> Self { + let multi = MultiProgress::new(); + let overall_progress = multi.add(ProgressBar::new(suites_len as u64)); + overall_progress.set_style( + indicatif::ProgressStyle::with_template("{bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") + .unwrap() + .progress_chars("##-"), + ); + overall_progress.set_message(format!("completed (with {} threads)", threads_no as u64)); + Self { multi, overall_progress, suites_progress: HashMap::default() } + } + + /// Creates new test suite progress and add it to overall progress. + pub fn start_suite_progress(&mut self, suite_name: &String) { + let suite_progress = self.multi.add(ProgressBar::new_spinner()); + suite_progress.set_style( + indicatif::ProgressStyle::with_template("{spinner} {wide_msg:.bold.dim}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), + ); + suite_progress.set_message(format!("{suite_name} ")); + suite_progress.enable_steady_tick(Duration::from_millis(100)); + self.suites_progress.insert(suite_name.to_owned(), suite_progress); + } + + /// Prints suite result summary and removes it from overall progress. + pub fn end_suite_progress(&mut self, suite_name: &String, result_summary: String) { + if let Some(suite_progress) = self.suites_progress.remove(suite_name) { + self.multi.suspend(|| { + let _ = sh_println!("{suite_name}\n ↪ {result_summary}"); + }); + suite_progress.finish_and_clear(); + // Increment test progress bar to reflect completed test suite. + self.overall_progress.inc(1); + } + } + + /// Creates progress entry for fuzz tests. + /// Set the prefix and total number of runs. Message is updated during execution with current + /// phase. Test progress is placed under test suite progress entry so all tests within suite + /// are grouped. + pub fn start_fuzz_progress( + &mut self, + suite_name: &str, + test_name: &String, + runs: u32, + ) -> Option { + if let Some(suite_progress) = self.suites_progress.get(suite_name) { + let fuzz_progress = + self.multi.insert_after(suite_progress, ProgressBar::new(runs as u64)); + fuzz_progress.set_style( + indicatif::ProgressStyle::with_template( + " ↪ {prefix:.bold.dim}: [{pos}/{len}]{msg} Runs", + ) + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), + ); + fuzz_progress.set_prefix(test_name.to_string()); + Some(fuzz_progress) + } else { + None + } + } + + /// Removes overall test progress. + pub fn clear(&mut self) { + self.multi.clear().unwrap(); + } +} + +/// Clonable wrapper around [TestsProgressState]. +#[derive(Debug, Clone)] +pub struct TestsProgress { + pub inner: Arc>, +} + +impl TestsProgress { + pub fn new(suites_len: usize, threads_no: usize) -> Self { + Self { inner: Arc::new(Mutex::new(TestsProgressState::new(suites_len, threads_no))) } + } +} + +/// Helper function for creating fuzz test progress bar. +pub fn start_fuzz_progress( + tests_progress: Option<&TestsProgress>, + suite_name: &str, + test_name: &String, + runs: u32, +) -> Option { + if let Some(progress) = tests_progress { + progress.inner.lock().start_fuzz_progress(suite_name, test_name, runs) + } else { + None + } +} diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 5cbc2296b48c0..02ca64fde18ad 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -1,25 +1,215 @@ -//! test outcomes +//! Test outcomes. -use crate::Address; -use ethers::prelude::Log; -use foundry_common::evm::Breakpoints; +use crate::{ + fuzz::{BaseCounterExample, FuzzedCases}, + gas_report::GasReport, +}; +use alloy_primitives::{ + map::{AddressHashMap, HashMap}, + Address, Log, +}; +use eyre::Report; +use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell}; use foundry_evm::{ coverage::HitMaps, - executor::EvmError, - fuzz::{CounterExample, FuzzCase}, - trace::{TraceKind, Traces}, + decode::SkipReason, + executors::{invariant::InvariantMetrics, RawCallResult}, + fuzz::{CounterExample, FuzzCase, FuzzFixtures, FuzzTestResult}, + traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, }; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt, time::Duration}; +use std::{ + collections::{BTreeMap, HashMap as Map}, + fmt::{self, Write}, + time::Duration, +}; +use yansi::Paint; + +/// The aggregated result of a test run. +#[derive(Clone, Debug)] +pub struct TestOutcome { + /// The results of all test suites by their identifier (`path:contract_name`). + /// + /// Essentially `identifier => signature => result`. + pub results: BTreeMap, + /// Whether to allow test failures without failing the entire test run. + pub allow_failure: bool, + /// The decoder used to decode traces and logs. + /// + /// This is `None` if traces and logs were not decoded. + /// + /// Note that `Address` fields only contain the last executed test case's data. + pub last_run_decoder: Option, + /// The gas report, if requested. + pub gas_report: Option, +} + +impl TestOutcome { + /// Creates a new test outcome with the given results. + pub fn new(results: BTreeMap, allow_failure: bool) -> Self { + Self { results, allow_failure, last_run_decoder: None, gas_report: None } + } -/// Results and duration for a set of tests included in the same test contract -#[derive(Debug, Clone, Serialize)] + /// Creates a new empty test outcome. + pub fn empty(allow_failure: bool) -> Self { + Self::new(BTreeMap::new(), allow_failure) + } + + /// Returns an iterator over all individual succeeding tests and their names. + pub fn successes(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status.is_success()) + } + + /// Returns an iterator over all individual skipped tests and their names. + pub fn skips(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status.is_skipped()) + } + + /// Returns an iterator over all individual failing tests and their names. + pub fn failures(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status.is_failure()) + } + + /// Returns an iterator over all individual tests and their names. + pub fn tests(&self) -> impl Iterator { + self.results.values().flat_map(|suite| suite.tests()) + } + + /// Flattens the test outcome into a list of individual tests. + // TODO: Replace this with `tests` and make it return `TestRef<'_>` + pub fn into_tests_cloned(&self) -> impl Iterator + '_ { + self.results + .iter() + .flat_map(|(file, suite)| { + suite + .test_results + .iter() + .map(move |(sig, result)| (file.clone(), sig.clone(), result.clone())) + }) + .map(|(artifact_id, signature, result)| SuiteTestResult { + artifact_id, + signature, + result, + }) + } + + /// Flattens the test outcome into a list of individual tests. + pub fn into_tests(self) -> impl Iterator { + self.results + .into_iter() + .flat_map(|(file, suite)| { + suite.test_results.into_iter().map(move |t| (file.clone(), t)) + }) + .map(|(artifact_id, (signature, result))| SuiteTestResult { + artifact_id, + signature, + result, + }) + } + + /// Returns the number of tests that passed. + pub fn passed(&self) -> usize { + self.successes().count() + } + + /// Returns the number of tests that were skipped. + pub fn skipped(&self) -> usize { + self.skips().count() + } + + /// Returns the number of tests that failed. + pub fn failed(&self) -> usize { + self.failures().count() + } + + /// Sums up all the durations of all individual test suites. + /// + /// Note that this is not necessarily the wall clock time of the entire test run. + pub fn total_time(&self) -> Duration { + self.results.values().map(|suite| suite.duration).sum() + } + + /// Formats the aggregated summary of all test suites into a string (for printing). + pub fn summary(&self, wall_clock_time: Duration) -> String { + let num_test_suites = self.results.len(); + let suites = if num_test_suites == 1 { "suite" } else { "suites" }; + let total_passed = self.passed(); + let total_failed = self.failed(); + let total_skipped = self.skipped(); + let total_tests = total_passed + total_failed + total_skipped; + format!( + "\nRan {} test {} in {:.2?} ({:.2?} CPU time): {} tests passed, {} failed, {} skipped ({} total tests)", + num_test_suites, + suites, + wall_clock_time, + self.total_time(), + total_passed.green(), + total_failed.red(), + total_skipped.yellow(), + total_tests + ) + } + + /// Checks if there are any failures and failures are disallowed. + pub fn ensure_ok(&self, silent: bool) -> eyre::Result<()> { + let outcome = self; + let failures = outcome.failures().count(); + if outcome.allow_failure || failures == 0 { + return Ok(()); + } + + if shell::is_quiet() || silent { + // TODO: Avoid process::exit + std::process::exit(1); + } + + sh_println!("\nFailing tests:")?; + for (suite_name, suite) in outcome.results.iter() { + let failed = suite.failed(); + if failed == 0 { + continue; + } + + let term = if failed > 1 { "tests" } else { "test" }; + sh_println!("Encountered {failed} failing {term} in {suite_name}")?; + for (name, result) in suite.failures() { + sh_println!("{}", result.short_result(name))?; + } + sh_println!()?; + } + let successes = outcome.passed(); + sh_println!( + "Encountered a total of {} failing tests, {} tests succeeded", + failures.to_string().red(), + successes.to_string().green() + )?; + + // TODO: Avoid process::exit + std::process::exit(1); + } + + /// Removes first test result, if any. + pub fn remove_first(&mut self) -> Option<(String, String, TestResult)> { + self.results.iter_mut().find_map(|(suite_name, suite)| { + if let Some(test_name) = suite.test_results.keys().next().cloned() { + let result = suite.test_results.remove(&test_name).unwrap(); + Some((suite_name.clone(), test_name, result)) + } else { + None + } + }) + } +} + +/// A set of test results for a single test suite, which is all the tests in a single contract. +#[derive(Clone, Debug, Serialize)] pub struct SuiteResult { - /// Total duration of the test run for this block of tests + /// Wall clock time it took to execute all tests in this suite. + #[serde(with = "humantime_serde")] pub duration: Duration, - /// Individual test results. `test method name -> TestResult` + /// Individual test results: `test fn signature -> TestResult`. pub test_results: BTreeMap, - /// Warnings + /// Generated warnings. pub warnings: Vec, } @@ -27,19 +217,57 @@ impl SuiteResult { pub fn new( duration: Duration, test_results: BTreeMap, - warnings: Vec, + mut warnings: Vec, ) -> Self { + // Add deprecated cheatcodes warning, if any of them used in current test suite. + let mut deprecated_cheatcodes = HashMap::new(); + for test_result in test_results.values() { + deprecated_cheatcodes.extend(test_result.deprecated_cheatcodes.clone()); + } + if !deprecated_cheatcodes.is_empty() { + let mut warning = + "the following cheatcode(s) are deprecated and will be removed in future versions:" + .to_string(); + for (cheatcode, reason) in deprecated_cheatcodes { + write!(warning, "\n {cheatcode}").unwrap(); + if let Some(reason) = reason { + write!(warning, ": {reason}").unwrap(); + } + } + warnings.push(warning); + } + Self { duration, test_results, warnings } } - /// Iterator over all succeeding tests and their names + /// Returns an iterator over all individual succeeding tests and their names. pub fn successes(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Success) + self.tests().filter(|(_, t)| t.status.is_success()) + } + + /// Returns an iterator over all individual skipped tests and their names. + pub fn skips(&self) -> impl Iterator { + self.tests().filter(|(_, t)| t.status.is_skipped()) } - /// Iterator over all failing tests and their names + /// Returns an iterator over all individual failing tests and their names. pub fn failures(&self) -> impl Iterator { - self.tests().filter(|(_, t)| t.status == TestStatus::Failure) + self.tests().filter(|(_, t)| t.status.is_failure()) + } + + /// Returns the number of tests that passed. + pub fn passed(&self) -> usize { + self.successes().count() + } + + /// Returns the number of tests that were skipped. + pub fn skipped(&self) -> usize { + self.skips().count() + } + + /// Returns the number of tests that failed. + pub fn failed(&self) -> usize { + self.failures().count() } /// Iterator over all tests and their names @@ -56,9 +284,63 @@ impl SuiteResult { pub fn len(&self) -> usize { self.test_results.len() } + + /// Sums up all the durations of all individual tests in this suite. + /// + /// Note that this is not necessarily the wall clock time of the entire test suite. + pub fn total_time(&self) -> Duration { + self.test_results.values().map(|result| result.duration).sum() + } + + /// Returns the summary of a single test suite. + pub fn summary(&self) -> String { + let failed = self.failed(); + let result = if failed == 0 { "ok".green() } else { "FAILED".red() }; + format!( + "Suite result: {}. {} passed; {} failed; {} skipped; finished in {:.2?} ({:.2?} CPU time)", + result, + self.passed().green(), + failed.red(), + self.skipped().yellow(), + self.duration, + self.total_time(), + ) + } } -#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq)] +/// The result of a single test in a test suite. +/// +/// This is flattened from a [`TestOutcome`]. +#[derive(Clone, Debug)] +pub struct SuiteTestResult { + /// The identifier of the artifact/contract in the form: + /// `:`. + pub artifact_id: String, + /// The function signature of the Solidity test. + pub signature: String, + /// The result of the executed test. + pub result: TestResult, +} + +impl SuiteTestResult { + /// Returns the gas used by the test. + pub fn gas_used(&self) -> u64 { + self.result.kind.report().gas() + } + + /// Returns the contract name of the artifact ID. + pub fn contract_name(&self) -> &str { + get_contract_name(&self.artifact_id) + } + + /// Returns the file name of the artifact ID. + pub fn file_name(&self) -> &str { + get_file_name(&self.artifact_id) + } +} + +/// The status of a test. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum TestStatus { Success, #[default] @@ -66,7 +348,27 @@ pub enum TestStatus { Skipped, } -/// The result of an executed solidity test +impl TestStatus { + /// Returns `true` if the test was successful. + #[inline] + pub fn is_success(self) -> bool { + matches!(self, Self::Success) + } + + /// Returns `true` if the test failed. + #[inline] + pub fn is_failure(self) -> bool { + matches!(self, Self::Failure) + } + + /// Returns `true` if the test was skipped. + #[inline] + pub fn is_skipped(self) -> bool { + matches!(self, Self::Skipped) + } +} + +/// The result of an executed test. #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct TestResult { /// The test status, indicating whether the test case succeeded, failed, or was marked as @@ -87,6 +389,7 @@ pub struct TestResult { pub logs: Vec, /// The decoded DSTest logging events and Hardhat's `console.log` from [logs](Self::logs). + /// Used for json output. pub decoded_logs: Vec, /// What kind of test this was @@ -95,46 +398,287 @@ pub struct TestResult { /// Traces pub traces: Traces, + /// Additional traces to use for gas report. + #[serde(skip)] + pub gas_report_traces: Vec>, + /// Raw coverage info #[serde(skip)] pub coverage: Option, /// Labeled addresses - pub labeled_addresses: BTreeMap, + pub labeled_addresses: AddressHashMap, + + pub duration: Duration, /// pc breakpoint char map pub breakpoints: Breakpoints, + + /// Any captured gas snapshots along the test's execution which should be accumulated. + pub gas_snapshots: BTreeMap>, + + /// Deprecated cheatcodes (mapped to their replacements, if any) used in current test. + #[serde(skip)] + pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, +} + +impl fmt::Display for TestResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.status { + TestStatus::Success => "[PASS]".green().fmt(f), + TestStatus::Skipped => { + let mut s = String::from("[SKIP"); + if let Some(reason) = &self.reason { + write!(s, ": {reason}").unwrap(); + } + s.push(']'); + s.yellow().fmt(f) + } + TestStatus::Failure => { + let mut s = String::from("[FAIL"); + if self.reason.is_some() || self.counterexample.is_some() { + if let Some(reason) = &self.reason { + write!(s, ": {reason}").unwrap(); + } + + if let Some(counterexample) = &self.counterexample { + match counterexample { + CounterExample::Single(ex) => { + write!(s, "; counterexample: {ex}]").unwrap(); + } + CounterExample::Sequence(original, sequence) => { + s.push_str( + format!( + "]\n\t[Sequence] (original: {original}, shrunk: {})\n", + sequence.len() + ) + .as_str(), + ); + for ex in sequence { + writeln!(s, "{ex}").unwrap(); + } + } + } + } else { + s.push(']'); + } + } else { + s.push(']'); + } + s.red().fmt(f) + } + } + } } impl TestResult { + /// Creates a new test result starting from test setup results. + pub fn new(setup: &TestSetup) -> Self { + Self { + labeled_addresses: setup.labels.clone(), + logs: setup.logs.clone(), + traces: setup.traces.clone(), + coverage: setup.coverage.clone(), + ..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 test setup result. + pub fn setup_result(setup: TestSetup) -> Self { + Self { + status: if setup.skipped { TestStatus::Skipped } else { TestStatus::Failure }, + reason: setup.reason, + logs: setup.logs, + traces: setup.traces, + coverage: setup.coverage, + labeled_addresses: setup.labels, + ..Default::default() + } + } + + /// Returns the skipped result for single test (used in skipped fuzz test too). + pub fn single_skip(&mut self, reason: SkipReason) { + self.status = TestStatus::Skipped; + self.reason = reason.0; + } + + /// Returns the failed result with reason for single test. + pub fn single_fail(&mut self, reason: Option) { + self.status = TestStatus::Failure; + self.reason = reason; + } + + /// 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.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.duration = Duration::default(); + self.gas_report_traces = Vec::new(); + + if let Some(cheatcodes) = raw_call_result.cheatcodes { + self.breakpoints = cheatcodes.breakpoints; + self.gas_snapshots = cheatcodes.gas_snapshots; + self.deprecated_cheatcodes = cheatcodes.deprecated; + } + } + + /// 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.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 = if result.skipped { + TestStatus::Skipped + } else if result.success { + TestStatus::Success + } else { + TestStatus::Failure + }; + self.reason = result.reason; + self.counterexample = result.counterexample; + self.duration = Duration::default(); + self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect(); + self.breakpoints = result.breakpoints.unwrap_or_default(); + self.deprecated_cheatcodes = result.deprecated_cheatcodes; + } + + /// Returns the skipped result for invariant test. + pub fn invariant_skip(&mut self, reason: SkipReason) { + self.kind = + TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; + self.status = TestStatus::Skipped; + self.reason = reason.0; + } + + /// 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.kind = + TestKind::Invariant { runs: 1, calls: 1, reverts: 1, metrics: HashMap::default() }; + self.status = TestStatus::Failure; + self.reason = if replayed_entirely { + Some(format!("{invariant_name} replay failure")) + } else { + Some(format!("{invariant_name} persisted failure revert")) + }; + self.counterexample = Some(CounterExample::Sequence(call_sequence.len(), call_sequence)); + } + + /// Returns the fail result for invariant test setup. + pub fn invariant_setup_fail(&mut self, e: Report) { + self.kind = + TestKind::Invariant { runs: 0, calls: 0, reverts: 0, metrics: HashMap::default() }; + self.status = TestStatus::Failure; + self.reason = Some(format!("failed to set up invariant testing environment: {e}")); + } + + /// Returns the invariant test result. + #[allow(clippy::too_many_arguments)] + pub fn invariant_result( + &mut self, + gas_report_traces: Vec>, + success: bool, + reason: Option, + counterexample: Option, + cases: Vec, + reverts: usize, + metrics: Map, + ) { + self.kind = TestKind::Invariant { + runs: cases.len(), + calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), + reverts, + metrics, + }; + self.status = match success { + true => TestStatus::Success, + false => TestStatus::Failure, + }; + self.reason = reason; + self.counterexample = counterexample; + self.gas_report_traces = gas_report_traces; + } + /// Returns `true` if this is the result of a fuzz test pub fn is_fuzz(&self) -> bool { matches!(self.kind, TestKind::Fuzz { .. }) } + + /// Formats the test result into a string (for printing). + pub fn short_result(&self, name: &str) -> String { + format!("{self} {name} {}", self.kind.report()) + } + + /// Merges the given raw call result into `self`. + pub fn extend(&mut self, call_result: RawCallResult) { + self.logs.extend(call_result.logs); + self.labeled_addresses.extend(call_result.labels); + self.traces.extend(call_result.traces.map(|traces| (TraceKind::Execution, traces))); + self.merge_coverages(call_result.coverage); + } + + /// Merges the given coverage result into `self`. + pub fn merge_coverages(&mut self, other_coverage: Option) { + HitMaps::merge_opt(&mut self.coverage, other_coverage); + } } /// Data report by a test. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum TestKindReport { - Standard { gas: u64 }, + Unit { gas: u64 }, Fuzz { runs: usize, mean_gas: u64, median_gas: u64 }, - Invariant { runs: usize, calls: usize, reverts: usize }, + Invariant { runs: usize, calls: usize, reverts: usize, metrics: Map }, } impl fmt::Display for TestKindReport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - TestKindReport::Standard { gas } => { + Self::Unit { gas } => { write!(f, "(gas: {gas})") } - TestKindReport::Fuzz { runs, mean_gas, median_gas } => { + Self::Fuzz { runs, mean_gas, median_gas } => { write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") } - TestKindReport::Invariant { runs, calls, reverts } => { + Self::Invariant { runs, calls, reverts, metrics: _ } => { write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})") } } @@ -144,12 +688,12 @@ impl fmt::Display for TestKindReport { impl TestKindReport { /// Returns the main gas value to compare against pub fn gas(&self) -> u64 { - match self { - TestKindReport::Standard { gas } => *gas, + match *self { + Self::Unit { gas } => gas, // We use the median for comparisons - TestKindReport::Fuzz { median_gas, .. } => *median_gas, + Self::Fuzz { median_gas, .. } => median_gas, // We return 0 since it's not applicable - TestKindReport::Invariant { .. } => 0, + Self::Invariant { .. } => 0, } } } @@ -157,11 +701,9 @@ impl TestKindReport { /// Various types of tests #[derive(Clone, Debug, Serialize, Deserialize)] pub enum TestKind { - /// A standard test that consists of calling the defined solidity function - /// - /// Holds the consumed gas - Standard(u64), - /// A solidity fuzz test, that stores all test cases + /// A unit test. + Unit { gas: u64 }, + /// A fuzz test. Fuzz { /// we keep this for the debugger first_case: FuzzCase, @@ -169,13 +711,13 @@ pub enum TestKind { mean_gas: u64, median_gas: u64, }, - /// A solidity invariant test, that stores all test cases - Invariant { runs: usize, calls: usize, reverts: usize }, + /// An invariant test. + Invariant { runs: usize, calls: usize, reverts: usize, metrics: Map }, } impl Default for TestKind { fn default() -> Self { - Self::Standard(0) + Self::Unit { gas: 0 } } } @@ -183,74 +725,63 @@ impl TestKind { /// The gas consumed by this test pub fn report(&self) -> TestKindReport { match self { - TestKind::Standard(gas) => TestKindReport::Standard { gas: *gas }, - TestKind::Fuzz { runs, mean_gas, median_gas, .. } => { + Self::Unit { gas } => TestKindReport::Unit { gas: *gas }, + Self::Fuzz { first_case: _, runs, mean_gas, median_gas } => { TestKindReport::Fuzz { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas } } - TestKind::Invariant { runs, calls, reverts } => { - TestKindReport::Invariant { runs: *runs, calls: *calls, reverts: *reverts } - } + Self::Invariant { runs, calls, reverts, metrics: _ } => TestKindReport::Invariant { + runs: *runs, + calls: *calls, + reverts: *reverts, + metrics: HashMap::default(), + }, } } } +/// The result of a test setup. +/// +/// Includes the deployment of the required libraries and the test contract itself, and the call to +/// the `setUp()` function. #[derive(Clone, Debug, Default)] pub struct TestSetup { - /// The address at which the test contract was deployed + /// The address at which the test contract was deployed. pub address: Address, - /// The logs emitted during setup + /// Defined fuzz test fixtures. + pub fuzz_fixtures: FuzzFixtures, + + /// The logs emitted during setup. pub logs: Vec, - /// Call traces of the setup + /// Addresses labeled during setup. + pub labels: AddressHashMap, + /// Call traces of the setup. pub traces: Traces, - /// Addresses labeled during setup - pub labeled_addresses: BTreeMap, - /// The reason the setup failed, if it did + /// Coverage info during setup. + pub coverage: Option, + /// Addresses of external libraries deployed during setup. + pub deployed_libs: Vec
, + + /// The reason the setup failed, if it did. pub reason: Option, + /// Whether setup and entire test suite is skipped. + pub skipped: bool, + /// Whether the test failed to deploy. + pub deployment_failure: bool, } impl TestSetup { - pub fn from_evm_error_with( - error: EvmError, - mut logs: Vec, - mut traces: Traces, - mut labeled_addresses: BTreeMap, - ) -> Self { - match error { - EvmError::Execution(err) => { - // force the tracekind to be setup so a trace is shown. - traces.extend(err.traces.map(|traces| (TraceKind::Setup, traces))); - logs.extend(err.logs); - labeled_addresses.extend(err.labels); - Self::failed_with(logs, traces, labeled_addresses, err.reason) - } - e => Self::failed_with( - logs, - traces, - labeled_addresses, - format!("Failed to deploy contract: {e}"), - ), - } - } - - pub fn success( - address: Address, - logs: Vec, - traces: Traces, - labeled_addresses: BTreeMap, - ) -> Self { - Self { address, logs, traces, labeled_addresses, reason: None } + pub fn failed(reason: String) -> Self { + Self { reason: Some(reason), ..Default::default() } } - pub fn failed_with( - logs: Vec, - traces: Traces, - labeled_addresses: BTreeMap, - reason: String, - ) -> Self { - Self { address: Address::zero(), logs, traces, labeled_addresses, reason: Some(reason) } + pub fn skipped(reason: String) -> Self { + Self { reason: Some(reason), skipped: true, ..Default::default() } } - pub fn failed(reason: String) -> Self { - Self { reason: Some(reason), ..Default::default() } + pub fn extend(&mut self, raw: RawCallResult, trace_kind: TraceKind) { + self.logs.extend(raw.logs); + self.labels.extend(raw.labels); + self.traces.extend(raw.traces.map(|traces| (trace_kind, traces))); + HitMaps::merge_opt(&mut self.coverage, raw.coverage); } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index b500309d4bc06..893c74baf449f 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -1,191 +1,287 @@ +//! The Forge test runner. + use crate::{ - result::{SuiteResult, TestKind, TestResult, TestSetup, TestStatus}, - TestFilter, TestOptions, -}; -use ethers::{ - abi::{Abi, Function}, - types::{Address, Bytes, U256}, + fuzz::{invariant::BasicTxDetails, BaseCounterExample}, + multi_runner::{is_matching_test, TestContract, TestRunnerConfig}, + progress::{start_fuzz_progress, TestsProgress}, + result::{SuiteResult, TestResult, TestSetup}, + MultiContractRunner, TestFilter, }; +use alloy_dyn_abi::DynSolValue; +use alloy_json_abi::Function; +use alloy_primitives::{address, map::HashMap, Address, U256}; use eyre::Result; -use foundry_common::{ - contracts::{ContractsByAddress, ContractsByArtifact}, - TestFunctionExt, -}; -use foundry_config::{FuzzConfig, InvariantConfig}; +use foundry_common::{contracts::ContractsByAddress, TestFunctionExt, TestFunctionKind}; +use foundry_config::Config; use foundry_evm::{ - decode::decode_console_logs, - executor::{CallResult, EvmError, ExecutionErr, Executor}, - fuzz::{ + constants::CALLER, + decode::RevertDecoder, + executors::{ + fuzz::FuzzedExecutor, invariant::{ - replay_run, InvariantContract, InvariantExecutor, InvariantFuzzError, - InvariantFuzzTestResult, + check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError, }, - FuzzedExecutor, + CallResult, EvmError, Executor, ITest, RawCallResult, }, - trace::{load_contracts, TraceKind}, - CALLER, + fuzz::{ + fixture_name, + invariant::{CallDetails, InvariantContract}, + CounterExample, FuzzFixtures, + }, + traces::{load_contracts, TraceKind, TraceMode}, }; -use proptest::test_runner::{TestError, TestRunner}; -use rayon::prelude::*; -use std::{ - collections::{BTreeMap, HashMap}, - time::Instant, +use proptest::test_runner::{ + FailurePersistence, FileFailurePersistence, RngAlgorithm, TestError, TestRng, TestRunner, }; +use rayon::prelude::*; +use std::{borrow::Cow, cmp::min, collections::BTreeMap, sync::Arc, time::Instant}; +use tracing::Span; + +/// When running tests, we deploy all external libraries present in the project. To avoid additional +/// libraries affecting nonces of senders used in tests, we are using separate address to +/// predeploy libraries. +/// +/// `address(uint160(uint256(keccak256("foundry library deployer"))))` +pub const LIBRARY_DEPLOYER: Address = address!("1F95D37F27EA0dEA9C252FC09D5A6eaA97647353"); /// A type that executes all tests of a contract -#[derive(Debug, Clone)] pub struct ContractRunner<'a> { - pub name: &'a str, - /// The executor used by the runner. - pub executor: Executor, - /// Library contracts to be deployed before the test contract - pub predeploy_libs: &'a [Bytes], - /// The deployed contract's code - pub code: Bytes, - /// The test contract's ABI - pub contract: &'a Abi, - /// All known errors, used to decode reverts - pub errors: Option<&'a Abi>, - - /// 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 - pub sender: Address, + /// The name of the contract. + name: &'a str, + /// The data of the contract. + contract: &'a TestContract, + /// The EVM executor. + executor: Executor, + /// Overall test run progress. + progress: Option<&'a TestsProgress>, + /// The handle to the tokio runtime. + tokio_handle: &'a tokio::runtime::Handle, + /// The span of the contract. + span: tracing::Span, + /// The contract-level configuration. + tcfg: Cow<'a, TestRunnerConfig>, + /// The parent runner. + mcr: &'a MultiContractRunner, +} + +impl<'a> std::ops::Deref for ContractRunner<'a> { + type Target = Cow<'a, TestRunnerConfig>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.tcfg + } } impl<'a> ContractRunner<'a> { - #[allow(clippy::too_many_arguments)] pub fn new( name: &'a str, + contract: &'a TestContract, executor: Executor, - contract: &'a Abi, - code: Bytes, - initial_balance: U256, - sender: Option
, - errors: Option<&'a Abi>, - predeploy_libs: &'a [Bytes], + progress: Option<&'a TestsProgress>, + tokio_handle: &'a tokio::runtime::Handle, + span: Span, + mcr: &'a MultiContractRunner, ) -> Self { Self { name, - executor, contract, - code, - initial_balance, - sender: sender.unwrap_or_default(), - errors, - predeploy_libs, + executor, + progress, + tokio_handle, + span, + tcfg: Cow::Borrowed(&mcr.tcfg), + mcr, } } -} -impl<'a> ContractRunner<'a> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. - pub fn setup(&mut self, setup: bool) -> TestSetup { - match self._setup(setup) { - Ok(setup) => setup, - Err(err) => TestSetup::failed(err.to_string()), - } + pub fn setup(&mut self, call_setup: bool) -> TestSetup { + self._setup(call_setup).unwrap_or_else(|err| { + if err.to_string().contains("skipped") { + TestSetup::skipped(err.to_string()) + } else { + TestSetup::failed(err.to_string()) + } + }) } - fn _setup(&mut self, setup: bool) -> Result { - trace!(?setup, "Setting test contract"); + fn _setup(&mut self, call_setup: bool) -> Result { + trace!(call_setup, "setting up"); + + self.apply_contract_inline_config()?; // We max out their balance so that they can deploy and make calls. self.executor.set_balance(self.sender, U256::MAX)?; self.executor.set_balance(CALLER, U256::MAX)?; - // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools + // We set the nonce of the deployer accounts to 1 to get the same addresses as DappTools. self.executor.set_nonce(self.sender, 1)?; - // Deploy libraries - let mut logs = Vec::new(); - let mut traces = Vec::with_capacity(self.predeploy_libs.len()); - for code in self.predeploy_libs.iter() { - match self.executor.deploy(self.sender, code.0.clone(), 0u32.into(), self.errors) { - Ok(d) => { - logs.extend(d.logs); - traces.extend(d.traces.map(|traces| (TraceKind::Deployment, traces))); - } - Err(e) => { - return Ok(TestSetup::from_evm_error_with(e, logs, traces, Default::default())) - } + // Deploy libraries. + self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?; + + let mut result = TestSetup::default(); + for code in self.mcr.libs_to_deploy.iter() { + let deploy_result = self.executor.deploy( + LIBRARY_DEPLOYER, + code.clone(), + U256::ZERO, + Some(&self.mcr.revert_decoder), + ); + + // Record deployed library address. + if let Ok(deployed) = &deploy_result { + result.deployed_libs.push(deployed.address); + } + + let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?; + result.extend(raw, TraceKind::Deployment); + if reason.is_some() { + result.reason = reason; + return Ok(result); } } + let address = self.sender.create(self.executor.get_nonce(self.sender)?); + result.address = address; + + // Set the contracts initial balance before deployment, so it is available during + // construction + self.executor.set_balance(address, self.initial_balance())?; + // Deploy the test contract - let address = match self.executor.deploy( + let deploy_result = self.executor.deploy( self.sender, - self.code.0.clone(), - 0u32.into(), - self.errors, - ) { - Ok(d) => { - logs.extend(d.logs); - traces.extend(d.traces.map(|traces| (TraceKind::Deployment, traces))); - d.address - } - Err(e) => { - return Ok(TestSetup::from_evm_error_with(e, logs, traces, Default::default())) - } - }; + self.contract.bytecode.clone(), + U256::ZERO, + Some(&self.mcr.revert_decoder), + ); + + result.deployment_failure = deploy_result.is_err(); + + if let Ok(dr) = &deploy_result { + debug_assert_eq!(dr.address, address); + } + let (raw, reason) = RawCallResult::from_evm_result(deploy_result.map(Into::into))?; + result.extend(raw, TraceKind::Deployment); + if reason.is_some() { + result.reason = reason; + return Ok(result); + } - // Now we set the contracts initial balance, and we also reset `self.sender`s and `CALLER`s - // balance to the initial balance we want - self.executor.set_balance(address, self.initial_balance)?; - self.executor.set_balance(self.sender, self.initial_balance)?; - self.executor.set_balance(CALLER, self.initial_balance)?; + // Reset `self.sender`s, `CALLER`s and `LIBRARY_DEPLOYER`'s balance to the initial balance. + self.executor.set_balance(self.sender, self.initial_balance())?; + self.executor.set_balance(CALLER, self.initial_balance())?; + self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance())?; self.executor.deploy_create2_deployer()?; // Optionally call the `setUp` function - let setup = if setup { - trace!("setting up"); - let (setup_logs, setup_traces, labeled_addresses, reason) = - match self.executor.setup(None, address) { - Ok(CallResult { traces, labels, logs, .. }) => { - trace!(contract = ?address, "successfully setUp test"); - (logs, traces, labels, None) - } - Err(EvmError::Execution(err)) => { - let ExecutionErr { traces, labels, logs, reason, .. } = *err; - error!(reason = ?reason, contract = ?address, "setUp failed"); - (logs, traces, labels, Some(format!("Setup failed: {reason}"))) - } - Err(err) => { - error!(reason=?err, contract= ?address, "setUp failed"); - (Vec::new(), None, BTreeMap::new(), Some(format!("Setup failed: {err}"))) - } - }; - traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); - logs.extend(setup_logs); + if call_setup { + trace!("calling setUp"); + let res = self.executor.setup(None, address, Some(&self.mcr.revert_decoder)); + let (raw, reason) = RawCallResult::from_evm_result(res)?; + result.extend(raw, TraceKind::Setup); + result.reason = reason; + } - TestSetup { address, logs, traces, labeled_addresses, reason } - } else { - TestSetup::success(address, logs, traces, Default::default()) - }; + result.fuzz_fixtures = self.fuzz_fixtures(address); + + Ok(result) + } - Ok(setup) + fn initial_balance(&self) -> U256 { + self.evm_opts.initial_balance + } + + /// Configures this runner with the inline configuration for the contract. + fn apply_contract_inline_config(&mut self) -> Result<()> { + if self.inline_config.contains_contract(self.name) { + let new_config = Arc::new(self.inline_config(None)?); + self.tcfg.to_mut().reconfigure_with(new_config); + let prev_tracer = self.executor.inspector_mut().tracer.take(); + self.tcfg.configure_executor(&mut self.executor); + // Don't set tracer here. + self.executor.inspector_mut().tracer = prev_tracer; + } + Ok(()) + } + + /// Returns the configuration for a contract or function. + fn inline_config(&self, func: Option<&Function>) -> Result { + let function = func.map(|f| f.name.as_str()).unwrap_or(""); + let config = + self.mcr.inline_config.merge(self.name, function, &self.config).extract::()?; + Ok(config) + } + + /// Collect fixtures from test contract. + /// + /// Fixtures can be defined: + /// - as storage arrays in test contract, prefixed with `fixture` + /// - as functions prefixed with `fixture` and followed by parameter name to be fuzzed + /// + /// Storage array fixtures: + /// `uint256[] public fixture_amount = [1, 2, 3];` + /// define an array of uint256 values to be used for fuzzing `amount` named parameter in scope + /// of the current test. + /// + /// Function fixtures: + /// `function fixture_owner() public returns (address[] memory){}` + /// returns an array of addresses to be used for fuzzing `owner` named parameter in scope of the + /// current test. + fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures { + let mut fixtures = HashMap::default(); + let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture()); + for func in fixture_functions { + if func.inputs.is_empty() { + // Read fixtures declared as functions. + if let Ok(CallResult { raw: _, decoded_result }) = + self.executor.call(CALLER, address, func, &[], U256::ZERO, None) + { + fixtures.insert(fixture_name(func.name.clone()), decoded_result); + } + } else { + // For reading fixtures from storage arrays we collect values by calling the + // function with incremented indexes until there's an error. + let mut vals = Vec::new(); + let mut index = 0; + loop { + if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call( + CALLER, + address, + func, + &[DynSolValue::Uint(U256::from(index), 256)], + U256::ZERO, + None, + ) { + vals.push(decoded_result); + } else { + // No result returned for this index, we reached the end of storage + // array or the function is not a valid fixture. + break; + } + index += 1; + } + fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals)); + }; + } + FuzzFixtures::new(fixtures) } /// Runs all tests for a contract whose names match the provided regular expression - pub fn run_tests( - mut self, - filter: impl TestFilter, - test_options: TestOptions, - known_contracts: Option<&ContractsByArtifact>, - ) -> SuiteResult { - info!("starting tests"); + pub fn run_tests(mut self, filter: &dyn TestFilter) -> SuiteResult { let start = Instant::now(); let mut warnings = Vec::new(); + // Check if `setUp` function with valid signature declared. let setup_fns: Vec<_> = - self.contract.functions().filter(|func| func.name.is_setup()).collect(); - - let needs_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp"; - + self.contract.abi.functions().filter(|func| func.name.is_setup()).collect(); + let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp"; // There is a single miss-cased `setUp` function, so we add a warning - for setup_fn in setup_fns.iter() { + for &setup_fn in setup_fns.iter() { if setup_fn.name != "setUp" { warnings.push(format!( "Found invalid setup function \"{}\" did you mean \"setUp()\"?", @@ -196,404 +292,605 @@ impl<'a> ContractRunner<'a> { // There are multiple setUp function, so we return a single test result for `setUp` if setup_fns.len() > 1 { + return SuiteResult::new( + start.elapsed(), + [("setUp()".to_string(), TestResult::fail("multiple setUp functions".to_string()))] + .into(), + warnings, + ) + } + + // Check if `afterInvariant` function with valid signature declared. + let after_invariant_fns: Vec<_> = + self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect(); + if after_invariant_fns.len() > 1 { + // Return a single test result failure if multiple functions declared. return SuiteResult::new( start.elapsed(), [( - "setUp()".to_string(), - // TODO-f: get the breakpoints here - TestResult::fail("Multiple setUp functions".to_string()), + "afterInvariant()".to_string(), + TestResult::fail("multiple afterInvariant functions".to_string()), )] .into(), warnings, ) } - - let has_invariants = self.contract.functions().any(|func| func.is_invariant_test()); + let call_after_invariant = after_invariant_fns.first().is_some_and(|after_invariant_fn| { + let match_sig = after_invariant_fn.name == "afterInvariant"; + if !match_sig { + warnings.push(format!( + "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?", + after_invariant_fn.signature() + )); + } + match_sig + }); // Invariant testing requires tracing to figure out what contracts were created. - let original_tracing = self.executor.inspector_config().tracing; - if has_invariants && needs_setup { - self.executor.set_tracing(true); + // 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 prev_tracer = self.executor.inspector_mut().tracer.take(); + if prev_tracer.is_some() || has_invariants { + self.executor.set_tracing(TraceMode::Call); } - let setup = self.setup(needs_setup); - self.executor.set_tracing(original_tracing); + let setup_time = Instant::now(); + let setup = self.setup(call_setup); + debug!("finished setting up in {:?}", setup_time.elapsed()); + + self.executor.inspector_mut().tracer = prev_tracer; if setup.reason.is_some() { // The setup failed, so we return a single test result for `setUp` + let fail_msg = if !setup.deployment_failure { + "setUp()".to_string() + } else { + "constructor()".to_string() + }; return SuiteResult::new( start.elapsed(), - [( - "setUp()".to_string(), - TestResult { - status: TestStatus::Failure, - reason: setup.reason, - counterexample: None, - decoded_logs: decode_console_logs(&setup.logs), - logs: setup.logs, - kind: TestKind::Standard(0), - traces: setup.traces, - coverage: None, - labeled_addresses: setup.labeled_addresses, - breakpoints: Default::default(), - }, - )] - .into(), + [(fail_msg, TestResult::setup_result(setup))].into(), warnings, ) } - let mut test_results = self + // Filter out functions sequentially since it's very fast and there is no need to do it + // in parallel. + let find_timer = Instant::now(); + let functions = self .contract - .functions + .abi + .functions() + .filter(|func| is_matching_test(func, filter)) + .collect::>(); + debug!( + "Found {} test functions out of {} in {:?}", + functions.len(), + self.contract.abi.functions().count(), + find_timer.elapsed(), + ); + + let identified_contracts = has_invariants.then(|| { + load_contracts(setup.traces.iter().map(|(_, t)| &t.arena), &self.mcr.known_contracts) + }); + + let test_fail_instances = functions + .iter() + .filter_map(|func| { + TestFunctionKind::classify(&func.name, !func.inputs.is_empty()) + .is_any_test_fail() + .then_some(func.name.clone()) + }) + .collect::>(); + + if !test_fail_instances.is_empty() { + let instances = format!( + "Found {} instances: {}", + test_fail_instances.len(), + test_fail_instances.join(", ") + ); + let fail = TestResult::fail("`testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert".to_string()); + return SuiteResult::new(start.elapsed(), [(instances, fail)].into(), warnings) + } + + let test_results = functions .par_iter() - .flat_map(|(_, f)| f) - .filter(|&func| func.is_test() && filter.matches_test(func.signature())) - .map(|func| { - let should_fail = func.is_test_fail(); - let res = if func.is_fuzz_test() { - 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.clone(), *fuzz_config) - } else { - self.clone().run_test(func, should_fail, setup.clone()) - }; - (func.signature(), res) + .map(|&func| { + let start = Instant::now(); + + let _guard = self.tokio_handle.enter(); + + 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 mut res = FunctionRunner::new(&self, &setup).run( + func, + kind, + call_after_invariant, + identified_contracts.as_ref(), + ); + res.duration = start.elapsed(); + + (sig, res) }) .collect::>(); - if has_invariants { - let identified_contracts = load_contracts(setup.traces.clone(), known_contracts); - - // TODO: par_iter ? - let functions = self - .contract - .functions() - .filter(|&func| func.is_invariant_test() && filter.matches_test(func.signature())); - for func in functions { - let runner = test_options.invariant_runner(self.name, &func.name); - let invariant_config = test_options.invariant_config(self.name, &func.name); - let results = self.run_invariant_test( - runner, - setup.clone(), - *invariant_config, - vec![func], - known_contracts, - identified_contracts.clone(), - ); - for result in results { - test_results.insert(func.signature(), result); - } - } + let duration = start.elapsed(); + SuiteResult::new(duration, test_results, warnings) + } +} + +/// Executes a single test function, returning a [`TestResult`]. +struct FunctionRunner<'a> { + /// The function-level configuration. + tcfg: Cow<'a, TestRunnerConfig>, + /// The EVM executor. + executor: Cow<'a, Executor>, + /// The parent runner. + cr: &'a ContractRunner<'a>, + /// The address of the test contract. + address: Address, + /// The test setup result. + setup: &'a TestSetup, + /// The test result. Returned after running the test. + result: TestResult, +} + +impl<'a> std::ops::Deref for FunctionRunner<'a> { + type Target = Cow<'a, TestRunnerConfig>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.tcfg + } +} + +impl<'a> FunctionRunner<'a> { + fn new(cr: &'a ContractRunner<'a>, setup: &'a TestSetup) -> Self { + Self { + tcfg: match &cr.tcfg { + Cow::Borrowed(tcfg) => Cow::Borrowed(tcfg), + Cow::Owned(tcfg) => Cow::Owned(tcfg.clone()), + }, + executor: Cow::Borrowed(&cr.executor), + cr, + address: setup.address, + setup, + result: TestResult::new(setup), } + } - let duration = start.elapsed(); - if !test_results.is_empty() { - let successful = - test_results.iter().filter(|(_, tst)| tst.status == TestStatus::Success).count(); - info!( - duration = ?duration, - "done. {}/{} successful", - successful, - test_results.len() - ); + fn revert_decoder(&self) -> &'a RevertDecoder { + &self.cr.mcr.revert_decoder + } + + /// Configures this runner with the inline configuration for the contract. + fn apply_function_inline_config(&mut self, func: &Function) -> Result<()> { + if self.inline_config.contains_function(self.cr.name, &func.name) { + let new_config = Arc::new(self.cr.inline_config(Some(func))?); + self.tcfg.to_mut().reconfigure_with(new_config); + self.tcfg.configure_executor(self.executor.to_mut()); } + Ok(()) + } - SuiteResult::new(duration, test_results, warnings) + fn run( + mut self, + func: &Function, + kind: TestFunctionKind, + call_after_invariant: bool, + identified_contracts: Option<&ContractsByAddress>, + ) -> TestResult { + if let Err(e) = self.apply_function_inline_config(func) { + self.result.single_fail(Some(e.to_string())); + return self.result; + } + + match kind { + TestFunctionKind::UnitTest { .. } => self.run_unit_test(func), + TestFunctionKind::FuzzTest { .. } => self.run_fuzz_test(func), + TestFunctionKind::InvariantTest => { + self.run_invariant_test(func, call_after_invariant, identified_contracts.unwrap()) + } + _ => unreachable!(), + } } - /// Runs a single test + /// Runs a single unit test. /// - /// Calls the given functions and returns the `TestResult`. + /// Applies before test txes (if any), runs current test and returns the `TestResult`. /// - /// State modifications are not committed to the evm database but discarded after the call, - /// similar to `eth_call`. - #[instrument(name = "test", skip_all, fields(name = %func.signature(), %should_fail))] - pub fn run_test(mut self, func: &Function, should_fail: bool, setup: TestSetup) -> TestResult { - let TestSetup { address, mut logs, mut traces, mut labeled_addresses, .. } = setup; + /// Before test txes are applied in order and state modifications committed to the EVM database + /// (therefore the unit test call will be made on modified state). + /// State modifications of before test txes and unit test function call are discarded after + /// test ends, similar to `eth_call`. + fn run_unit_test(mut self, func: &Function) -> TestResult { + // Prepare unit test execution. + if self.prepare_test(func).is_err() { + return self.result; + } - // Run unit test - let start = Instant::now(); - let (reverted, reason, gas, stipend, coverage, state_changeset, breakpoints) = match self - .executor - .execute_test::<(), _, _>(self.sender, address, func.clone(), (), 0.into(), self.errors) - { - Ok(CallResult { - reverted, - gas_used: gas, - stipend, - logs: execution_logs, - traces: execution_trace, - coverage, - labels: new_labels, - state_changeset, - breakpoints, - .. - }) => { - traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(new_labels); - logs.extend(execution_logs); - (reverted, None, gas, stipend, coverage, state_changeset, breakpoints) - } - Err(EvmError::Execution(err)) => { - traces.extend(err.traces.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(err.labels); - logs.extend(err.logs); - ( - err.reverted, - Some(err.reason), - err.gas_used, - err.stipend, - None, - err.state_changeset, - HashMap::new(), - ) - } - Err(EvmError::SkipError) => { - return TestResult { - status: TestStatus::Skipped, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - ..Default::default() - } + // Run current unit test. + let (mut raw_call_result, reason) = match self.executor.call( + self.sender, + self.address, + func, + &[], + U256::ZERO, + Some(self.revert_decoder()), + ) { + Ok(res) => (res.raw, None), + Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), + Err(EvmError::Skip(reason)) => { + self.result.single_skip(reason); + return self.result; } Err(err) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(err.to_string()), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - ..Default::default() - } + self.result.single_fail(Some(err.to_string())); + return self.result; } }; - let success = self.executor.is_success( - setup.address, - reverted, - state_changeset.expect("we should have a state changeset"), - should_fail, - ); - - // Record test execution time - debug!( - duration = ?start.elapsed(), - %success, - %gas - ); - - TestResult { - status: match success { - true => TestStatus::Success, - false => TestStatus::Failure, - }, - reason, - counterexample: None, - decoded_logs: decode_console_logs(&logs), - logs, - kind: TestKind::Standard(gas.overflowing_sub(stipend).0), - traces, - coverage, - labeled_addresses, - breakpoints, - } + let success = + self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false); + self.result.single_result(success, reason, raw_call_result); + self.result } - #[instrument(name = "invariant-test", skip_all)] - pub fn run_invariant_test( - &mut self, - runner: TestRunner, - setup: TestSetup, - invariant_config: InvariantConfig, - functions: Vec<&Function>, - known_contracts: Option<&ContractsByArtifact>, - identified_contracts: ContractsByAddress, - ) -> Vec { - trace!(target: "forge::test::fuzz", "executing invariant test with invariant functions {:?}", functions.iter().map(|f|&f.name).collect::>()); - let empty = ContractsByArtifact::default(); - let project_contracts = known_contracts.unwrap_or(&empty); - let TestSetup { address, logs, traces, labeled_addresses, .. } = setup; - + fn run_invariant_test( + mut self, + func: &Function, + call_after_invariant: bool, + identified_contracts: &ContractsByAddress, + ) -> TestResult { // First, run the test normally to see if it needs to be skipped. - if let Err(EvmError::SkipError) = self.executor.execute_test::<(), _, _>( + if let Err(EvmError::Skip(reason)) = self.executor.call( self.sender, - address, - functions[0].clone(), - (), - 0.into(), - self.errors, + self.address, + func, + &[], + U256::ZERO, + Some(self.revert_decoder()), ) { - return vec![TestResult { - status: TestStatus::Skipped, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - ..Default::default() - }] + self.result.invariant_skip(reason); + return self.result; }; + let runner = self.invariant_runner(); + let invariant_config = &self.config.invariant; + let mut evm = InvariantExecutor::new( - &mut self.executor, + self.clone_executor(), runner, - invariant_config, - &identified_contracts, - project_contracts, + invariant_config.clone(), + identified_contracts, + &self.cr.mcr.known_contracts, ); - - let invariant_contract = - InvariantContract { address, invariant_functions: functions, abi: self.contract }; - - let Ok(InvariantFuzzTestResult { invariants, cases, reverts, last_run_inputs }) = - evm.invariant_fuzz(invariant_contract.clone()) - else { - return vec![] + let invariant_contract = InvariantContract { + address: self.address, + invariant_function: func, + call_after_invariant, + abi: &self.cr.contract.abi, }; - invariants - .into_values() - .map(|test_error| { - let (test_error, invariant) = test_error; - let mut counterexample = None; - let mut logs = logs.clone(); - let mut traces = traces.clone(); - - let success = test_error.is_none(); - let reason = test_error.as_ref().and_then(|err| { - (!err.revert_reason.is_empty()).then(|| err.revert_reason.clone()) - }); - - match test_error { - // If invariants were broken, replay the error to collect logs and traces - Some(error @ InvariantFuzzError { test_error: TestError::Fail(_, _), .. }) => { - match error.replay( - self.executor.clone(), - known_contracts, - identified_contracts.clone(), - &mut logs, - &mut traces, - ) { - Ok(c) => counterexample = c, - Err(err) => { - error!(?err, "Failed to replay invariant error") - } - }; + let failure_dir = invariant_config.clone().failure_dir(self.cr.name); + let failure_file = failure_dir.join(&invariant_contract.invariant_function.name); + let show_solidity = invariant_config.clone().show_solidity; - logs.extend(error.logs); + // Try to replay recorded failure if any. + if let Ok(mut call_sequence) = + foundry_common::fs::read_json_file::>(failure_file.as_path()) + { + // Create calls from failed sequence and check if invariant still broken. + let txes = call_sequence + .iter_mut() + .map(|seq| { + seq.show_solidity = show_solidity; + BasicTxDetails { + sender: seq.sender.unwrap_or_default(), + call_details: CallDetails { + target: seq.addr.unwrap_or_default(), + calldata: seq.calldata.clone(), + }, + } + }) + .collect::>(); + if let Ok((success, replayed_entirely)) = check_sequence( + self.clone_executor(), + &txes, + (0..min(txes.len(), invariant_config.depth as usize)).collect(), + invariant_contract.address, + invariant_contract.invariant_function.selector().to_vec().into(), + invariant_config.fail_on_revert, + invariant_contract.call_after_invariant, + ) { + if !success { + let _= sh_warn!("\ + Replayed invariant failure from {:?} file. \ + Run `forge clean` or remove file to ignore failure and to continue invariant test campaign.", + failure_file.as_path() + ); + // If sequence still fails then replay error to collect traces and + // exit without executing new runs. + let _ = replay_run( + &invariant_contract, + self.clone_executor(), + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.coverage, + &mut self.result.deprecated_cheatcodes, + &txes, + show_solidity, + ); + self.result.invariant_replay_fail( + replayed_entirely, + &invariant_contract.invariant_function.name, + call_sequence, + ); + return self.result; + } + } + } - if let Some(error_traces) = error.traces { - traces.push((TraceKind::Execution, error_traces)); + let progress = + start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, invariant_config.runs); + let invariant_result = match evm.invariant_fuzz( + invariant_contract.clone(), + &self.setup.fuzz_fixtures, + &self.setup.deployed_libs, + progress.as_ref(), + ) { + Ok(x) => x, + Err(e) => { + self.result.invariant_setup_fail(e); + return self.result; + } + }; + // Merge coverage collected during invariant run with test setup coverage. + self.result.merge_coverages(invariant_result.coverage); + + let mut counterexample = None; + let success = invariant_result.error.is_none(); + let reason = invariant_result.error.as_ref().and_then(|err| err.revert_reason()); + + match invariant_result.error { + // If invariants were broken, replay the error to collect logs and traces + Some(error) => match error { + InvariantFuzzError::BrokenInvariant(case_data) | + InvariantFuzzError::Revert(case_data) => { + // Replay error to create counterexample and to collect logs, traces and + // coverage. + match replay_error( + &case_data, + &invariant_contract, + self.clone_executor(), + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.coverage, + &mut self.result.deprecated_cheatcodes, + progress.as_ref(), + show_solidity, + ) { + Ok(call_sequence) => { + if !call_sequence.is_empty() { + // Persist error in invariant failure dir. + if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) { + error!(%err, "Failed to create invariant failure dir"); + } else if let Err(err) = foundry_common::fs::write_json_file( + failure_file.as_path(), + &call_sequence, + ) { + error!(%err, "Failed to record call sequence"); + } + + let original_seq_len = + if let TestError::Fail(_, calls) = &case_data.test_error { + calls.len() + } else { + call_sequence.len() + }; + + counterexample = + Some(CounterExample::Sequence(original_seq_len, call_sequence)) + } } - } - _ => { - // If invariants ran successfully, replay the last run to collect logs and - // traces. - replay_run( - &invariant_contract.clone(), - self.executor.clone(), - known_contracts, - identified_contracts.clone(), - &mut logs, - &mut traces, - invariant, - last_run_inputs.clone(), - ); - } + Err(err) => { + error!(%err, "Failed to replay invariant error"); + } + }; } + InvariantFuzzError::MaxAssumeRejects(_) => {} + }, - let kind = TestKind::Invariant { - runs: cases.len(), - calls: cases.iter().map(|sequence| sequence.cases().len()).sum(), - reverts, - }; - - TestResult { - status: match success { - true => TestStatus::Success, - false => TestStatus::Failure, - }, - reason, - counterexample, - decoded_logs: decode_console_logs(&logs), - logs, - kind, - coverage: None, // TODO ? - traces, - labeled_addresses: labeled_addresses.clone(), - breakpoints: Default::default(), + // If invariants ran successfully, replay the last run to collect logs and + // traces. + _ => { + if let Err(err) = replay_run( + &invariant_contract, + self.clone_executor(), + &self.cr.mcr.known_contracts, + identified_contracts.clone(), + &mut self.result.logs, + &mut self.result.traces, + &mut self.result.coverage, + &mut self.result.deprecated_cheatcodes, + &invariant_result.last_run_inputs, + show_solidity, + ) { + error!(%err, "Failed to replay last invariant run"); } - }) - .collect() + } + } + + self.result.invariant_result( + invariant_result.gas_report_traces, + success, + reason, + counterexample, + invariant_result.cases, + invariant_result.reverts, + invariant_result.metrics, + ); + self.result } - #[instrument(name = "fuzz-test", skip_all, fields(name = %func.signature(), %should_fail))] - pub fn run_fuzz_test( - &self, - func: &Function, - should_fail: bool, - runner: TestRunner, - setup: TestSetup, - fuzz_config: FuzzConfig, - ) -> TestResult { - let TestSetup { address, mut logs, mut traces, mut labeled_addresses, .. } = setup; + /// Runs a fuzzed test. + /// + /// Applies the before test txes (if any), fuzzes the current function and returns the + /// `TestResult`. + /// + /// Before test txes are applied in order and state modifications committed to the EVM database + /// (therefore the fuzz test will use the modified state). + /// State modifications of before test txes and fuzz test are discarded after test ends, + /// similar to `eth_call`. + fn run_fuzz_test(mut self, func: &Function) -> TestResult { + // Prepare fuzz test execution. + if self.prepare_test(func).is_err() { + return self.result; + } - // Run fuzz test - let start = Instant::now(); - let mut result = FuzzedExecutor::new(&self.executor, runner, self.sender, fuzz_config) - .fuzz(func, address, should_fail, self.errors); - - // 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, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - ..Default::default() + let runner = self.fuzz_runner(); + let fuzz_config = self.config.fuzz.clone(); + + let progress = + start_fuzz_progress(self.cr.progress, self.cr.name, &func.name, fuzz_config.runs); + + // Run fuzz test. + let fuzzed_executor = + FuzzedExecutor::new(self.executor.into_owned(), runner, self.tcfg.sender, fuzz_config); + let result = fuzzed_executor.fuzz( + func, + &self.setup.fuzz_fixtures, + &self.setup.deployed_libs, + self.address, + &self.cr.mcr.revert_decoder, + progress.as_ref(), + ); + self.result.fuzz_result(result); + self.result + } + + /// Prepares single unit test and fuzz test execution: + /// - set up the test result and executor + /// - check if before test txes are configured and apply them in order + /// + /// Before test txes are arrays of arbitrary calldata obtained by calling the `beforeTest` + /// function with test selector as a parameter. + /// + /// Unit tests within same contract (or even current test) are valid options for before test tx + /// configuration. Test execution stops if any of before test txes fails. + fn prepare_test(&mut self, func: &Function) -> Result<(), ()> { + let address = self.setup.address; + + // Apply before test configured functions (if any). + if self.cr.contract.abi.functions().filter(|func| func.name.is_before_test_setup()).count() == + 1 + { + for calldata in self + .executor + .call_sol_default( + address, + &ITest::beforeTestSetupCall { testSelector: func.selector() }, + ) + .beforeTestCalldata + { + // Apply before test configured calldata. + match self.executor.to_mut().transact_raw( + self.tcfg.sender, + address, + calldata, + U256::ZERO, + ) { + Ok(call_result) => { + let reverted = call_result.reverted; + + // Merge tx result traces in unit test result. + self.result.extend(call_result); + + // To continue unit test execution the call should not revert. + if reverted { + self.result.single_fail(None); + return Err(()); + } + } + Err(_) => { + self.result.single_fail(None); + return Err(()); + } + } } } + Ok(()) + } - 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(), - }; + fn fuzz_runner(&self) -> TestRunner { + let config = &self.config.fuzz; + let failure_persist_path = config + .failure_persist_dir + .as_ref() + .unwrap() + .join(config.failure_persist_file.as_ref().unwrap()) + .into_os_string() + .into_string() + .unwrap(); + fuzzer_with_cases( + config.seed, + config.runs, + config.max_test_rejects, + Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), + ) + } - // Record logs, labels and traces - logs.append(&mut result.logs); - labeled_addresses.append(&mut result.labeled_addresses); - traces.extend(result.traces.map(|traces| (TraceKind::Execution, traces))); + fn invariant_runner(&self) -> TestRunner { + let config = &self.config.invariant; + fuzzer_with_cases(self.config.fuzz.seed, config.runs, config.max_assume_rejects, None) + } - // Record test execution time - debug!( - duration = ?start.elapsed(), - success = %result.success - ); + fn clone_executor(&self) -> Executor { + self.executor.clone().into_owned() + } +} - 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: result.coverage, - labeled_addresses, - breakpoints: Default::default(), - } +fn fuzzer_with_cases( + seed: Option, + cases: u32, + max_global_rejects: u32, + file_failure_persistence: Option>, +) -> TestRunner { + let config = proptest::test_runner::Config { + failure_persistence: file_failure_persistence, + cases, + max_global_rejects, + // Disable proptest shrink: for fuzz tests we provide single counterexample, + // for invariant tests we shrink outside proptest. + max_shrink_iters: 0, + ..Default::default() + }; + + if let Some(seed) = seed { + trace!(target: "forge::test", %seed, "building deterministic fuzzer"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(config, rng) + } else { + trace!(target: "forge::test", "building stochastic fuzzer"); + TestRunner::new(config) } } diff --git a/crates/forge/tests/cli/bind_json.rs b/crates/forge/tests/cli/bind_json.rs new file mode 100644 index 0000000000000..fcc081f6b6f06 --- /dev/null +++ b/crates/forge/tests/cli/bind_json.rs @@ -0,0 +1,125 @@ +use foundry_test_utils::snapbox; + +// 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(); + + snapbox::assert_data_eq!( + snapbox::Data::read_from(&prj.root().join("utils/JsonBindings.sol"), None), + snapbox::str![[r#" +// Automatically generated by forge bind-json. + +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; + +import {BindJsonTest, TopLevelStruct} from "test/JsonBindings.sol"; + +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); +} + +library JsonBindings { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + string constant schema_TopLevelStruct = "TopLevelStruct(uint256 param1,int8 param2)"; + string constant schema_ContractLevelStruct = "ContractLevelStruct(address[][] param1,address addrParam)"; + + function serialize(TopLevelStruct memory value) internal pure returns (string memory) { + return vm.serializeJsonType(schema_TopLevelStruct, abi.encode(value)); + } + + function serialize(TopLevelStruct memory value, string memory objectKey, string memory valueKey) internal returns (string memory) { + return vm.serializeJsonType(objectKey, valueKey, schema_TopLevelStruct, abi.encode(value)); + } + + function deserializeTopLevelStruct(string memory json) public pure returns (TopLevelStruct memory) { + return abi.decode(vm.parseJsonType(json, schema_TopLevelStruct), (TopLevelStruct)); + } + + function deserializeTopLevelStruct(string memory json, string memory path) public pure returns (TopLevelStruct memory) { + return abi.decode(vm.parseJsonType(json, path, schema_TopLevelStruct), (TopLevelStruct)); + } + + function deserializeTopLevelStructArray(string memory json, string memory path) public pure returns (TopLevelStruct[] memory) { + return abi.decode(vm.parseJsonTypeArray(json, path, schema_TopLevelStruct), (TopLevelStruct[])); + } + + function serialize(BindJsonTest.ContractLevelStruct memory value) internal pure returns (string memory) { + return vm.serializeJsonType(schema_ContractLevelStruct, abi.encode(value)); + } + + function serialize(BindJsonTest.ContractLevelStruct memory value, string memory objectKey, string memory valueKey) internal returns (string memory) { + return vm.serializeJsonType(objectKey, valueKey, schema_ContractLevelStruct, abi.encode(value)); + } + + function deserializeContractLevelStruct(string memory json) public pure returns (BindJsonTest.ContractLevelStruct memory) { + return abi.decode(vm.parseJsonType(json, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct)); + } + + function deserializeContractLevelStruct(string memory json, string memory path) public pure returns (BindJsonTest.ContractLevelStruct memory) { + return abi.decode(vm.parseJsonType(json, path, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct)); + } + + function deserializeContractLevelStructArray(string memory json, string memory path) public pure returns (BindJsonTest.ContractLevelStruct[] memory) { + return abi.decode(vm.parseJsonTypeArray(json, path, schema_ContractLevelStruct), (BindJsonTest.ContractLevelStruct[])); + } +} + +"#]], + ); + + cmd.forge_fuse().args(["test"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs new file mode 100644 index 0000000000000..c586db4a66525 --- /dev/null +++ b/crates/forge/tests/cli/build.rs @@ -0,0 +1,205 @@ +use crate::utils::generate_large_init_contract; +use foundry_test_utils::{forgetest, snapbox::IntoData, str}; +use globset::Glob; + +forgetest_init!(can_parse_build_filters, |prj, cmd| { + prj.clear(); + + cmd.args(["build", "--names", "--skip", "tests", "scripts"]).assert_success().stdout_eq(str![ + [r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + compiler version: [..] + - Counter + +"#] + ]); +}); + +forgetest!(throws_on_conflicting_args, |prj, cmd| { + prj.clear(); + + cmd.args(["compile", "--format-json", "--quiet"]).assert_failure().stderr_eq(str![[r#" +error: the argument '--json' cannot be used with '--quiet' + +Usage: forge[..] build --json [PATHS]... + +For more information, try '--help'. + +"#]]); +}); + +// tests that json is printed when --format-json is passed +forgetest!(compile_json, |prj, cmd| { + prj.add_source( + "jsonError", + r" +contract Dummy { + uint256 public number; + function something(uint256 newNumber) public { + number = newnumber; // error here + } +} +", + ) + .unwrap(); + + // set up command + cmd.args(["compile", "--format-json"]).assert_success().stdout_eq(str![[r#" +{ + "errors": [ + { + "sourceLocation": { + "file": "src/jsonError.sol", + "start": 184, + "end": 193 + }, + "type": "DeclarationError", + "component": "general", + "severity": "error", + "errorCode": "7576", + "message": "Undeclared identifier. Did you mean \"newNumber\"?", + "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n [FILE]:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n" + } + ], + "sources": {}, + "contracts": {}, + "build_infos": "{...}" +} +"#]].is_json()); +}); + +forgetest!(initcode_size_exceeds_limit, |prj, cmd| { + prj.add_source("LargeContract.sol", generate_large_init_contract(50_000).as_str()).unwrap(); + cmd.args(["build", "--sizes"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +╭---------------+------------------+-------------------+--------------------+---------------------╮ +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | ++=================================================================================================+ +| LargeContract | 62 | 50,125 | 24,514 | -973 | +╰---------------+------------------+-------------------+--------------------+---------------------╯ + + +"#]]); + + cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_failure().stdout_eq( + str![[r#" +{ + "LargeContract": { + "runtime_size": 62, + "init_size": 50125, + "runtime_margin": 24514, + "init_margin": -973 + } +} +"#]] + .is_json(), + ); + + // Ignore EIP-3860 + + cmd.forge_fuse().args(["build", "--sizes", "--ignore-eip-3860"]).assert_success().stdout_eq( + str![[r#" +No files changed, compilation skipped + +╭---------------+------------------+-------------------+--------------------+---------------------╮ +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | ++=================================================================================================+ +| LargeContract | 62 | 50,125 | 24,514 | -973 | +╰---------------+------------------+-------------------+--------------------+---------------------╯ + + +"#]], + ); + + cmd.forge_fuse() + .args(["build", "--sizes", "--ignore-eip-3860", "--json"]) + .assert_success() + .stdout_eq( + str![[r#" +{ + "LargeContract": { + "runtime_size": 62, + "init_size": 50125, + "runtime_margin": 24514, + "init_margin": -973 + } +} +"#]] + .is_json(), + ); +}); + +// tests build output is as expected +forgetest_init!(exact_build_output, |prj, cmd| { + cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); +}); + +// tests build output is as expected +forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { + prj.update_config(|config| { + config.solc = Some(foundry_config::SolcReq::Version(semver::Version::new(0, 8, 27))); + }); + + cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" +... + +╭----------+------------------+-------------------+--------------------+---------------------╮ +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | ++============================================================================================+ +| Counter | 481 | 509 | 24,095 | 48,643 | +╰----------+------------------+-------------------+--------------------+---------------------╯ + + +"#]]); + + cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_success().stdout_eq( + str![[r#" +{ + "Counter": { + "runtime_size": 481, + "init_size": 509, + "runtime_margin": 24095, + "init_margin": 48643 + } +} +"#]] + .is_json(), + ); +}); + +// tests that skip key in config can be used to skip non-compilable contract +forgetest_init!(test_can_skip_contract, |prj, cmd| { + prj.add_source( + "InvalidContract", + r" +contract InvalidContract { + some_invalid_syntax +} +", + ) + .unwrap(); + + prj.add_source( + "ValidContract", + r" +contract ValidContract {} +", + ) + .unwrap(); + + prj.update_config(|config| { + config.skip = vec![Glob::new("src/InvalidContract.sol").unwrap().into()]; + }); + + cmd.args(["build"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/cache.rs b/crates/forge/tests/cli/cache.rs index 22fc6ea2339ec..b2f6484262bdf 100644 --- a/crates/forge/tests/cli/cache.rs +++ b/crates/forge/tests/cli/cache.rs @@ -1,20 +1,26 @@ -//! Tests for various cache command -use foundry_test_utils::{ - forgetest, - util::{TestCommand, TestProject}, -}; +//! Tests for various cache command. -forgetest!(can_list_cache, |_prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_list_cache, |_prj, cmd| { cmd.args(["cache", "ls"]); cmd.assert_success(); }); -forgetest!(can_list_cache_all, |_prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_list_cache_all, |_prj, cmd| { cmd.args(["cache", "ls", "all"]); cmd.assert_success(); }); -forgetest!(can_list_specific_chain, |_prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_list_specific_chain, |_prj, cmd| { cmd.args(["cache", "ls", "mainnet"]); cmd.assert_success(); }); + +forgetest_init!(can_test_no_cache, |prj, cmd| { + prj.clear_cache(); + + cmd.args(["test", "--no-cache"]).assert_success(); + assert!(!prj.cache().exists(), "cache file should not exist"); + + cmd.forge_fuse().arg("test").assert_success(); + assert!(prj.cache().exists(), "cache file should exist"); +}); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 09a504ffe0db0..9d37c09977a92 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1,35 +1,81 @@ //! Contains various tests for checking forge's commands use crate::constants::*; -use ethers::{ - prelude::remappings::Remapping, - solc::{ - artifacts::{BytecodeHash, Metadata}, - ConfigurableContractArtifact, - }, +use foundry_compilers::artifacts::{remappings::Remapping, ConfigurableContractArtifact, Metadata}; +use foundry_config::{ + parse_with_profile, BasicConfig, Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, }; -use foundry_config::{parse_with_profile, BasicConfig, Chain, Config, SolidityErrorCode}; use foundry_test_utils::{ - ethers_solc::PathStyle, - forgetest, forgetest_init, - util::{pretty_err, read_string, OutputExt, TestCommand, TestProject}, + foundry_compilers::PathStyle, + rpc::next_mainnet_etherscan_api_key, + snapbox::IntoData, + util::{pretty_err, read_string, OutputExt, TestCommand}, }; use semver::Version; use std::{ - env, fs, - path::PathBuf, + fs, + path::Path, process::{Command, Stdio}, str::FromStr, }; // tests `--help` is printed to std out -forgetest!(print_help, |_: TestProject, mut cmd: TestCommand| { - cmd.arg("--help"); - cmd.assert_non_empty_stdout(); +forgetest!(print_help, |_prj, cmd| { + cmd.arg("--help").assert_success().stdout_eq(str![[r#" +Build, test, fuzz, debug and deploy Solidity contracts + +Usage: forge[..] + +Commands: +... + +Options: + -h, --help + Print help (see a summary with '-h') + + -j, --threads + Number of threads to use. Specifying 0 defaults to the number of logical cores + + [aliases: jobs] + + -V, --version + Print version + +Display options: + --color + The color of the log messages + + Possible values: + - auto: Intelligently guess whether to use color output (default) + - always: Force color output + - never: Force disable color output + + --json + Format log messages as JSON + + -q, --quiet + Do not print log messages + + -v, --verbosity... + Verbosity level of the log messages. + + Pass multiple times to increase the verbosity (e.g. -v, -vv, -vvv). + + Depending on the context the verbosity levels have different meanings. + + For example, the verbosity levels of the EVM are: + - 2 (-vv): Print logs for all tests. + - 3 (-vvv): Print execution traces for failing tests. + - 4 (-vvvv): Print execution traces for all tests, and setup traces for failing tests. + - 5 (-vvvvv): Print execution and setup traces for all tests, including storage changes. + +Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html + +"#]]); }); // checks that `clean` can be invoked even if out and cache don't exist -forgetest!(can_clean_non_existing, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_clean_non_existing, |prj, cmd| { cmd.arg("clean"); cmd.assert_empty_stdout(); prj.assert_cleaned(); @@ -39,8 +85,8 @@ forgetest!(can_clean_non_existing, |prj: TestProject, mut cmd: TestCommand| { forgetest!( #[ignore] can_cache_ls, - |_: TestProject, mut cmd: TestCommand| { - let chain = Chain::Named(ethers::prelude::Chain::Mainnet); + |_prj, cmd| { + let chain = Chain::mainnet(); let block1 = 100; let block2 = 101; @@ -55,10 +101,9 @@ forgetest!( fs::write(block2_file, "{}").unwrap(); fs::create_dir_all(etherscan_cache_dir).unwrap(); - cmd.args(["cache", "ls"]); - let output_string = String::from_utf8_lossy(&cmd.output().stdout).to_string(); - let output_lines = output_string.split('\n').collect::>(); - println!("{output_string}"); + let output = cmd.args(["cache", "ls"]).assert_success().get_output().stdout_lossy(); + let output_lines = output.split('\n').collect::>(); + println!("{output}"); assert_eq!(output_lines.len(), 6); assert!(output_lines[0].starts_with("-️ mainnet (")); @@ -77,7 +122,7 @@ forgetest!( forgetest!( #[ignore] can_cache_clean, - |_: TestProject, mut cmd: TestCommand| { + |_prj, cmd| { let cache_dir = Config::foundry_cache_dir().unwrap(); let path = cache_dir.as_path(); fs::create_dir_all(path).unwrap(); @@ -93,7 +138,7 @@ forgetest!( forgetest!( #[ignore] can_cache_clean_etherscan, - |_: TestProject, mut cmd: TestCommand| { + |_prj, cmd| { let cache_dir = Config::foundry_cache_dir().unwrap(); let etherscan_cache_dir = Config::foundry_etherscan_cache_dir().unwrap(); let path = cache_dir.as_path(); @@ -114,7 +159,7 @@ forgetest!( forgetest!( #[ignore] can_cache_clean_all_etherscan, - |_: TestProject, mut cmd: TestCommand| { + |_prj, cmd| { let rpc_cache_dir = Config::foundry_rpc_cache_dir().unwrap(); let etherscan_cache_dir = Config::foundry_etherscan_cache_dir().unwrap(); let rpc_path = rpc_cache_dir.as_path(); @@ -136,8 +181,8 @@ forgetest!( forgetest!( #[ignore] can_cache_clean_chain, - |_: TestProject, mut cmd: TestCommand| { - let chain = Chain::Named(ethers::prelude::Chain::Mainnet); + |_prj, cmd| { + let chain = Chain::mainnet(); let cache_dir = Config::foundry_chain_cache_dir(chain).unwrap(); let etherscan_cache_dir = Config::foundry_etherscan_chain_cache_dir(chain).unwrap(); let path = cache_dir.as_path(); @@ -159,8 +204,8 @@ forgetest!( forgetest!( #[ignore] can_cache_clean_blocks, - |_: TestProject, mut cmd: TestCommand| { - let chain = Chain::Named(ethers::prelude::Chain::Mainnet); + |_prj, cmd| { + let chain = Chain::mainnet(); let block1 = 100; let block2 = 101; let block3 = 102; @@ -193,13 +238,10 @@ forgetest!( forgetest!( #[ignore] can_cache_clean_chain_etherscan, - |_: TestProject, mut cmd: TestCommand| { - let cache_dir = - Config::foundry_chain_cache_dir(Chain::Named(ethers::prelude::Chain::Mainnet)).unwrap(); - let etherscan_cache_dir = Config::foundry_etherscan_chain_cache_dir(Chain::Named( - ethers::prelude::Chain::Mainnet, - )) - .unwrap(); + |_prj, cmd| { + let cache_dir = Config::foundry_chain_cache_dir(Chain::mainnet()).unwrap(); + let etherscan_cache_dir = + Config::foundry_etherscan_chain_cache_dir(Chain::mainnet()).unwrap(); let path = cache_dir.as_path(); let etherscan_path = etherscan_cache_dir.as_path(); fs::create_dir_all(path).unwrap(); @@ -215,19 +257,32 @@ forgetest!( ); // checks that init works -forgetest!(can_init_repo_with_config, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_repo_with_config, |prj, cmd| { let foundry_toml = prj.root().join(Config::FILE_NAME); assert!(!foundry_toml.exists()); - cmd.args(["init", "--force"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); let s = read_string(&foundry_toml); let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); // Checks that a forge project fails to initialise if dir is already git repo and dirty -forgetest!(can_detect_dirty_git_status_on_init, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_detect_dirty_git_status_on_init, |prj, cmd| { prj.wipe(); // initialize new git repo @@ -240,22 +295,31 @@ forgetest!(can_detect_dirty_git_status_on_init, |prj: TestProject, mut cmd: Test fs::create_dir_all(&nested).unwrap(); cmd.current_dir(&nested); - cmd.arg("init"); - cmd.unchecked_output().stderr_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_detect_dirty_git_status_on_init.stderr"), - ); + cmd.args(["init", "--commit"]).assert_failure().stderr_eq(str![[r#" +Error: The target directory is a part of or on its own an already initialized git repository, +and it requires clean working and staging areas, including no untracked files. + +Check the current git repository's status with `git status`. +Then, you can track files with `git add ...` and then commit them with `git commit`, +ignore them in the `.gitignore` file. + +"#]]); // ensure nothing was emitted, dir is empty assert!(!nested.read_dir().map(|mut i| i.next().is_some()).unwrap_or_default()); }); // Checks that a forge project can be initialized without creating a git repository -forgetest!(can_init_no_git, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_no_git, |prj, cmd| { prj.wipe(); - cmd.arg("init").arg(prj.root()).arg("--no-git"); - cmd.assert_non_empty_stdout(); + cmd.arg("init").arg(prj.root()).arg("--no-git").assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); prj.assert_config_exists(); assert!(!prj.root().join(".git").exists()); @@ -264,15 +328,14 @@ forgetest!(can_init_no_git, |prj: TestProject, mut cmd: TestCommand| { }); // Checks that quiet mode does not print anything -forgetest!(can_init_quiet, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_quiet, |prj, cmd| { prj.wipe(); - cmd.arg("init").arg(prj.root()).arg("-q"); - let _ = cmd.output(); + cmd.arg("init").arg(prj.root()).arg("-q").assert_empty_stdout(); }); // `forge init foobar` works with dir argument -forgetest!(can_init_with_dir, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_with_dir, |prj, cmd| { prj.create_file("README.md", "non-empty dir"); cmd.args(["init", "foobar"]); @@ -280,20 +343,80 @@ forgetest!(can_init_with_dir, |prj: TestProject, mut cmd: TestCommand| { assert!(prj.root().join("foobar").exists()); }); +// `forge init foobar --template [template]` works with dir argument +forgetest!(can_init_with_dir_and_template, |prj, cmd| { + cmd.args(["init", "foobar", "--template", "foundry-rs/forge-template"]) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..] from https://github.com/foundry-rs/forge-template... + Initialized forge project + +"#]]); + + assert!(prj.root().join("foobar/.git").exists()); + assert!(prj.root().join("foobar/foundry.toml").exists()); + assert!(prj.root().join("foobar/lib/forge-std").exists()); + // assert that gitmodules were correctly initialized + assert!(prj.root().join("foobar/.git/modules").exists()); + assert!(prj.root().join("foobar/src").exists()); + assert!(prj.root().join("foobar/test").exists()); +}); + +// `forge init foobar --template [template] --branch [branch]` works with dir argument +forgetest!(can_init_with_dir_and_template_and_branch, |prj, cmd| { + cmd.args([ + "init", + "foobar", + "--template", + "foundry-rs/forge-template", + "--branch", + "test/deployments", + ]) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..] from https://github.com/foundry-rs/forge-template... + Initialized forge project + +"#]]); + + assert!(prj.root().join("foobar/.dapprc").exists()); + assert!(prj.root().join("foobar/lib/ds-test").exists()); + // assert that gitmodules were correctly initialized + assert!(prj.root().join("foobar/.git/modules").exists()); + assert!(prj.root().join("foobar/src").exists()); + assert!(prj.root().join("foobar/scripts").exists()); +}); + // `forge init --force` works on non-empty dirs -forgetest!(can_init_non_empty, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_non_empty, |prj, cmd| { prj.create_file("README.md", "non-empty dir"); - cmd.arg("init").arg(prj.root()); - cmd.assert_err(); + cmd.arg("init").arg(prj.root()).assert_failure().stderr_eq(str![[r#" +Error: Cannot run `init` on a non-empty directory. +Run with the `--force` flag to initialize regardless. + +"#]]); + + cmd.arg("--force") + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); - cmd.arg("--force"); - cmd.assert_non_empty_stdout(); assert!(prj.root().join(".git").exists()); assert!(prj.root().join("lib/forge-std").exists()); }); // `forge init --force` works on already initialized git repository -forgetest!(can_init_in_empty_repo, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_in_empty_repo, |prj, cmd| { let root = prj.root(); // initialize new git repo @@ -307,16 +430,32 @@ forgetest!(can_init_in_empty_repo, |prj: TestProject, mut cmd: TestCommand| { assert!(status.success()); assert!(root.join(".git").exists()); - cmd.arg("init").arg(root); - cmd.assert_err(); + cmd.arg("init").arg(root).assert_failure().stderr_eq(str![[r#" +Error: Cannot run `init` on a non-empty directory. +Run with the `--force` flag to initialize regardless. + +"#]]); + + cmd.arg("--force") + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); - cmd.arg("--force"); - cmd.assert_non_empty_stdout(); assert!(root.join("lib/forge-std").exists()); }); // `forge init --force` works on already initialized git repository -forgetest!(can_init_in_non_empty_repo, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_in_non_empty_repo, |prj, cmd| { let root = prj.root(); // initialize new git repo @@ -333,11 +472,27 @@ forgetest!(can_init_in_non_empty_repo, |prj: TestProject, mut cmd: TestCommand| prj.create_file("README.md", "non-empty dir"); prj.create_file(".gitignore", "not foundry .gitignore"); - cmd.arg("init").arg(root); - cmd.assert_err(); + cmd.arg("init").arg(root).assert_failure().stderr_eq(str![[r#" +Error: Cannot run `init` on a non-empty directory. +Run with the `--force` flag to initialize regardless. + +"#]]); + + cmd.arg("--force") + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); - cmd.arg("--force"); - cmd.assert_non_empty_stdout(); assert!(root.join("lib/forge-std").exists()); // not overwritten @@ -347,19 +502,24 @@ forgetest!(can_init_in_non_empty_repo, |prj: TestProject, mut cmd: TestCommand| }); // Checks that remappings.txt and .vscode/settings.json is generated -forgetest!(can_init_vscode, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_vscode, |prj, cmd| { prj.wipe(); - cmd.arg("init").arg(prj.root()).arg("--vscode"); - cmd.assert_non_empty_stdout(); + cmd.arg("init").arg(prj.root()).arg("--vscode").assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); let settings = prj.root().join(".vscode/settings.json"); assert!(settings.is_file()); - let settings: serde_json::Value = ethers::solc::utils::read_json_file(&settings).unwrap(); + let settings: serde_json::Value = foundry_compilers::utils::read_json_file(&settings).unwrap(); assert_eq!( settings, serde_json::json!({ - "solidity.packageDefaultDependenciesContractsDirectory": "src", + "solidity.packageDefaultDependenciesContractsDirectory": "src", "solidity.packageDefaultDependenciesDirectory": "lib" }) ); @@ -367,30 +527,209 @@ forgetest!(can_init_vscode, |prj: TestProject, mut cmd: TestCommand| { let remappings = prj.root().join("remappings.txt"); assert!(remappings.is_file()); let content = std::fs::read_to_string(remappings).unwrap(); - assert_eq!(content, "ds-test/=lib/forge-std/lib/ds-test/src/\nforge-std/=lib/forge-std/src/",); + assert_eq!(content, "forge-std/=lib/forge-std/src/",); }); // checks that forge can init with template -forgetest!(can_init_template, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_init_template, |prj, cmd| { prj.wipe(); - cmd.args(["init", "--template", "foundry-rs/forge-template"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); + + cmd.args(["init", "--template", "foundry-rs/forge-template"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..] from https://github.com/foundry-rs/forge-template... + Initialized forge project + +"#]]); + assert!(prj.root().join(".git").exists()); assert!(prj.root().join("foundry.toml").exists()); assert!(prj.root().join("lib/forge-std").exists()); + // assert that gitmodules were correctly initialized + assert!(prj.root().join(".git/modules").exists()); assert!(prj.root().join("src").exists()); assert!(prj.root().join("test").exists()); }); +// checks that forge can init with template and branch +forgetest!(can_init_template_with_branch, |prj, cmd| { + prj.wipe(); + cmd.args(["init", "--template", "foundry-rs/forge-template", "--branch", "test/deployments"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..] from https://github.com/foundry-rs/forge-template... + Initialized forge project + +"#]]); + + assert!(prj.root().join(".git").exists()); + assert!(prj.root().join(".dapprc").exists()); + assert!(prj.root().join("lib/ds-test").exists()); + // assert that gitmodules were correctly initialized + assert!(prj.root().join(".git/modules").exists()); + assert!(prj.root().join("src").exists()); + assert!(prj.root().join("scripts").exists()); +}); + // checks that init fails when the provided template doesn't exist -forgetest!(fail_init_nonexistent_template, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(fail_init_nonexistent_template, |prj, cmd| { + prj.wipe(); + cmd.args(["init", "--template", "a"]).arg(prj.root()).assert_failure().stderr_eq(str![[r#" +remote: Not Found +fatal: repository 'https://github.com/a/' not found +Error: git fetch exited with code 128 + +"#]]); +}); + +// checks that `forge init --template [template] works by default i.e without committing +forgetest!(can_init_template_with_no_commit, |prj, cmd| { + prj.wipe(); + cmd.args(["init", "--template", "foundry-rs/forge-template"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..] from https://github.com/foundry-rs/forge-template... + Initialized forge project + +"#]]); + + // show the latest commit message was not changed + let output = Command::new("git") + .args(["log", "-1", "--pretty=%s"]) // Get the latest commit message + .output() + .expect("Failed to execute git command"); + + let commit_message = String::from_utf8_lossy(&output.stdout); + assert!( + !commit_message.starts_with("chore: init from foundry-rs/forge-template"), + "Commit message should not start with 'chore: init from foundry-rs/forge-template'" + ); +}); + +// checks that clone works +forgetest!(can_clone, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "0x044b75f554b886A065b9567891e45c79542d7357", + ]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Downloading the source code of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan... +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project +Collecting the creation information of 0x044b75f554b886A065b9567891e45c79542d7357 from Etherscan... +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + +// Checks that quiet mode does not print anything for clone +forgetest!(can_clone_quiet, |prj, cmd| { + prj.wipe(); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "--quiet", + "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", + ]) + .arg(prj.root()) + .assert_empty_stdout(); +}); + +// checks that clone works with --no-remappings-txt +forgetest!(can_clone_no_remappings_txt, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "--no-remappings-txt", + "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", + ]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Downloading the source code of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan... +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project +Collecting the creation information of 0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf from Etherscan... +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + +// checks that clone works with --keep-directory-structure +forgetest!(can_clone_keep_directory_structure, |prj, cmd| { prj.wipe(); - cmd.args(["init", "--template", "a"]).arg(prj.root()); - cmd.assert_non_empty_stderr(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + let output = cmd + .forge_fuse() + .args([ + "clone", + "--etherscan-api-key", + next_mainnet_etherscan_api_key().as_str(), + "--keep-directory-structure", + "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", + ]) + .arg(prj.root()) + .assert_success() + .get_output() + .stdout_lossy(); + + if output.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"); + return; + } + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); // checks that `clean` removes dapptools style paths -forgetest!(can_clean, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_clean, |prj, cmd| { prj.assert_create_dirs_exists(); prj.assert_style_paths_exist(PathStyle::Dapptools); cmd.arg("clean"); @@ -399,7 +738,7 @@ forgetest!(can_clean, |prj: TestProject, mut cmd: TestCommand| { }); // checks that `clean` removes hardhat style paths -forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj, cmd| { prj.assert_create_dirs_exists(); prj.assert_style_paths_exist(PathStyle::HardHat); cmd.arg("clean"); @@ -408,48 +747,99 @@ forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj: TestProject, mut cmd: Te }); // checks that `clean` also works with the "out" value set in Config -forgetest_init!(can_clean_config, |prj: TestProject, mut cmd: TestCommand| { - let config = Config { out: "custom-out".into(), ..Default::default() }; - prj.write_config(config); - cmd.arg("build"); - cmd.assert_non_empty_stdout(); +forgetest_init!(can_clean_config, |prj, cmd| { + prj.update_config(|config| config.out = "custom-out".into()); + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); // default test contract is written in custom out directory let artifact = prj.root().join(format!("custom-out/{TEMPLATE_TEST_CONTRACT_ARTIFACT_JSON}")); assert!(artifact.exists()); - cmd.forge_fuse().arg("clean"); - cmd.output(); + cmd.forge_fuse().arg("clean").assert_empty_stdout(); assert!(!artifact.exists()); }); +// checks that `clean` removes fuzz and invariant cache dirs +forgetest_init!(can_clean_test_cache, |prj, cmd| { + prj.update_config(|config| { + config.fuzz = FuzzConfig::new("cache/fuzz".into()); + config.invariant = InvariantConfig::new("cache/invariant".into()); + }); + // default test contract is written in custom out directory + let fuzz_cache_dir = prj.root().join("cache/fuzz"); + let _ = fs::create_dir(fuzz_cache_dir.clone()); + let invariant_cache_dir = prj.root().join("cache/invariant"); + let _ = fs::create_dir(invariant_cache_dir.clone()); + + assert!(fuzz_cache_dir.exists()); + assert!(invariant_cache_dir.exists()); + + cmd.forge_fuse().arg("clean").assert_empty_stdout(); + assert!(!fuzz_cache_dir.exists()); + assert!(!invariant_cache_dir.exists()); +}); + // checks that extra output works -forgetest_init!(can_emit_extra_output, |prj: TestProject, mut cmd: TestCommand| { - cmd.args(["build", "--extra-output", "metadata"]); - cmd.assert_non_empty_stdout(); +forgetest_init!(can_emit_extra_output, |prj, cmd| { + prj.clear(); + + cmd.args(["build", "--extra-output", "metadata"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); let artifact_path = prj.paths().artifacts.join(TEMPLATE_CONTRACT_ARTIFACT_JSON); let artifact: ConfigurableContractArtifact = - ethers::solc::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(); - cmd.assert_non_empty_stdout(); + cmd.forge_fuse() + .args(["build", "--extra-output-files", "metadata", "--force"]) + .root_arg() + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); let metadata_path = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.metadata.json")); - let _artifact: Metadata = ethers::solc::utils::read_json_file(metadata_path).unwrap(); + let _artifact: Metadata = foundry_compilers::utils::read_json_file(&metadata_path).unwrap(); }); // checks that extra output works -forgetest_init!(can_emit_multiple_extra_output, |prj: TestProject, mut cmd: TestCommand| { - cmd.args(["build", "--extra-output", "metadata", "ir-optimized", "--extra-output", "ir"]); - cmd.assert_non_empty_stdout(); +forgetest_init!(can_emit_multiple_extra_output, |prj, cmd| { + cmd.args([ + "build", + "--extra-output", + "metadata", + "legacyAssembly", + "ir-optimized", + "--extra-output", + "ir", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); let artifact_path = prj.paths().artifacts.join(TEMPLATE_CONTRACT_ARTIFACT_JSON); let artifact: ConfigurableContractArtifact = - ethers::solc::utils::read_json_file(artifact_path).unwrap(); + foundry_compilers::utils::read_json_file(&artifact_path).unwrap(); assert!(artifact.metadata.is_some()); + assert!(artifact.legacy_assembly.is_some()); assert!(artifact.ir.is_some()); assert!(artifact.ir_optimized.is_some()); @@ -460,14 +850,21 @@ forgetest_init!(can_emit_multiple_extra_output, |prj: TestProject, mut cmd: Test "metadata", "ir-optimized", "evm.bytecode.sourceMap", + "evm.legacyAssembly", "--force", ]) - .root_arg(); - cmd.assert_non_empty_stdout(); + .root_arg() + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); let metadata_path = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.metadata.json")); - let _artifact: Metadata = ethers::solc::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(); @@ -475,63 +872,58 @@ forgetest_init!(can_emit_multiple_extra_output, |prj: TestProject, mut cmd: Test let sourcemap = prj.paths().artifacts.join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.sourcemap")); std::fs::read_to_string(sourcemap).unwrap(); + + let legacy_assembly = prj + .paths() + .artifacts + .join(format!("{TEMPLATE_CONTRACT_ARTIFACT_BASE}.legacyAssembly.json")); + std::fs::read_to_string(legacy_assembly).unwrap(); }); -forgetest!(can_print_warnings, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity >0.8.9; +forgetest!(can_print_warnings, |prj, cmd| { + prj.add_source( + "Foo", + r" contract Greeter { function foo(uint256 a) public { uint256 x = 1; } } - "#, - ) - .unwrap(); - - // explicitly set to run with 0.8.10 - let config = Config { solc: Some("0.8.10".into()), ..Default::default() }; - prj.write_config(config); - - cmd.arg("build"); + ", + ) + .unwrap(); - let output = cmd.stdout_lossy(); - assert!(output.contains( - " + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] Compiler run successful with warnings: -Warning (5667): Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. -", - )); +Warning (5667): Unused function parameter. Remove or comment out the variable name to silence this warning. + [FILE]:5:18: + | +5 | function foo(uint256 a) public { + | ^^^^^^^^^ + +Warning (2072): Unused local variable. + [FILE]:6:9: + | +6 | uint256 x = 1; + | ^^^^^^^^^ + +Warning (2018): Function state mutability can be restricted to pure + [FILE]:5:5: + | +5 | function foo(uint256 a) public { + | ^ (Relevant source part starts here and spans across multiple lines). + + +"#]]); }); // Tests that direct import paths are handled correctly -// -// NOTE(onbjerg): Disabled for Windows -- for some reason solc fails with a bogus error message -// here: error[9553]: TypeError: Invalid type for argument in function call. Invalid implicit -// conversion from struct Bar memory to struct Bar memory requested. --> src\Foo.sol:12:22: -// | -// 12 | FooLib.check(b); -// | ^ -// -// -// -// error[9553]: TypeError: Invalid type for argument in function call. Invalid implicit conversion -// from contract Foo to contract Foo requested. --> src\Foo.sol:15:23: -// | -// 15 | FooLib.check2(this); -// | ^^^^ -#[cfg(not(target_os = "windows"))] -forgetest!(can_handle_direct_imports_into_src, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +forgetest!(can_handle_direct_imports_into_src, |prj, cmd| { + prj.add_source( + "Foo", + r#" import {FooLib} from "src/FooLib.sol"; struct Bar { uint8 x; @@ -547,46 +939,36 @@ contract Foo { } } "#, - ) - .unwrap(); + ) + .unwrap(); - prj.inner() - .add_source( - "FooLib", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "FooLib", + r#" import {Foo, Bar} from "src/Foo.sol"; library FooLib { function check(Bar memory b) public {} function check2(Foo f) public {} } "#, - ) - .unwrap(); - - cmd.arg("build"); + ) + .unwrap(); - assert!(cmd.stdout_lossy().ends_with( - " + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] Compiler run successful! -" - )); + +"#]]); }); // tests that the `inspect` command works correctly -forgetest!(can_execute_inspect_command, |prj: TestProject, mut cmd: TestCommand| { - // explicitly set to include the ipfs bytecode hash - let config = Config { bytecode_hash: BytecodeHash::Ipfs, ..Default::default() }; - prj.write_config(config); +forgetest!(can_execute_inspect_command, |prj, cmd| { let contract_name = "Foo"; let path = prj - .inner() .add_source( contract_name, r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; contract Foo { event log_string(string); function run() external { @@ -597,38 +979,27 @@ contract Foo { ) .unwrap(); - // Remove the ipfs hash from the metadata - let mut dynamic_bytecode = "0x608060405234801561001057600080fd5b5060c08061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063c040622614602d575b600080fd5b60336035565b005b7f0b2e13ff20ac7b474198655583edf70dedd2c1dc980e329c4fbb2fc0748b796b6040516080906020808252600a908201526939b1b934b83a103930b760b11b604082015260600190565b60405180910390a156fea264697066735822122065c066d19101ad1707272b9a884891af8ab0cf5a0e0bba70c4650594492c14be64736f6c634300080a0033\n".to_string(); - let ipfs_start = dynamic_bytecode.len() - (24 + 64); - let ipfs_end = ipfs_start + 65; - dynamic_bytecode.replace_range(ipfs_start..ipfs_end, ""); - - let check_output = |mut output: String| { - output.replace_range(ipfs_start..ipfs_end, ""); - assert_eq!(dynamic_bytecode, output); - }; + cmd.arg("inspect").arg(contract_name).arg("bytecode").assert_success().stdout_eq(str![[r#" +0x60806040[..] - cmd.arg("inspect").arg(contract_name).arg("bytecode"); - check_output(cmd.stdout_lossy()); +"#]]); let info = format!("src/{}:{}", path.file_name().unwrap().to_string_lossy(), contract_name); - cmd.forge_fuse().arg("inspect").arg(info).arg("bytecode"); - check_output(cmd.stdout_lossy()); + cmd.forge_fuse().arg("inspect").arg(info).arg("bytecode").assert_success().stdout_eq(str![[ + r#" +0x60806040[..] + +"# + ]]); }); // test that `forge snapshot` commands work -forgetest!( - #[serial_test::serial] - can_check_snapshot, - |prj: TestProject, mut cmd: TestCommand| { - prj.insert_ds_test(); - - prj.inner() - .add_source( - "ATest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +forgetest!(can_check_snapshot, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "ATest.t.sol", + r#" import "./test.sol"; contract ATest is DSTest { function testExample() public { @@ -636,141 +1007,180 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("snapshot"); + cmd.args(["snapshot"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_check_snapshot.stdout"), - ); +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testExample() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - cmd.arg("--check"); - let _ = cmd.output(); - } -); +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) -// test that `forge build` does not print `(with warnings)` if there arent any -forgetest!(can_compile_without_warnings, |prj: TestProject, mut cmd: TestCommand| { - let config = Config { - ignored_error_codes: vec![SolidityErrorCode::SpdxLicenseNotProvided], - ..Default::default() - }; - prj.write_config(config); - prj.inner() - .add_source( - "A", - r#" -pragma solidity 0.8.10; +"#]]); + + cmd.arg("--check").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testExample() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// test that `forge build` does not print `(with warnings)` if file path is ignored +forgetest!(can_compile_without_warnings_ignored_file_paths, |prj, cmd| { + // Ignoring path and setting empty error_codes as default would set would set some error codes + prj.update_config(|config| { + config.ignored_file_paths = vec![Path::new("src").to_path_buf()]; + config.ignored_error_codes = vec![]; + }); + + prj.add_raw_source( + "src/example.sol", + r" +pragma solidity *; contract A { function testExample() public {} } - "#, - ) - .unwrap(); +", + ) + .unwrap(); + + cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + // Reconfigure without ignored paths or error codes and check for warnings + prj.update_config(|config| config.ignored_file_paths = vec![]); + + cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +[FILE] + + +"#]]); +}); + +// test that `forge build` does not print `(with warnings)` if there aren't any +forgetest!(can_compile_without_warnings, |prj, cmd| { + prj.update_config(|config| { + config.ignored_error_codes = vec![SolidityErrorCode::SpdxLicenseNotProvided]; + }); + prj.add_raw_source( + "A", + r" +pragma solidity *; +contract A { + function testExample() public {} +} + ", + ) + .unwrap(); + + cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - cmd.args(["build", "--force"]); - let out = cmd.stdout(); - // no warnings - assert!(out.trim().contains("Compiler run successful!")); - assert!(!out.trim().contains("Compiler run successful with warnings:")); +"#]]); // don't ignore errors - let config = Config { ignored_error_codes: vec![], ..Default::default() }; - prj.write_config(config); - let out = cmd.stdout(); + prj.update_config(|config| { + config.ignored_error_codes = vec![]; + }); - assert!(out.trim().contains("Compiler run successful with warnings:")); - assert!( - out.contains( - r#"Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information."# - ) - ); + cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +[FILE] + + +"#]]); }); // test that `forge build` compiles when severity set to error, fails when set to warning, and // handles ignored error codes as an exception -forgetest!(can_fail_compile_with_warnings, |prj: TestProject, mut cmd: TestCommand| { - let config = Config { ignored_error_codes: vec![], deny_warnings: false, ..Default::default() }; - prj.write_config(config); - prj.inner() - .add_source( - "A", - r#" -pragma solidity 0.8.10; +forgetest!(can_fail_compile_with_warnings, |prj, cmd| { + prj.update_config(|config| { + config.ignored_error_codes = vec![]; + config.deny_warnings = false; + }); + prj.add_raw_source( + "A", + r" +pragma solidity *; contract A { function testExample() public {} } - "#, - ) - .unwrap(); + ", + ) + .unwrap(); - cmd.args(["build", "--force"]); - let out = cmd.stdout(); // there are no errors - assert!(out.trim().contains("Compiler run successful")); - assert!(out.trim().contains("Compiler run successful with warnings:")); + cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +[FILE] + + +"#]]); // warning fails to compile - let config = Config { ignored_error_codes: vec![], deny_warnings: true, ..Default::default() }; - prj.write_config(config); - cmd.assert_err(); + prj.update_config(|config| { + config.ignored_error_codes = vec![]; + config.deny_warnings = true; + }); + + cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#" +Error: Compiler run failed: +Warning (1878): SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: " to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. +[FILE] + +"#]]); // ignores error code and compiles - let config = Config { - ignored_error_codes: vec![SolidityErrorCode::SpdxLicenseNotProvided], - deny_warnings: true, - ..Default::default() - }; - prj.write_config(config); - let out = cmd.stdout(); + prj.update_config(|config| { + config.ignored_error_codes = vec![SolidityErrorCode::SpdxLicenseNotProvided]; + config.deny_warnings = true; + }); - assert!(out.trim().contains("Compiler run successful!")); - assert!(!out.trim().contains("Compiler run successful with warnings:")); -}); + cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! -// test against a local checkout, useful to debug with local ethers-rs patch -forgetest!( - #[ignore] - can_compile_local_spells, - |_: TestProject, mut cmd: TestCommand| { - let current_dir = std::env::current_dir().unwrap(); - let root = current_dir - .join("../../foundry-integration-tests/testdata/spells-mainnet") - .to_string_lossy() - .to_string(); - println!("project root: \"{root}\""); - - let eth_rpc_url = foundry_utils::rpc::next_http_archive_rpc_endpoint(); - let dss_exec_lib = "src/DssSpell.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4"; - - cmd.args([ - "test", - "--root", - root.as_str(), - "--fork-url", - eth_rpc_url.as_str(), - "--fork-block-number", - "14435000", - "--libraries", - dss_exec_lib, - "-vvv", - ]); - cmd.print_output(); - } -); +"#]]); +}); // test that a failing `forge build` does not impact followup builds -forgetest!(can_build_after_failure, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_build_after_failure, |prj, cmd| { prj.insert_ds_test(); - prj.inner() - .add_source( - "ATest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "ATest.t.sol", + r#" import "./test.sol"; contract ATest is DSTest { function testExample() public { @@ -778,14 +1188,11 @@ contract ATest is DSTest { } } "#, - ) - .unwrap(); - prj.inner() - .add_source( - "BTest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + ) + .unwrap(); + prj.add_source( + "BTest.t.sol", + r#" import "./test.sol"; contract BTest is DSTest { function testExample() public { @@ -793,17 +1200,21 @@ contract BTest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +... +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); - cmd.arg("build"); - cmd.assert_non_empty_stdout(); prj.assert_cache_exists(); prj.assert_artifacts_dir_exists(); let syntax_err = r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; import "./test.sol"; contract CTest is DSTest { function testExample() public { @@ -813,27 +1224,38 @@ contract CTest is DSTest { "#; // introduce contract with syntax error - prj.inner().add_source("CTest.t.sol", syntax_err).unwrap(); + prj.add_source("CTest.t.sol", syntax_err).unwrap(); // `forge build --force` which should fail - cmd.arg("--force"); - cmd.assert_err(); + cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#" +Error: Compiler run failed: +Error (2314): Expected ';' but got identifier + [FILE]:7:19: + | +7 | THIS WILL CAUSE AN ERROR + | ^^^^^ + +"#]]); // but ensure this cleaned cache and artifacts assert!(!prj.paths().artifacts.exists()); - assert!(!prj.cache_path().exists()); + assert!(!prj.cache().exists()); // still errors - cmd.forge_fuse().arg("build"); - cmd.assert_err(); + cmd.forge_fuse().args(["build", "--force"]).assert_failure().stderr_eq(str![[r#" +Error: Compiler run failed: +Error (2314): Expected ';' but got identifier + [FILE]:7:19: + | +7 | THIS WILL CAUSE AN ERROR + | ^^^^^ + +"#]]); // resolve the error by replacing the file - prj.inner() - .add_source( - "CTest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "CTest.t.sol", + r#" import "./test.sol"; contract CTest is DSTest { function testExample() public { @@ -841,27 +1263,41 @@ contract CTest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.forge_fuse().args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); - cmd.assert_non_empty_stdout(); prj.assert_cache_exists(); prj.assert_artifacts_dir_exists(); // ensure cache is unchanged after error - let cache = fs::read_to_string(prj.cache_path()).unwrap(); + let cache = fs::read_to_string(prj.cache()).unwrap(); // introduce the error again but building without force - prj.inner().add_source("CTest.t.sol", syntax_err).unwrap(); - cmd.assert_err(); + prj.add_source("CTest.t.sol", syntax_err).unwrap(); + cmd.forge_fuse().arg("build").assert_failure().stderr_eq(str![[r#" +Error: Compiler run failed: +Error (2314): Expected ';' but got identifier + [FILE]:7:19: + | +7 | THIS WILL CAUSE AN ERROR + | ^^^^^ + +"#]]); // ensure unchanged cache file - let cache_after = fs::read_to_string(prj.cache_path()).unwrap(); + let cache_after = fs::read_to_string(prj.cache()).unwrap(); assert_eq!(cache, cache_after); }); // test to check that install/remove works properly -forgetest!(can_install_and_remove, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_install_and_remove, |prj, cmd| { cmd.git_init(); let libs = prj.root().join("lib"); @@ -872,8 +1308,14 @@ forgetest!(can_install_and_remove, |prj: TestProject, mut cmd: TestCommand| { let forge_std_mod = git_mod.join("forge-std"); let install = |cmd: &mut TestCommand| { - cmd.forge_fuse().args(["install", "foundry-rs/forge-std", "--no-commit"]); - cmd.assert_non_empty_stdout(); + cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq( + str![[r#" +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + +"#]], + ); + assert!(forge_std.exists()); assert!(forge_std_mod.exists()); @@ -882,8 +1324,14 @@ forgetest!(can_install_and_remove, |prj: TestProject, mut cmd: TestCommand| { }; let remove = |cmd: &mut TestCommand, target: &str| { - cmd.forge_fuse().args(["remove", "--force", target]); - cmd.assert_non_empty_stdout(); + // TODO: flaky behavior with URL, sometimes it is None, sometimes it is Some("https://github.com/lib/forge-std") + cmd.forge_fuse().args(["remove", "--force", target]).assert_success().stdout_eq(str![[ + r#" +Removing 'forge-std' in [..], (url: [..], tag: None) + +"# + ]]); + assert!(!forge_std.exists()); assert!(!forge_std_mod.exists()); let submods = read_string(&git_mod_file); @@ -898,8 +1346,25 @@ forgetest!(can_install_and_remove, |prj: TestProject, mut cmd: TestCommand| { remove(&mut cmd, "lib/forge-std"); }); +// test to check we can run `forge install` in an empty dir +forgetest!(can_install_empty, |prj, cmd| { + // create + cmd.git_init(); + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); + + // create initial commit + fs::write(prj.root().join("README.md"), "Initial commit").unwrap(); + + cmd.git_add(); + cmd.git_commit("Initial commit"); + + cmd.forge_fuse().args(["install"]); + cmd.assert_empty_stdout(); +}); + // test to check that package can be reinstalled after manually removing the directory -forgetest!(can_reinstall_after_manual_remove, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_reinstall_after_manual_remove, |prj, cmd| { cmd.git_init(); let libs = prj.root().join("lib"); @@ -910,8 +1375,14 @@ forgetest!(can_reinstall_after_manual_remove, |prj: TestProject, mut cmd: TestCo let forge_std_mod = git_mod.join("forge-std"); let install = |cmd: &mut TestCommand| { - cmd.forge_fuse().args(["install", "foundry-rs/forge-std", "--no-commit"]); - cmd.assert_non_empty_stdout(); + cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq( + str![[r#" +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + +"#]], + ); + assert!(forge_std.exists()); assert!(forge_std_mod.exists()); @@ -927,7 +1398,7 @@ forgetest!(can_reinstall_after_manual_remove, |prj: TestProject, mut cmd: TestCo }); // test that we can repeatedly install the same dependency without changes -forgetest!(can_install_repeatedly, |_prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_install_repeatedly, |_prj, cmd| { cmd.git_init(); cmd.forge_fuse().args(["install", "foundry-rs/forge-std"]); @@ -938,7 +1409,7 @@ forgetest!(can_install_repeatedly, |_prj: TestProject, mut cmd: TestCommand| { // test that by default we install the latest semver release tag // -forgetest!(can_install_latest_release_tag, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_install_latest_release_tag, |prj, cmd| { cmd.git_init(); cmd.forge_fuse().args(["install", "openzeppelin/openzeppelin-contracts"]); cmd.assert_success(); @@ -958,60 +1429,78 @@ forgetest!(can_install_latest_release_tag, |prj: TestProject, mut cmd: TestComma // Tests that forge update doesn't break a working dependency by recursively updating nested // dependencies forgetest!( - #[ignore] + #[cfg_attr(windows, ignore = "weird git fail")] can_update_library_with_outdated_nested_dependency, - |prj: TestProject, mut cmd: TestCommand| { + |prj, cmd| { cmd.git_init(); let libs = prj.root().join("lib"); let git_mod = prj.root().join(".git/modules/lib"); let git_mod_file = prj.root().join(".gitmodules"); - let package = libs.join("issue-2264-repro"); - let package_mod = git_mod.join("issue-2264-repro"); - - let install = |cmd: &mut TestCommand| { - cmd.forge_fuse().args(["install", "foundry-rs/issue-2264-repro", "--no-commit"]); - cmd.assert_non_empty_stdout(); - assert!(package.exists()); - assert!(package_mod.exists()); - - let submods = read_string(&git_mod_file); - assert!(submods.contains("https://github.com/foundry-rs/issue-2264-repro")); - }; - - install(&mut cmd); - cmd.forge_fuse().args(["update", "lib/issue-2264-repro"]); - cmd.stdout_lossy(); - - prj.inner() - .add_source( - "MyTokenCopy", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.6.0; -import "issue-2264-repro/MyToken.sol"; -contract MyTokenCopy is MyToken { + // get paths to check inside install fn + let package = libs.join("forge-5980-test"); + let package_mod = git_mod.join("forge-5980-test"); + + // install main dependency + cmd.forge_fuse() + .args(["install", "evalir/forge-5980-test"]) + .assert_success() + .stdout_eq(str![[r#" +Installing forge-5980-test in [..] (url: Some("https://github.com/evalir/forge-5980-test"), tag: None) + Installed forge-5980-test + +"#]]); + + // assert paths exist + assert!(package.exists()); + assert!(package_mod.exists()); + + let submods = read_string(git_mod_file); + assert!(submods.contains("https://github.com/evalir/forge-5980-test")); + + // try to update the top-level dependency; there should be no update for this dependency, + // but its sub-dependency has upstream (breaking) changes; forge should not attempt to + // update the sub-dependency + cmd.forge_fuse().args(["update", "lib/forge-5980-test"]).assert_empty_stdout(); + + // add explicit remappings for test file + prj.update_config(|config| { + config.remappings = vec![ + Remapping::from_str("forge-5980-test/=lib/forge-5980-test/src/").unwrap().into(), + // explicit remapping for sub-dependendy seems necessary for some reason + Remapping::from_str( + "forge-5980-test-dep/=lib/forge-5980-test/lib/forge-5980-test-dep/src/", + ) + .unwrap() + .into(), + ]; + }); + + // create test file that uses the top-level dependency; if the sub-dependency is updated, + // compilation will fail + prj.add_source( + "CounterCopy", + r#" +import "forge-5980-test/Counter.sol"; +contract CounterCopy is Counter { } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.forge_fuse().args(["build"]); - let output = cmd.stdout_lossy(); + // build and check output + cmd.forge_fuse().arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - assert!(output.contains("Compiler run successful",)); +"#]]); } ); -forgetest!(gas_report_all_contracts, |prj: TestProject, mut cmd: TestCommand| { - prj.insert_ds_test(); - prj.inner() - .add_source( - "Contracts.sol", - r#" +const GAS_REPORT_CONTRACTS: &str = r#" //SPDX-license-identifier: MIT -pragma solidity ^0.8.0; import "./test.sol"; @@ -1093,525 +1582,2177 @@ contract ContractThreeTest is DSTest { c3.baz(); } } - "#, - ) - .unwrap(); +"#; + +forgetest!(gas_report_all_contracts, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); // report for all - prj.write_config(Config { - gas_reports: (vec!["*".to_string()]), - gas_reports_ignore: (vec![]), - ..Default::default() + prj.update_config(|config| { + config.gas_reports = vec!["*".to_string()]; + config.gas_reports_ignore = vec![]; }); - cmd.forge_fuse(); - let first_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(first_out.contains("foo") && first_out.contains("bar") && first_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); - cmd.forge_fuse(); - prj.write_config(Config { gas_reports: (vec![]), ..Default::default() }); - cmd.forge_fuse(); - let second_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(second_out.contains("foo") && second_out.contains("bar") && second_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); - - cmd.forge_fuse(); - prj.write_config(Config { gas_reports: (vec!["*".to_string()]), ..Default::default() }); - cmd.forge_fuse(); - let third_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } + } + }, + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } + } + }, + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } + } + } +] +"#]] + .is_json(), + ); - cmd.forge_fuse(); - prj.write_config(Config { - gas_reports: (vec![ - "ContractOne".to_string(), - "ContractTwo".to_string(), - "ContractThree".to_string(), - ]), - ..Default::default() - }); - cmd.forge_fuse(); - let fourth_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(fourth_out.contains("foo") && fourth_out.contains("bar") && fourth_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); -}); - -forgetest!(gas_report_some_contracts, |prj: TestProject, mut cmd: TestCommand| { - prj.insert_ds_test(); - prj.inner() - .add_source( - "Contracts.sol", - r#" -//SPDX-license-identifier: MIT -pragma solidity ^0.8.0; - -import "./test.sol"; - -contract ContractOne { - int public i; - - constructor() { - i = 0; + prj.update_config(|config| config.gas_reports = vec![]); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } } - - function foo() public{ - while(i<5){ - i++; - } + }, + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } } -} - -contract ContractOneTest is DSTest { - ContractOne c1; - - function setUp() public { - c1 = new ContractOne(); + }, + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } } + } +] +"#]] + .is_json(), + ); - function testFoo() public { - c1.foo(); + prj.update_config(|config| config.gas_reports = vec!["*".to_string()]); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } } -} - - -contract ContractTwo { - int public i; - - constructor() { - i = 0; + }, + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } } - - function bar() public{ - while(i<50){ - i++; - } + }, + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } } -} - -contract ContractTwoTest is DSTest { - ContractTwo c2; + } +] +"#]] + .is_json(), + ); - function setUp() public { - c2 = new ContractTwo(); + prj.update_config(|config| { + config.gas_reports = + vec!["ContractOne".to_string(), "ContractTwo".to_string(), "ContractThree".to_string()]; + }); + cmd.forge_fuse().arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } } - - function testBar() public { - c2.bar(); + }, + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } } -} + }, + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } + } + } +] +"#]] + .is_json(), + ); +}); -contract ContractThree { - int public i; +forgetest!(gas_report_some_contracts, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); - constructor() { - i = 0; + // report for One + prj.update_config(|config| config.gas_reports = vec!["ContractOne".to_string()]); + cmd.forge_fuse(); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } } + } +] +"#]] + .is_json(), + ); - function baz() public{ - while(i<500){ - i++; - } + // report for Two + prj.update_config(|config| config.gas_reports = vec!["ContractTwo".to_string()]); + cmd.forge_fuse(); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } } -} - -contract ContractThreeTest is DSTest { - ContractThree c3; + } +] +"#]] + .is_json(), + ); - function setUp() public { - c3 = new ContractThree(); + // report for Three + prj.update_config(|config| config.gas_reports = vec!["ContractThree".to_string()]); + cmd.forge_fuse(); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } } + } +] +"#]] + .is_json(), + ); +}); - function testBaz() public { - c3.baz(); - } -} - "#, - ) - .unwrap(); +forgetest!(gas_report_ignore_some_contracts, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("Contracts.sol", GAS_REPORT_CONTRACTS).unwrap(); - // report for One - prj.write_config(Config { - gas_reports: (vec!["ContractOne".to_string()]), - gas_reports_ignore: (vec![]), - ..Default::default() + // ignore ContractOne + prj.update_config(|config| { + config.gas_reports = vec!["*".to_string()]; + config.gas_reports_ignore = vec!["ContractOne".to_string()]; }); cmd.forge_fuse(); - let first_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(first_out.contains("foo") && !first_out.contains("bar") && !first_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } + } + }, + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } + } + } +] +"#]] + .is_json(), + ); - // report for Two + // ignore ContractTwo cmd.forge_fuse(); - prj.write_config(Config { - gas_reports: (vec!["ContractTwo".to_string()]), - ..Default::default() + prj.update_config(|config| { + config.gas_reports = vec![]; + config.gas_reports_ignore = vec!["ContractTwo".to_string()]; }); cmd.forge_fuse(); - let second_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!( - !second_out.contains("foo") && second_out.contains("bar") && !second_out.contains("baz") + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } + } + }, + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } + } + } +] +"#]] + .is_json(), ); - // cmd.arg("test").arg("--gas-report").print_output(); - // report for Three + // If the user listed the contract in 'gas_reports' (the foundry.toml field) a + // report for the contract is generated even if it's listed in the ignore + // list. This is addressed this way because getting a report you don't expect is + // preferable than not getting one you expect. A warning is printed to stderr + // indicating the "double listing". cmd.forge_fuse(); - prj.write_config(Config { - gas_reports: (vec!["ContractThree".to_string()]), - ..Default::default() + prj.update_config(|config| { + config.gas_reports = + vec!["ContractOne".to_string(), "ContractTwo".to_string(), "ContractThree".to_string()]; + config.gas_reports_ignore = vec!["ContractThree".to_string()]; }); cmd.forge_fuse(); - let third_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(!third_out.contains("foo") && !third_out.contains("bar") && third_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); + cmd.arg("test") + .arg("--gas-report") + .assert_success() + .stdout_eq(str![[r#" +... +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractOne Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| foo | 45656 | 45656 | 45656 | 45656 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+--------+--------+--------+---------╮ +| src/Contracts.sol:ContractThree Contract | | | | | | ++=================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| 133243 | 395 | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+--------+--------+--------+---------| +| baz | 287711 | 287711 | 287711 | 287711 | 1 | +╰------------------------------------------+-----------------+--------+--------+--------+---------╯ + +╭----------------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Contracts.sol:ContractTwo Contract | | | | | | ++=============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| 133027 | 394 | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------+-----------------+-------+--------+-------+---------| +| bar | 67683 | 67683 | 67683 | 67683 | 1 | +╰----------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 3 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]) + .stderr_eq(str![[r#" +... +Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. +... +"#]]); + cmd.forge_fuse() + .arg("test") + .arg("--gas-report") + .arg("--json") + .assert_success() + .stdout_eq( + str![[r#" +[ + { + "contract": "src/Contracts.sol:ContractOne", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "foo()": { + "calls": 1, + "min": 45656, + "mean": 45656, + "median": 45656, + "max": 45656 + } + } + }, + { + "contract": "src/Contracts.sol:ContractThree", + "deployment": { + "gas": 133243, + "size": 395 + }, + "functions": { + "baz()": { + "calls": 1, + "min": 287711, + "mean": 287711, + "median": 287711, + "max": 287711 + } + } + }, + { + "contract": "src/Contracts.sol:ContractTwo", + "deployment": { + "gas": 133027, + "size": 394 + }, + "functions": { + "bar()": { + "calls": 1, + "min": 67683, + "mean": 67683, + "median": 67683, + "max": 67683 + } + } + } +] +"#]] + .is_json(), + ) + .stderr_eq(str![[r#" +... +Warning: ContractThree is listed in both 'gas_reports' and 'gas_reports_ignore'. +... +"#]]); }); -forgetest!(gas_ignore_some_contracts, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(gas_report_flatten_multiple_selectors, |prj, cmd| { prj.insert_ds_test(); - prj.inner() - .add_source( - "Contracts.sol", - r#" -//SPDX-license-identifier: MIT -pragma solidity ^0.8.0; + prj.add_source( + "Counter.sol", + r#" +contract Counter { + uint256 public a; + int256 public b; + + function setNumber(uint256 x) public { + a = x; + } + + function setNumber(int256 x) public { + b = x; + } +} +"#, + ) + .unwrap(); + prj.add_source( + "CounterTest.t.sol", + r#" import "./test.sol"; +import {Counter} from "./Counter.sol"; -contract ContractOne { - int public i; +contract CounterTest is DSTest { + Counter public counter; - constructor() { - i = 0; + function setUp() public { + counter = new Counter(); + counter.setNumber(uint256(0)); + counter.setNumber(int256(0)); } - function foo() public{ - while(i<5){ - i++; - } + function test_Increment() public { + counter.setNumber(uint256(counter.a() + 1)); + counter.setNumber(int256(counter.b() + 1)); } } +"#, + ) + .unwrap(); + + cmd.arg("test").arg("--gas-report").assert_success().stdout_eq(str![[r#" +... +╭----------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Counter.sol:Counter Contract | | | | | | ++=======================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| 172107 | 578 | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------+-----------------+-------+--------+-------+---------| +| a | 2402 | 2402 | 2402 | 2402 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| +| b | 2447 | 2447 | 2447 | 2447 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| +| setNumber(int256) | 23851 | 33807 | 33807 | 43763 | 2 | +|----------------------------------+-----------------+-------+--------+-------+---------| +| setNumber(uint256) | 23806 | 33762 | 33762 | 43718 | 2 | +╰----------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + cmd.forge_fuse().arg("test").arg("--gas-report").arg("--json").assert_success().stdout_eq( + str![[r#" +[ + { + "contract": "src/Counter.sol:Counter", + "deployment": { + "gas": 172107, + "size": 578 + }, + "functions": { + "a()": { + "calls": 1, + "min": 2402, + "mean": 2402, + "median": 2402, + "max": 2402 + }, + "b()": { + "calls": 1, + "min": 2447, + "mean": 2447, + "median": 2447, + "max": 2447 + }, + "setNumber(int256)": { + "calls": 2, + "min": 23851, + "mean": 33807, + "median": 33807, + "max": 43763 + }, + "setNumber(uint256)": { + "calls": 2, + "min": 23806, + "mean": 33762, + "median": 33762, + "max": 43718 + } + } + } +] +"#]] + .is_json(), + ); +}); -contract ContractOneTest is DSTest { - ContractOne c1; +// +forgetest_init!(gas_report_with_fallback, |prj, cmd| { + prj.add_test( + "DelegateProxyTest.sol", + r#" +import {Test} from "forge-std/Test.sol"; - function setUp() public { - c1 = new ContractOne(); +contract ProxiedContract { + uint256 public amount; + + function deposit(uint256 aba) external { + amount = amount * 2; } - function testFoo() public { - c1.foo(); + function deposit() external { } } +contract DelegateProxy { + address internal implementation; -contract ContractTwo { - int public i; + constructor(address counter) { + implementation = counter; + } - constructor() { - i = 0; + function deposit() external { } - function bar() public{ - while(i<50){ - i++; + fallback() external payable { + address addr = implementation; + + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), addr, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } } } } -contract ContractTwoTest is DSTest { - ContractTwo c2; - - function setUp() public { - c2 = new ContractTwo(); +contract GasReportFallbackTest is Test { + function test_fallback_gas_report() public { + ProxiedContract proxied = ProxiedContract(address(new DelegateProxy(address(new ProxiedContract())))); + proxied.deposit(100); + proxied.deposit(); } +} +"#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_fallback_gas_report", "-vvvv", "--gas-report"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭---------------------------------------------------+-----------------+-------+--------+-------+---------╮ +| test/DelegateProxyTest.sol:DelegateProxy Contract | | | | | | ++========================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| 117171 | 471 | | | | | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| deposit | 21185 | 21185 | 21185 | 21185 | 1 | +|---------------------------------------------------+-----------------+-------+--------+-------+---------| +| fallback | 29758 | 29758 | 29758 | 29758 | 1 | +╰---------------------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭-----------------------------------------------------+-----------------+------+--------+------+---------╮ +| test/DelegateProxyTest.sol:ProxiedContract Contract | | | | | | ++========================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|-----------------------------------------------------+-----------------+------+--------+------+---------| +| 153531 | 494 | | | | | +|-----------------------------------------------------+-----------------+------+--------+------+---------| +| | | | | | | +|-----------------------------------------------------+-----------------+------+--------+------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-----------------------------------------------------+-----------------+------+--------+------+---------| +| deposit | 3661 | 3661 | 3661 | 3661 | 1 | +╰-----------------------------------------------------+-----------------+------+--------+------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); - function testBar() public { - c2.bar(); + cmd.forge_fuse() + .args(["test", "--mt", "test_fallback_gas_report", "--gas-report", "--json"]) + .assert_success() + .stdout_eq( + str![[r#" +[ + { + "contract": "test/DelegateProxyTest.sol:DelegateProxy", + "deployment": { + "gas": 117171, + "size": 471 + }, + "functions": { + "deposit()": { + "calls": 1, + "min": 21185, + "mean": 21185, + "median": 21185, + "max": 21185 + }, + "fallback()": { + "calls": 1, + "min": 29758, + "mean": 29758, + "median": 29758, + "max": 29758 + } } -} + }, + { + "contract": "test/DelegateProxyTest.sol:ProxiedContract", + "deployment": { + "gas": 153531, + "size": 494 + }, + "functions": { + "deposit(uint256)": { + "calls": 1, + "min": 3661, + "mean": 3661, + "median": 3661, + "max": 3661 + } + } + } +] +"#]] + .is_json(), + ); +}); -contract ContractThree { - int public i; +// +forgetest_init!(gas_report_fallback_with_calldata, |prj, cmd| { + prj.add_test( + "FallbackWithCalldataTest.sol", + r#" +import {Test} from "forge-std/Test.sol"; - constructor() { - i = 0; +contract CounterWithFallback { + uint256 public number; + + function increment() public { + number++; } - function baz() public{ - while(i<500){ - i++; - } + fallback() external { + number++; } } -contract ContractThreeTest is DSTest { - ContractThree c3; +contract CounterWithFallbackTest is Test { + CounterWithFallback public counter; function setUp() public { - c3 = new ContractThree(); + counter = new CounterWithFallback(); } - function testBaz() public { - c3.baz(); + function test_fallback_with_calldata() public { + (bool success,) = address(counter).call("hello"); + require(success); } } - "#, - ) - .unwrap(); +"#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_fallback_with_calldata", "-vvvv", "--gas-report"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - // ignore ContractOne - prj.write_config(Config { - gas_reports: (vec!["*".to_string()]), - gas_reports_ignore: (vec!["ContractOne".to_string()]), - ..Default::default() - }); - cmd.forge_fuse(); - let first_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(!first_out.contains("foo") && first_out.contains("bar") && first_out.contains("baz")); - // cmd.arg("test").arg("--gas-report").print_output(); +Ran 1 test for test/FallbackWithCalldataTest.sol:CounterWithFallbackTest +[PASS] test_fallback_with_calldata() ([GAS]) +Traces: + [48777] CounterWithFallbackTest::test_fallback_with_calldata() + ├─ [43461] CounterWithFallback::fallback(0x68656c6c6f) + │ └─ ← [Stop] + └─ ← [Stop] - // ignore ContractTwo - cmd.forge_fuse(); - prj.write_config(Config { - gas_reports: (vec![]), - gas_reports_ignore: (vec!["ContractTwo".to_string()]), - ..Default::default() - }); - cmd.forge_fuse(); - let second_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!( - second_out.contains("foo") && !second_out.contains("bar") && second_out.contains("baz") - ); - // cmd.arg("test").arg("--gas-report").print_output(); +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - // ignore ContractThree - cmd.forge_fuse(); - prj.write_config(Config { - gas_reports: (vec![ - "ContractOne".to_string(), - "ContractTwo".to_string(), - "ContractThree".to_string(), - ]), - gas_reports_ignore: (vec!["ContractThree".to_string()]), - ..Default::default() - }); - cmd.forge_fuse(); - let third_out = cmd.arg("test").arg("--gas-report").stdout(); - assert!(third_out.contains("foo") && third_out.contains("bar") && third_out.contains("baz")); +╭----------------------------------------------------------------+-----------------+-------+--------+-------+---------╮ +| test/FallbackWithCalldataTest.sol:CounterWithFallback Contract | | | | | | ++=====================================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------------------------------------+-----------------+-------+--------+-------+---------| +| 132471 | 396 | | | | | +|----------------------------------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------------------------------------+-----------------+-------+--------+-------+---------| +| fallback | 43461 | 43461 | 43461 | 43461 | 1 | +╰----------------------------------------------------------------+-----------------+-------+--------+-------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + cmd.forge_fuse() + .args(["test", "--mt", "test_fallback_with_calldata", "--gas-report", "--json"]) + .assert_success() + .stdout_eq( + str![[r#" +[ + { + "contract": "test/FallbackWithCalldataTest.sol:CounterWithFallback", + "deployment": { + "gas": 132471, + "size": 396 + }, + "functions": { + "fallback()": { + "calls": 1, + "min": 43461, + "mean": 43461, + "median": 43461, + "max": 43461 + } + } + } +] +"#]] + .is_json(), + ); }); -forgetest_init!(can_use_absolute_imports, |prj: TestProject, mut cmd: TestCommand| { - let remapping = prj.paths().libraries[0].join("myDepdendency"); - let config = Config { - remappings: vec![Remapping::from_str(&format!("myDepdendency/={}", remapping.display())) - .unwrap() - .into()], - ..Default::default() - }; - prj.write_config(config); +// +forgetest_init!(gas_report_size_for_nested_create, |prj, cmd| { + prj.add_test( + "NestedDeployTest.sol", + r#" +import {Test} from "forge-std/Test.sol"; +contract Child { + AnotherChild public child; + constructor() { + child = new AnotherChild(); + } + function w() external { + child.w(); + } +} +contract AnotherChild { + function w() external {} +} +contract Parent { + Child public immutable child; + constructor() { + child = new Child(); + } +} +contract NestedDeploy is Test { + function test_nested_create_gas_report() external { + Parent p = new Parent(); + p.child().child().w(); + } +} +"#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_nested_create_gas_report", "--gas-report"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------------------------------------------+-----------------+-------+--------+-------+---------╮ +| test/NestedDeployTest.sol:AnotherChild Contract | | | | | | ++======================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| +| 0 | 132 | | | | | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-------------------------------------------------+-----------------+-------+--------+-------+---------| +| w | 21185 | 21185 | 21185 | 21185 | 1 | +╰-------------------------------------------------+-----------------+-------+--------+-------+---------╯ + +╭------------------------------------------+-----------------+-----+--------+-----+---------╮ +| test/NestedDeployTest.sol:Child Contract | | | | | | ++===========================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|------------------------------------------+-----------------+-----+--------+-----+---------| +| 0 | 731 | | | | | +|------------------------------------------+-----------------+-----+--------+-----+---------| +| | | | | | | +|------------------------------------------+-----------------+-----+--------+-----+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|------------------------------------------+-----------------+-----+--------+-----+---------| +| child | 681 | 681 | 681 | 681 | 1 | +╰------------------------------------------+-----------------+-----+--------+-----+---------╯ + +╭-------------------------------------------+-----------------+-----+--------+-----+---------╮ +| test/NestedDeployTest.sol:Parent Contract | | | | | | ++============================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|-------------------------------------------+-----------------+-----+--------+-----+---------| +| 328961 | 1163 | | | | | +|-------------------------------------------+-----------------+-----+--------+-----+---------| +| | | | | | | +|-------------------------------------------+-----------------+-----+--------+-----+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-------------------------------------------+-----------------+-----+--------+-----+---------| +| child | 525 | 525 | 525 | 525 | 1 | +╰-------------------------------------------+-----------------+-----+--------+-----+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); - prj.inner() - .add_lib( - "myDepdendency/src/interfaces/IConfig.sol", - r#" - pragma solidity ^0.8.10; + cmd.forge_fuse() + .args(["test", "--mt", "test_nested_create_gas_report", "--gas-report", "--json"]) + .assert_success() + .stdout_eq( + str![[r#" +[ + { + "contract": "test/NestedDeployTest.sol:AnotherChild", + "deployment": { + "gas": 0, + "size": 132 + }, + "functions": { + "w()": { + "calls": 1, + "min": 21185, + "mean": 21185, + "median": 21185, + "max": 21185 + } + } + }, + { + "contract": "test/NestedDeployTest.sol:Child", + "deployment": { + "gas": 0, + "size": 731 + }, + "functions": { + "child()": { + "calls": 1, + "min": 681, + "mean": 681, + "median": 681, + "max": 681 + } + } + }, + { + "contract": "test/NestedDeployTest.sol:Parent", + "deployment": { + "gas": 328961, + "size": 1163 + }, + "functions": { + "child()": { + "calls": 1, + "min": 525, + "mean": 525, + "median": 525, + "max": 525 + } + } + } +] +"#]] + .is_json(), + ); +}); + +forgetest_init!(can_use_absolute_imports, |prj, cmd| { + prj.update_config(|config| { + let remapping = prj.paths().libraries[0].join("myDependency"); + config.remappings = + vec![Remapping::from_str(&format!("myDependency/={}", remapping.display())) + .unwrap() + .into()]; + }); + + prj.add_lib( + "myDependency/src/interfaces/IConfig.sol", + r" interface IConfig {} - "#, - ) - .unwrap(); + ", + ) + .unwrap(); - prj.inner() - .add_lib( - "myDepdendency/src/Config.sol", - r#" - pragma solidity ^0.8.10; - import "src/interfaces/IConfig.sol"; + prj.add_lib( + "myDependency/src/Config.sol", + r#" + import "src/interfaces/IConfig.sol"; contract Config {} "#, - ) - .unwrap(); + ) + .unwrap(); - prj.inner() - .add_source( - "Greeter", - r#" - pragma solidity ^0.8.10; - import "myDepdendency/src/Config.sol"; + prj.add_source( + "Greeter", + r#" + import "myDependency/src/Config.sol"; contract Greeter {} "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - cmd.arg("build"); - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("Compiler run successful")); +"#]]); }); // -forgetest_init!( - can_use_absolute_imports_from_test_and_script, - |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_script( - "IMyScript.sol", - r#" - pragma solidity ^0.8.10; - - interface IMyScript {} - "#, - ) - .unwrap(); - - prj.inner() - .add_script( - "MyScript.sol", - r#" - pragma solidity ^0.8.10; - import "script/IMyScript.sol"; - - contract MyScript is IMyScript {} - "#, - ) - .unwrap(); - - prj.inner() - .add_test( - "IMyTest.sol", - r#" - pragma solidity ^0.8.10; - - interface IMyTest {} - "#, - ) - .unwrap(); - - prj.inner() - .add_test( - "MyTest.sol", - r#" - pragma solidity ^0.8.10; - import "test/IMyTest.sol"; +forgetest_init!(can_use_absolute_imports_from_test_and_script, |prj, cmd| { + prj.add_script( + "IMyScript.sol", + r" +interface IMyScript {} + ", + ) + .unwrap(); + + prj.add_script( + "MyScript.sol", + r#" +import "script/IMyScript.sol"; + +contract MyScript is IMyScript {} + "#, + ) + .unwrap(); + + prj.add_test( + "IMyTest.sol", + r" +interface IMyTest {} + ", + ) + .unwrap(); + + prj.add_test( + "MyTest.sol", + r#" +import "test/IMyTest.sol"; + +contract MyTest is IMyTest {} + "#, + ) + .unwrap(); - contract MyTest is IMyTest {} - "#, - ) - .unwrap(); + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - cmd.arg("build"); - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("Compiler run successful")); - } -); +"#]]); +}); // checks `forge inspect irOptimized works -forgetest_init!(can_inspect_ir_optimized, |_prj: TestProject, mut cmd: TestCommand| { +forgetest_init!(can_inspect_ir_optimized, |_prj, cmd| { cmd.args(["inspect", TEMPLATE_CONTRACT, "irOptimized"]); - cmd.assert_success(); + cmd.assert_success().stdout_eq(str![[r#" +/// @use-src 0:"src/Counter.sol" +object "Counter_21" { + code { + { + /// @src 0:65:257 "contract Counter {..." + mstore(64, memoryguard(0x80)) +... +"#]]); + + // check inspect with strip comments + cmd.forge_fuse().args(["inspect", TEMPLATE_CONTRACT, "irOptimized", "-s"]); + cmd.assert_success().stdout_eq(str![[r#" +object "Counter_21" { + code { + { + mstore(64, memoryguard(0x80)) + if callvalue() +... +"#]]); +}); + +// checks `forge inspect irOptimized works +forgetest_init!(can_inspect_ir, |_prj, cmd| { + cmd.args(["inspect", TEMPLATE_CONTRACT, "ir"]); + cmd.assert_success().stdout_eq(str![[r#" + +/// @use-src 0:"src/Counter.sol" +object "Counter_21" { + code { + /// @src 0:65:257 "contract Counter {..." + mstore(64, memoryguard(128)) +... +"#]]); + + // check inspect with strip comments + cmd.forge_fuse().args(["inspect", TEMPLATE_CONTRACT, "ir", "-s"]); + cmd.assert_success().stdout_eq(str![[r#" + +object "Counter_21" { + code { + mstore(64, memoryguard(128)) + if callvalue() { revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() } +... +"#]]); }); // checks forge bind works correctly on the default project -forgetest_init!(can_bind, |_prj: TestProject, mut cmd: TestCommand| { - cmd.arg("bind"); - cmd.assert_non_empty_stdout(); +forgetest_init!(can_bind, |prj, cmd| { + prj.clear(); + + cmd.arg("bind").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Generating bindings for [..] contracts +Bindings have been generated to [..] + +"#]]); }); // checks missing dependencies are auto installed -forgetest_init!(can_install_missing_deps_test, |prj: TestProject, mut cmd: TestCommand| { +forgetest_init!(can_install_missing_deps_test, |prj, cmd| { + prj.clear(); + // wipe forge-std let forge_std_dir = prj.root().join("lib/forge-std"); pretty_err(&forge_std_dir, fs::remove_dir_all(&forge_std_dir)); - cmd.arg("test"); + cmd.arg("test").assert_success().stdout_eq(str![[r#" +Missing dependencies found. Installing now... + +[UPDATING_DEPENDENCIES] +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] - let output = cmd.stdout_lossy(); - assert!(output.contains("Missing dependencies found. Installing now"), "{}", output); - assert!(output.contains("[PASS]"), "{}", output); +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); }); // checks missing dependencies are auto installed -forgetest_init!(can_install_missing_deps_build, |prj: TestProject, mut cmd: TestCommand| { +forgetest_init!(can_install_missing_deps_build, |prj, cmd| { + prj.clear(); + // wipe forge-std let forge_std_dir = prj.root().join("lib/forge-std"); pretty_err(&forge_std_dir, fs::remove_dir_all(&forge_std_dir)); - cmd.arg("build"); + // Build the project + cmd.arg("build").assert_success().stdout_eq(str![[r#" +Missing dependencies found. Installing now... + +[UPDATING_DEPENDENCIES] +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); - let output = cmd.stdout_lossy(); - assert!(output.contains("Missing dependencies found. Installing now"), "{}", output); - assert!(output.contains("Compiler run successful"), "{}", output); + // Expect compilation to be skipped as no files have changed + cmd.forge_fuse().arg("build").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +"#]]); }); // checks that extra output works -forgetest_init!(can_build_skip_contracts, |prj: TestProject, mut cmd: TestCommand| { - // explicitly set to run with 0.8.17 for consistent output - let config = Config { solc: Some("0.8.17".into()), ..Default::default() }; - prj.write_config(config); +forgetest_init!(can_build_skip_contracts, |prj, cmd| { + prj.clear(); + + // Only builds the single template contract `src/*` + cmd.args(["build", "--skip", "tests", "--skip", "scripts"]).assert_success().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - // only builds the single template contract `src/*` - cmd.args(["build", "--skip", "tests", "--skip", "scripts"]); +"# + ]]); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_build_skip_contracts.stdout"), - ); - // re-run command - let out = cmd.stdout(); + // Expect compilation to be skipped as no files have changed + cmd.arg("build").assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped - // unchanged - assert!(out.trim().contains("No files changed, compilation skipped"), "{}", out); +"#]]); }); -forgetest_init!(can_build_skip_glob, |prj: TestProject, mut cmd: TestCommand| { - // explicitly set to run with 0.8.17 for consistent output - let config = Config { solc: Some("0.8.17".into()), ..Default::default() }; - prj.write_config(config); - prj.inner() - .add_test( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.17; +forgetest_init!(can_build_skip_glob, |prj, cmd| { + prj.add_test( + "Foo", + r" contract TestDemo { function test_run() external {} -}"#, - ) - .unwrap(); +}", + ) + .unwrap(); + // only builds the single template contract `src/*` even if `*.t.sol` or `.s.sol` is absent - cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**"]); + prj.clear(); + cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**", "--force"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_build_skip_glob.stdout"), - ); +"#]]); + + cmd.forge_fuse() + .args(["build", "--skip", "./test/**", "--skip", "./script/**", "--force"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); }); -// checks that build --sizes includes all contracts even if unchanged -forgetest_init!(can_build_sizes_repeatedly, |_prj: TestProject, mut cmd: TestCommand| { - cmd.args(["build", "--sizes"]); - let out = cmd.stdout(); +forgetest_init!(can_build_specific_paths, |prj, cmd| { + prj.wipe(); + prj.add_source( + "Counter.sol", + r" +contract Counter { +function count() external {} +}", + ) + .unwrap(); + prj.add_test( + "Foo.sol", + r" +contract Foo { +function test_foo() external {} +}", + ) + .unwrap(); + prj.add_test( + "Bar.sol", + r" +contract Bar { +function test_bar() external {} +}", + ) + .unwrap(); + + // Build 2 files within test dir + prj.clear(); + cmd.args(["build", "test", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + // Build one file within src dir + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "src", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - // contains: Counter ┆ 0.247 ┆ 24.329 - assert!(out.contains(TEMPLATE_CONTRACT)); +"#]]); - // get the entire table - let table = out.split("Compiler run successful!").nth(1).unwrap().trim(); + // Build 3 files from test and src dirs + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "src", "test", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - let unchanged = cmd.stdout(); - assert!(unchanged.contains(table), "{}", table); +"#]]); + + // Build single test file + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "test/Bar.sol", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + // Fail if no source file found. + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "test/Dummy.sol", "--force"]).assert_failure().stderr_eq(str![[r#" +Error: No source files found in specified build paths. + +"#]]); +}); + +// checks that build --sizes includes all contracts even if unchanged +forgetest_init!(can_build_sizes_repeatedly, |prj, cmd| { + prj.clear_cache(); + + cmd.args(["build", "--sizes"]).assert_success().stdout_eq(str![[r#" +... +╭----------+------------------+-------------------+--------------------+---------------------╮ +| Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | ++============================================================================================+ +| Counter | 481 | 509 | 24,095 | 48,643 | +╰----------+------------------+-------------------+--------------------+---------------------╯ + + +"#]]); + + cmd.forge_fuse().args(["build", "--sizes", "--json"]).assert_success().stdout_eq( + str![[r#" +{ + "Counter": { + "runtime_size": 481, + "init_size": 509, + "runtime_margin": 24095, + "init_margin": 48643 + } +} +"#]] + .is_json(), + ); }); // checks that build --names includes all contracts even if unchanged -forgetest_init!(can_build_names_repeatedly, |_prj: TestProject, mut cmd: TestCommand| { - cmd.args(["build", "--names"]); - let out = cmd.stdout(); +forgetest_init!(can_build_names_repeatedly, |prj, cmd| { + prj.clear_cache(); + + cmd.args(["build", "--names"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + compiler version: [..] + - [..] +... + +"#]]); + + cmd.forge_fuse() + .args(["build", "--names", "--json"]) + .assert_success() + .stdout_eq(str![[r#""{...}""#]].is_json()); +}); + +forgetest_init!(can_inspect_counter_pretty, |prj, cmd| { + cmd.args(["inspect", "src/Counter.sol:Counter", "abi"]).assert_success().stdout_eq(str![[r#" + +╭----------+---------------------------------+------------╮ +| Type | Signature | Selector | ++=========================================================+ +| function | increment() nonpayable | 0xd09de08a | +|----------+---------------------------------+------------| +| function | number() view returns (uint256) | 0x8381f58a | +|----------+---------------------------------+------------| +| function | setNumber(uint256) nonpayable | 0x3fb5c1cb | +╰----------+---------------------------------+------------╯ + + +"#]]); +}); + +const CUSTOM_COUNTER: &str = r#" + contract Counter { + uint256 public number; + uint64 public count; + struct MyStruct { + uint64 count; + } + struct ErrWithMsg { + string message; + } + + event Incremented(uint256 newValue); + event Decremented(uint256 newValue); + + error NumberIsZero(); + error CustomErr(ErrWithMsg e); + + constructor(uint256 _number) { + number = _number; + } + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() external { + number++; + } + + function decrement() public payable { + if (number == 0) { + return; + } + number--; + } + + function square() public { + number = number * number; + } + + fallback() external payable { + ErrWithMsg memory err = ErrWithMsg("Fallback function is not allowed"); + revert CustomErr(err); + } + + receive() external payable { + count++; + } + + function setStruct(MyStruct memory s, uint32 b) public { + count = s.count; + } +} + "#; + +const ANOTHER_COUNTER: &str = r#" + contract AnotherCounter is Counter { + constructor(uint256 _number) Counter(_number) {} + } +"#; +forgetest!(inspect_custom_counter_abi, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "abi"]).assert_success().stdout_eq(str![[r#" + +╭-------------+-----------------------------------------------+--------------------------------------------------------------------╮ +| Type | Signature | Selector | ++==================================================================================================================================+ +| event | Decremented(uint256) | 0xc9118d86370931e39644ee137c931308fa3774f6c90ab057f0c3febf427ef94a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| event | Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| error | CustomErr(Counter.ErrWithMsg) | 0x0625625a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| error | NumberIsZero() | 0xde5d32ac | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | count() view returns (uint64) | 0x06661abd | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | decrement() payable | 0x2baeceb7 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | increment() nonpayable | 0xd09de08a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | number() view returns (uint256) | 0x8381f58a | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | setNumber(uint256) nonpayable | 0x3fb5c1cb | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | setStruct(Counter.MyStruct,uint32) nonpayable | 0x08ef7366 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| function | square() nonpayable | 0xd742cb01 | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| constructor | constructor(uint256) nonpayable | | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| fallback | fallback() payable | | +|-------------+-----------------------------------------------+--------------------------------------------------------------------| +| receive | receive() payable | | +╰-------------+-----------------------------------------------+--------------------------------------------------------------------╯ + + +"#]]); +}); + +forgetest!(inspect_custom_counter_events, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "events"]).assert_success().stdout_eq(str![[r#" + +╭----------------------+--------------------------------------------------------------------╮ +| Event | Topic | ++===========================================================================================+ +| Decremented(uint256) | 0xc9118d86370931e39644ee137c931308fa3774f6c90ab057f0c3febf427ef94a | +|----------------------+--------------------------------------------------------------------| +| Incremented(uint256) | 0x20d8a6f5a693f9d1d627a598e8820f7a55ee74c183aa8f1a30e8d4e8dd9a8d84 | +╰----------------------+--------------------------------------------------------------------╯ + + +"#]]); +}); + +forgetest!(inspect_custom_counter_errors, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "errors"]).assert_success().stdout_eq(str![[r#" + +╭-------------------------------+----------╮ +| Error | Selector | ++==========================================+ +| CustomErr(Counter.ErrWithMsg) | 0625625a | +|-------------------------------+----------| +| NumberIsZero() | de5d32ac | +╰-------------------------------+----------╯ + + +"#]]); +}); - assert!(out.contains(TEMPLATE_CONTRACT)); +forgetest!(inspect_path_only_identifier, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); - // get the entire list - let list = out.split("Compiler run successful!").nth(1).unwrap().trim(); + cmd.args(["inspect", "src/Counter.sol", "errors"]).assert_success().stdout_eq(str![[r#" - let unchanged = cmd.stdout(); - assert!(unchanged.contains(list), "{}", list); +╭-------------------------------+----------╮ +| Error | Selector | ++==========================================+ +| CustomErr(Counter.ErrWithMsg) | 0625625a | +|-------------------------------+----------| +| NumberIsZero() | de5d32ac | +╰-------------------------------+----------╯ + + +"#]]); +}); + +forgetest!(test_inspect_contract_with_same_name, |prj, cmd| { + let source = format!("{CUSTOM_COUNTER}\n{ANOTHER_COUNTER}"); + prj.add_source("Counter.sol", &source).unwrap(); + + cmd.args(["inspect", "src/Counter.sol", "errors"]).assert_failure().stderr_eq(str![[r#"Error: Multiple contracts found in the same file, please specify the target : or [..]"#]]); + + cmd.forge_fuse().args(["inspect", "Counter", "errors"]).assert_success().stdout_eq(str![[r#" + +╭-------------------------------+----------╮ +| Error | Selector | ++==========================================+ +| CustomErr(Counter.ErrWithMsg) | 0625625a | +|-------------------------------+----------| +| NumberIsZero() | de5d32ac | +╰-------------------------------+----------╯ + + +"#]]); +}); + +forgetest!(inspect_custom_counter_method_identifiers, |prj, cmd| { + prj.add_source("Counter.sol", CUSTOM_COUNTER).unwrap(); + + cmd.args(["inspect", "Counter", "method-identifiers"]).assert_success().stdout_eq(str![[r#" + +╭----------------------------+------------╮ +| Method | Identifier | ++=========================================+ +| count() | 06661abd | +|----------------------------+------------| +| decrement() | 2baeceb7 | +|----------------------------+------------| +| increment() | d09de08a | +|----------------------------+------------| +| number() | 8381f58a | +|----------------------------+------------| +| setNumber(uint256) | 3fb5c1cb | +|----------------------------+------------| +| setStruct((uint64),uint32) | 08ef7366 | +|----------------------------+------------| +| square() | d742cb01 | +╰----------------------------+------------╯ + + +"#]]); +}); + +// checks that `clean` also works with the "out" value set in Config +forgetest_init!(gas_report_include_tests, |prj, cmd| { + prj.update_config(|config| { + config.gas_reports_include_tests = true; + config.fuzz.runs = 1; + }); + + cmd.args(["test", "--match-test", "test_Increment", "--gas-report"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭----------------------------------+-----------------+-------+--------+-------+---------╮ +| src/Counter.sol:Counter Contract | | | | | | ++=======================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| 156813 | 509 | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| | | | | | | +|----------------------------------+-----------------+-------+--------+-------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|----------------------------------+-----------------+-------+--------+-------+---------| +| increment | 43482 | 43482 | 43482 | 43482 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| +| number | 424 | 424 | 424 | 424 | 1 | +|----------------------------------+-----------------+-------+--------+-------+---------| +| setNumber | 23784 | 23784 | 23784 | 23784 | 1 | +╰----------------------------------+-----------------+-------+--------+-------+---------╯ + +╭-----------------------------------------+-----------------+--------+--------+--------+---------╮ +| test/Counter.t.sol:CounterTest Contract | | | | | | ++================================================================================================+ +| Deployment Cost | Deployment Size | | | | | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| 1545498 | 7578 | | | | | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| | | | | | | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| Function Name | Min | Avg | Median | Max | # Calls | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| setUp | 218902 | 218902 | 218902 | 218902 | 1 | +|-----------------------------------------+-----------------+--------+--------+--------+---------| +| test_Increment | 52915 | 52915 | 52915 | 52915 | 1 | +╰-----------------------------------------+-----------------+--------+--------+--------+---------╯ + + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + cmd.forge_fuse() + .args(["test", "--mt", "test_Increment", "--gas-report", "--json"]) + .assert_success() + .stdout_eq( + str![[r#" +[ + { + "contract": "src/Counter.sol:Counter", + "deployment": { + "gas": 156813, + "size": 509 + }, + "functions": { + "increment()": { + "calls": 1, + "min": 43482, + "mean": 43482, + "median": 43482, + "max": 43482 + }, + "number()": { + "calls": 1, + "min": 424, + "mean": 424, + "median": 424, + "max": 424 + }, + "setNumber(uint256)": { + "calls": 1, + "min": 23784, + "mean": 23784, + "median": 23784, + "max": 23784 + } + } + }, + { + "contract": "test/Counter.t.sol:CounterTest", + "deployment": { + "gas": 1545498, + "size": 7578 + }, + "functions": { + "setUp()": { + "calls": 1, + "min": 218902, + "mean": 218902, + "median": 218902, + "max": 218902 + }, + "test_Increment()": { + "calls": 1, + "min": 52915, + "mean": 52915, + "median": 52915, + "max": 52915 + } + } + } +] +"#]] + .is_json(), + ); +}); + +forgetest_async!(gas_report_fuzz_invariant, |prj, cmd| { + // speed up test by running with depth of 15 + prj.update_config(|config| config.invariant.depth = 15); + + prj.insert_ds_test(); + prj.add_source( + "Contracts.sol", + r#" +import "./test.sol"; + +contract Foo { + function foo() public {} +} + +contract Bar { + function bar() public {} +} + +contract FooBarTest is DSTest { + Foo public targetContract; + + function setUp() public { + targetContract = new Foo(); + } + + function invariant_dummy() public { + assertTrue(true); + } + + function testFuzz_bar(uint256 _val) public { + (new Bar()).bar(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--gas-report"]).assert_success(); +}); + +// +forgetest_init!(can_bind_enum_modules, |prj, cmd| { + prj.clear(); + + prj.add_source( + "Enum.sol", + r#" + contract Enum { + enum MyEnum { A, B, C } + } + "#, + ) + .unwrap(); + + prj.add_source( + "UseEnum.sol", + r#" + import "./Enum.sol"; + contract UseEnum { + Enum.MyEnum public myEnum; + }"#, + ) + .unwrap(); + + cmd.args(["bind", "--select", "^Enum$"]).assert_success().stdout_eq(str![[ + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Generating bindings for 1 contracts +Bindings have been generated to [..]"# + ]]); }); diff --git a/crates/forge/tests/cli/compiler.rs b/crates/forge/tests/cli/compiler.rs new file mode 100644 index 0000000000000..0b58221f2a1d0 --- /dev/null +++ b/crates/forge/tests/cli/compiler.rs @@ -0,0 +1,302 @@ +//! Tests for the `forge compiler` command. + +use foundry_test_utils::snapbox::IntoData; + +const CONTRACT_A: &str = r#" +// SPDX-license-identifier: MIT +pragma solidity 0.8.4; + +contract ContractA {} +"#; + +const CONTRACT_B: &str = r#" +// SPDX-license-identifier: MIT +pragma solidity 0.8.11; + +contract ContractB {} +"#; + +const CONTRACT_C: &str = r#" +// SPDX-license-identifier: MIT +pragma solidity 0.8.27; + +contract ContractC {} +"#; + +const CONTRACT_D: &str = r#" +// SPDX-license-identifier: MIT +pragma solidity 0.8.27; + +contract ContractD {} +"#; + +const VYPER_INTERFACE: &str = r#" +# pragma version >=0.4.0 + +@external +@view +def number() -> uint256: + return empty(uint256) + +@external +def set_number(new_number: uint256): + pass + +@external +def increment() -> uint256: + return empty(uint256) +"#; + +const VYPER_CONTRACT: &str = r#" +import ICounter +implements: ICounter + +number: public(uint256) + +@external +def set_number(new_number: uint256): + self.number = new_number + +@external +def increment() -> uint256: + self.number += 1 + return self.number +"#; + +forgetest!(can_resolve_path, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + + cmd.args(["compiler", "resolve", "--root", prj.root().to_str().unwrap()]) + .assert_success() + .stdout_eq(str![[r#" +Solidity: +- 0.8.4 + + +"#]]); +}); + +forgetest!(can_list_resolved_compiler_versions, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + + cmd.args(["compiler", "resolve"]).assert_success().stdout_eq(str![[r#" +Solidity: +- 0.8.4 + + +"#]]); +}); + +forgetest!(can_list_resolved_compiler_versions_json, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + + cmd.args(["compiler", "resolve", "--json"]).assert_success().stdout_eq( + str![[r#" +{ + "Solidity":[ + { + "version":"0.8.4" + } + ] +} +"#]] + .is_json(), + ); +}); + +forgetest!(can_list_resolved_compiler_versions_verbose, |prj, cmd| { + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + + cmd.args(["compiler", "resolve", "-v"]).assert_success().stdout_eq(str![[r#" +Solidity: + +0.8.27: +├── src/ContractC.sol +└── src/ContractD.sol + + +"#]]); +}); + +forgetest!(can_list_resolved_compiler_versions_verbose_json, |prj, cmd| { + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + + cmd.args(["compiler", "resolve", "--json", "-v"]).assert_success().stdout_eq( + str![[r#" +{ + "Solidity": [ + { + "version": "0.8.27", + "paths": [ + "src/ContractC.sol", + "src/ContractD.sol" + ] + } + ] +} +"#]] + .is_json(), + ); +}); + +forgetest!(can_list_resolved_multiple_compiler_versions, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractB", CONTRACT_B).unwrap(); + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + + cmd.args(["compiler", "resolve"]).assert_success().stdout_eq(str![[r#" +Solidity: +- 0.8.4 +- 0.8.11 +- 0.8.27 + +Vyper: +- 0.4.0 + + +"#]]); +}); + +forgetest!(can_list_resolved_multiple_compiler_versions_skipped, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractB", CONTRACT_B).unwrap(); + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + + cmd.args(["compiler", "resolve", "--skip", ".sol", "-v"]).assert_success().stdout_eq(str![[ + r#" +Vyper: + +0.4.0: +├── src/Counter.vy +└── src/ICounter.vyi + + +"# + ]]); +}); + +forgetest!(can_list_resolved_multiple_compiler_versions_skipped_json, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractB", CONTRACT_B).unwrap(); + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + + cmd.args(["compiler", "resolve", "--skip", "Contract(A|B|C)", "--json", "-v"]) + .assert_success() + .stdout_eq( + str![[r#" +{ + "Solidity": [ + { + "version": "0.8.27", + "paths": [ + "src/ContractD.sol" + ] + } + ], + "Vyper": [ + { + "version": "0.4.0", + "paths": [ + "src/Counter.vy", + "src/ICounter.vyi" + ] + } + ] +} +"#]] + .is_json(), + ); +}); + +forgetest!(can_list_resolved_multiple_compiler_versions_verbose, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractB", CONTRACT_B).unwrap(); + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + + cmd.args(["compiler", "resolve", "-vv"]).assert_success().stdout_eq(str![[r#" +Solidity: + +0.8.4 (<= istanbul): +└── src/ContractA.sol + +0.8.11 (<= london): +└── src/ContractB.sol + +0.8.27 (<= [..]): +├── src/ContractC.sol +└── src/ContractD.sol + +Vyper: + +0.4.0 (<= cancun): +├── src/Counter.vy +└── src/ICounter.vyi + + +"#]]); +}); + +forgetest!(can_list_resolved_multiple_compiler_versions_verbose_json, |prj, cmd| { + prj.add_source("ContractA", CONTRACT_A).unwrap(); + prj.add_source("ContractB", CONTRACT_B).unwrap(); + prj.add_source("ContractC", CONTRACT_C).unwrap(); + prj.add_source("ContractD", CONTRACT_D).unwrap(); + prj.add_raw_source("ICounter.vyi", VYPER_INTERFACE).unwrap(); + prj.add_raw_source("Counter.vy", VYPER_CONTRACT).unwrap(); + + cmd.args(["compiler", "resolve", "--json", "-vv"]).assert_success().stdout_eq( + str![[r#" +{ + "Solidity": [ + { + "version": "0.8.4", + "evm_version": "Istanbul", + "paths": [ + "src/ContractA.sol" + ] + }, + { + "version": "0.8.11", + "evm_version": "London", + "paths": [ + "src/ContractB.sol" + ] + }, + { + "version": "0.8.27", + "evm_version": "[..]", + "paths": [ + "src/ContractC.sol", + "src/ContractD.sol" + ] + } + ], + "Vyper": [ + { + "version": "0.4.0", + "evm_version": "[..]", + "paths": [ + "src/Counter.vy", + "src/ICounter.vyi" + ] + } + ] +} +"#]] + .is_json(), + ); +}); diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 597f304f75154..23b72a86526ff 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -1,30 +1,41 @@ //! Contains various tests for checking forge commands related to config values -use ethers::{ - prelude::artifacts::YulDetails, - solc::artifacts::RevertStrings, - types::{Address, H256, U256}, -}; +use alloy_primitives::{Address, B256, U256}; use foundry_cli::utils as forge_utils; +use foundry_compilers::{ + artifacts::{BytecodeHash, OptimizerDetails, RevertStrings, YulDetails}, + solc::Solc, +}; use foundry_config::{ cache::{CachedChains, CachedEndpoints, StorageCachingConfig}, - Config, FuzzConfig, InvariantConfig, OptimizerDetails, SolcReq, + filter::GlobMatcher, + fs_permissions::{FsAccessPermission, PathPermission}, + CompilationRestrictions, Config, FsPermissions, FuzzConfig, InvariantConfig, SettingsOverrides, + SolcReq, }; -use foundry_evm::executor::opts::EvmOpts; +use foundry_evm::opts::EvmOpts; use foundry_test_utils::{ - ethers_solc::{remappings::Remapping, EvmVersion}, - forgetest, forgetest_init, pretty_eq, - util::{pretty_err, OutputExt, TestCommand, TestProject}, + foundry_compilers::artifacts::{remappings::Remapping, EvmVersion}, + util::{pretty_err, OutputExt, TestCommand, OTHER_SOLC_VERSION}, }; use path_slash::PathBufExt; -use std::{fs, path::PathBuf, str::FromStr}; +use semver::VersionReq; +use serde_json::Value; +use similar_asserts::assert_eq; +use std::{ + fs, + path::{Path, PathBuf}, + str::FromStr, +}; // tests all config values that are in use -forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_extract_config_values, |prj, cmd| { // explicitly set all values let input = Config { profile: Config::DEFAULT_PROFILE, - __root: Default::default(), + // `profiles` is not serialized. + profiles: vec![], + root: ".".into(), src: "test-src".into(), test: "test-test".into(), script: "test-script".into(), @@ -32,17 +43,21 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { libs: vec!["lib-test".into()], cache: true, cache_path: "test-cache".into(), + snapshots: "snapshots".into(), + gas_snapshot_check: false, + gas_snapshot_emit: true, broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, gas_reports: vec!["Contract".to_string()], gas_reports_ignore: vec![], + gas_reports_include_tests: false, solc: Some(SolcReq::Local(PathBuf::from("custom-solc"))), auto_detect_solc: false, auto_detect_remappings: true, offline: true, - optimizer: false, - optimizer_runs: 1000, + optimizer: Some(false), + optimizer_runs: Some(1000), optimizer_details: Some(OptimizerDetails { yul: Some(false), yul_details: Some(YulDetails { stack_allocation: Some(true), ..Default::default() }), @@ -59,20 +74,34 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { 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(1000.into()), + seed: Some(U256::from(1000)), + failure_persist_dir: Some("test-cache/fuzz".into()), + failure_persist_file: Some("failures".to_string()), + show_logs: false, + ..Default::default() + }, + invariant: InvariantConfig { + runs: 256, + failure_persist_dir: Some("test-cache/fuzz".into()), ..Default::default() }, - invariant: InvariantConfig { runs: 256, ..Default::default() }, ffi: true, + allow_internal_expect_revert: false, + always_use_create_2_factory: false, + prompt_timeout: 0, sender: "00a329c0648769A73afAc7F9381D08FB43dBEA72".parse().unwrap(), tx_origin: "00a329c0648769A73afAc7F9F81E08FB43dBEA72".parse().unwrap(), initial_balance: U256::from(0xffffffffffffffffffffffffu128), block_number: 10, fork_block_number: Some(200), - chain_id: Some(9999.into()), + chain: Some(9999.into()), gas_limit: 99_000_000u64.into(), code_size_limit: Some(100000), gas_price: Some(999), @@ -80,20 +109,26 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { block_coinbase: Address::random(), block_timestamp: 10, block_difficulty: 10, - block_prevrandao: H256::random(), + block_prevrandao: B256::random(), block_gas_limit: Some(100u64.into()), - memory_limit: 2u64.pow(25), + disable_block_gas_limit: false, + memory_limit: 1 << 27, eth_rpc_url: Some("localhost".to_string()), + eth_rpc_jwt: None, + eth_rpc_timeout: None, + eth_rpc_headers: None, 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() ], ignored_error_codes: vec![], + ignored_file_paths: vec![], deny_warnings: false, via_ir: true, + ast: false, rpc_storage_caching: StorageCachingConfig { chains: CachedChains::None, endpoints: CachedEndpoints::Remote, @@ -112,277 +147,295 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| { build_info_path: None, fmt: Default::default(), doc: Default::default(), + bind_json: Default::default(), fs_permissions: Default::default(), - __non_exhaustive: (), - __warnings: vec![], + labels: Default::default(), + isolate: true, + unchecked_cheatcode_artifacts: false, + create2_library_salt: Config::DEFAULT_CREATE2_LIBRARY_SALT, + create2_deployer: Config::DEFAULT_CREATE2_DEPLOYER, + vyper: Default::default(), + skip: vec![], + dependencies: Default::default(), + soldeer: Default::default(), + warnings: vec![], + assertions_revert: true, + legacy_assertions: false, + extra_args: vec![], + eof_version: None, + odyssey: false, + transaction_timeout: 120, + additional_compiler_profiles: Default::default(), + compilation_restrictions: Default::default(), + eof: false, + _non_exhaustive: (), }; prj.write_config(input.clone()); let config = cmd.config(); - pretty_assertions::assert_eq!(input, config); + similar_asserts::assert_eq!(input, config); }); // tests config gets printed to std out -forgetest!( - #[serial_test::serial] - can_show_config, - |prj: TestProject, mut cmd: TestCommand| { - cmd.arg("config"); - let expected = - Config::load_with_root(prj.root()).to_string_pretty().unwrap().trim().to_string(); - assert_eq!(expected, cmd.stdout().trim().to_string()); - } -); +forgetest!(can_show_config, |prj, cmd| { + let expected = + Config::load_with_root(prj.root()).unwrap().to_string_pretty().unwrap().trim().to_string(); + let output = cmd.arg("config").assert_success().get_output().stdout_lossy().trim().to_string(); + assert_eq!(expected, output); +}); // checks that config works // - foundry.toml is properly generated // - paths are resolved properly // - config supports overrides from env, and cli -forgetest_init!( - #[serial_test::serial] - can_override_config, - |prj: TestProject, mut cmd: TestCommand| { - cmd.set_current_dir(prj.root()); - let foundry_toml = prj.root().join(Config::FILE_NAME); - assert!(foundry_toml.exists()); - - let profile = Config::load_with_root(prj.root()); - // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - pretty_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - - // ensure remappings contain test - pretty_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - // the loaded config has resolved, absolute paths - pretty_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", - Remapping::from(profile.remappings[0].clone()).to_string() - ); - - cmd.arg("config"); - let expected = profile.to_string_pretty().unwrap(); - pretty_eq!(expected.trim().to_string(), cmd.stdout().trim().to_string()); - - // remappings work - let remappings_txt = - prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - pretty_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-file").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - // env vars work - std::env::set_var("DAPP_REMAPPINGS", "ds-test/=lib/forge-std/lib/ds-test/from-env/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - pretty_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-env").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - let config = - prj.config_from_output(["--remappings", "ds-test/=lib/forge-std/lib/ds-test/from-cli"]); - pretty_eq!( - format!( - "ds-test/={}/", - prj.root().join("lib/forge-std/lib/ds-test/from-cli").to_slash_lossy() - ), - Remapping::from(config.remappings[0].clone()).to_string() - ); - - let config = prj.config_from_output(["--remappings", "other-key/=lib/other/"]); - assert_eq!(config.remappings.len(), 3); - pretty_eq!( - format!("other-key/={}/", prj.root().join("lib/other").to_slash_lossy()), - // As CLI has the higher priority, it'll be found at the first slot. - Remapping::from(config.remappings[0].clone()).to_string() - ); - - std::env::remove_var("DAPP_REMAPPINGS"); - pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); - - cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); - let expected = profile.into_basic().to_string_pretty().unwrap(); - pretty_eq!(expected.trim().to_string(), cmd.stdout().trim().to_string()); - } -); - -forgetest_init!( - #[serial_test::serial] - can_parse_remappings_correctly, - |prj: TestProject, mut cmd: TestCommand| { - cmd.set_current_dir(prj.root()); - let foundry_toml = prj.root().join(Config::FILE_NAME); - assert!(foundry_toml.exists()); - - let profile = Config::load_with_root(prj.root()); - // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - pretty_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - - // ensure remappings contain test - pretty_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); - // the loaded config has resolved, absolute paths - pretty_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", - Remapping::from(profile.remappings[0].clone()).to_string() - ); - - cmd.arg("config"); - let expected = profile.to_string_pretty().unwrap(); - pretty_eq!(expected.trim().to_string(), cmd.stdout().trim().to_string()); - - let install = |cmd: &mut TestCommand, dep: &str| { - cmd.forge_fuse().args(["install", dep, "--no-commit"]); - cmd.assert_non_empty_stdout(); - }; +forgetest_init!(can_override_config, |prj, cmd| { + cmd.set_current_dir(prj.root()); + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(foundry_toml.exists()); + + let profile = Config::load_with_root(prj.root()).unwrap(); + // ensure that the auto-generated internal remapping for forge-std's ds-test exists + assert_eq!(profile.remappings.len(), 1); + assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); + + // ensure remappings contain test + assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); + // the loaded config has resolved, absolute paths + assert_eq!( + "forge-std/=lib/forge-std/src/", + Remapping::from(profile.remappings[0].clone()).to_string() + ); - install(&mut cmd, "transmissions11/solmate"); - let profile = Config::load_with_root(prj.root()); - // remappings work - let remappings_txt = prj.create_file( - "remappings.txt", - "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", - ); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); - pretty_eq!( - format!("solmate/={}", prj.root().join("lib/solmate/src/").to_slash_lossy()), - Remapping::from(config.remappings[0].clone()).to_string() - ); - // As this is an user-generated remapping, it is not removed, even if it points to the same - // location. - pretty_eq!( - format!("solmate-contracts/={}", prj.root().join("lib/solmate/src/").to_slash_lossy()), - Remapping::from(config.remappings[1].clone()).to_string() - ); - pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); - - cmd.set_cmd(prj.forge_bin()).args(["config", "--basic"]); - let expected = profile.into_basic().to_string_pretty().unwrap(); - pretty_eq!(expected.trim().to_string(), cmd.stdout().trim().to_string()); - } -); - -forgetest_init!( - #[serial_test::serial] - can_detect_config_vals, - |prj: TestProject, _cmd: TestCommand| { - let url = "http://127.0.0.1:8545"; - let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); - assert!(!config.auto_detect_solc); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - - let mut config = Config::load_with_root(prj.root()); - config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); - config.auto_detect_solc = false; - // write to `foundry.toml` - prj.create_file( - Config::FILE_NAME, - &config.to_string_pretty().unwrap().replace("eth_rpc_url", "eth-rpc-url"), - ); - let config = prj.config_from_output(["--force"]); - assert!(!config.auto_detect_solc); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - } -); + let expected = profile.to_string_pretty().unwrap().trim().to_string(); + let output = cmd.arg("config").assert_success().get_output().stdout_lossy().trim().to_string(); + assert_eq!(expected, output); + + // remappings work + let remappings_txt = + prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); + let config = forge_utils::load_config_with_root(Some(prj.root())).unwrap(); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-file").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + let config = + prj.config_from_output(["--remappings", "ds-test/=lib/forge-std/lib/ds-test/from-cli"]); + assert_eq!( + format!( + "ds-test/={}/", + prj.root().join("lib/forge-std/lib/ds-test/from-cli").to_slash_lossy() + ), + Remapping::from(config.remappings[0].clone()).to_string() + ); + + let config = prj.config_from_output(["--remappings", "other-key/=lib/other/"]); + assert_eq!(config.remappings.len(), 3); + assert_eq!( + format!("other-key/={}/", prj.root().join("lib/other").to_slash_lossy()), + // As CLI has the higher priority, it'll be found at the first slot. + Remapping::from(config.remappings[0].clone()).to_string() + ); + + pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); + + let expected = profile.into_basic().to_string_pretty().unwrap().trim().to_string(); + let output = cmd + .forge_fuse() + .args(["config", "--basic"]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + assert_eq!(expected, output); +}); + +forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { + cmd.set_current_dir(prj.root()); + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(foundry_toml.exists()); + + let profile = Config::load_with_root(prj.root()).unwrap(); + // ensure that the auto-generated internal remapping for forge-std's ds-test exists + assert_eq!(profile.remappings.len(), 1); + let r = &profile.remappings[0]; + assert_eq!("forge-std/=lib/forge-std/src/", r.to_string()); + + // the loaded config has resolved, absolute paths + assert_eq!("forge-std/=lib/forge-std/src/", Remapping::from(r.clone()).to_string()); + + let expected = profile.to_string_pretty().unwrap().trim().to_string(); + let output = cmd.arg("config").assert_success().get_output().stdout_lossy().trim().to_string(); + assert_eq!(expected, output); + + let install = |cmd: &mut TestCommand, dep: &str| { + cmd.forge_fuse().args(["install", dep]).assert_success().stdout_eq(str![[r#" +Installing solmate in [..] (url: Some("https://github.com/transmissions11/solmate"), tag: None) + Installed solmate[..] + +"#]]); + }; + + install(&mut cmd, "transmissions11/solmate"); + let profile = Config::load_with_root(prj.root()).unwrap(); + // remappings work + let remappings_txt = prj.create_file( + "remappings.txt", + "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", + ); + let config = forge_utils::load_config_with_root(Some(prj.root())).unwrap(); + // trailing slashes are removed on windows `to_slash_lossy` + let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); + #[cfg(windows)] + let path = path + "/"; + assert_eq!( + format!("solmate/={path}"), + Remapping::from(config.remappings[0].clone()).to_string() + ); + // As this is an user-generated remapping, it is not removed, even if it points to the same + // location. + assert_eq!( + format!("solmate-contracts/={path}"), + Remapping::from(config.remappings[1].clone()).to_string() + ); + pretty_err(&remappings_txt, fs::remove_file(&remappings_txt)); + + let expected = profile.into_basic().to_string_pretty().unwrap().trim().to_string(); + let output = cmd + .forge_fuse() + .args(["config", "--basic"]) + .assert_success() + .get_output() + .stdout_lossy() + .trim() + .to_string(); + assert_eq!(expected, output); +}); + +forgetest_init!(can_detect_config_vals, |prj, _cmd| { + let url = "http://127.0.0.1:8545"; + let config = prj.config_from_output(["--no-auto-detect", "--rpc-url", url]); + assert!(!config.auto_detect_solc); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); + + let mut config = Config::load_with_root(prj.root()).unwrap(); + config.eth_rpc_url = Some("http://127.0.0.1:8545".to_string()); + config.auto_detect_solc = false; + // write to `foundry.toml` + prj.create_file( + Config::FILE_NAME, + &config.to_string_pretty().unwrap().replace("eth_rpc_url", "eth-rpc-url"), + ); + let config = prj.config_from_output(["--force"]); + assert!(!config.auto_detect_solc); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); +}); // checks that `clean` removes dapptools style paths -forgetest_init!( - #[serial_test::serial] - can_get_evm_opts, - |prj: TestProject, _cmd: TestCommand| { - let url = "http://127.0.0.1:8545"; - let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); - assert_eq!(config.eth_rpc_url, Some(url.to_string())); - assert!(config.ffi); - - std::env::set_var("FOUNDRY_ETH_RPC_URL", url); - let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); - let evm_opts: EvmOpts = figment.extract().unwrap(); - assert_eq!(evm_opts.fork_url, Some(url.to_string())); - std::env::remove_var("FOUNDRY_ETH_RPC_URL"); - } -); +forgetest_init!(can_get_evm_opts, |prj, _cmd| { + let url = "http://127.0.0.1:8545"; + let config = prj.config_from_output(["--rpc-url", url, "--ffi"]); + assert_eq!(config.eth_rpc_url, Some(url.to_string())); + assert!(config.ffi); + + std::env::set_var("FOUNDRY_ETH_RPC_URL", url); + let figment = Config::figment_with_root(prj.root()).merge(("debug", false)); + let evm_opts: EvmOpts = figment.extract().unwrap(); + assert_eq!(evm_opts.fork_url, Some(url.to_string())); + std::env::remove_var("FOUNDRY_ETH_RPC_URL"); +}); // checks that we can set various config values -forgetest_init!(can_set_config_values, |prj: TestProject, _cmd: TestCommand| { - let config = prj.config_from_output(["--via-ir"]); +forgetest_init!(can_set_config_values, |prj, _cmd| { + let config = prj.config_from_output(["--via-ir", "--no-metadata"]); assert!(config.via_ir); + assert_eq!(config.cbor_metadata, false); + assert_eq!(config.bytecode_hash, BytecodeHash::None); }); // tests that solc can be explicitly set -forgetest!(can_set_solc_explicitly, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity >0.8.9; +forgetest!(can_set_solc_explicitly, |prj, cmd| { + prj.add_source( + "Foo", + r" +pragma solidity *; contract Greeter {} - "#, - ) - .unwrap(); - - // explicitly set to run with 0.8.10 - let config = Config { solc: Some("0.8.10".into()), ..Default::default() }; - prj.write_config(config); + ", + ) + .unwrap(); - cmd.arg("build"); + prj.update_config(|config| { + config.solc = Some(OTHER_SOLC_VERSION.into()); + }); - assert!(cmd.stdout_lossy().ends_with( - " + cmd.arg("build").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] Compiler run successful! -", - )); + +"#]]); }); // tests that `--use ` works -forgetest!(can_use_solc, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.7.0; +forgetest!(can_use_solc, |prj, cmd| { + prj.add_raw_source( + "Foo", + r" +pragma solidity *; contract Foo {} - "#, - ) - .unwrap(); + ", + ) + .unwrap(); - cmd.args(["build", "--use", "0.7.1"]); + cmd.args(["build", "--use", OTHER_SOLC_VERSION]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - let stdout = cmd.stdout_lossy(); - assert!(stdout.contains("Compiler run successful")); +"#]]); - cmd.forge_fuse().args(["build", "--force", "--use", "solc:0.7.1"]).root_arg(); + cmd.forge_fuse() + .args(["build", "--force", "--use", &format!("solc:{OTHER_SOLC_VERSION}")]) + .root_arg() + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - assert!(stdout.contains("Compiler run successful")); +"#]]); // fails to use solc that does not exist cmd.forge_fuse().args(["build", "--use", "this/solc/does/not/exist"]); - assert!(cmd.stderr_lossy().contains("this/solc/does/not/exist does not exist")); + cmd.assert_failure().stderr_eq(str![[r#" +Error: `solc` this/solc/does/not/exist does not exist + +"#]]); + + // `OTHER_SOLC_VERSION` was installed in previous step, so we can use the path to this directly + let local_solc = Solc::find_or_install(&OTHER_SOLC_VERSION.parse().unwrap()).unwrap(); + cmd.forge_fuse() + .args(["build", "--force", "--use"]) + .arg(local_solc.solc) + .root_arg() + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - // 0.7.1 was installed in previous step, so we can use the path to this directly - let local_solc = ethers::solc::Solc::find_svm_installed_version("0.7.1") - .unwrap() - .expect("solc 0.7.1 is installed"); - cmd.forge_fuse().args(["build", "--force", "--use"]).arg(local_solc.solc).root_arg(); - assert!(stdout.contains("Compiler run successful")); +"#]]); }); // test to ensure yul optimizer can be set as intended -forgetest!(can_set_yul_optimizer, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +forgetest!(can_set_yul_optimizer, |prj, cmd| { + prj.update_config(|config| config.optimizer = Some(true)); + prj.add_source( + "foo.sol", + r" contract Foo { function bar() public pure { assembly { @@ -390,33 +443,28 @@ contract Foo { } } } - "#, - ) - .unwrap(); + ", + ) + .unwrap(); - cmd.arg("build"); - cmd.unchecked_output().stderr_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_set_yul_optimizer.stderr"), - ); + cmd.arg("build").assert_failure().stderr_eq(str![[r#" +Error: Compiler run failed: +Error (6553): The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction. + [FILE]:6:8: + | +6 | assembly { + | ^ (Relevant source part starts here and spans across multiple lines). - // disable yul optimizer explicitly - let config = Config { - optimizer_details: Some(OptimizerDetails { yul: Some(false), ..Default::default() }), - ..Default::default() - }; - prj.write_config(config); +"#]]); - assert!(cmd.stdout_lossy().ends_with( - " -Compiler run successful! -", - )); + // disable yul optimizer explicitly + prj.update_config(|config| config.optimizer_details.get_or_insert_default().yul = Some(false)); + cmd.assert_success(); }); // tests that the lib triple can be parsed -forgetest_init!(can_parse_dapp_libraries, |_prj: TestProject, mut cmd: TestCommand| { - cmd.set_env( +forgetest_init!(can_parse_dapp_libraries, |_prj, cmd| { + cmd.env( "DAPP_LIBRARIES", "src/DssSpell.sol:DssExecLib:0x8De6DDbCd5053d32292AAA0D2105A32d108484a6", ); @@ -428,23 +476,39 @@ forgetest_init!(can_parse_dapp_libraries, |_prj: TestProject, mut cmd: TestComma }); // test that optimizer runs works -forgetest!(can_set_optimizer_runs, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_set_optimizer_runs, |prj, cmd| { // explicitly set optimizer runs - let config = Config { optimizer_runs: 1337, ..Default::default() }; - prj.write_config(config); + prj.update_config(|config| config.optimizer_runs = Some(1337)); let config = cmd.config(); - assert_eq!(config.optimizer_runs, 1337); + assert_eq!(config.optimizer_runs, Some(1337)); let config = prj.config_from_output(["--optimizer-runs", "300"]); - assert_eq!(config.optimizer_runs, 300); + assert_eq!(config.optimizer_runs, Some(300)); +}); + +// +forgetest!(enable_optimizer_when_runs_set, |prj, cmd| { + // explicitly set optimizer runs + prj.update_config(|config| config.optimizer_runs = Some(1337)); + + let config = cmd.config(); + assert!(config.optimizer.unwrap()); +}); + +// test `optimizer_runs` set to 200 by default if optimizer enabled +forgetest!(optimizer_runs_default, |prj, cmd| { + // explicitly set optimizer + prj.update_config(|config| config.optimizer = Some(true)); + + let config = cmd.config(); + assert_eq!(config.optimizer_runs, Some(200)); }); // test that gas_price can be set -forgetest!(can_set_gas_price, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_set_gas_price, |prj, cmd| { // explicitly set gas_price - let config = Config { gas_price: Some(1337), ..Default::default() }; - prj.write_config(config); + prj.update_config(|config| config.gas_price = Some(1337)); let config = cmd.config(); assert_eq!(config.gas_price, Some(1337)); @@ -454,14 +518,13 @@ forgetest!(can_set_gas_price, |prj: TestProject, mut cmd: TestCommand| { }); // test that we can detect remappings from foundry.toml -forgetest_init!(can_detect_lib_foundry_toml, |prj: TestProject, mut cmd: TestCommand| { +forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), ] ); @@ -476,11 +539,10 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj: TestProject, mut cmd: TestCom let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // default - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), // remapping is local to the lib "nested-lib/=lib/nested-lib/src/".parse().unwrap(), @@ -500,13 +562,12 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj: TestProject, mut cmd: TestCom let another_config = cmd.config(); let remappings = another_config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // local to the lib "another-lib/=lib/nested-lib/lib/another-lib/src/".parse().unwrap(), // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), "nested-lib/=lib/nested-lib/src/".parse().unwrap(), // remappings local to the lib @@ -519,13 +580,31 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj: TestProject, mut cmd: TestCom pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( + remappings, + vec![ + // local to the lib + "another-lib/=lib/nested-lib/lib/another-lib/custom-source-dir/".parse().unwrap(), + // global + "forge-std/=lib/forge-std/src/".parse().unwrap(), + "nested-lib/=lib/nested-lib/src/".parse().unwrap(), + // remappings local to the lib + "nested-twice/=lib/nested-lib/lib/another-lib/lib/nested-twice/".parse().unwrap(), + "nested/=lib/nested-lib/lib/nested/".parse().unwrap(), + ] + ); + + // check if lib path is absolute, it should deteect nested lib + let mut config = cmd.config(); + config.libs = vec![nested]; + + let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); + similar_asserts::assert_eq!( remappings, vec![ // local to the lib "another-lib/=lib/nested-lib/lib/another-lib/custom-source-dir/".parse().unwrap(), // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), "nested-lib/=lib/nested-lib/src/".parse().unwrap(), // remappings local to the lib @@ -537,44 +616,91 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj: TestProject, mut cmd: TestCom // test remappings with closer paths are prioritised // so that `dep/=lib/a/src` will take precedent over `dep/=lib/a/lib/b/src` -forgetest_init!( - #[serial_test::serial] - can_prioritise_closer_lib_remappings, - |prj: TestProject, mut cmd: TestCommand| { - let config = cmd.config(); - - // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` - let mut config = config; - config.remappings = - vec![Remapping::from_str("forge-std/=lib/forge-std/src/").unwrap().into()]; - let nested = prj.paths().libraries[0].join("dep1"); - pretty_err(&nested, fs::create_dir_all(&nested)); - let toml_file = nested.join("foundry.toml"); - pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); - - let config = cmd.config(); - let remappings = config.get_all_remappings(); - pretty_assertions::assert_eq!( - remappings, - vec![ - "dep1/=lib/dep1/src/".parse().unwrap(), - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), - "forge-std/=lib/forge-std/src/".parse().unwrap() - ] - ); - } -); +forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { + let config = cmd.config(); + + // create a new lib directly in the `lib` folder with conflicting remapping `forge-std/` + let mut config = config; + config.remappings = vec![Remapping::from_str("forge-std/=lib/forge-std/src/").unwrap().into()]; + let nested = prj.paths().libraries[0].join("dep1"); + pretty_err(&nested, fs::create_dir_all(&nested)); + let toml_file = nested.join("foundry.toml"); + pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); + + let config = cmd.config(); + let remappings = config.get_all_remappings().collect::>(); + similar_asserts::assert_eq!( + remappings, + vec![ + "dep1/=lib/dep1/src/".parse().unwrap(), + "forge-std/=lib/forge-std/src/".parse().unwrap() + ] + ); +}); + +// Test that remappings within root of the project have priority over remappings of sub-projects. +// E.g. `@utils/libraries` mapping from library shouldn't be added if project already has `@utils` +// remapping. +// See +// Test that +// - project defined `@openzeppelin/contracts` remapping is added +// - library defined `@openzeppelin/contracts-upgradeable` remapping is added +// - library defined `@openzeppelin/contracts/upgradeable` remapping is not added as it conflicts +// with project defined `@openzeppelin/contracts` remapping +// See +forgetest_init!(can_prioritise_project_remappings, |prj, cmd| { + let mut config = cmd.config(); + // Add `@utils/` remapping in project config. + config.remappings = vec![ + Remapping::from_str("@utils/=src/").unwrap().into(), + Remapping::from_str("@openzeppelin/contracts=lib/openzeppelin-contracts/").unwrap().into(), + ]; + let proj_toml_file = prj.paths().root.join("foundry.toml"); + pretty_err(&proj_toml_file, fs::write(&proj_toml_file, config.to_string_pretty().unwrap())); + + // Create a new lib in the `lib` folder with conflicting `@utils/libraries` remapping. + // This should be filtered out from final remappings as root project already has `@utils/`. + let nested = prj.paths().libraries[0].join("dep1"); + pretty_err(&nested, fs::create_dir_all(&nested)); + let mut lib_config = Config::load_with_root(&nested).unwrap(); + lib_config.remappings = vec![ + Remapping::from_str("@utils/libraries/=src/").unwrap().into(), + Remapping::from_str("@openzeppelin/contracts-upgradeable/=lib/openzeppelin-upgradeable/") + .unwrap() + .into(), + Remapping::from_str( + "@openzeppelin/contracts/upgradeable/=lib/openzeppelin-contracts/upgradeable/", + ) + .unwrap() + .into(), + ]; + let lib_toml_file = nested.join("foundry.toml"); + pretty_err(&lib_toml_file, fs::write(&lib_toml_file, lib_config.to_string_pretty().unwrap())); + + cmd.args(["remappings", "--pretty"]).assert_success().stdout_eq(str![[r#" +Global: +- @utils/=src/ +- @openzeppelin/contracts/=lib/openzeppelin-contracts/ +- @openzeppelin/contracts-upgradeable/=lib/dep1/lib/openzeppelin-upgradeable/ +- dep1/=lib/dep1/src/ +- forge-std/=lib/forge-std/src/ + + +"#]]); +}); // test to check that foundry.toml libs section updates on install -forgetest!(can_update_libs_section, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_update_libs_section, |prj, cmd| { cmd.git_init(); // explicitly set gas_price - let init = Config { libs: vec!["node_modules".into()], ..Default::default() }; - prj.write_config(init); + prj.update_config(|config| config.libs = vec!["node_modules".into()]); + + cmd.args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq(str![[r#" +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] - cmd.args(["install", "foundry-rs/forge-std", "--no-commit"]); - cmd.assert_non_empty_stdout(); +"#]]); let config = cmd.forge_fuse().config(); // `lib` was added automatically @@ -582,8 +708,11 @@ forgetest!(can_update_libs_section, |prj: TestProject, mut cmd: TestCommand| { assert_eq!(config.libs, expected); // additional install don't edit `libs` - cmd.forge_fuse().args(["install", "dapphub/ds-test", "--no-commit"]); - cmd.assert_non_empty_stdout(); + cmd.forge_fuse().args(["install", "dapphub/ds-test"]).assert_success().stdout_eq(str![[r#" +Installing ds-test in [..] (url: Some("https://github.com/dapphub/ds-test"), tag: None) + Installed ds-test + +"#]]); let config = cmd.forge_fuse().config(); assert_eq!(config.libs, expected); @@ -591,40 +720,37 @@ forgetest!(can_update_libs_section, |prj: TestProject, mut cmd: TestCommand| { // test to check that loading the config emits warnings on the root foundry.toml and // is silent for any libs -forgetest!(config_emit_warnings, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(config_emit_warnings, |prj, cmd| { cmd.git_init(); - cmd.args(["install", "foundry-rs/forge-std", "--no-commit"]); - cmd.assert_non_empty_stdout(); + cmd.args(["install", "foundry-rs/forge-std"]).assert_success().stdout_eq(str![[r#" +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + +"#]]); - let faulty_toml = r#"[default] + let faulty_toml = r"[default] src = 'src' out = 'out' - libs = ['lib']"#; + libs = ['lib']"; fs::write(prj.root().join("foundry.toml"), faulty_toml).unwrap(); fs::write(prj.root().join("lib").join("forge-std").join("foundry.toml"), faulty_toml).unwrap(); - cmd.forge_fuse().args(["config"]); - let output = cmd.execute(); - assert!(output.status.success()); - assert_eq!( - String::from_utf8_lossy(&output.stderr) - .lines() - .filter(|line| { line.contains("Unknown section [default]") }) - .count(), - 1 - ) + cmd.forge_fuse().args(["config"]).assert_success().stderr_eq(str![[r#" +Warning: Found unknown config section in foundry.toml: [default] +This notation for profiles has been deprecated and may result in the profile not being registered in future versions. +Please use [profile.default] instead or run `forge config --fix`. + +"#]]); }); -forgetest_init!(can_skip_remappings_auto_detection, |prj: TestProject, mut cmd: TestCommand| { +forgetest_init!(can_skip_remappings_auto_detection, |prj, cmd| { // explicitly set remapping and libraries - let config = Config { - remappings: vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()], - auto_detect_remappings: false, - ..Default::default() - }; - prj.write_config(config); + prj.update_config(|config| { + config.remappings = vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()]; + config.auto_detect_remappings = false; + }); let config = cmd.config(); @@ -632,3 +758,1038 @@ forgetest_init!(can_skip_remappings_auto_detection, |prj: TestProject, mut cmd: assert_eq!(config.remappings.len(), 1); assert_eq!("remapping/=lib/remapping/", config.remappings[0].to_string()); }); + +forgetest_init!(can_parse_default_fs_permissions, |_prj, cmd| { + let config = cmd.config(); + + assert_eq!(config.fs_permissions.len(), 1); + let permissions = config.fs_permissions.joined(Path::new("test")); + let out_permission = permissions.find_permission(Path::new("test/out")).unwrap(); + assert_eq!(FsAccessPermission::Read, out_permission); +}); + +forgetest_init!(can_parse_custom_fs_permissions, |prj, cmd| { + // explicitly set fs permissions + prj.update_config(|config| { + config.fs_permissions = FsPermissions::new(vec![ + PathPermission::read("./read"), + PathPermission::write("./write"), + PathPermission::read_write("./write/contracts"), + ]); + }); + + let config = cmd.config(); + + assert_eq!(config.fs_permissions.len(), 3); + + // check read permission + let permission = config.fs_permissions.find_permission(Path::new("./read")).unwrap(); + assert_eq!(permission, FsAccessPermission::Read); + // check nested write permission + let permission = + config.fs_permissions.find_permission(Path::new("./write/MyContract.sol")).unwrap(); + assert_eq!(permission, FsAccessPermission::Write); + // check nested read-write permission + let permission = config + .fs_permissions + .find_permission(Path::new("./write/contracts/MyContract.sol")) + .unwrap(); + assert_eq!(permission, FsAccessPermission::ReadWrite); + // check no permission + let permission = + config.fs_permissions.find_permission(Path::new("./bogus")).unwrap_or_default(); + assert_eq!(permission, FsAccessPermission::None); +}); + +#[cfg(not(target_os = "windows"))] +forgetest_init!(can_resolve_symlink_fs_permissions, |prj, cmd| { + // write config in packages/files/config.json + let config_path = prj.root().join("packages").join("files"); + fs::create_dir_all(&config_path).unwrap(); + fs::write(config_path.join("config.json"), "{ enabled: true }").unwrap(); + + // symlink packages/files dir as links/ + std::os::unix::fs::symlink( + Path::new("./packages/../packages/../packages/files"), + prj.root().join("links"), + ) + .unwrap(); + + // write config, give read access to links/ symlink to packages/files/ + prj.update_config(|config| { + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read(Path::new("./links/config.json"))]); + }); + + let config = cmd.config(); + let mut fs_permissions = config.fs_permissions; + fs_permissions.join_all(prj.root()); + assert_eq!(fs_permissions.len(), 1); + + // read permission to file should be granted through symlink + let permission = fs_permissions.find_permission(&config_path.join("config.json")).unwrap(); + assert_eq!(permission, FsAccessPermission::Read); +}); + +// tests if evm version is normalized for config output +forgetest!(normalize_config_evm_version, |_prj, cmd| { + let output = cmd + .args(["config", "--use", "0.8.0", "--json"]) + .assert_success() + .get_output() + .stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::Istanbul); + + // See + let output = cmd + .forge_fuse() + .args(["config", "--use", "0.8.17", "--json"]) + .assert_success() + .get_output() + .stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::London); + + let output = cmd + .forge_fuse() + .args(["config", "--use", "0.8.18", "--json"]) + .assert_success() + .get_output() + .stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::Paris); + + let output = cmd + .forge_fuse() + .args(["config", "--use", "0.8.23", "--json"]) + .assert_success() + .get_output() + .stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::Shanghai); + + let output = cmd + .forge_fuse() + .args(["config", "--use", "0.8.26", "--json"]) + .assert_success() + .get_output() + .stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::Cancun); +}); + +// Tests that root paths are properly resolved even if submodule specifies remappings for them. +// See +forgetest_init!(test_submodule_root_path_remappings, |prj, cmd| { + prj.add_script( + "BaseScript.sol", + r#" +import "forge-std/Script.sol"; + +contract BaseScript is Script { +} + "#, + ) + .unwrap(); + prj.add_script( + "MyScript.sol", + r#" +import "script/BaseScript.sol"; + +contract MyScript is BaseScript { +} + "#, + ) + .unwrap(); + + let nested = prj.paths().libraries[0].join("another-dep"); + pretty_err(&nested, fs::create_dir_all(&nested)); + let mut lib_config = Config::load_with_root(&nested).unwrap(); + lib_config.remappings = vec![ + Remapping::from_str("test/=test/").unwrap().into(), + Remapping::from_str("script/=script/").unwrap().into(), + ]; + let lib_toml_file = nested.join("foundry.toml"); + pretty_err(&lib_toml_file, fs::write(&lib_toml_file, lib_config.to_string_pretty().unwrap())); + cmd.forge_fuse().args(["build"]).assert_success(); +}); + +// Tests that project remappings use config paths. +// For `src=src/contracts` config, remapping should be `src/contracts/ = src/contracts/`. +// For `src=src` config, remapping should be `src/ = src/`. +// +forgetest_init!(test_project_remappings, |prj, cmd| { + prj.update_config(|config| { + config.src = "src/contracts".into(); + config.remappings = vec![Remapping::from_str("contracts/=src/contracts/").unwrap().into()]; + }); + + // Add Counter.sol in `src/contracts` project dir. + let src_dir = &prj.root().join("src/contracts"); + pretty_err(src_dir, fs::create_dir_all(src_dir)); + pretty_err( + src_dir.join("Counter.sol"), + fs::write(src_dir.join("Counter.sol"), "contract Counter{}"), + ); + prj.add_test( + "CounterTest.sol", + r#" +import "contracts/Counter.sol"; + +contract CounterTest { +} + "#, + ) + .unwrap(); + cmd.forge_fuse().args(["build"]).assert_success(); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(test_default_config, |prj, cmd| { + prj.write_config(Config::default()); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +[profile.default] +src = "src" +test = "test" +script = "script" +out = "out" +libs = ["lib"] +remappings = ["forge-std/=lib/forge-std/src/"] +auto_detect_remappings = true +libraries = [] +cache = true +cache_path = "cache" +snapshots = "snapshots" +gas_snapshot_check = false +gas_snapshot_emit = true +broadcast = "broadcast" +allow_paths = [] +include_paths = [] +skip = [] +force = false +evm_version = "cancun" +gas_reports = ["*"] +gas_reports_ignore = [] +gas_reports_include_tests = false +auto_detect_solc = true +offline = false +optimizer = false +optimizer_runs = 200 +verbosity = 0 +ignored_error_codes = [ + "license", + "code-size", + "init-code-size", + "transient-storage", +] +ignored_warnings_from = [] +deny_warnings = false +test_failures_file = "cache/test-failures" +show_progress = false +ffi = false +allow_internal_expect_revert = false +always_use_create_2_factory = false +prompt_timeout = 120 +sender = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" +tx_origin = "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38" +initial_balance = "0xffffffffffffffffffffffff" +block_number = 1 +gas_limit = 1073741824 +block_base_fee_per_gas = 0 +block_coinbase = "0x0000000000000000000000000000000000000000" +block_timestamp = 1 +block_difficulty = 0 +block_prevrandao = "0x0000000000000000000000000000000000000000000000000000000000000000" +memory_limit = 134217728 +extra_output = [] +extra_output_files = [] +names = false +sizes = false +via_ir = false +ast = false +no_storage_caching = false +no_rpc_rate_limit = false +use_literal_content = false +bytecode_hash = "ipfs" +cbor_metadata = true +sparse_mode = false +build_info = false +isolate = false +disable_block_gas_limit = false +unchecked_cheatcode_artifacts = false +create2_library_salt = "0x0000000000000000000000000000000000000000000000000000000000000000" +create2_deployer = "0x4e59b44847b379578588920ca78fbf26c0b4956c" +assertions_revert = true +legacy_assertions = false +odyssey = false +transaction_timeout = 120 +eof = false +additional_compiler_profiles = [] +compilation_restrictions = [] + +[[profile.default.fs_permissions]] +access = "read" +path = "out" + +[profile.default.rpc_storage_caching] +chains = "all" +endpoints = "all" + +[fmt] +line_length = 120 +tab_width = 4 +bracket_spacing = false +int_types = "long" +multiline_func_header = "attributes_first" +quote_style = "double" +number_underscore = "preserve" +hex_underscore = "remove" +single_line_statement_blocks = "preserve" +override_spacing = false +wrap_comments = false +ignore = [] +contract_new_lines = false +sort_imports = false + +[doc] +out = "docs" +title = "" +book = "book.toml" +homepage = "README.md" +ignore = [] + +[fuzz] +runs = 256 +max_test_rejects = 65536 +dictionary_weight = 40 +include_storage = true +include_push_bytes = true +max_fuzz_dictionary_addresses = 15728640 +max_fuzz_dictionary_values = 6553600 +gas_report_samples = 256 +failure_persist_dir = "cache/fuzz" +failure_persist_file = "failures" +show_logs = false + +[invariant] +runs = 256 +depth = 500 +fail_on_revert = false +call_override = false +dictionary_weight = 80 +include_storage = true +include_push_bytes = true +max_fuzz_dictionary_addresses = 15728640 +max_fuzz_dictionary_values = 6553600 +shrink_run_limit = 5000 +max_assume_rejects = 65536 +gas_report_samples = 256 +failure_persist_dir = "cache/invariant" +show_metrics = false +show_solidity = false + +[labels] + +[vyper] + +[bind_json] +out = "utils/JsonBindings.sol" +include = [] +exclude = [] + + +"#]]); + + cmd.forge_fuse().args(["config", "--json"]).assert_success().stdout_eq(str![[r#" +{ + "src": "src", + "test": "test", + "script": "script", + "out": "out", + "libs": [ + "lib" + ], + "remappings": [ + "forge-std/=lib/forge-std/src/" + ], + "auto_detect_remappings": true, + "libraries": [], + "cache": true, + "cache_path": "cache", + "snapshots": "snapshots", + "gas_snapshot_check": false, + "gas_snapshot_emit": true, + "broadcast": "broadcast", + "allow_paths": [], + "include_paths": [], + "skip": [], + "force": false, + "evm_version": "cancun", + "gas_reports": [ + "*" + ], + "gas_reports_ignore": [], + "gas_reports_include_tests": false, + "solc": null, + "auto_detect_solc": true, + "offline": false, + "optimizer": false, + "optimizer_runs": 200, + "optimizer_details": null, + "model_checker": null, + "verbosity": 0, + "eth_rpc_url": null, + "eth_rpc_jwt": null, + "eth_rpc_timeout": null, + "eth_rpc_headers": null, + "etherscan_api_key": null, + "ignored_error_codes": [ + "license", + "code-size", + "init-code-size", + "transient-storage" + ], + "ignored_warnings_from": [], + "deny_warnings": false, + "match_test": null, + "no_match_test": null, + "match_contract": null, + "no_match_contract": null, + "match_path": null, + "no_match_path": null, + "no_match_coverage": null, + "test_failures_file": "cache/test-failures", + "threads": null, + "show_progress": false, + "fuzz": { + "runs": 256, + "max_test_rejects": 65536, + "seed": null, + "dictionary_weight": 40, + "include_storage": true, + "include_push_bytes": true, + "max_fuzz_dictionary_addresses": 15728640, + "max_fuzz_dictionary_values": 6553600, + "gas_report_samples": 256, + "failure_persist_dir": "cache/fuzz", + "failure_persist_file": "failures", + "show_logs": false, + "timeout": null + }, + "invariant": { + "runs": 256, + "depth": 500, + "fail_on_revert": false, + "call_override": false, + "dictionary_weight": 80, + "include_storage": true, + "include_push_bytes": true, + "max_fuzz_dictionary_addresses": 15728640, + "max_fuzz_dictionary_values": 6553600, + "shrink_run_limit": 5000, + "max_assume_rejects": 65536, + "gas_report_samples": 256, + "failure_persist_dir": "cache/invariant", + "show_metrics": false, + "timeout": null, + "show_solidity": false + }, + "ffi": false, + "allow_internal_expect_revert": false, + "always_use_create_2_factory": false, + "prompt_timeout": 120, + "sender": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "tx_origin": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "initial_balance": "0xffffffffffffffffffffffff", + "block_number": 1, + "fork_block_number": null, + "chain_id": null, + "gas_limit": 1073741824, + "code_size_limit": null, + "gas_price": null, + "block_base_fee_per_gas": 0, + "block_coinbase": "0x0000000000000000000000000000000000000000", + "block_timestamp": 1, + "block_difficulty": 0, + "block_prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "block_gas_limit": null, + "memory_limit": 134217728, + "extra_output": [], + "extra_output_files": [], + "names": false, + "sizes": false, + "via_ir": false, + "ast": false, + "rpc_storage_caching": { + "chains": "all", + "endpoints": "all" + }, + "no_storage_caching": false, + "no_rpc_rate_limit": false, + "use_literal_content": false, + "bytecode_hash": "ipfs", + "cbor_metadata": true, + "revert_strings": null, + "sparse_mode": false, + "build_info": false, + "build_info_path": null, + "fmt": { + "line_length": 120, + "tab_width": 4, + "bracket_spacing": false, + "int_types": "long", + "multiline_func_header": "attributes_first", + "quote_style": "double", + "number_underscore": "preserve", + "hex_underscore": "remove", + "single_line_statement_blocks": "preserve", + "override_spacing": false, + "wrap_comments": false, + "ignore": [], + "contract_new_lines": false, + "sort_imports": false + }, + "doc": { + "out": "docs", + "title": "", + "book": "book.toml", + "homepage": "README.md", + "ignore": [] + }, + "bind_json": { + "out": "utils/JsonBindings.sol", + "include": [], + "exclude": [] + }, + "fs_permissions": [ + { + "access": "read", + "path": "out" + } + ], + "isolate": false, + "disable_block_gas_limit": false, + "labels": {}, + "unchecked_cheatcode_artifacts": false, + "create2_library_salt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "create2_deployer": "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "vyper": {}, + "dependencies": null, + "soldeer": null, + "assertions_revert": true, + "legacy_assertions": false, + "odyssey": false, + "transaction_timeout": 120, + "eof": false, + "additional_compiler_profiles": [], + "compilation_restrictions": [] +} + +"#]]); +}); + +forgetest_init!(test_optimizer_config, |prj, cmd| { + // Default settings: optimizer disabled, optimizer runs 200. + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = false +optimizer_runs = 200 +... + +"#]]); + + // Optimizer set to true: optimizer runs set to default value of 200. + prj.update_config(|config| config.optimizer = Some(true)); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = true +optimizer_runs = 200 +... + +"#]]); + + // Optimizer runs set to 0: optimizer should be disabled, runs set to 0. + prj.update_config(|config| { + config.optimizer = None; + config.optimizer_runs = Some(0); + }); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = false +optimizer_runs = 0 +... + +"#]]); + + // Optimizer runs set to 500: optimizer should be enabled, runs set to 500. + prj.update_config(|config| { + config.optimizer = None; + config.optimizer_runs = Some(500); + }); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = true +optimizer_runs = 500 +... + +"#]]); + + // Optimizer disabled and runs set to 500: optimizer should be disabled, runs set to 500. + prj.update_config(|config| { + config.optimizer = Some(false); + config.optimizer_runs = Some(500); + }); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = false +optimizer_runs = 500 +... + +"#]]); + + // Optimizer enabled and runs set to 0: optimizer should be enabled, runs set to 0. + prj.update_config(|config| { + config.optimizer = Some(true); + config.optimizer_runs = Some(0); + }); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +optimizer = true +optimizer_runs = 0 +... + +"#]]); +}); + +forgetest_init!(test_gas_snapshot_check_config, |prj, cmd| { + // Default settings: gas_snapshot_check disabled. + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +gas_snapshot_check = false +... + +"#]]); + + prj.insert_ds_test(); + + prj.add_source( + "Flare.sol", + r#" +contract Flare { + bytes32[] public data; + + function run(uint256 n_) public { + for (uint256 i = 0; i < n_; i++) { + data.push(keccak256(abi.encodePacked(i))); + } + } +} + "#, + ) + .unwrap(); + + let test_contract = |n: u32| { + format!( + r#" +import "./test.sol"; +import "./Flare.sol"; + +interface Vm {{ + function startSnapshotGas(string memory name) external; + function stopSnapshotGas() external returns (uint256); +}} + +contract GasSnapshotCheckTest is DSTest {{ + Vm constant vm = Vm(HEVM_ADDRESS); + + Flare public flare; + + function setUp() public {{ + flare = new Flare(); + }} + + function testSnapshotGasSectionExternal() public {{ + vm.startSnapshotGas("testAssertGasExternal"); + flare.run({n}); + vm.stopSnapshotGas(); + }} +}} + "# + ) + }; + + // Assert that gas_snapshot_check is disabled by default. + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(1)).unwrap(); + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for src/GasSnapshotCheckTest.sol:GasSnapshotCheckTest +[PASS] testSnapshotGasSectionExternal() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // Enable gas_snapshot_check. + prj.update_config(|config| config.gas_snapshot_check = true); + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +gas_snapshot_check = true +... + +"#]]); + + // Replace the test contract with a new one that will fail the gas snapshot check. + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(2)).unwrap(); + cmd.forge_fuse().args(["test"]).assert_failure().stderr_eq(str![[r#" +... +[GasSnapshotCheckTest] Failed to match snapshots: +- [testAssertGasExternal] [..] → [..] + +Error: Snapshots differ from previous run +... +"#]]); + + // Disable gas_snapshot_check, assert that running the test will pass. + prj.update_config(|config| config.gas_snapshot_check = false); + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for src/GasSnapshotCheckTest.sol:GasSnapshotCheckTest +[PASS] testSnapshotGasSectionExternal() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // Re-enable gas_snapshot_check + // Assert that the new value has been stored from the previous run and re-run the test. + prj.update_config(|config| config.gas_snapshot_check = true); + cmd.forge_fuse().args(["test"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for src/GasSnapshotCheckTest.sol:GasSnapshotCheckTest +[PASS] testSnapshotGasSectionExternal() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... +"#]]); + + // Replace the test contract with a new one that will fail the gas_snapshot_check. + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(3)).unwrap(); + cmd.forge_fuse().args(["test"]).assert_failure().stderr_eq(str![[r#" +... +[GasSnapshotCheckTest] Failed to match snapshots: +- [testAssertGasExternal] [..] → [..] + +Error: Snapshots differ from previous run +... +"#]]); + + // Test that `--gas-snapshot-check=false` flag can be used to disable the gas_snapshot_check. + cmd.forge_fuse().args(["test", "--gas-snapshot-check=false"]).assert_success().stdout_eq(str![ + [r#" +... +Ran 1 test for src/GasSnapshotCheckTest.sol:GasSnapshotCheckTest +[PASS] testSnapshotGasSectionExternal() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... +"#] + ]); + + // Disable gas_snapshot_check in the config file. + // Enable using `FORGE_SNAPSHOT_CHECK` environment variable. + // Assert that this will override the config file value. + prj.update_config(|config| config.gas_snapshot_check = false); + prj.add_source("GasSnapshotCheckTest.sol", &test_contract(4)).unwrap(); + cmd.forge_fuse(); + cmd.env("FORGE_SNAPSHOT_CHECK", "true"); + cmd.args(["test"]).assert_failure().stderr_eq(str![[r#" +... +[GasSnapshotCheckTest] Failed to match snapshots: +- [testAssertGasExternal] [..] → [..] + +Error: Snapshots differ from previous run +... +"#]]); + + // Assert that `--gas-snapshot-check=true` flag can be used to enable the gas_snapshot_check + // even when `FORGE_SNAPSHOT_CHECK` is set to false in the environment variable. + cmd.forge_fuse(); + cmd.env("FORGE_SNAPSHOT_CHECK", "false"); + cmd.args(["test", "--gas-snapshot-check=true"]).assert_failure().stderr_eq(str![[r#" +... +[GasSnapshotCheckTest] Failed to match snapshots: +- [testAssertGasExternal] [..] → [..] + +Error: Snapshots differ from previous run +... +"#]]); + + // Finally assert that `--gas-snapshot-check=false` flag can be used to disable the + // gas_snapshot_check even when `FORGE_SNAPSHOT_CHECK` is set to true + cmd.forge_fuse(); + cmd.env("FORGE_SNAPSHOT_CHECK", "true"); + cmd.args(["test", "--gas-snapshot-check=false"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for src/GasSnapshotCheckTest.sol:GasSnapshotCheckTest +[PASS] testSnapshotGasSectionExternal() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... +"#]]); +}); + +forgetest_init!(test_gas_snapshot_emit_config, |prj, cmd| { + // Default settings: gas_snapshot_emit enabled. + cmd.forge_fuse().args(["config"]).assert_success().stdout_eq(str![[r#" +... +gas_snapshot_emit = true +... +"#]]); + + prj.insert_ds_test(); + + prj.add_source( + "GasSnapshotEmitTest.sol", + r#" +import "./test.sol"; + +interface Vm { + function startSnapshotGas(string memory name) external; + function stopSnapshotGas() external returns (uint256); +} + +contract GasSnapshotEmitTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSnapshotGasSection() public { + vm.startSnapshotGas("testSection"); + int n = 1; + vm.stopSnapshotGas(); + } +} + "#, + ) + .unwrap(); + + // Assert that gas_snapshot_emit is enabled by default. + cmd.forge_fuse().args(["test"]).assert_success(); + // Assert that snapshots were emitted to disk. + assert!(prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Remove the snapshot file. + fs::remove_file(prj.root().join("snapshots/GasSnapshotEmitTest.json")).unwrap(); + + // Test that `--gas-snapshot-emit=false` flag can be used to disable writing snapshots. + cmd.forge_fuse().args(["test", "--gas-snapshot-emit=false"]).assert_success(); + // Assert that snapshots were not emitted to disk. + assert!(!prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Test that environment variable `FORGE_SNAPSHOT_EMIT` can be used to disable writing + // snapshots. + cmd.forge_fuse(); + cmd.env("FORGE_SNAPSHOT_EMIT", "false"); + cmd.args(["test"]).assert_success(); + // Assert that snapshots were not emitted to disk. + assert!(!prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Test that `--gas-snapshot-emit=true` flag can be used to enable writing snapshots, even when + // `FORGE_SNAPSHOT_EMIT` is set to false. + cmd.forge_fuse(); + cmd.env("FORGE_SNAPSHOT_EMIT", "false"); + cmd.args(["test", "--gas-snapshot-emit=true"]).assert_success(); + // Assert that snapshots were emitted to disk. + assert!(prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Remove the snapshot file. + fs::remove_file(prj.root().join("snapshots/GasSnapshotEmitTest.json")).unwrap(); + + // Disable gas_snapshot_emit in the config file. + prj.update_config(|config| config.gas_snapshot_emit = false); + cmd.forge_fuse().args(["config"]).assert_success(); + + // Test that snapshots are not emitted to disk, when disabled by config. + cmd.forge_fuse().args(["test"]).assert_success(); + // Assert that snapshots were not emitted to disk. + assert!(!prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Test that `--gas-snapshot-emit=true` flag can be used to enable writing snapshots, when + // disabled by config. + cmd.forge_fuse(); + cmd.args(["test", "--gas-snapshot-emit=true"]).assert_success(); + // Assert that snapshots were emitted to disk. + assert!(prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Remove the snapshot file. + fs::remove_file(prj.root().join("snapshots/GasSnapshotEmitTest.json")).unwrap(); + + // Test that environment variable `FORGE_SNAPSHOT_EMIT` can be used to enable writing snapshots. + cmd.forge_fuse(); + cmd.env("FORGE_SNAPSHOT_EMIT", "true"); + cmd.args(["test"]).assert_success(); + // Assert that snapshots were emitted to disk. + assert!(prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); + + // Remove the snapshot file. + fs::remove_file(prj.root().join("snapshots/GasSnapshotEmitTest.json")).unwrap(); + + // Test that `--gas-snapshot-emit=false` flag can be used to disable writing snapshots, + // even when `FORGE_SNAPSHOT_EMIT` is set to true. + cmd.forge_fuse().args(["test", "--gas-snapshot-emit=false"]).assert_success(); + + // Assert that snapshots were not emitted to disk. + assert!(!prj.root().join("snapshots/GasSnapshotEmitTest.json").exists()); +}); + +// Tests compilation restrictions enables optimizer if optimizer runs set to a value higher than 0. +forgetest_init!(test_additional_compiler_profiles, |prj, cmd| { + prj.add_source( + "v1/Counter.sol", + r#" +contract Counter { +} + "#, + ) + .unwrap(); + + prj.add_source( + "v2/Counter.sol", + r#" +contract Counter { +} + "#, + ) + .unwrap(); + + prj.add_source( + "v3/Counter.sol", + r#" +contract Counter { +} + "#, + ) + .unwrap(); + + // Additional profiles are defined with optimizer runs but without explicitly enabling + // optimizer + // + // additional_compiler_profiles = [ + // { name = "v1", optimizer_runs = 44444444, via_ir = true, evm_version = "cancun" }, + // { name = "v2", optimizer_runs = 111, via_ir = true }, + // { name = "v3", optimizer_runs = 800, evm_version = "istanbul", via_ir = false }, + // ] + // + // compilation_restrictions = [ + // # v1 + // { paths = "src/v1/[!i]*.sol", version = "0.8.16", optimizer_runs = 44444444 }, + // # v2 + // { paths = "src/v2/{Counter}.sol", optimizer_runs = 111 }, + // # v3 + // { paths = "src/v3/*", optimizer_runs = 800 }, + // ] + let v1_profile = SettingsOverrides { + name: "v1".to_string(), + via_ir: Some(true), + evm_version: Some(EvmVersion::Cancun), + optimizer: None, + optimizer_runs: Some(44444444), + bytecode_hash: None, + }; + let v1_restrictions = CompilationRestrictions { + paths: GlobMatcher::from_str("src/v1/[!i]*.sol").unwrap(), + version: Some(VersionReq::from_str("0.8.16").unwrap()), + via_ir: None, + bytecode_hash: None, + min_optimizer_runs: None, + optimizer_runs: Some(44444444), + max_optimizer_runs: None, + min_evm_version: None, + evm_version: None, + max_evm_version: None, + }; + let v2_profile = SettingsOverrides { + name: "v2".to_string(), + via_ir: Some(true), + evm_version: None, + optimizer: None, + optimizer_runs: Some(111), + bytecode_hash: None, + }; + let v2_restrictions = CompilationRestrictions { + paths: GlobMatcher::from_str("src/v2/{Counter}.sol").unwrap(), + version: None, + via_ir: None, + bytecode_hash: None, + min_optimizer_runs: None, + optimizer_runs: Some(111), + max_optimizer_runs: None, + min_evm_version: None, + evm_version: None, + max_evm_version: None, + }; + let v3_profile = SettingsOverrides { + name: "v3".to_string(), + via_ir: Some(false), + evm_version: Some(EvmVersion::Istanbul), + optimizer: None, + optimizer_runs: Some(800), + bytecode_hash: None, + }; + let v3_restrictions = CompilationRestrictions { + paths: GlobMatcher::from_str("src/v3/*").unwrap(), + version: None, + via_ir: None, + bytecode_hash: None, + min_optimizer_runs: None, + optimizer_runs: Some(800), + max_optimizer_runs: None, + min_evm_version: None, + evm_version: None, + max_evm_version: None, + }; + let additional_compiler_profiles = vec![v1_profile, v2_profile, v3_profile]; + let compilation_restrictions = vec![v1_restrictions, v2_restrictions, v3_restrictions]; + prj.update_config(|config| { + config.additional_compiler_profiles = additional_compiler_profiles; + config.compilation_restrictions = compilation_restrictions; + }); + // Should find and build all profiles satisfying settings restrictions. + cmd.forge_fuse().args(["build"]).assert_success(); + prj.assert_artifacts_dir_exists(); + + let artifact_settings = + |artifact| -> (Option, Option, Option, Option) { + let artifact: serde_json::Value = serde_json::from_reader( + fs::File::open(prj.artifacts().join(artifact)).expect("no artifact"), + ) + .expect("invalid artifact"); + let settings = + artifact.get("metadata").unwrap().get("settings").unwrap().as_object().unwrap(); + let optimizer = settings.get("optimizer").unwrap(); + ( + settings.get("viaIR").cloned(), + settings.get("evmVersion").cloned(), + optimizer.get("enabled").cloned(), + optimizer.get("runs").cloned(), + ) + }; + + let (via_ir, evm_version, enabled, runs) = artifact_settings("Counter.sol/Counter.json"); + assert_eq!(None, via_ir); + assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("false", enabled.unwrap().to_string()); + assert_eq!("200", runs.unwrap().to_string()); + + let (via_ir, evm_version, enabled, runs) = artifact_settings("v1/Counter.sol/Counter.json"); + assert_eq!("true", via_ir.unwrap().to_string()); + assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("true", enabled.unwrap().to_string()); + assert_eq!("44444444", runs.unwrap().to_string()); + + let (via_ir, evm_version, enabled, runs) = artifact_settings("v2/Counter.sol/Counter.json"); + assert_eq!("true", via_ir.unwrap().to_string()); + assert_eq!("\"cancun\"", evm_version.unwrap().to_string()); + assert_eq!("true", enabled.unwrap().to_string()); + assert_eq!("111", runs.unwrap().to_string()); + + let (via_ir, evm_version, enabled, runs) = artifact_settings("v3/Counter.sol/Counter.json"); + assert_eq!(None, via_ir); + assert_eq!("\"istanbul\"", evm_version.unwrap().to_string()); + assert_eq!("true", enabled.unwrap().to_string()); + assert_eq!("800", runs.unwrap().to_string()); +}); diff --git a/crates/forge/tests/cli/context.rs b/crates/forge/tests/cli/context.rs new file mode 100644 index 0000000000000..34a7598a37cd9 --- /dev/null +++ b/crates/forge/tests/cli/context.rs @@ -0,0 +1,81 @@ +//! Contains tests for checking forge execution context cheatcodes +const FORGE_TEST_CONTEXT_CONTRACT: &str = r#" +import "./test.sol"; +interface Vm { + enum ForgeContext { TestGroup, Test, Coverage, Snapshot, ScriptGroup, ScriptDryRun, ScriptBroadcast, ScriptResume, Unknown } + function isContext(ForgeContext context) external view returns (bool isContext); +} + +contract ForgeContextTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testForgeTestContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + } + function testForgeSnapshotContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + } + function testForgeCoverageContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + } + + function runDryRun() external view { + require(vm.isContext(Vm.ForgeContext.ScriptGroup) && !vm.isContext(Vm.ForgeContext.TestGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.ScriptDryRun), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptBroadcast), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptResume), "wrong context"); + } + function runBroadcast() external view { + require(vm.isContext(Vm.ForgeContext.ScriptGroup) && !vm.isContext(Vm.ForgeContext.TestGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.ScriptBroadcast), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptDryRun), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptResume), "wrong context"); + } +} + "#; + +// tests that context properly set for `forge test` command +forgetest!(can_set_forge_test_standard_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["test", "--match-test", "testForgeTestContext"]).assert_success(); +}); + +// tests that context properly set for `forge snapshot` command +forgetest!(can_set_forge_test_snapshot_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["snapshot", "--match-test", "testForgeSnapshotContext"]).assert_success(); +}); + +// tests that context properly set for `forge coverage` command +forgetest!(can_set_forge_test_coverage_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["coverage", "--match-test", "testForgeCoverageContext"]).assert_success(); +}); + +// tests that context properly set for `forge script` command +forgetest!(can_set_forge_script_dry_run_context, |prj, cmd| { + prj.insert_ds_test(); + let script = + prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.arg("script").arg(script).args(["--sig", "runDryRun()"]).assert_success(); +}); + +// tests that context properly set for `forge script --broadcast` command +forgetest!(can_set_forge_script_broadcast_context, |prj, cmd| { + prj.insert_ds_test(); + let script = + prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.arg("script").arg(script).args(["--broadcast", "--sig", "runBroadcast()"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs new file mode 100644 index 0000000000000..d06c9e37fa9cd --- /dev/null +++ b/crates/forge/tests/cli/coverage.rs @@ -0,0 +1,1753 @@ +use foundry_common::fs::{self, files_with_ext}; +use foundry_test_utils::{ + snapbox::{Data, IntoData}, + TestCommand, TestProject, +}; +use std::path::Path; + +fn basic_base(prj: TestProject, mut cmd: TestCommand) { + cmd.args(["coverage", "--report=lcov", "--report=summary"]).assert_success().stdout_eq(str![[ + r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Analysing contracts... +Running tests... + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) +Wrote LCOV report. + +╭----------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++======================================================================================+ +| script/Counter.s.sol | 0.00% (0/5) | 0.00% (0/3) | 100.00% (0/0) | 0.00% (0/2) | +|----------------------+---------------+---------------+---------------+---------------| +| src/Counter.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|----------------------+---------------+---------------+---------------+---------------| +| Total | 44.44% (4/9) | 40.00% (2/5) | 100.00% (0/0) | 50.00% (2/4) | +╰----------------------+---------------+---------------+---------------+---------------╯ + +"# + ]]); + + let lcov = prj.root().join("lcov.info"); + assert!(lcov.exists(), "lcov.info was not created"); + let default_lcov = str![[r#" +TN: +SF:script/Counter.s.sol +DA:10,0 +FN:10,CounterScript.setUp +FNDA:0,CounterScript.setUp +DA:12,0 +FN:12,CounterScript.run +FNDA:0,CounterScript.run +DA:13,0 +DA:15,0 +DA:17,0 +FNF:2 +FNH:0 +LF:5 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Counter.sol +DA:7,258 +FN:7,Counter.setNumber +FNDA:258,Counter.setNumber +DA:8,258 +DA:11,1 +FN:11,Counter.increment +FNDA:1,Counter.increment +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]]; + assert_data_eq!(Data::read_from(&lcov, None), default_lcov.clone()); + assert_lcov( + cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=1"]), + default_lcov, + ); + + assert_lcov( + cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=2"]), + str![[r#" +TN: +SF:script/Counter.s.sol +DA:10,0 +FN:10,10,CounterScript.setUp +FNDA:0,CounterScript.setUp +DA:12,0 +FN:12,18,CounterScript.run +FNDA:0,CounterScript.run +DA:13,0 +DA:15,0 +DA:17,0 +FNF:2 +FNH:0 +LF:5 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Counter.sol +DA:7,258 +FN:7,9,Counter.setNumber +FNDA:258,Counter.setNumber +DA:8,258 +DA:11,1 +FN:11,13,Counter.increment +FNDA:1,Counter.increment +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); + + assert_lcov( + cmd.forge_fuse().args(["coverage", "--report=lcov", "--lcov-version=2.2"]), + str![[r#" +TN: +SF:script/Counter.s.sol +DA:10,0 +FNL:0,10,10 +FNA:0,0,CounterScript.setUp +DA:12,0 +FNL:1,12,18 +FNA:1,0,CounterScript.run +DA:13,0 +DA:15,0 +DA:17,0 +FNF:2 +FNH:0 +LF:5 +LH:0 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Counter.sol +DA:7,258 +FNL:2,7,9 +FNA:2,258,Counter.setNumber +DA:8,258 +DA:11,1 +FNL:3,11,13 +FNA:3,1,Counter.increment +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); +} + +forgetest_init!(basic, |prj, cmd| { + basic_base(prj, cmd); +}); + +forgetest_init!(basic_crlf, |prj, cmd| { + // Manually replace `\n` with `\r\n` in the source file. + let make_crlf = |path: &Path| { + fs::write(path, fs::read_to_string(path).unwrap().replace('\n', "\r\n")).unwrap() + }; + make_crlf(&prj.paths().sources.join("Counter.sol")); + make_crlf(&prj.paths().scripts.join("Counter.s.sol")); + + // Should have identical stdout and lcov output. + basic_base(prj, cmd); +}); + +forgetest!(setup, |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(); + + // Assert 100% coverage (init function coverage called in setUp is accounted). + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(no_match, |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").arg("--no-match-coverage=AContract").assert_success().stdout_eq(str![[ + r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/BContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"# + ]]); +}); + +forgetest!(assert, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + function checkA(uint256 a) external pure returns (bool) { + assert(a > 2); + return true; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +interface Vm { + function expectRevert() external; +} + +contract AContractTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + function testAssertBranch() external { + AContract a = new AContract(); + bool result = a.checkA(10); + assertTrue(result); + } + + function testAssertRevertBranch() external { + AContract a = new AContract(); + vm.expectRevert(); + a.checkA(1); + } +} + "#, + ) + .unwrap(); + + // Assert 50% statement coverage for assert failure (assert not considered a branch). + cmd.arg("coverage").args(["--mt", "testAssertRevertBranch"]).assert_success().stdout_eq(str![ + [r#" +... +╭-------------------+--------------+--------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=================================================================================+ +| src/AContract.sol | 66.67% (2/3) | 50.00% (1/2) | 100.00% (0/0) | 100.00% (1/1) | +|-------------------+--------------+--------------+---------------+---------------| +| Total | 66.67% (2/3) | 50.00% (1/2) | 100.00% (0/0) | 100.00% (1/1) | +╰-------------------+--------------+--------------+---------------+---------------╯ + +"#] + ]); + + // Assert 100% statement coverage for proper assert (assert not considered a branch). + cmd.forge_fuse().arg("coverage").args(["--mt", "testAssertBranch"]).assert_success().stdout_eq( + str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (3/3) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (3/3) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]], + ); +}); + +forgetest!(require, |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", "testRequireRevert"]).assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++==================================================================================+ +| src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+--------------+---------------| +| Total | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+--------------+---------------╯ + +"#]]); + + // Assert 50% branch coverage if only happy path tested. + cmd.forge_fuse() + .arg("coverage") + .args(["--mt", "testRequireNoRevert"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++==================================================================================+ +| src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+--------------+---------------| +| Total | 100.00% (2/2) | 100.00% (1/1) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+--------------+---------------╯ + +"#]]); + + // Assert 100% branch coverage. + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (2/2) | 100.00% (1/1) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(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(); + + // We want to make sure DA:8,1 is added only once so line hit is not doubled. + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/AContract.sol +DA:7,1 +FN:7,AContract.foo +FNDA:1,AContract.foo +DA:8,1 +FNF:1 +FNH:1 +LF:2 +LH:2 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); +}); + +forgetest!(branch, |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 < 3) { + 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; + } + + function checkEmptyStatements(uint256 number, uint256[] memory arr) external pure returns (bool) { + // Check that empty statements are covered. + if (number >= arr[0]) { + // Do nothing + } else { + // Do nothing. + } + if (number >= arr[0]) {} + + return true; + } + + function singlePathCoverage(uint256 number) external pure { + if (number < 10) { + if (number < 5) { + number++; + } + number++; + } + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "FooTest.sol", + r#" +import "./test.sol"; +import {Foo} from "./Foo.sol"; + +interface Vm { + function expectRevert(bytes calldata revertData) external; + function expectRevert() external; +} + +contract FooTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Foo internal foo = new Foo(); + + function test_issue_7784() external { + foo.foo(1); + vm.expectRevert(); + foo.foo(2); + vm.expectRevert(); + foo.foo(4); + foo.foo(5); + foo.foo(60); + vm.expectRevert(); + foo.foo(70); + } + + 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); + } + + function test_issue_4314() external { + uint256[] memory arr = new uint256[](1); + arr[0] = 1; + foo.checkEmptyStatements(0, arr); + } + + function test_single_path_child_branch() external { + foo.singlePathCoverage(1); + } + + function test_single_path_parent_branch() external { + foo.singlePathCoverage(9); + } + + function test_single_path_branch() external { + foo.singlePathCoverage(15); + } +} + "#, + ) + .unwrap(); + + // Assert no coverage for single path branch. 2 branches (parent and child) not covered. + cmd.arg("coverage") + .args(["--nmt", "test_single_path_child_branch|test_single_path_parent_branch"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------+----------------+----------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===============================================================================+ +| src/Foo.sol | 91.67% (33/36) | 90.00% (27/30) | 80.00% (8/10) | 100.00% (9/9) | +|-------------+----------------+----------------+---------------+---------------| +| Total | 91.67% (33/36) | 90.00% (27/30) | 80.00% (8/10) | 100.00% (9/9) | +╰-------------+----------------+----------------+---------------+---------------╯ + +"#]]); + + // Assert no coverage for single path child branch. 1 branch (child) not covered. + cmd.forge_fuse() + .arg("coverage") + .args(["--nmt", "test_single_path_child_branch"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------+----------------+----------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===============================================================================+ +| src/Foo.sol | 97.22% (35/36) | 96.67% (29/30) | 90.00% (9/10) | 100.00% (9/9) | +|-------------+----------------+----------------+---------------+---------------| +| Total | 97.22% (35/36) | 96.67% (29/30) | 90.00% (9/10) | 100.00% (9/9) | +╰-------------+----------------+----------------+---------------+---------------╯ + +"#]]); + + // Assert 100% coverage. + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------+-----------------+-----------------+-----------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/Foo.sol | 100.00% (36/36) | 100.00% (30/30) | 100.00% (10/10) | 100.00% (9/9) | +|-------------+-----------------+-----------------+-----------------+---------------| +| Total | 100.00% (36/36) | 100.00% (30/30) | 100.00% (10/10) | 100.00% (9/9) | +╰-------------+-----------------+-----------------+-----------------+---------------╯ + +"#]]); +}); + +forgetest!(function_call, |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. + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+-----------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=====================================================================================+ +| src/AContract.sol | 100.00% (14/14) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +|-------------------+-----------------+---------------+---------------+---------------| +| Total | 100.00% (14/14) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (5/5) | +╰-------------------+-----------------+---------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(try_catch, |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", "happy"]).assert_success().stdout_eq(str![[r#" +... +╭-------------+----------------+----------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++==============================================================================+ +| src/Foo.sol | 75.00% (15/20) | 66.67% (14/21) | 75.00% (3/4) | 100.00% (5/5) | +|-------------+----------------+----------------+--------------+---------------| +| Total | 75.00% (15/20) | 66.67% (14/21) | 75.00% (3/4) | 100.00% (5/5) | +╰-------------+----------------+----------------+--------------+---------------╯ + +"#]]); + + // Assert 100% branch coverage (including clauses without body). + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------+-----------------+-----------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=================================================================================+ +| src/Foo.sol | 100.00% (20/20) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | +|-------------+-----------------+-----------------+---------------+---------------| +| Total | 100.00% (20/20) | 100.00% (21/21) | 100.00% (4/4) | 100.00% (5/5) | +╰-------------+-----------------+-----------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(yul, |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").assert_success().stdout_eq(str![[r#" +... +╭-------------+-----------------+-----------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=================================================================================+ +| src/Foo.sol | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +|-------------+-----------------+-----------------+---------------+---------------| +| Total | 100.00% (30/30) | 100.00% (40/40) | 100.00% (1/1) | 100.00% (7/7) | +╰-------------+-----------------+-----------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(misc, |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").assert_success().stdout_eq(str![[r#" +... +╭-------------+-----------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===============================================================================+ +| src/Foo.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +|-------------+-----------------+---------------+---------------+---------------| +| Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (4/4) | +╰-------------+-----------------+---------------+---------------+---------------╯ + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/8605 +forgetest!(single_statement, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + event IsTrue(bool isTrue); + event IsFalse(bool isFalse); + + function ifElseStatementIgnored(bool flag) external { + if (flag) emit IsTrue(true); + else emit IsFalse(false); + + if (flag) flag = true; + else flag = false; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + function testTrueCoverage() external { + AContract a = new AContract(); + a.ifElseStatementIgnored(true); + } + + function testFalseCoverage() external { + AContract a = new AContract(); + a.ifElseStatementIgnored(false); + } +} + "#, + ) + .unwrap(); + + // Assert 50% coverage for true branches. + cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" +... +╭-------------------+--------------+--------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++================================================================================+ +| src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| +| Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ + +"#]]); + + // Assert 50% coverage for false branches. + cmd.forge_fuse() + .arg("coverage") + .args(["--mt", "testFalseCoverage"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------------+--------------+--------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++================================================================================+ +| src/AContract.sol | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| +| Total | 60.00% (3/5) | 50.00% (2/4) | 50.00% (2/4) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ + +"#]]); + + // Assert 100% coverage (true/false branches properly covered). + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (5/5) | 100.00% (4/4) | 100.00% (4/4) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/8604 +forgetest!(branch_with_calldata_reads, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + event IsTrue(bool isTrue); + event IsFalse(bool isFalse); + + function execute(bool[] calldata isTrue) external { + for (uint256 i = 0; i < isTrue.length; i++) { + if (isTrue[i]) { + emit IsTrue(isTrue[i]); + } else { + emit IsFalse(!isTrue[i]); + } + } + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + function testTrueCoverage() external { + AContract a = new AContract(); + bool[] memory isTrue = new bool[](1); + isTrue[0] = true; + a.execute(isTrue); + } + + function testFalseCoverage() external { + AContract a = new AContract(); + bool[] memory isFalse = new bool[](1); + isFalse[0] = false; + a.execute(isFalse); + } +} + "#, + ) + .unwrap(); + + // Assert 50% coverage for true branches. + cmd.arg("coverage").args(["--mt", "testTrueCoverage"]).assert_success().stdout_eq(str![[r#" +... +╭-------------------+--------------+--------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++================================================================================+ +| src/AContract.sol | 80.00% (4/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| +| Total | 80.00% (4/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ + +"#]]); + + // Assert 50% coverage for false branches. + cmd.forge_fuse() + .arg("coverage") + .args(["--mt", "testFalseCoverage"]) + .assert_success() + .stdout_eq(str![[r#" +... +╭-------------------+--------------+--------------+--------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++================================================================================+ +| src/AContract.sol | 60.00% (3/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +|-------------------+--------------+--------------+--------------+---------------| +| Total | 60.00% (3/5) | 80.00% (4/5) | 50.00% (1/2) | 100.00% (1/1) | +╰-------------------+--------------+--------------+--------------+---------------╯ + +"#]]); + + // Assert 100% coverage (true/false branches properly covered). + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (5/5) | 100.00% (5/5) | 100.00% (2/2) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(identical_bytecodes, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + uint256 public number; + address public immutable usdc1; + address public immutable usdc2; + address public immutable usdc3; + address public immutable usdc4; + address public immutable usdc5; + address public immutable usdc6; + + constructor() { + address a = 0x176211869cA2b568f2A7D4EE941E073a821EE1ff; + usdc1 = a; + usdc2 = a; + usdc3 = a; + usdc4 = a; + usdc5 = a; + usdc6 = a; + } + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + AContract public counter; + + function setUp() public { + counter = new AContract(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } +} + "#, + ) + .unwrap(); + + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+-----------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=====================================================================================+ +| src/AContract.sol | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | +|-------------------+-----------------+---------------+---------------+---------------| +| Total | 100.00% (12/12) | 100.00% (9/9) | 100.00% (0/0) | 100.00% (3/3) | +╰-------------------+-----------------+---------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(constructors, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + bool public active; + + constructor() { + active = true; + } +} + +contract BContract { + bool public active; + + constructor() { + active = true; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import "./AContract.sol"; + +contract AContractTest is DSTest { + function test_constructors() public { + AContract a = new AContract(); + BContract b = new BContract(); + } +} + "#, + ) + .unwrap(); + + cmd.arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/9270, https://github.com/foundry-rs/foundry/issues/9444 +// Test that special functions with no statements are not counted. +// TODO: We should support this, but for now just ignore them. +// See TODO in `visit_function_definition`: https://github.com/foundry-rs/foundry/issues/9458 +forgetest!(empty_functions, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + constructor() {} + + receive() external payable {} + + function increment() public {} +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import "./AContract.sol"; + +contract AContractTest is DSTest { + function test_constructors() public { + AContract a = new AContract(); + a.increment(); + (bool success,) = address(a).call{value: 1}(""); + require(success); + } +} + "#, + ) + .unwrap(); + + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/AContract.sol +DA:9,1 +FN:9,AContract.increment +FNDA:1,AContract.increment +FNF:1 +FNH:1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); + + // Assert there's only one function (`increment`) reported. + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (1/1) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (1/1) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (1/1) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +// Test coverage for `receive` functions. +forgetest!(receive, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + uint256 public counter = 0; + + constructor() { + counter = 1; + } + + receive() external payable { + counter = msg.value; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import "./AContract.sol"; + +contract AContractTest is DSTest { + function test_constructors() public { + AContract a = new AContract(); + address(a).call{value: 5}(""); + require(a.counter() == 5); + } +} + "#, + ) + .unwrap(); + + // Assert both constructor and receive functions coverage reported and appear in LCOV. + assert_lcov( + cmd.arg("coverage"), + str![[r#" +TN: +SF:src/AContract.sol +DA:7,1 +FN:7,AContract.constructor +FNDA:1,AContract.constructor +DA:8,1 +DA:11,1 +FN:11,AContract.receive +FNDA:1,AContract.receive +DA:12,1 +FNF:2 +FNH:2 +LF:4 +LH:4 +BRF:0 +BRH:0 +end_of_record + +"#]], + ); + + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/9322 +// Test coverage with `--ir-minimum` for solidity < 0.8.5. +forgetest!(ir_minimum_early, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +pragma solidity 0.8.4; + +contract AContract { + function isContract(address account) internal view returns (bool) { + bytes32 codehash; + bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + assembly { + codehash := extcodehash(account) + } + return (codehash != accountHash && codehash != 0x0); + } +} + "#, + ) + .unwrap(); + + // Assert coverage doesn't fail with `Error: Unknown key "inliner"`. + cmd.arg("coverage").arg("--ir-minimum").assert_success().stdout_eq(str![[r#" +... +╭-------------------+-------------+--------------+---------------+-------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++==============================================================================+ +| src/AContract.sol | 0.00% (0/5) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | +|-------------------+-------------+--------------+---------------+-------------| +| Total | 0.00% (0/5) | 0.00% (0/4) | 100.00% (0/0) | 0.00% (0/1) | +╰-------------------+-------------+--------------+---------------+-------------╯ + +"#]]); +}); + +forgetest!(no_artifacts_written, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source( + "AContract.sol", + r#" +contract AContract { + int public i; + + function init() public { + i = 0; + } + + function foo() public { + i = 1; + } +} + "#, + ) + .unwrap(); + + prj.add_source( + "AContractTest.sol", + r#" +import "./test.sol"; +import {AContract} from "./AContract.sol"; + +contract AContractTest is DSTest { + AContract a; + + function setUp() public { + a = new AContract(); + a.init(); + } + + function testFoo() public { + a.foo(); + } +} + "#, + ) + .unwrap(); + + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +... +╭-------------------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++===================================================================================+ +| src/AContract.sol | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +|-------------------+---------------+---------------+---------------+---------------| +| Total | 100.00% (4/4) | 100.00% (2/2) | 100.00% (0/0) | 100.00% (2/2) | +╰-------------------+---------------+---------------+---------------+---------------╯ +... +"#]]); + + // no artifacts are to be written + let files = files_with_ext(prj.artifacts(), "json").collect::>(); + + assert!(files.is_empty()); +}); + +#[track_caller] +fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) { + cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data()); +} diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 4f2d67ff58d43..d20e9130c602c 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -4,17 +4,16 @@ use crate::{ constants::*, utils::{self, EnvExternalities}, }; +use alloy_primitives::{hex, Address}; use anvil::{spawn, NodeConfig}; -use ethers::{ - solc::{artifacts::BytecodeHash, remappings::Remapping}, - types::Address, -}; -use foundry_config::Config; +use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; use foundry_test_utils::{ forgetest, forgetest_async, + snapbox::IntoData, + str, util::{OutputExt, TestCommand, TestProject}, }; -use std::{path::PathBuf, str::FromStr}; +use std::str::FromStr; /// This will insert _dummy_ contract that uses a library /// @@ -26,18 +25,14 @@ use std::{path::PathBuf, str::FromStr}; /// returns the contract argument for the create command fn setup_with_simple_remapping(prj: &TestProject) -> String { // explicitly set remapping and libraries - let config = Config { - remappings: vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()], - libraries: vec![format!("remapping/MyLib.sol:MyLib:{:?}", Address::random())], - ..Default::default() - }; - prj.write_config(config); - - prj.inner() - .add_source( - "LinkTest", - r#" -// SPDX-License-Identifier: MIT + prj.update_config(|config| { + config.remappings = vec![Remapping::from_str("remapping/=lib/remapping/").unwrap().into()]; + config.libraries = vec![format!("remapping/MyLib.sol:MyLib:{:?}", Address::random())]; + }); + + prj.add_source( + "LinkTest", + r#" import "remapping/MyLib.sol"; contract LinkTest { function foo() public returns (uint256) { @@ -45,42 +40,35 @@ contract LinkTest { } } "#, - ) - .unwrap(); - - prj.inner() - .add_lib( - "remapping/MyLib", - r#" -// SPDX-License-Identifier: MIT + ) + .unwrap(); + + prj.add_lib( + "remapping/MyLib", + r" library MyLib { function foobar(uint256 a) public view returns (uint256) { return a * 100; } } -"#, - ) - .unwrap(); +", + ) + .unwrap(); "src/LinkTest.sol:LinkTest".to_string() } fn setup_oracle(prj: &TestProject) -> String { - let config = Config { - libraries: vec![format!( + prj.update_config(|c| { + c.libraries = vec![format!( "./src/libraries/ChainlinkTWAP.sol:ChainlinkTWAP:{:?}", Address::random() - )], - ..Default::default() - }; - prj.write_config(config); - - prj.inner() - .add_source( - "Contract", - r#" -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; + )]; + }); + + prj.add_source( + "Contract", + r#" import {ChainlinkTWAP} from "./libraries/ChainlinkTWAP.sol"; contract Contract { function getPrice() public view returns (int latest) { @@ -88,24 +76,20 @@ contract Contract { } } "#, - ) - .unwrap(); - - prj.inner() - .add_source( - "libraries/ChainlinkTWAP", - r#" -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; + ) + .unwrap(); + prj.add_source( + "libraries/ChainlinkTWAP", + r" library ChainlinkTWAP { function getLatestPrice(address base) public view returns (int256) { return 0; } } -"#, - ) - .unwrap(); +", + ) + .unwrap(); "src/Contract.sol:Contract".to_string() } @@ -117,104 +101,365 @@ where { if let Some(info) = info { let contract_path = f(&prj); - cmd.arg("create"); - cmd.args(info.create_args()).arg(contract_path); - let out = cmd.stdout_lossy(); - let _address = utils::parse_deployed_address(out.as_str()) - .unwrap_or_else(|| panic!("Failed to parse deployer {out}")); + let output = cmd + .arg("create") + .args(info.create_args()) + .arg(contract_path) + .assert_success() + .get_output() + .stdout_lossy(); + let _address = utils::parse_deployed_address(output.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {output}")); } } // tests `forge` create on goerli if correct env vars are set -forgetest!(can_create_simple_on_goerli, |prj: TestProject, cmd: TestCommand| { +forgetest!(can_create_simple_on_goerli, |prj, cmd| { create_on_chain(EnvExternalities::goerli(), prj, cmd, setup_with_simple_remapping); }); // tests `forge` create on goerli if correct env vars are set -forgetest!(can_create_oracle_on_goerli, |prj: TestProject, cmd: TestCommand| { +forgetest!(can_create_oracle_on_goerli, |prj, cmd| { create_on_chain(EnvExternalities::goerli(), prj, cmd, setup_oracle); }); // tests `forge` create on mumbai if correct env vars are set -forgetest!(can_create_oracle_on_mumbai, |prj: TestProject, cmd: TestCommand| { +forgetest!(can_create_oracle_on_mumbai, |prj, cmd| { create_on_chain(EnvExternalities::mumbai(), prj, cmd, setup_oracle); }); // tests that we can deploy the template contract -forgetest_async!( - #[serial_test::serial] - can_create_template_contract, - |prj: TestProject, mut cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); - cmd.args(["init", "--force"]); - cmd.assert_non_empty_stdout(); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - cmd.forge_fuse().args([ +forgetest_async!(can_create_template_contract, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.credential().to_bytes()); + + // explicitly byte code hash for consistent checks + prj.update_config(|c| c.bytecode_hash = BytecodeHash::None); + + // Dry-run without the `--broadcast` flag + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + ]); + + // Dry-run + cmd.assert().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Contract: Counter +Transaction: { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "gas": "0x241e7", + "input": "[..]", + "nonce": "0x0", + "chainId": "0x7a69" +} +ABI: [ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setNumber", + "inputs": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] + + +"#]]); + + // Dry-run with `--json` flag + cmd.arg("--json").assert().stdout_eq( + str![[r#" +{ + "contract": "Counter", + "transaction": { + "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "to": null, + "maxFeePerGas": "0x77359401", + "maxPriorityFeePerGas": "0x1", + "gas": "0x241e7", + "input": "[..]", + "nonce": "0x0", + "chainId": "0x7a69" + }, + "abi": [ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setNumber", + "inputs": [ + { + "name": "newNumber", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } + ] +} + +"#]] + .is_json(), + ); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--broadcast", + ]); + + cmd.assert().stdout_eq(str![[r#" +No files changed, compilation skipped +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +[TX_HASH] + +"#]]); +}); + +// tests that we can deploy the template contract +forgetest_async!(can_create_using_unlocked, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let dev = handle.dev_accounts().next().unwrap(); + + // explicitly byte code hash for consistent checks + prj.update_config(|c| c.bytecode_hash = BytecodeHash::None); + + cmd.forge_fuse().args([ + "create", + format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), + "--rpc-url", + rpc.as_str(), + "--from", + format!("{dev:?}").as_str(), + "--unlocked", + "--broadcast", + ]); + + cmd.assert().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +[TX_HASH] + +"#]]); + + cmd.assert().stdout_eq(str![[r#" +No files changed, compilation skipped +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +[TX_HASH] + +"#]]); +}); + +// tests that we can deploy with constructor args +forgetest_async!(can_create_with_constructor_args, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.credential().to_bytes()); + + // explicitly byte code hash for consistent checks + prj.update_config(|c| c.bytecode_hash = BytecodeHash::None); + + prj.add_source( + "ConstructorContract", + r#" +contract ConstructorContract { + string public name; + + constructor(string memory _name) { + name = _name; + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse() + .args([ "create", - format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), - "--use", - "solc:0.8.15", + "./src/ConstructorContract.sol:ConstructorContract", "--rpc-url", rpc.as_str(), "--private-key", pk.as_str(), - ]); + "--broadcast", + "--constructor-args", + "My Constructor", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +[TX_HASH] - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract.stdout"), - ); +"#]]); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_create_template_contract-2nd.stdout"), - ); - } -); + prj.add_source( + "TupleArrayConstructorContract", + r#" +struct Point { + uint256 x; + uint256 y; +} -// tests that we can deploy the template contract -forgetest_async!( - #[serial_test::serial] - can_create_using_unlocked, - |prj: TestProject, mut cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let rpc = handle.http_endpoint(); - let dev = handle.dev_accounts().next().unwrap(); - cmd.args(["init", "--force"]); - cmd.assert_non_empty_stdout(); - - // explicitly byte code hash for consistent checks - let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; - prj.write_config(config); - - cmd.forge_fuse().args([ +contract TupleArrayConstructorContract { + constructor(Point[] memory _points) {} +} +"#, + ) + .unwrap(); + + cmd.forge_fuse() + .args([ "create", - format!("./src/{TEMPLATE_CONTRACT}.sol:{TEMPLATE_CONTRACT}").as_str(), - "--use", - "solc:0.8.15", + "./src/TupleArrayConstructorContract.sol:TupleArrayConstructorContract", "--rpc-url", rpc.as_str(), - "--from", - format!("{dev:?}").as_str(), - "--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"), - ); + "--private-key", + pk.as_str(), + "--broadcast", + "--constructor-args", + "[(1,2), (2,3), (3,4)]", + ]) + .assert() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 +[TX_HASH] + +"#]]); +}); + +// +forgetest_async!(can_create_and_call, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let rpc = handle.http_endpoint(); + let wallet = handle.dev_wallets().next().unwrap(); + let pk = hex::encode(wallet.credential().to_bytes()); + + // explicitly byte code hash for consistent checks + prj.update_config(|c| c.bytecode_hash = BytecodeHash::None); + + prj.add_source( + "UniswapV2Swap", + r#" +contract UniswapV2Swap { + + function pairInfo() public view returns (uint reserveA, uint reserveB, uint totalSupply) { + (reserveA, reserveB, totalSupply) = (0,0,0); } -); + +} +"#, + ) + .unwrap(); + + cmd.forge_fuse() + .args([ + "create", + "./src/UniswapV2Swap.sol:UniswapV2Swap", + "--rpc-url", + rpc.as_str(), + "--private-key", + pk.as_str(), + "--broadcast", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2018): Function state mutability can be restricted to pure + [FILE]:6:5: + | +6 | function pairInfo() public view returns (uint reserveA, uint reserveB, uint totalSupply) { + | ^ (Relevant source part starts here and spans across multiple lines). + +Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 +Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +[TX_HASH] + +"#]]); +}); diff --git a/crates/forge/tests/cli/debug.rs b/crates/forge/tests/cli/debug.rs new file mode 100644 index 0000000000000..c217beeb501ff --- /dev/null +++ b/crates/forge/tests/cli/debug.rs @@ -0,0 +1,106 @@ +use itertools::Itertools; +use std::path::Path; + +// Sets up a debuggable test case. +// Run with `cargo test-debugger`. +forgetest!( + #[ignore = "ran manually"] + manual_debug_setup, + |prj, cmd| { + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); + + prj.add_source("Counter2.sol", r#" +contract A { + address public a; + uint public b; + int public c; + bytes32 public d; + bool public e; + bytes public f; + string public g; + + constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, string memory _g) { + a = _a; + b = _b; + c = _c; + d = _d; + e = _e; + f = _f; + g = _g; + } + + function getA() public view returns (address) { + return a; + } + + function setA(address _a) public { + a = _a; + } +}"#, + ) + .unwrap(); + + let script = prj.add_script("Counter.s.sol", r#" +import "../src/Counter2.sol"; +import "forge-std/Script.sol"; +import "forge-std/Test.sol"; + +contract B is A { + A public other; + address public self = address(this); + + constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, string memory _g) + A(_a, _b, _c, _d, _e, _f, _g) + { + other = new A(_a, _b, _c, _d, _e, _f, _g); + } +} + +contract Script0 is Script, Test { + function run() external { + assertEq(uint256(1), uint256(1)); + + vm.startBroadcast(); + B b = new B(msg.sender, 2 ** 32, -1 * (2 ** 32), keccak256(abi.encode(1)), true, "abcdef", "hello"); + assertEq(b.getA(), msg.sender); + b.setA(tx.origin); + assertEq(b.getA(), tx.origin); + address _b = b.self(); + bytes32 _d = b.d(); + bytes32 _d2 = b.other().d(); + } +}"#, + ) + .unwrap(); + + cmd.forge_fuse().args(["build"]).assert_success(); + + cmd.args([ + "script", + script.to_str().unwrap(), + "--root", + prj.root().to_str().unwrap(), + "--tc=Script0", + "--debug", + ]); + eprintln!("root: {}", prj.root().display()); + let cmd_path = Path::new(cmd.cmd().get_program()).canonicalize().unwrap(); + let args = cmd.cmd().get_args().map(|s| s.to_str().unwrap()).format(" "); + eprintln!(" cmd: {} {args}", cmd_path.display()); + std::mem::forget(prj); + } +); diff --git a/crates/forge/tests/cli/doc.rs b/crates/forge/tests/cli/doc.rs index 9ad34c1df77a5..699b023d0b26e 100644 --- a/crates/forge/tests/cli/doc.rs +++ b/crates/forge/tests/cli/doc.rs @@ -4,8 +4,5 @@ use foundry_test_utils::util::{setup_forge_remote, RemoteProject}; fn can_generate_solmate_docs() { let (prj, _) = setup_forge_remote(RemoteProject::new("transmissions11/solmate").set_build(false)); - prj.forge_command() - .args(["doc", "--build"]) - .ensure_execute_success() - .expect("`forge doc` failed"); + prj.forge_command().args(["doc", "--build"]).assert_success(); } diff --git a/crates/forge/tests/cli/eip712.rs b/crates/forge/tests/cli/eip712.rs new file mode 100644 index 0000000000000..9ec944631d9db --- /dev/null +++ b/crates/forge/tests/cli/eip712.rs @@ -0,0 +1,82 @@ +forgetest!(test_eip712, |prj, cmd| { + let path = prj + .add_source( + "Structs", + r#" +library Structs { + struct Foo { + Bar bar; + } + + struct Bar { + Art art; + } + + struct Art { + uint256 id; + } + + struct Complex { + Structs2.Foo foo2; + Foo[] foos; + Rec[][] recs; + } + + struct Rec { + Rec[] rec; + } +} + +library Structs2 { + struct Foo { + uint256 id; + } + + struct Rec { + Bar[] bar; + } + + struct Bar { + Rec rec; + } + + struct FooBar { + Foo[] foos; + Bar[] bars; + Structs.Foo foo; + Structs.Bar bar; + Rec[] recs; + Structs.Rec rec; + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse().args(["eip712", path.to_string_lossy().as_ref()]).assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +No files changed, compilation skipped +Foo(Bar bar)Art(uint256 id)Bar(Art art) + +Bar(Art art)Art(uint256 id) + +Art(uint256 id) + +Complex(Foo foo2,Foo_1[] foos,Rec[][] recs)Art(uint256 id)Bar(Art art)Foo(uint256 id)Foo_1(Bar bar)Rec(Rec[] rec) + +Rec(Rec[] rec) + +Foo(uint256 id) + +Rec(Bar[] bar)Bar(Rec rec) + +Bar(Rec rec)Rec(Bar[] bar) + +FooBar(Foo[] foos,Bar[] bars,Foo_1 foo,Bar_1 bar,Rec[] recs,Rec_1 rec)Art(uint256 id)Bar(Rec rec)Bar_1(Art art)Foo(uint256 id)Foo_1(Bar_1 bar)Rec(Bar[] bar)Rec_1(Rec_1[] rec) + + +"#]], + ); +}); diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs new file mode 100644 index 0000000000000..5dcdf4be132b4 --- /dev/null +++ b/crates/forge/tests/cli/ext_integration.rs @@ -0,0 +1,127 @@ +use foundry_test_utils::util::ExtTester; + +// Actively maintained tests + +// +#[test] +fn forge_std() { + ExtTester::new("foundry-rs", "forge-std", "464587138602dd194ed0eb5aab15b4721859d422") + // Skip fork tests. + .args(["--nmc", "Fork"]) + .run(); +} + +// +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn prb_math() { + ExtTester::new("PaulRBerg", "prb-math", "b03f814a03558ed5b62f89a57bcc8d720a393f67") + .install_command(&["bun", "install", "--prefer-offline"]) + // Try npm if bun fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +// +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn prb_proxy() { + ExtTester::new("PaulRBerg", "prb-proxy", "e45f5325d4b6003227a6c4bdaefac9453f89de2e") + .install_command(&["bun", "install", "--prefer-offline"]) + // Try npm if bun fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +// +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +fn sablier_v2_core() { + let mut tester = + ExtTester::new("sablier-labs", "v2-core", "43cf7c9d968e61a5a03e9237a71a27165b125414") + // 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] +fn solady() { + ExtTester::new("Vectorized", "solady", "66162801e022c268a2a0f621ac5eb0df4986f6eb").run(); +} + +// +#[test] +#[cfg_attr(windows, ignore = "Windows cannot find installed programs")] +#[cfg(not(feature = "isolate-by-default"))] +fn snekmate() { + ExtTester::new("pcaversaccio", "snekmate", "df226f4a45e86c8f8c3ff1f9fa3443d260002050") + .args(["--nmc", "ERC4626VaultTest"]) + .install_command(&["pnpm", "install", "--prefer-offline"]) + // Try npm if pnpm fails / is not installed. + .install_command(&["npm", "install", "--prefer-offline"]) + .run(); +} + +// +#[test] +fn mds1_multicall3() { + ExtTester::new("mds1", "multicall", "f534fbc9f98386a217eaaf9b29d3d4f6f920d5ec").run(); +} + +// Legacy tests + +// +#[test] +fn solidity_stringutils() { + ExtTester::new("Arachnid", "solidity-stringutils", "4b2fcc43fa0426e19ce88b1f1ec16f5903a2e461") + .run(); +} + +// +#[test] +fn lil_web3() { + ExtTester::new("m1guelpf", "lil-web3", "7346bd28c2586da3b07102d5290175a276949b15").run(); +} + +// +#[test] +fn makerdao_multicall() { + ExtTester::new("makerdao", "multicall", "103a8a28e4e372d582d6539b30031bda4cd48e21").run(); +} + +// Legacy forking tests + +// +#[test] +fn gunilev() { + ExtTester::new("hexonaut", "guni-lev", "15ee8b4c2d28e553c5cd5ba9a2a274af97563bc4") + .fork_block(13633752) + .run(); +} + +// +#[test] +fn convex_shutdown_simulation() { + ExtTester::new( + "mds1", + "convex-shutdown-simulation", + "2537cdebce4396753225c5e616c8e00547d2fcea", + ) + .fork_block(14445961) + .run(); +} diff --git a/crates/forge/tests/cli/failure_assertions.rs b/crates/forge/tests/cli/failure_assertions.rs new file mode 100644 index 0000000000000..611d4d0bd221b --- /dev/null +++ b/crates/forge/tests/cli/failure_assertions.rs @@ -0,0 +1,438 @@ +// Tests in which we want to assert failures. + +forgetest!(test_fail_deprecation, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "DeprecationTestFail.t.sol", + r#" + import "./test.sol"; + contract DeprecationTestFail is DSTest { + function testFail_deprecated() public { + revert("deprecated"); + } + + function testFail_deprecated2() public { + revert("deprecated2"); + } + } + "#, + ) + .unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "DeprecationTestFail"]).assert_failure().stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: `testFail*` has been removed. Consider changing to test_Revert[If|When]_Condition and expecting a revert] Found 2 instances: testFail_deprecated, testFail_deprecated2 ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#, + ); +}); + +forgetest!(expect_revert_tests_should_fail, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + let expect_revert_failure_tests = include_str!("../fixtures/ExpectRevertFailures.t.sol"); + + prj.add_source("ExpectRevertFailures.sol", expect_revert_failure_tests).unwrap(); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectRevertFailureTest"]) + .assert_failure() + .stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: next call did not revert as expected] testShouldFailExpectRevertAnyRevertDidNotRevert() ([GAS]) +[FAIL: next call did not revert as expected] testShouldFailExpectRevertDangling() ([GAS]) +[FAIL: next call did not revert as expected] testShouldFailExpectRevertDidNotRevert() ([GAS]) +[FAIL: Error != expected error: but reverts with this message != should revert with this message] testShouldFailExpectRevertErrorDoesNotMatch() ([GAS]) +[FAIL: next call did not revert as expected] testShouldFailRevertNotOnImmediateNextCall() ([GAS]) +[FAIL: revert: some message] testShouldFailexpectCheatcodeRevertForCreate() ([GAS]) +[FAIL: revert: revert] testShouldFailexpectCheatcodeRevertForExtCall() ([GAS]) +Suite result: FAILED. 0 passed; 7 failed; 0 skipped; [ELAPSED] +... +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectRevertWithReverterFailureTest"]) + .assert_failure() + .stdout_eq( + r#"No files changed, compilation skipped +... +[FAIL: next call did not revert as expected] testShouldFailExpectRevertsNotOnImmediateNextCall() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectRevertCountFailureTest"]) + .assert_failure() + .stdout_eq( + r#"No files changed, compilation skipped +... +[FAIL: call reverted when it was expected not to revert] testShouldFailNoRevert() ([GAS]) +[FAIL: expected 0 reverts with reason: revert, but got one] testShouldFailNoRevertSpecific() ([GAS]) +[FAIL: Error != expected error: second-revert != revert] testShouldFailReverCountSpecifc() ([GAS]) +[FAIL: next call did not revert as expected] testShouldFailRevertCountAny() ([GAS]) +[FAIL: Error != expected error: wrong revert != called a function and then reverted] testShouldFailRevertCountCallsThenReverts() ([GAS]) +Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] +... +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectRevertCountWithReverterFailures"]) + .assert_failure() + .stdout_eq(r#"No files changed, compilation skipped +... +[FAIL: expected 0 reverts from address: 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f, but got one] testShouldFailNoRevertWithReverter() ([GAS]) +[FAIL: Reverter != expected reverter: 0x2e234DAe75C793f67A35089C9d99245E1C58470b != 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] testShouldFailRevertCountWithReverter() ([GAS]) +[FAIL: Error != expected error: wrong revert != revert] testShouldFailReverterCountWithWrongData() ([GAS]) +[FAIL: Reverter != expected reverter: 0x2e234DAe75C793f67A35089C9d99245E1C58470b != 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f] testShouldFailWrongReverterCountWithData() ([GAS]) +Suite result: FAILED. 0 passed; 4 failed; 0 skipped; [ELAPSED] +... +"#); +}); + +forgetest!(expect_call_tests_should_fail, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + let expect_call_failure_tests = include_str!("../fixtures/ExpectCallFailures.t.sol"); + + prj.add_source("ExpectCallFailures.sol", expect_call_failure_tests).unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "ExpectCallFailureTest"]).assert_failure().stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0xc290d6910000000000000000000000000000000000000000000000000000000000000002, value 1 to be called 1 time, but was called 0 times] testShouldFailExpectCallValue() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002 to be called 1 time, but was called 0 times] testShouldFailExpectCallWithData() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f7000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000003 to be called 1 time, but was called 0 times] testShouldFailExpectCallWithMoreParameters() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001, value 0, gas 25000 to be called 1 time, but was called 0 times] testShouldFailExpectCallWithNoValueAndWrongGas() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001, value 0, minimum gas 50001 to be called 1 time, but was called 0 times] testShouldFailExpectCallWithNoValueAndWrongMinGas() ([GAS]) +[FAIL: next call did not revert as expected] testShouldFailExpectCallWithRevertDisallowed() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x3fc7c698 to be called 1 time, but was called 0 times] testShouldFailExpectInnerCall() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002 to be called 3 times, but was called 2 times] testShouldFailExpectMultipleCallsWithDataAdditive() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f7 to be called 1 time, but was called 0 times] testShouldFailExpectSelectorCall() ([GAS]) +Suite result: FAILED. 0 passed; 9 failed; 0 skipped; [ELAPSED] +... +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectCallCountFailureTest"]) + .assert_failure() + .stdout_eq( + r#"No files changed, compilation skipped +... +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0xc290d6910000000000000000000000000000000000000000000000000000000000000002, value 1 to be called 1 time, but was called 0 times] testShouldFailExpectCallCountValue() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001, value 0, gas 25000 to be called 2 times, but was called 0 times] testShouldFailExpectCallCountWithNoValueAndWrongGas() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001, value 0, minimum gas 50001 to be called 1 time, but was called 0 times] testShouldFailExpectCallCountWithNoValueAndWrongMinGas() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002 to be called 2 times, but was called 1 time] testShouldFailExpectCallCountWithWrongCount() ([GAS]) +[FAIL: expected call to 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f with data 0x3fc7c698 to be called 1 time, but was called 0 times] testShouldFailExpectCountInnerCall() ([GAS]) +Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] +... +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectCallMixedFailureTest"]) + .assert_failure() + .stdout_eq( + r#"No files changed, compilation skipped +... +[FAIL: vm.expectCall: counted expected calls can only bet set once] testShouldFailOverrideCountWithCount() ([GAS]) +[FAIL: vm.expectCall: cannot overwrite a counted expectCall with a non-counted expectCall] testShouldFailOverrideCountWithNoCount() ([GAS]) +[FAIL: vm.expectCall: counted expected calls can only bet set once] testShouldFailOverrideNoCountWithCount() ([GAS]) +Suite result: FAILED. 0 passed; 3 failed; 0 skipped; [ELAPSED] +... +"#, + ); +}); + +forgetest!(expect_emit_tests_should_fail, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + let expect_emit_failure_tests = include_str!("../fixtures/ExpectEmitFailures.t.sol"); + + prj.add_source("ExpectEmitFailures.sol", expect_emit_failure_tests).unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "ExpectEmitFailureTest"]).assert_failure().stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: log != expected log] testShouldFailCanMatchConsecutiveEvents() ([GAS]) +[FAIL: log != expected log] testShouldFailDifferentIndexedParameters() ([GAS]) +[FAIL: log != expected log] testShouldFailEmitOnlyAppliesToNextCall() ([GAS]) +[FAIL: next call did not revert as expected] testShouldFailEmitWindowWithRevertDisallowed() ([GAS]) +[FAIL: log != expected log] testShouldFailEventsOnTwoCalls() ([GAS]) +[FAIL: log != expected log; counterexample: calldata=[..] args=[..]] testShouldFailExpectEmit(bool,bool,bool,bool,uint128,uint128,uint128,uint128) (runs: 0, [AVG_GAS]) +[FAIL: log != expected log] testShouldFailExpectEmitAddress() ([GAS]) +[FAIL: log != expected log] testShouldFailExpectEmitAddressWithArgs() ([GAS]) +[FAIL: log != expected log] testShouldFailExpectEmitCanMatchWithoutExactOrder() ([GAS]) +[FAIL: expected an emit, but no logs were emitted afterwards. you might have mismatched events or not enough events were emitted] testShouldFailExpectEmitDanglingNoReference() ([GAS]) +[FAIL: expected an emit, but no logs were emitted afterwards. you might have mismatched events or not enough events were emitted] testShouldFailExpectEmitDanglingWithReference() ([GAS]) +[FAIL: log != expected log; counterexample: calldata=[..] args=[..]] testShouldFailExpectEmitNested(bool,bool,bool,bool,uint128,uint128,uint128,uint128) (runs: 0, [AVG_GAS]) +[FAIL: log != expected log] testShouldFailLowLevelWithoutEmit() ([GAS]) +[FAIL: log != expected log] testShouldFailMatchRepeatedEventsOutOfOrder() ([GAS]) +[FAIL: log != expected log] testShouldFailNoEmitDirectlyOnNextCall() ([GAS]) +Suite result: FAILED. 0 passed; 15 failed; 0 skipped; [ELAPSED] +... +"#, + ); + + cmd.forge_fuse() + .args(["test", "--mc", "ExpectEmitCountFailureTest"]) + .assert_failure() + .stdout_eq( + r#"No files changed, compilation skipped +... +[FAIL: log != expected log] testShouldFailCountEmitsFromAddress() ([GAS]) +[FAIL: log != expected log] testShouldFailCountLessEmits() ([GAS]) +[FAIL: log != expected log] testShouldFailEmitSomethingElse() ([GAS]) +[FAIL: log emitted 1 times, expected 0] testShouldFailNoEmit() ([GAS]) +[FAIL: log emitted 1 times, expected 0] testShouldFailNoEmitFromAddress() ([GAS]) +Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED] +... +"#, + ); +}); + +forgetest!(mem_safety_test_should_fail, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + + let mem_safety_failure_tests = include_str!("../fixtures/MemSafetyFailures.t.sol"); + + prj.add_source("MemSafetyFailures.sol", mem_safety_failure_tests).unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "MemSafetyFailureTest"]).assert_failure().stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: revert: Expected call to fail] testShouldFailExpectSafeMemoryCall() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CALL() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CALLCODE() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]; counterexample: calldata=[..] args=[..]] testShouldFailExpectSafeMemory_CALLDATACOPY(uint256) (runs: 0, [AVG_GAS]) +[FAIL: memory write at offset 0x80 of size [..] not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_CODECOPY() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CREATE() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_CREATE2() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_DELEGATECALL() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_EXTCODECOPY() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_LOG0() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_MLOAD() ([GAS]) +[FAIL: memory write at offset 0x81 of size 0x01 not allowed; safe range: (0x00, 0x60] U (0x80, 0x81]] testShouldFailExpectSafeMemory_MSTORE8_High() ([GAS]) +[FAIL: memory write at offset 0x60 of size 0x01 not allowed; safe range: (0x00, 0x60] U (0x80, 0x81]] testShouldFailExpectSafeMemory_MSTORE8_Low() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_MSTORE_High() ([GAS]) +[FAIL: memory write at offset 0x60 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailExpectSafeMemory_MSTORE_Low() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_RETURN() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_RETURNDATACOPY() ([GAS]) +[FAIL: EvmError: Revert] testShouldFailExpectSafeMemory_REVERT() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_SHA3() ([GAS]) +[FAIL: memory write at offset 0x100 of size 0x60 not allowed; safe range: (0x00, 0x60] U (0x80, 0x100]] testShouldFailExpectSafeMemory_STATICCALL() ([GAS]) +[FAIL: memory write at offset 0xA0 of size 0x20 not allowed; safe range: (0x00, 0x60] U (0x80, 0xA0]] testShouldFailStopExpectSafeMemory() ([GAS]) +Suite result: FAILED. 0 passed; 21 failed; 0 skipped; [ELAPSED] +... +"#, + ); +}); + +forgetest!(ds_style_test_failing, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "DSStyleTest.t.sol", + r#" + import "./test.sol"; + + contract DSStyleTest is DSTest { + function testDSTestFailingAssertions() public { + emit log_string("assertionOne"); + assertEq(uint256(1), uint256(2)); + emit log_string("assertionTwo"); + assertEq(uint256(3), uint256(4)); + emit log_string("done"); + } + } + "#, + ) + .unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "DSStyleTest", "-vv"]).assert_failure().stdout_eq( + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL] testDSTestFailingAssertions() ([GAS]) +Logs: + assertionOne + Error: a == b not satisfied [uint] + Expected: 2 + Actual: 1 + assertionTwo + Error: a == b not satisfied [uint] + Expected: 4 + Actual: 3 + done + +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#, + ); +}); + +forgetest!(failing_setup, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "FailingSetupTest.t.sol", + r#" +import "./test.sol"; + +contract FailingSetupTest is DSTest { + event Test(uint256 n); + + function setUp() public { + emit Test(42); + require(false, "setup failed predictably"); + } + + function testShouldBeMarkedAsFailedBecauseOfSetup() public { + emit log("setup did not fail"); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "FailingSetupTest"]).assert_failure().stdout_eq(str![[ + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: revert: setup failed predictably] setUp() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"# + ]]); +}); + +forgetest!(multiple_after_invariants, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "MultipleAfterInvariantsTest.t.sol", + r#" +import "./test.sol"; + +contract MultipleAfterInvariant is DSTest { + function afterInvariant() public {} + + function afterinvariant() public {} + + function testFailShouldBeMarkedAsFailedBecauseOfAfterInvariant() + public + pure + { + assert(true); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "MultipleAfterInvariant"]).assert_failure().stdout_eq(str![[ + r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: multiple afterInvariant functions] afterInvariant() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"# + ]]); +}); + +forgetest!(multiple_setups, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "MultipleSetupsTest.t.sol", + r#" + +import "./test.sol"; + +contract MultipleSetup is DSTest { + function setUp() public {} + + function setup() public {} + + function testFailShouldBeMarkedAsFailedBecauseOfSetup() public { + assert(true); + } +} + + "#, + ) + .unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "MultipleSetup"]).assert_failure().stdout_eq(str![[ + r#"[COMPILING_FILES] with [SOLC_VERSION] +... +[FAIL: multiple setUp functions] setUp() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +..."# + ]]); +}); + +forgetest!(emit_diff_anonymous, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.add_source( + "EmitDiffAnonymousTest.t.sol", + r#" + import "./test.sol"; + import "./Vm.sol"; + + contract Target { + event AnonymousEventNonIndexed(uint256 a) anonymous; + + function emitAnonymousEventNonIndexed(uint256 a) external { + emit AnonymousEventNonIndexed(a); + } + } + + contract EmitDiffAnonymousTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Target target; + + event DifferentAnonymousEventNonIndexed(string a) anonymous; + + function setUp() public { + target = new Target(); + } + + function testShouldFailEmitDifferentEventNonIndexed() public { + vm.expectEmitAnonymous(false, false, false, false, true); + emit DifferentAnonymousEventNonIndexed("1"); + target.emitAnonymousEventNonIndexed(1); + } + } + "#, + ) + .unwrap(); + + cmd.forge_fuse().args(["test", "--mc", "EmitDiffAnonymousTest"]).assert_failure().stdout_eq( + str![[r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +[FAIL: log != expected log] testShouldFailEmitDifferentEventNonIndexed() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... +"#]], + ); +}); diff --git a/crates/forge/tests/cli/geiger.rs b/crates/forge/tests/cli/geiger.rs new file mode 100644 index 0000000000000..fd21656284744 --- /dev/null +++ b/crates/forge/tests/cli/geiger.rs @@ -0,0 +1,92 @@ +forgetest!(call, |prj, cmd| { + prj.add_source( + "call.sol", + r#" + contract A is Test { + function do_ffi() public { + string[] memory inputs = new string[](1); + vm.ffi(inputs); + } + } + "#, + ) + .unwrap(); + + cmd.arg("geiger").assert_code(1).stderr_eq(str![[r#" +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:7:20 + | +7 | vm.ffi(inputs); + | ^^^ + | + + +"#]]); +}); + +forgetest!(assignment, |prj, cmd| { + prj.add_source( + "assignment.sol", + r#" + contract A is Test { + function do_ffi() public { + string[] memory inputs = new string[](1); + bytes stuff = vm.ffi(inputs); + } + } + "#, + ) + .unwrap(); + + cmd.arg("geiger").assert_code(1).stderr_eq(str![[r#" +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:7:34 + | +7 | bytes stuff = vm.ffi(inputs); + | ^^^ + | + + +"#]]); +}); + +forgetest!(exit_code, |prj, cmd| { + prj.add_source( + "multiple.sol", + r#" + contract A is Test { + function do_ffi() public { + vm.ffi(inputs); + vm.ffi(inputs); + vm.ffi(inputs); + } + } + "#, + ) + .unwrap(); + + cmd.arg("geiger").assert_code(3).stderr_eq(str![[r#" +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:6:20 + | +6 | vm.ffi(inputs); + | ^^^ + | + +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:7:20 + | +7 | vm.ffi(inputs); + | ^^^ + | + +error: usage of unsafe cheatcode `vm.ffi` + [FILE]:8:20 + | +8 | vm.ffi(inputs); + | ^^^ + | + + +"#]]); +}); diff --git a/crates/forge/tests/cli/heavy_integration.rs b/crates/forge/tests/cli/heavy_integration.rs deleted file mode 100644 index 7363b0ca4ef68..0000000000000 --- a/crates/forge/tests/cli/heavy_integration.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Heavy integration tests that can take an hour to run or more. - -use foundry_test_utils::forgetest_external; - -forgetest_external!(maple, "maple-labs/maple-core-v2"); diff --git a/crates/forge/tests/cli/inline_config.rs b/crates/forge/tests/cli/inline_config.rs new file mode 100644 index 0000000000000..ad3f529b17ff4 --- /dev/null +++ b/crates/forge/tests/cli/inline_config.rs @@ -0,0 +1,403 @@ +use std::{fs, path::Path}; + +use serde::{Deserialize, Deserializer}; + +forgetest!(runs, |prj, cmd| { + prj.add_test( + "inline.sol", + " + contract Inline { + /** forge-config: default.fuzz.runs = 2 */ + function test1(bool) public {} + + \t///\t forge-config:\tdefault.fuzz.runs=\t3 \t + + function test2(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/inline.sol:Inline +[PASS] test1(bool) (runs: 2, [AVG_GAS]) +[PASS] test2(bool) (runs: 3, [AVG_GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); + + // Make sure inline config is parsed in coverage too. + cmd.forge_fuse().arg("coverage").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Analysing contracts... +Running tests... + +Ran 2 tests for test/inline.sol:Inline +[PASS] test1(bool) (runs: 2, [AVG_GAS]) +[PASS] test2(bool) (runs: 3, [AVG_GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +╭-------+---------------+---------------+---------------+---------------╮ +| File | % Lines | % Statements | % Branches | % Funcs | ++=======================================================================+ +| Total | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | 100.00% (0/0) | +╰-------+---------------+---------------+---------------+---------------╯ + +"#]]); +}); + +forgetest!(invalid_profile, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: unknown.fuzz.runs = 2 */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[r#" +Error: Inline config error at test/inline.sol:80:123:0: invalid profile `unknown.fuzz.runs = 2`; valid profiles: default + +"#]]); +}); + +// TODO: Uncomment once this done for normal config too. +/* +forgetest!(invalid_key, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: default.fuzzz.runs = 2 */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest!(invalid_key_2, |prj, cmd| { + prj.add_test( + "inline.sol", + " +/** forge-config: default.fuzz.runss = 2 */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: failed to get inline configuration: unknown config section `default`] test(bool) ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); +*/ + +forgetest!(invalid_value, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: default.fuzz.runs = [2] */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: invalid type: found sequence, expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: invalid type: found sequence, expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest!(invalid_value_2, |prj, cmd| { + prj.add_test( + "inline.sol", + " + /** forge-config: default.fuzz.runs = '2' */ + contract Inline { + function test(bool) public {} + } + ", + ) + .unwrap(); + + cmd.arg("test").assert_failure().stderr_eq(str![[]]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:Inline +[FAIL: invalid type: found string "2", expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/inline.sol:Inline +[FAIL: invalid type: found string "2", expected u32 for key "default.fuzz.runs" in inline config] setUp() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest_init!(config_inline_isolate, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "inline.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract Dummy { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + } + + contract FunctionConfig is Test { + Dummy dummy; + + function setUp() public { + dummy = new Dummy(); + } + + /// forge-config: default.isolate = true + function test_isolate() public { + vm.startSnapshotGas("testIsolatedFunction"); + dummy.setNumber(1); + vm.stopSnapshotGas(); + } + + function test_non_isolate() public { + vm.startSnapshotGas("testNonIsolatedFunction"); + dummy.setNumber(2); + vm.stopSnapshotGas(); + } + } + + /// forge-config: default.isolate = true + contract ContractConfig is Test { + Dummy dummy; + + function setUp() public { + dummy = new Dummy(); + } + + function test_non_isolate() public { + vm.startSnapshotGas("testIsolatedContract"); + dummy.setNumber(3); + vm.stopSnapshotGas(); + } + } + "#, + ) + .unwrap(); + + cmd.args(["test", "-j1"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/inline.sol:ContractConfig +[PASS] test_non_isolate() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 tests for test/inline.sol:FunctionConfig +[PASS] test_isolate() ([GAS]) +[PASS] test_non_isolate() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) + +"#]]); + + assert!(prj.root().join("snapshots/FunctionConfig.json").exists()); + assert!(prj.root().join("snapshots/ContractConfig.json").exists()); + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct FunctionConfig { + #[serde(deserialize_with = "string_to_u64")] + test_isolated_function: u64, + + #[serde(deserialize_with = "string_to_u64")] + test_non_isolated_function: u64, + } + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct ContractConfig { + #[serde(deserialize_with = "string_to_u64")] + test_isolated_contract: u64, + } + + fn string_to_u64<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: serde_json::Value = Deserialize::deserialize(deserializer)?; + match s { + serde_json::Value::String(s) => s.parse::().map_err(serde::de::Error::custom), + serde_json::Value::Number(n) if n.is_u64() => Ok(n.as_u64().unwrap()), + _ => Err(serde::de::Error::custom("Expected a string or number")), + } + } + + fn read_snapshot Deserialize<'de>>(path: &Path) -> T { + let content = fs::read_to_string(path).expect("Failed to read file"); + serde_json::from_str(&content).expect("Failed to parse snapshot") + } + + let function_config: FunctionConfig = + read_snapshot(&prj.root().join("snapshots/FunctionConfig.json")); + let contract_config: ContractConfig = + read_snapshot(&prj.root().join("snapshots/ContractConfig.json")); + + // FunctionConfig { + // test_isolated_function: 48926, + // test_non_isolated_function: 27722, + // } + + // ContractConfig { + // test_isolated_contract: 48926, + // } + + assert!(function_config.test_isolated_function > function_config.test_non_isolated_function); + assert_eq!(function_config.test_isolated_function, contract_config.test_isolated_contract); +}); + +forgetest_init!(config_inline_evm_version, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "inline.sol", + r#" + import {Test} from "forge-std/Test.sol"; + + contract Dummy { + function getBlobBaseFee() public returns (uint256) { + return block.blobbasefee; + } + } + + contract FunctionConfig is Test { + Dummy dummy; + + function setUp() public { + dummy = new Dummy(); + } + + /// forge-config: default.evm_version = "shanghai" + function test_old() public { + vm.expectRevert(); + dummy.getBlobBaseFee(); + } + + function test_new() public { + dummy.getBlobBaseFee(); + } + } + + /// forge-config: default.evm_version = "shanghai" + contract ContractConfig is Test { + Dummy dummy; + + function setUp() public { + dummy = new Dummy(); + } + + function test_old() public { + vm.expectRevert(); + dummy.getBlobBaseFee(); + } + + /// forge-config: default.evm_version = "cancun" + function test_new() public { + dummy.getBlobBaseFee(); + } + } + "#, + ) + .unwrap(); + + cmd.args(["test", "--evm-version=cancun", "-j1"]).assert_success().stdout_eq(str![[r#" +... +Ran 2 tests for test/inline.sol:ContractConfig +[PASS] test_new() ([GAS]) +[PASS] test_old() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 tests for test/inline.sol:FunctionConfig +[PASS] test_new() ([GAS]) +[PASS] test_old() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 2 test suites [ELAPSED]: 4 tests passed, 0 failed, 0 skipped (4 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/cli/integration.rs b/crates/forge/tests/cli/integration.rs deleted file mode 100644 index a32bd95e22a4d..0000000000000 --- a/crates/forge/tests/cli/integration.rs +++ /dev/null @@ -1,37 +0,0 @@ -use foundry_test_utils::{forgetest_external, util::setup_forge_remote}; - -forgetest_external!(solmate, "transmissions11/solmate"); -forgetest_external!(prb_math, "PaulRBerg/prb-math"); -forgetest_external!(prb_proxy, "PaulRBerg/prb-proxy"); -forgetest_external!(solady, "Vectorized/solady"); -forgetest_external!( - geb, - "reflexer-labs/geb", - &["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"] -); -forgetest_external!(stringutils, "Arachnid/solidity-stringutils"); -forgetest_external!(lootloose, "gakonst/lootloose"); -forgetest_external!(lil_web3, "m1guelpf/lil-web3"); - -/// clone + build in one step -#[test] -#[ignore] -fn can_checkout_build() { - let (_prj, _cmd) = setup_forge_remote("transmissions11/solmate"); -} - -/// Forking tests -mod fork_integration { - use foundry_test_utils::forgetest_external; - - forgetest_external!(multicall, "makerdao/multicall", &["--block-number", "1"]); - forgetest_external!( - drai, - "mds1/drai", - 13633752, - &["--chain-id", "99", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"] - ); - forgetest_external!(gunilev, "hexonaut/guni-lev", 13633752); - forgetest_external!(convex, "mds1/convex-shutdown-simulation", 14445961); - forgetest_external!(sparklend, "marsfoundation/sparklend"); -} diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index 1892172dabecf..cd12f45ab7143 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -1,29 +1,32 @@ -#[cfg(not(feature = "external-integration-tests"))] +#[macro_use] +extern crate foundry_test_utils; + +pub mod constants; +pub mod utils; + +mod bind_json; +mod build; mod cache; -#[cfg(not(feature = "external-integration-tests"))] mod cmd; -#[cfg(not(feature = "external-integration-tests"))] +mod compiler; mod config; -#[cfg(not(feature = "external-integration-tests"))] +mod context; +mod coverage; mod create; -#[cfg(not(feature = "external-integration-tests"))] +mod debug; mod doc; -#[cfg(not(feature = "external-integration-tests"))] +mod eip712; +mod failure_assertions; +mod geiger; +mod inline_config; mod multi_script; -#[cfg(not(feature = "external-integration-tests"))] +mod odyssey; mod script; +mod soldeer; mod svm; -#[cfg(not(feature = "external-integration-tests"))] mod test_cmd; -#[cfg(not(feature = "external-integration-tests"))] -mod utils; -#[cfg(not(feature = "external-integration-tests"))] mod verify; +mod verify_bytecode; +mod version; -#[cfg(feature = "external-integration-tests")] -mod integration; - -#[cfg(feature = "heavy-integration-tests")] -mod heavy_integration; - -pub mod constants; +mod ext_integration; diff --git a/crates/forge/tests/cli/multi_script.rs b/crates/forge/tests/cli/multi_script.rs index f571585b96b66..d6f7628da1694 100644 --- a/crates/forge/tests/cli/multi_script.rs +++ b/crates/forge/tests/cli/multi_script.rs @@ -1,63 +1,66 @@ //! Contains various tests related to forge script use anvil::{spawn, NodeConfig}; -use foundry_test_utils::{ - forgetest_async, - util::{TestCommand, TestProject}, - ScriptOutcome, ScriptTester, -}; - -forgetest_async!( - can_deploy_multi_chain_script_without_lib, - |prj: TestProject, cmd: TestCommand| async move { - let (api1, handle1) = spawn(NodeConfig::test()).await; - let (api2, handle2) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); - - tester - .load_private_keys(vec![0, 1]) - .await - .add_sig("MultiChainBroadcastNoLink", "deploy(string memory,string memory)") - .args(vec![handle1.http_endpoint(), handle2.http_endpoint()]) - .broadcast(ScriptOutcome::OkBroadcast); - - assert!(1 == api1.transaction_count(tester.accounts_pub[0], None).await.unwrap().as_u32()); - assert!(1 == api1.transaction_count(tester.accounts_pub[1], None).await.unwrap().as_u32()); - - assert!(2 == api2.transaction_count(tester.accounts_pub[0], None).await.unwrap().as_u32()); - assert!(3 == api2.transaction_count(tester.accounts_pub[1], None).await.unwrap().as_u32()); - } -); - -forgetest_async!( - can_not_deploy_multi_chain_script_with_lib, - |prj: TestProject, cmd: TestCommand| async move { - let (_, handle1) = spawn(NodeConfig::test()).await; - let (_, handle2) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); - - tester - .load_private_keys(vec![0, 1]) - .await - .add_deployer(0) - .add_sig("MultiChainBroadcastLink", "deploy(string memory,string memory)") - .args(vec![handle1.http_endpoint(), handle2.http_endpoint()]) - .broadcast(ScriptOutcome::UnsupportedLibraries); - } -); - -forgetest_async!( - can_not_change_fork_during_broadcast, - |prj: TestProject, cmd: TestCommand| async move { - let (_, handle1) = spawn(NodeConfig::test()).await; - let (_, handle2) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); - - tester - .load_private_keys(vec![0, 1]) - .await - .add_deployer(0) - .add_sig("MultiChainBroadcastNoLink", "deployError(string memory,string memory)") - .args(vec![handle1.http_endpoint(), handle2.http_endpoint()]) - .broadcast(ScriptOutcome::ErrorSelectForkOnBroadcast); - } -); + +use foundry_test_utils::{ScriptOutcome, ScriptTester}; + +forgetest_async!(can_deploy_multi_chain_script_without_lib, |prj, cmd| { + let (api1, handle1) = spawn(NodeConfig::test()).await; + let (api2, handle2) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("MultiChainBroadcastNoLink", "deploy(string memory,string memory)") + .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) + .broadcast(ScriptOutcome::OkBroadcast); + + assert_eq!(api1.transaction_count(tester.accounts_pub[0], None).await.unwrap().to::(), 1); + assert_eq!(api1.transaction_count(tester.accounts_pub[1], None).await.unwrap().to::(), 1); + + assert_eq!(api2.transaction_count(tester.accounts_pub[0], None).await.unwrap().to::(), 2); + assert_eq!(api2.transaction_count(tester.accounts_pub[1], None).await.unwrap().to::(), 3); +}); + +forgetest_async!(can_not_deploy_multi_chain_script_with_lib, |prj, cmd| { + let (_, handle1) = spawn(NodeConfig::test()).await; + let (_, handle2) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_deployer(0) + .add_sig("MultiChainBroadcastLink", "deploy(string memory,string memory)") + .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) + .broadcast(ScriptOutcome::UnsupportedLibraries); +}); + +forgetest_async!(can_not_change_fork_during_broadcast, |prj, cmd| { + let (_, handle1) = spawn(NodeConfig::test()).await; + let (_, handle2) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_deployer(0) + .add_sig("MultiChainBroadcastNoLink", "deployError(string memory,string memory)") + .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) + .broadcast(ScriptOutcome::ErrorSelectForkOnBroadcast); +}); + +forgetest_async!(can_resume_multi_chain_script, |prj, cmd| { + let (_, handle1) = spawn(NodeConfig::test()).await; + let (_, handle2) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + + tester + .add_sig("MultiChainBroadcastNoLink", "deploy(string memory,string memory)") + .args(&[&handle1.http_endpoint(), &handle2.http_endpoint()]) + .broadcast(ScriptOutcome::MissingWallet) + .load_private_keys(&[0, 1]) + .await + .arg("--multi") + .resume(ScriptOutcome::OkBroadcast); +}); diff --git a/crates/forge/tests/cli/odyssey.rs b/crates/forge/tests/cli/odyssey.rs new file mode 100644 index 0000000000000..7d98e79fcf082 --- /dev/null +++ b/crates/forge/tests/cli/odyssey.rs @@ -0,0 +1,31 @@ +// Ensure we can run basic counter tests with EOF support. +forgetest_init!(test_eof_flag, |prj, cmd| { + if !has_docker() { + println!("skipping because no docker is available"); + return; + } + + cmd.forge_fuse().args(["test", "--eof"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (3805): This is a pre-release compiler version, please do not use it in production. + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); +}); + +fn has_docker() -> bool { + if !cfg!(target_os = "linux") { + return false; + } + + // `images` will also check for the daemon. + std::process::Command::new("docker").arg("images").output().is_ok_and(|o| o.status.success()) +} diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index a6ce2d588cbcd..fb17349be11bf 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -1,14 +1,15 @@ -//! Contains various tests related to forge script +//! Contains various tests related to `forge script`. + use crate::constants::TEMPLATE_CONTRACT; +use alloy_primitives::{address, hex, Address, Bytes}; use anvil::{spawn, NodeConfig}; -use ethers::abi::Address; -use foundry_config::Config; +use forge_script_sequence::ScriptSequence; use foundry_test_utils::{ - forgetest, forgetest_async, forgetest_init, - util::{OutputExt, TestCommand, TestProject}, + rpc::{self, next_http_rpc_endpoint}, + snapbox::IntoData, + util::{OTHER_SOLC_VERSION, SOLC_VERSION}, ScriptOutcome, ScriptTester, }; -use foundry_utils::rpc; use regex::Regex; use serde_json::Value; use std::{env, path::PathBuf, str::FromStr}; @@ -17,15 +18,11 @@ use std::{env, path::PathBuf, str::FromStr}; forgetest_init!( #[ignore] can_use_fork_cheat_codes_in_script, - |prj: TestProject, mut cmd: TestCommand| { + |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.10; - import "forge-std/Script.sol"; contract ContractScript is Script { @@ -40,21 +37,18 @@ contract ContractScript is Script { ) .unwrap(); - let rpc = foundry_utils::rpc::next_http_rpc_endpoint(); + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); - cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvv"]); + cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvvv"]).assert_success(); } ); // Tests that the `run` command works correctly -forgetest!(can_execute_script_command2, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_execute_script_command2, |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; contract Demo { event log_string(string); function run() external { @@ -65,22 +59,25 @@ contract Demo { ) .unwrap(); - cmd.arg("script").arg(script); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_execute_script_command.stdout"), - ); + cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + script ran + +"#]]); }); // Tests that the `run` command works correctly when path *and* script name is specified -forgetest!(can_execute_script_command_fqn, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_execute_script_command_fqn, |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; contract Demo { event log_string(string); function run() external { @@ -91,22 +88,25 @@ contract Demo { ) .unwrap(); - cmd.arg("script").arg(format!("{}:Demo", script.display())); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_execute_script_command_fqn.stdout"), - ); + cmd.arg("script").arg(format!("{}:Demo", script.display())).assert_success().stdout_eq(str![[ + r#" +... +Script ran successfully. +[GAS] + +== Logs == + script ran +... +"# + ]]); }); // Tests that the run command can run arbitrary functions -forgetest!(can_execute_script_command_with_sig, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_execute_script_command_with_sig, |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; contract Demo { event log_string(string); function myFunction() external { @@ -117,142 +117,275 @@ contract Demo { ) .unwrap(); - cmd.arg("script").arg(script).arg("--sig").arg("myFunction()"); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_execute_script_command_with_sig.stdout"), + cmd.arg("script").arg(script).arg("--sig").arg("myFunction()").assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + script ran + +"#]], ); }); +static FAILING_SCRIPT: &str = r#" +import "forge-std/Script.sol"; + +contract FailingScript is Script { + function run() external { + revert("failed"); + } +} +"#; + +// Tests that execution throws upon encountering a revert in the script. +forgetest_async!(assert_exit_code_error_on_failure_script, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let script = prj.add_source("FailingScript", FAILING_SCRIPT).unwrap(); + + // set up command + cmd.arg("script").arg(script); + + // run command and assert error exit code + cmd.assert_failure().stderr_eq(str![[r#" +Error: script failed: revert: failed + +"#]]); +}); + +// Tests that execution throws upon encountering a revert in the script with --json option. +// +forgetest_async!(assert_exit_code_error_on_failure_script_with_json, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let script = prj.add_source("FailingScript", FAILING_SCRIPT).unwrap(); + + // set up command + cmd.arg("script").arg(script).arg("--json"); + + // run command and assert error exit code + cmd.assert_failure().stderr_eq(str![[r#" +Error: script failed: revert: failed + +"#]]); +}); + // Tests that the manually specified gas limit is used when using the --unlocked option -forgetest_async!( - can_execute_script_command_with_manual_gas_limit_unlocked, - |prj: TestProject, mut cmd: TestCommand| async move { - foundry_test_utils::util::initialize(prj.root()); - let deploy_script = prj - .inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +forgetest_async!(can_execute_script_command_with_manual_gas_limit_unlocked, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let deploy_script = prj + .add_source( + "Foo", + r#" import "forge-std/Script.sol"; contract GasWaster { function wasteGas(uint256 minGas) public { - require(gasleft() >= minGas, "Gas left needs to be higher"); + require(gasleft() >= minGas, "Gas left needs to be higher"); } } contract DeployScript is Script { - function run() external returns (uint256 result, uint8) { + function run() external { vm.startBroadcast(); GasWaster gasWaster = new GasWaster(); gasWaster.wasteGas{gas: 500000}(200000); } } "#, - ) - .unwrap(); + ) + .unwrap(); - let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; + let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; - let node_config = NodeConfig::test() - .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())) - .silent(); - let (_api, handle) = spawn(node_config).await; - let dev = handle.dev_accounts().next().unwrap(); - cmd.set_current_dir(prj.root()); + let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())); + let (_api, handle) = spawn(node_config).await; + let dev = handle.dev_accounts().next().unwrap(); + cmd.set_current_dir(prj.root()); - cmd.args([ - "script", - &deploy_contract, - "--root", - prj.root().to_str().unwrap(), - "--fork-url", - &handle.http_endpoint(), - "--sender", - format!("{dev:?}").as_str(), - "-vvvvv", - "--slow", - "--broadcast", - "--unlocked", - ]); + cmd.args([ + "script", + &deploy_contract, + "--root", + prj.root().to_str().unwrap(), + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "-vvvvv", + "--slow", + "--broadcast", + "--unlocked", + "--ignored-error-codes=2018", // `wasteGas` can be restricted to view + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] DeployScript::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new GasWaster@[..] + │ └─ ← [Return] 415 bytes of code + ├─ [..] GasWaster::wasteGas(200000 [2e5]) + │ └─ ← [Stop] + └─ ← [Stop] - let output = cmd.stdout_lossy(); - assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); - assert!(output.contains("Gas limit was set in script to 500000")); - } -); + +Script ran successfully. + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [..] → new GasWaster@[..] + └─ ← [Return] 415 bytes of code + + [..] GasWaster::wasteGas(200000 [2e5]) + └─ ← [Stop] + + +========================== + +Chain 1 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); // Tests that the manually specified gas limit is used. -forgetest_async!( - can_execute_script_command_with_manual_gas_limit, - |prj: TestProject, mut cmd: TestCommand| async move { - foundry_test_utils::util::initialize(prj.root()); - let deploy_script = prj - .inner() - .add_source( - "Foo", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +forgetest_async!(can_execute_script_command_with_manual_gas_limit, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + let deploy_script = prj + .add_source( + "Foo", + r#" import "forge-std/Script.sol"; contract GasWaster { function wasteGas(uint256 minGas) public { - require(gasleft() >= minGas, "Gas left needs to be higher"); + require(gasleft() >= minGas, "Gas left needs to be higher"); } } contract DeployScript is Script { - function run() external returns (uint256 result, uint8) { + function run() external { vm.startBroadcast(); GasWaster gasWaster = new GasWaster(); gasWaster.wasteGas{gas: 500000}(200000); } } "#, - ) - .unwrap(); + ) + .unwrap(); - let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; + let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; - let node_config = NodeConfig::test() - .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())) - .silent(); - let (_api, handle) = spawn(node_config).await; - let private_key = - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); - cmd.set_current_dir(prj.root()); + let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())); + let (_api, handle) = spawn(node_config).await; + let private_key = + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); + cmd.set_current_dir(prj.root()); - cmd.args([ - "script", - &deploy_contract, - "--root", - prj.root().to_str().unwrap(), - "--fork-url", - &handle.http_endpoint(), - "-vvvvv", - "--slow", - "--broadcast", - "--private-key", - &private_key, - ]); + cmd.args([ + "script", + &deploy_contract, + "--root", + prj.root().to_str().unwrap(), + "--fork-url", + &handle.http_endpoint(), + "-vvvvv", + "--slow", + "--broadcast", + "--private-key", + &private_key, + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2018): Function state mutability can be restricted to view + [FILE]:7:5: + | +7 | function wasteGas(uint256 minGas) public { + | ^ (Relevant source part starts here and spans across multiple lines). - let output = cmd.stdout_lossy(); - assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); - assert!(output.contains("Gas limit was set in script to 500000")); - } -); +Traces: + [..] DeployScript::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new GasWaster@[..] + │ └─ ← [Return] 415 bytes of code + ├─ [..] GasWaster::wasteGas(200000 [2e5]) + │ └─ ← [Stop] + └─ ← [Stop] + + +Script ran successfully. + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [..] → new GasWaster@[..] + └─ ← [Return] 415 bytes of code + + [..] GasWaster::wasteGas(200000 [2e5]) + └─ ← [Stop] + + +========================== + +Chain 1 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); // Tests that the run command can run functions with arguments -forgetest!(can_execute_script_command_with_args, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_execute_script_command_with_args, |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; contract Demo { event log_string(string); event log_uint(uint); @@ -266,22 +399,34 @@ contract Demo { ) .unwrap(); - cmd.arg("script").arg(script).arg("--sig").arg("run(uint256,uint256)").arg("1").arg("2"); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_execute_script_command_with_args.stdout"), - ); + cmd.arg("script") + .arg(script) + .arg("--sig") + .arg("run(uint256,uint256)") + .arg("1") + .arg("2") + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + script ran + 1 + 2 + +"#]]); }); // Tests that the run command can run functions with return values -forgetest!(can_execute_script_command_with_returned, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_execute_script_command_with_returned, |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; contract Demo { event log_string(string); function run() external returns (uint256 result, uint8) { @@ -291,96 +436,129 @@ contract Demo { }"#, ) .unwrap(); - cmd.arg("script").arg(script); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_execute_script_command_with_returned.stdout"), - ); + + cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Return == +result: uint256 255 +1: uint8 3 + +== Logs == + script ran + +"#]]); }); -forgetest_async!( - can_broadcast_script_skipping_simulation, - |prj: TestProject, mut cmd: TestCommand| async move { - foundry_test_utils::util::initialize(prj.root()); - // This example script would fail in on-chain simulation - let deploy_script = prj - .inner() - .add_source( - "DeployScript", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +forgetest_async!(can_broadcast_script_skipping_simulation, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + // This example script would fail in on-chain simulation + let deploy_script = prj + .add_source( + "DeployScript", + r#" import "forge-std/Script.sol"; contract HashChecker { bytes32 public lastHash; + function update() public { bytes32 newHash = blockhash(block.number - 1); require(newHash != lastHash, "Hash didn't change"); lastHash = newHash; } - function checkLastHash() public { - require(lastHash != bytes32(0), "Hash shouldn't be zero"); + function checkLastHash() public view { + require(lastHash != bytes32(0), "Hash shouldn't be zero"); } } + contract DeployScript is Script { - function run() external returns (uint256 result, uint8) { + HashChecker public hashChecker; + + function run() external { vm.startBroadcast(); - HashChecker hashChecker = new HashChecker(); + hashChecker = new HashChecker(); } }"#, - ) - .unwrap(); + ) + .unwrap(); - let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; + let deploy_contract = deploy_script.display().to_string() + ":DeployScript"; - let node_config = NodeConfig::test() - .with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())) - .silent(); - let (_api, handle) = spawn(node_config).await; - let private_key = - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); - cmd.set_current_dir(prj.root()); + let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url())); + let (_api, handle) = spawn(node_config).await; + let private_key = + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string(); + cmd.set_current_dir(prj.root()); - cmd.args([ - "script", - &deploy_contract, - "--root", - prj.root().to_str().unwrap(), - "--fork-url", - &handle.http_endpoint(), - "-vvvvv", - "--broadcast", - "--slow", - "--skip-simulation", - "--private-key", - &private_key, - ]); + cmd.args([ + "script", + &deploy_contract, + "--root", + prj.root().to_str().unwrap(), + "--fork-url", + &handle.http_endpoint(), + "-vvvvv", + "--broadcast", + "--slow", + "--skip-simulation", + "--private-key", + &private_key, + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] DeployScript::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new HashChecker@[..] + │ └─ ← [Return] 718 bytes of code + └─ ← [Stop] - let output = cmd.stdout_lossy(); - assert!(output.contains("SKIPPING ON CHAIN SIMULATION")); - assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); +Script ran successfully. + +SKIPPING ON CHAIN SIMULATION. - let run_log = - std::fs::read_to_string("broadcast/DeployScript.sol/1/run-latest.json").unwrap(); - let run_object: Value = serde_json::from_str(&run_log).unwrap(); - let contract_address = ethers::utils::to_checksum( - &run_object["receipts"][0]["contractAddress"].as_str().unwrap().parse().unwrap(), - None, - ); - let run_code = r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); + + let run_log = std::fs::read_to_string("broadcast/DeployScript.sol/1/run-latest.json").unwrap(); + let run_object: Value = serde_json::from_str(&run_log).unwrap(); + let contract_address = &run_object["receipts"][0]["contractAddress"] + .as_str() + .unwrap() + .parse::
() + .unwrap() + .to_string(); + + let run_code = r#" import "forge-std/Script.sol"; import { HashChecker } from "./DeployScript.sol"; contract RunScript is Script { - function run() external returns (uint256 result, uint8) { + HashChecker public hashChecker; + + function run() external { vm.startBroadcast(); - HashChecker hashChecker = HashChecker(CONTRACT_ADDRESS); + hashChecker = HashChecker(CONTRACT_ADDRESS); uint numUpdates = 8; vm.roll(block.number - numUpdates); for(uint i = 0; i < numUpdates; i++) { @@ -390,14 +568,13 @@ contract RunScript is Script { } } }"# - .replace("CONTRACT_ADDRESS", &contract_address); + .replace("CONTRACT_ADDRESS", contract_address); - let run_script = prj.inner().add_source("RunScript", run_code).unwrap(); - let run_contract = run_script.display().to_string() + ":RunScript"; + let run_script = prj.add_source("RunScript", &run_code).unwrap(); + let run_contract = run_script.display().to_string() + ":RunScript"; - cmd.forge_fuse(); - cmd.set_current_dir(prj.root()); - cmd.args([ + cmd.forge_fuse() + .args([ "script", &run_contract, "--root", @@ -412,354 +589,458 @@ contract RunScript is Script { "200", "--private-key", &private_key, - ]); - - let output = cmd.stdout_lossy(); - assert!(output.contains("SKIPPING ON CHAIN SIMULATION")); - assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); - } -); + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] RunScript::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + ├─ [0] VM::roll([..]) + │ └─ ← [Return] + ├─ [..] [..]::update() + │ └─ ← [Stop] + ├─ [..] [..]::checkLastHash() [staticcall] + │ └─ ← [Stop] + └─ ← [Stop] + + +Script ran successfully. + +SKIPPING ON CHAIN SIMULATION. + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); -forgetest_async!(can_deploy_script_without_lib, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_deploy_script_without_lib, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); tester - .load_private_keys(vec![0, 1]) + .load_private_keys(&[0, 1]) .await .add_sig("BroadcastTestNoLinking", "deployDoesntPanic()") .simulate(ScriptOutcome::OkSimulation) .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 1), (1, 2)]) + .assert_nonce_increment(&[(0, 1), (1, 2)]) .await; }); -forgetest_async!(can_deploy_script_with_lib, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_deploy_script_with_lib, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); tester - .load_private_keys(vec![0, 1]) + .load_private_keys(&[0, 1]) .await .add_sig("BroadcastTest", "deploy()") .simulate(ScriptOutcome::OkSimulation) .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 2), (1, 1)]) + .assert_nonce_increment(&[(0, 2), (1, 1)]) .await; }); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_private_key, - |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_addresses(vec![ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployPrivateKey()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(vec![( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 3, - )]) - .await; - } -); +forgetest_async!(can_deploy_script_private_key, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_unlocked, - |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()) - .unlocked() - .add_sig("BroadcastTest", "deployOther()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast); - } -); + tester + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployPrivateKey()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 3, + )]) + .await; +}); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_remember_key, - |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_addresses(vec![ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployRememberKey()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(vec![( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 2, - )]) - .await; - } -); +forgetest_async!(can_deploy_unlocked, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); -forgetest_async!( - #[serial_test::serial] - can_deploy_script_remember_key_and_resume, - |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .add_deployer(0) - .load_addresses(vec![ - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap() - ]) - .await - .add_sig("BroadcastTest", "deployRememberKeyResume()") - .simulate(ScriptOutcome::OkSimulation) - .resume(ScriptOutcome::MissingWallet) - // load missing wallet - .load_private_keys(vec![0]) - .await - .run(ScriptOutcome::OkBroadcast) - .assert_nonce_increment_addresses(vec![( - Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), - 1, - )]) - .await - .assert_nonce_increment(vec![(0, 2)]) - .await; - } -); + tester + .sender("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()) + .unlocked() + .add_sig("BroadcastTest", "deployOther()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast); +}); + +forgetest_async!(can_deploy_script_remember_key, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployRememberKey()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 2, + )]) + .await; +}); + +forgetest_async!(can_deploy_script_remember_key_and_resume, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .add_deployer(0) + .load_addresses(&[Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap()]) + .await + .add_sig("BroadcastTest", "deployRememberKeyResume()") + .simulate(ScriptOutcome::OkSimulation) + .resume(ScriptOutcome::MissingWallet) + // load missing wallet + .load_private_keys(&[0]) + .await + .run(ScriptOutcome::OkBroadcast) + .assert_nonce_increment_addresses(&[( + Address::from_str("0x90F79bf6EB2c4f870365E785982E1f101E93b906").unwrap(), + 1, + )]) + .await + .assert_nonce_increment(&[(0, 2)]) + .await; +}); -forgetest_async!(can_resume_script, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_resume_script, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); tester - .load_private_keys(vec![0]) + .load_private_keys(&[0]) .await .add_sig("BroadcastTest", "deploy()") .simulate(ScriptOutcome::OkSimulation) .resume(ScriptOutcome::MissingWallet) // load missing wallet - .load_private_keys(vec![1]) + .load_private_keys(&[1]) .await .run(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 2), (1, 1)]) + .assert_nonce_increment(&[(0, 2), (1, 1)]) .await; }); -forgetest_async!(can_deploy_broadcast_wrap, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_deploy_broadcast_wrap, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); tester .add_deployer(2) - .load_private_keys(vec![0, 1, 2]) + .load_private_keys(&[0, 1, 2]) .await .add_sig("BroadcastTest", "deployOther()") .simulate(ScriptOutcome::OkSimulation) .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 4), (1, 4), (2, 1)]) + .assert_nonce_increment(&[(0, 4), (1, 4), (2, 1)]) .await; }); -forgetest_async!(panic_no_deployer_set, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(panic_no_deployer_set, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); tester - .load_private_keys(vec![0, 1]) + .load_private_keys(&[0, 1]) .await .add_sig("BroadcastTest", "deployOther()") .simulate(ScriptOutcome::WarnSpecifyDeployer) .broadcast(ScriptOutcome::MissingSender); }); -forgetest_async!(can_deploy_no_arg_broadcast, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_deploy_no_arg_broadcast, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); tester .add_deployer(0) - .load_private_keys(vec![0]) + .load_private_keys(&[0]) .await .add_sig("BroadcastTest", "deployNoArgs()") .simulate(ScriptOutcome::OkSimulation) .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 3)]) + .assert_nonce_increment(&[(0, 3)]) .await; }); -forgetest_async!(can_deploy_with_create2, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_deploy_with_create2, |prj, cmd| { let (api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); // Prepare CREATE2 Deployer - let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); - let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); - api.anvil_set_code(addr, code).await.unwrap(); + api.anvil_set_code( + foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER, + Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE), + ) + .await + .unwrap(); tester .add_deployer(0) - .load_private_keys(vec![0]) + .load_private_keys(&[0]) .await .add_sig("BroadcastTestNoLinking", "deployCreate2()") .simulate(ScriptOutcome::OkSimulation) .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 2)]) + .assert_nonce_increment(&[(0, 2)]) .await // Running again results in error, since we're repeating the salt passed to CREATE2 - .run(ScriptOutcome::FailedScript); + .run(ScriptOutcome::ScriptFailed); }); -forgetest_async!( - #[serial_test::serial] - can_deploy_and_simulate_25_txes_concurrently, - |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_private_keys(vec![0]) - .await - .add_sig("BroadcastTestNoLinking", "deployMany()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 25)]) - .await; - } -); - -forgetest_async!( - #[serial_test::serial] - can_deploy_and_simulate_mixed_broadcast_modes, - |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - tester - .load_private_keys(vec![0]) - .await - .add_sig("BroadcastMix", "deployMix()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 15)]) - .await; - } -); - -forgetest_async!(deploy_with_setup, |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; +forgetest_async!(can_deploy_with_custom_create2, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + let create2 = Address::from_str("0x0000000000000000000000000000000000b4956c").unwrap(); + + // Prepare CREATE2 Deployer + api.anvil_set_code( + create2, + Bytes::from_static(foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE), + ) + .await + .unwrap(); tester - .load_private_keys(vec![0]) + .add_deployer(0) + .load_private_keys(&[0]) .await - .add_sig("BroadcastTestSetup", "run()") + .add_create2_deployer(create2) + .add_sig("BroadcastTestNoLinking", "deployCreate2(address)") + .arg(&create2.to_string()) .simulate(ScriptOutcome::OkSimulation) .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 6)]) + .assert_nonce_increment(&[(0, 2)]) .await; }); -forgetest_async!(fail_broadcast_staticcall, |prj: TestProject, cmd: TestCommand| async move { - let (_api, handle) = spawn(NodeConfig::test()).await; +forgetest_async!(can_deploy_with_custom_create2_notmatched_bytecode, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + let create2 = Address::from_str("0x0000000000000000000000000000000000b4956c").unwrap(); + + // Prepare CREATE2 Deployer + api.anvil_set_code( + create2, + Bytes::from_static(&hex!("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cef")), + ) + .await + .unwrap(); tester - .load_private_keys(vec![0]) + .add_deployer(0) + .load_private_keys(&[0]) .await - .add_sig("BroadcastTestNoLinking", "errorStaticCall()") - .simulate(ScriptOutcome::StaticCallNotAllowed); + .add_create2_deployer(create2) + .add_sig("BroadcastTestNoLinking", "deployCreate2()") + .simulate(ScriptOutcome::ScriptFailed) + .broadcast(ScriptOutcome::ScriptFailed); }); -forgetest_async!( - #[serial_test::serial] - check_broadcast_log, - |prj: TestProject, cmd: TestCommand| async move { - let (api, handle) = spawn(NodeConfig::test()).await; - let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); - - // Prepare CREATE2 Deployer - let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); - let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); - api.anvil_set_code(addr, code).await.unwrap(); - - tester - .load_private_keys(vec![0]) - .await - .add_sig("BroadcastTestSetup", "run()") - .simulate(ScriptOutcome::OkSimulation) - .broadcast(ScriptOutcome::OkBroadcast) - .assert_nonce_increment(vec![(0, 6)]) - .await; - - // Uncomment to recreate the broadcast log - // std::fs::copy( - // "broadcast/Broadcast.t.sol/31337/run-latest.json", - // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast. - // log. json" ), ); - - // Check broadcast logs - // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice, - // transactionIndex and logIndex values since they can change inbetween runs - let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap(); - - let fixtures_log = std::fs::read_to_string( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/fixtures/broadcast.log.json"), - ) - .unwrap(); - let _fixtures_log = re.replace_all(&fixtures_log, ""); - - let run_log = - std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); - let _run_log = re.replace_all(&run_log, ""); - - // pretty_assertions::assert_eq!(fixtures_log, run_log); - - // Uncomment to recreate the sensitive log - // std::fs::copy( - // "cache/Broadcast.t.sol/31337/run-latest.json", - // PathBuf::from(env!("CARGO_MANIFEST_DIR")) - // .join("../../testdata/fixtures/broadcast.sensitive.log.json"), - // ); - - // Check sensitive logs - // Ignore port number since it can change inbetween runs - let re = Regex::new(r#":[0-9]+"#).unwrap(); - - let fixtures_log = std::fs::read_to_string( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/fixtures/broadcast.sensitive.log.json"), - ) - .unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, ""); - - let run_log = - std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap(); - let run_log = re.replace_all(&run_log, ""); - - // Clean up carriage return OS differences - let re = Regex::new(r"\\r\\n").unwrap(); - let fixtures_log = re.replace_all(&fixtures_log, "\n"); - let run_log = re.replace_all(&run_log, "\n"); +forgetest_async!(canot_deploy_with_nonexist_create2, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + let create2 = Address::from_str("0x0000000000000000000000000000000000b4956c").unwrap(); - pretty_assertions::assert_eq!(fixtures_log, run_log); - } -); + tester + .add_deployer(0) + .load_private_keys(&[0]) + .await + .add_create2_deployer(create2) + .add_sig("BroadcastTestNoLinking", "deployCreate2()") + .simulate(ScriptOutcome::ScriptFailed) + .broadcast(ScriptOutcome::ScriptFailed); +}); -forgetest_async!(test_default_sender_balance, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(can_deploy_and_simulate_25_txes_concurrently, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestNoLinking", "deployMany()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 25)]) + .await; +}); + +forgetest_async!(can_deploy_and_simulate_mixed_broadcast_modes, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastMix", "deployMix()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 15)]) + .await; +}); + +forgetest_async!(deploy_with_setup, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestSetup", "run()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 6)]) + .await; +}); + +forgetest_async!(fail_broadcast_staticcall, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestNoLinking", "errorStaticCall()") + .simulate(ScriptOutcome::StaticCallNotAllowed); +}); + +forgetest_async!(check_broadcast_log, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + // Prepare CREATE2 Deployer + let addr = Address::from_str("0x4e59b44847b379578588920ca78fbf26c0b4956c").unwrap(); + let code = hex::decode("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3").expect("Could not decode create2 deployer init_code").into(); + api.anvil_set_code(addr, code).await.unwrap(); + + tester + .load_private_keys(&[0]) + .await + .add_sig("BroadcastTestSetup", "run()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 6)]) + .await; + + // Uncomment to recreate the broadcast log + // std::fs::copy( + // "broadcast/Broadcast.t.sol/31337/run-latest.json", + // PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/fixtures/broadcast. + // log. json" ), ); + + // Check broadcast logs + // Ignore timestamp, blockHash, blockNumber, cumulativeGasUsed, effectiveGasPrice, + // transactionIndex and logIndex values since they can change in between runs + let re = Regex::new(r#"((timestamp":).[0-9]*)|((blockHash":).*)|((blockNumber":).*)|((cumulativeGasUsed":).*)|((effectiveGasPrice":).*)|((transactionIndex":).*)|((logIndex":).*)"#).unwrap(); + + let fixtures_log = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/fixtures/broadcast.log.json"), + ) + .unwrap(); + let _fixtures_log = re.replace_all(&fixtures_log, ""); + + let run_log = + std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); + let _run_log = re.replace_all(&run_log, ""); + + // similar_asserts::assert_eq!(fixtures_log, run_log); + + // Uncomment to recreate the sensitive log + // std::fs::copy( + // "cache/Broadcast.t.sol/31337/run-latest.json", + // PathBuf::from(env!("CARGO_MANIFEST_DIR")) + // .join("../../testdata/fixtures/broadcast.sensitive.log.json"), + // ); + + // Check sensitive logs + // Ignore port number since it can change in between runs + let re = Regex::new(r":[0-9]+").unwrap(); + + let fixtures_log = std::fs::read_to_string( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/fixtures/broadcast.sensitive.log.json"), + ) + .unwrap(); + let fixtures_log = re.replace_all(&fixtures_log, ""); + + let run_log = std::fs::read_to_string("cache/Broadcast.t.sol/31337/run-latest.json").unwrap(); + let run_log = re.replace_all(&run_log, ""); + + // Clean up carriage return OS differences + let re = Regex::new(r"\r\n").unwrap(); + let fixtures_log = re.replace_all(&fixtures_log, "\n"); + let run_log = re.replace_all(&run_log, "\n"); + + similar_asserts::assert_eq!(fixtures_log, run_log); +}); + +forgetest_async!(test_default_sender_balance, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); @@ -769,7 +1050,7 @@ forgetest_async!(test_default_sender_balance, |prj: TestProject, cmd: TestComman .simulate(ScriptOutcome::OkSimulation); }); -forgetest_async!(test_custom_sender_balance, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(test_custom_sender_balance, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); @@ -791,21 +1072,27 @@ struct Transaction { } // test we output arguments -forgetest_async!( - can_execute_script_with_arguments, - |prj: TestProject, mut cmd: TestCommand| async move { - cmd.args(["init", "--force"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); - cmd.forge_fuse(); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let script = prj - .inner() - .add_script( +forgetest_async!(can_execute_script_with_arguments, |prj, cmd| { + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); + + let (_api, handle) = spawn(NodeConfig::test()).await; + let script = prj.add_script( "Counter.s.sol", r#" -pragma solidity ^0.8.15; - import "forge-std/Script.sol"; struct Point { @@ -819,6 +1106,9 @@ contract A { int c; bytes32 d; bool e; + bytes f; + Point g; + string h; constructor(address _a, uint _b, int _c, bytes32 _d, bool _e, bytes memory _f, Point memory _g, string memory _h) { a = _a; @@ -826,6 +1116,9 @@ contract A { c = _c; d = _d; e = _e; + f = _f; + g = _g; + h = _h; } } @@ -840,59 +1133,96 @@ contract Script0 is Script { ) .unwrap(); - cmd.arg("script").arg(script).args([ + cmd + .forge_fuse() + .arg("script") + .arg(script) + .args([ "--tc", "Script0", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", "--rpc-url", handle.http_endpoint().as_str(), - ]); - - assert!(cmd.stdout_lossy().contains("SIMULATION COMPLETE")); - - let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast")) - .into_iter() - .find(|file| file.ends_with("run-latest.json")) - .expect("No broadcast artifacts"); - - let content = foundry_common::fs::read_to_string(run_latest).unwrap(); - - let transactions: Transactions = serde_json::from_str(&content).unwrap(); - let transactions = transactions.transactions; - assert_eq!(transactions.len(), 1); - assert_eq!( - transactions[0].arguments, - vec![ - "0x00a329c0648769A73afAc7F9381E08FB43dBEA72".to_string(), - "4294967296".to_string(), - "-4294967296".to_string(), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6".to_string(), - "true".to_string(), - "0x616263646566".to_string(), - "(10, 99)".to_string(), - "hello".to_string(), - ] - ); - } -); + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +... +Script ran successfully. + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + +SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); + + let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast")) + .find(|path| path.ends_with("run-latest.json")) + .expect("No broadcast artifacts"); + + let content = foundry_common::fs::read_to_string(run_latest).unwrap(); + + let transactions: Transactions = serde_json::from_str(&content).unwrap(); + let transactions = transactions.transactions; + assert_eq!(transactions.len(), 1); + assert_eq!( + transactions[0].arguments, + vec![ + "0x00a329c0648769A73afAc7F9381E08FB43dBEA72".to_string(), + "4294967296".to_string(), + "-4294967296".to_string(), + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6".to_string(), + "true".to_string(), + "0x616263646566".to_string(), + "(10, 99)".to_string(), + "hello".to_string(), + ] + ); +}); // test we output arguments -forgetest_async!( - can_execute_script_with_arguments_nested_deploy, - |prj: TestProject, mut cmd: TestCommand| async move { - cmd.args(["init", "--force"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); - cmd.forge_fuse(); - - let (_api, handle) = spawn(NodeConfig::test()).await; - let script = prj - .inner() - .add_script( - "Counter.s.sol", - r#" -pragma solidity ^0.8.13; +forgetest_async!(can_execute_script_with_arguments_nested_deploy, |prj, cmd| { + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); + let (_api, handle) = spawn(NodeConfig::test()).await; + let script = prj + .add_script( + "Counter.s.sol", + r#" import "forge-std/Script.sol"; contract A { @@ -928,58 +1258,81 @@ contract Script0 is Script { } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("script").arg(script).args([ + cmd + .forge_fuse() + .arg("script") + .arg(script) + .args([ "--tc", "Script0", "--sender", "0x00a329c0648769A73afAc7F9381E08FB43dBEA72", "--rpc-url", handle.http_endpoint().as_str(), - ]); - - assert!(cmd.stdout_lossy().contains("SIMULATION COMPLETE")); - - let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast")) - .into_iter() - .find(|file| file.ends_with("run-latest.json")) - .expect("No broadcast artifacts"); - - let content = foundry_common::fs::read_to_string(run_latest).unwrap(); - - let transactions: Transactions = serde_json::from_str(&content).unwrap(); - let transactions = transactions.transactions; - assert_eq!(transactions.len(), 1); - assert_eq!( - transactions[0].arguments, - vec![ - "0x00a329c0648769A73afAc7F9381E08FB43dBEA72".to_string(), - "4294967296".to_string(), - "-4294967296".to_string(), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6".to_string(), - "true".to_string(), - "0x616263646566".to_string(), - "hello".to_string(), - ] - ); - } -); + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +... +Script ran successfully. -// checks that skipping build -forgetest_init!(can_execute_script_and_skip_contracts, |prj: TestProject, mut cmd: TestCommand| { - // explicitly set to run with 0.8.17 for consistent output - let config = Config { solc: Some("0.8.17".into()), ..Default::default() }; - prj.write_config(config); +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + +SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); + + let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast")) + .find(|file| file.ends_with("run-latest.json")) + .expect("No broadcast artifacts"); + + let content = foundry_common::fs::read_to_string(run_latest).unwrap(); + + let transactions: Transactions = serde_json::from_str(&content).unwrap(); + let transactions = transactions.transactions; + assert_eq!(transactions.len(), 1); + assert_eq!( + transactions[0].arguments, + vec![ + "0x00a329c0648769A73afAc7F9381E08FB43dBEA72".to_string(), + "4294967296".to_string(), + "-4294967296".to_string(), + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6".to_string(), + "true".to_string(), + "0x616263646566".to_string(), + "hello".to_string(), + ] + ); +}); + +// checks that skipping build +forgetest_init!(can_execute_script_and_skip_contracts, |prj, cmd| { let script = prj - .inner() .add_source( "Foo", r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.17; contract Demo { event log_string(string); function run() external returns (uint256 result, uint8) { @@ -989,43 +1342,60 @@ contract Demo { }"#, ) .unwrap(); - cmd.arg("script").arg(script).args(["--skip", "tests", "--skip", TEMPLATE_CONTRACT]); - - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_execute_script_and_skip_contracts.stdout"), - ); + cmd.arg("script") + .arg(script) + .args(["--skip", "tests", "--skip", TEMPLATE_CONTRACT]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Return == +result: uint256 255 +1: uint8 3 + +== Logs == + script ran + +"#]]); }); -forgetest_async!( - can_run_script_with_empty_setup, - |prj: TestProject, cmd: TestCommand| async move { - let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); +forgetest_async!(can_run_script_with_empty_setup, |prj, cmd| { + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); - tester.add_sig("BroadcastEmptySetUp", "run()").simulate(ScriptOutcome::OkNoEndpoint); - } -); + tester.add_sig("BroadcastEmptySetUp", "run()").simulate(ScriptOutcome::OkNoEndpoint); +}); -forgetest_async!(does_script_override_correctly, |prj: TestProject, cmd: TestCommand| async move { +forgetest_async!(does_script_override_correctly, |prj, cmd| { let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); tester.add_sig("CheckOverrides", "run()").simulate(ScriptOutcome::OkNoEndpoint); }); -forgetest_async!( - assert_tx_origin_is_not_overritten, - |prj: TestProject, mut cmd: TestCommand| async move { - cmd.args(["init", "--force"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); - cmd.forge_fuse(); +forgetest_async!(assert_tx_origin_is_not_overritten, |prj, cmd| { + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project - let script = prj - .inner() - .add_script( - "ScriptTxOrigin.s.sol", - r#" -pragma solidity ^0.8.13; +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); + let script = prj + .add_script( + "ScriptTxOrigin.s.sol", + r#" import { Script } from "forge-std/Script.sol"; contract ScriptTxOrigin is Script { @@ -1046,54 +1416,74 @@ contract ScriptTxOrigin is Script { contract ContractA { function test(address _contractB) public { - require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "sender 1"); + require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin 1"); ContractB contractB = ContractB(_contractB); ContractC contractC = new ContractC(); - require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "sender 2"); + require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin 2"); contractB.method(address(this)); contractC.method(address(this)); - require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); - require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + require(msg.sender == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "sender 3"); + require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin 3"); } } contract ContractB { function method(address sender) public view { - require(msg.sender == sender); - require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + require(msg.sender == sender, "sender"); + require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin"); } } + contract ContractC { function method(address sender) public view { - require(msg.sender == sender); - require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + require(msg.sender == sender, "sender"); + require(tx.origin == 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, "origin"); } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("script").arg(script).args(["--tc", "ScriptTxOrigin"]); - assert!(cmd.stdout_lossy().contains("Script ran successfully.")); - } -); + cmd.forge_fuse() + .arg("script") + .arg(script) + .args(["--tc", "ScriptTxOrigin"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +If you wish to simulate on-chain transactions pass a RPC URL. + +"#]]); +}); -forgetest_async!( - assert_can_create_multiple_contracts_with_correct_nonce, - |prj: TestProject, mut cmd: TestCommand| async move { - cmd.args(["init", "--force"]).arg(prj.root()); - cmd.assert_non_empty_stdout(); - cmd.forge_fuse(); +forgetest_async!(assert_can_create_multiple_contracts_with_correct_nonce, |prj, cmd| { + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project - let script = prj - .inner() - .add_script( - "ScriptTxOrigin.s.sol", - r#" -pragma solidity ^0.8.17; +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... +"#]]); + + let script = prj + .add_script( + "ScriptTxOrigin.s.sol", + r#" import {Script, console} from "forge-std/Script.sol"; contract Contract { @@ -1101,18 +1491,20 @@ contract Contract { console.log(tx.origin); } } + contract SubContract { constructor() { console.log(tx.origin); } } + contract BadContract { constructor() { - // new SubContract(); + new SubContract(); console.log(tx.origin); } } -contract NestedCreateFail is Script { +contract NestedCreate is Script { function run() public { address sender = address(uint160(uint(keccak256("woops")))); @@ -1124,10 +1516,1103 @@ contract NestedCreateFail is Script { } } "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.forge_fuse() + .arg("script") + .arg(script) + .args(["--tc", "NestedCreate"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + 0x159E2f2F1C094625A2c6c8bF59526d91454c2F3c + 0x159E2f2F1C094625A2c6c8bF59526d91454c2F3c + 0x159E2f2F1C094625A2c6c8bF59526d91454c2F3c + +If you wish to simulate on-chain transactions pass a RPC URL. + +"#]]); +}); + +forgetest_async!(assert_can_detect_target_contract_with_interfaces, |prj, cmd| { + let script = prj + .add_script( + "ScriptWithInterface.s.sol", + r#" +contract Script { + function run() external {} +} + +interface Interface {} + "#, + ) + .unwrap(); + + cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +"#]]); +}); + +forgetest_async!(assert_can_detect_unlinked_target_with_libraries, |prj, cmd| { + let script = prj + .add_script( + "ScriptWithExtLib.s.sol", + r#" +library Lib { + function f() public {} +} - cmd.arg("script").arg(script).args(["--tc", "NestedCreateFail"]); - assert!(cmd.stdout_lossy().contains("Script ran successfully.")); +contract Script { + function run() external { + Lib.f(); } -); +} + "#, + ) + .unwrap(); + + cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +If you wish to simulate on-chain transactions pass a RPC URL. + +"#]]); +}); + +forgetest_async!(assert_can_resume_with_additional_contracts, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .add_deployer(0) + .add_sig("ScriptAdditionalContracts", "run()") + .broadcast(ScriptOutcome::MissingWallet) + .load_private_keys(&[0]) + .await + .resume(ScriptOutcome::OkBroadcast); +}); + +forgetest_async!(can_detect_contract_when_multiple_versions, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + prj.add_script( + "A.sol", + &format!( + r#" +pragma solidity {SOLC_VERSION}; +import "./B.sol"; + +contract ScriptA {{}} +"# + ), + ) + .unwrap(); + + prj.add_script( + "B.sol", + &format!( + r#" +pragma solidity >={OTHER_SOLC_VERSION} <={SOLC_VERSION}; +import 'forge-std/Script.sol'; + +contract ScriptB is Script {{ + function run() external {{ + vm.broadcast(); + address(0).call(""); + }} +}} +"# + ), + ) + .unwrap(); + + prj.add_script( + "C.sol", + &format!( + r#" +pragma solidity {OTHER_SOLC_VERSION}; +import "./B.sol"; + +contract ScriptC {{}} +"# + ), + ) + .unwrap(); + + let mut tester = ScriptTester::new(cmd, None, prj.root(), "script/B.sol"); + tester.cmd.forge_fuse().args(["script", "script/B.sol"]); + tester.simulate(ScriptOutcome::OkNoEndpoint); +}); + +forgetest_async!(can_sign_with_script_wallet_single, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + tester + .add_sig("ScriptSign", "run()") + .load_private_keys(&[0]) + .await + .simulate(ScriptOutcome::OkNoEndpoint); +}); + +forgetest_async!(can_sign_with_script_wallet_multiple, |prj, cmd| { + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + let acc = tester.accounts_pub[0].to_checksum(None); + tester + .add_sig("ScriptSign", "run(address)") + .arg(&acc) + .load_private_keys(&[0, 1, 2]) + .await + .simulate(ScriptOutcome::OkRun); +}); + +forgetest_async!(fails_with_function_name_and_overloads, |prj, cmd| { + let script = prj + .add_script( + "Script.s.sol", + r#" +contract Script { + function run() external {} + + function run(address,uint256) external {} +} + "#, + ) + .unwrap(); + + cmd.arg("script").args([&script.to_string_lossy(), "--sig", "run"]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: Multiple functions with the same name `run` found in the ABI + +"#]]); +}); + +forgetest_async!(can_decode_custom_errors, |prj, cmd| { + cmd.args(["init", "--force"]) + .arg(prj.root()) + .assert_success() + .stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]) + .stderr_eq(str![[r#" +Warning: Target directory is not empty, but `--force` was specified +... + +"#]]); + + let script = prj + .add_script( + "CustomErrorScript.s.sol", + r#" +import { Script } from "forge-std/Script.sol"; + +contract ContractWithCustomError { + error CustomError(); + + constructor() { + revert CustomError(); + } +} + +contract CustomErrorScript is Script { + ContractWithCustomError test; + + function run() public { + test = new ContractWithCustomError(); + } +} +"#, + ) + .unwrap(); + + cmd.forge_fuse().arg("script").arg(script).args(["--tc", "CustomErrorScript"]); + cmd.assert_failure().stderr_eq(str![[r#" +Error: script failed: CustomError() + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/7620 +forgetest_async!(can_run_zero_base_fee, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let node_config = NodeConfig::test().with_base_fee(Some(0)); + let (_api, handle) = spawn(node_config).await; + let dev = handle.dev_accounts().next().unwrap(); + + // Firstly run script with non-zero gas prices to ensure that eth_feeHistory contains non-zero + // values. + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "--broadcast", + "--unlocked", + "--with-gas-price", + "2000000", + "--priority-gas-price", + "100000", + "--non-interactive", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +... +Script ran successfully. + +== Return == +success: bool true + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]).stderr_eq(str![[r#" +Warning: Script contains a transaction to 0x0000000000000000000000000000000000000000 which does not contain any code. + +"#]]); + + // Ensure that we can correctly estimate gas when base fee is zero but priority fee is not. + cmd.forge_fuse() + .args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "--broadcast", + "--unlocked", + "--non-interactive", + ]) + .assert_success() + .stdout_eq(str![[r#" +No files changed, compilation skipped +... +Script ran successfully. + +== Return == +success: bool true + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]).stderr_eq(str![[r#" +Warning: Script contains a transaction to 0x0000000000000000000000000000000000000000 which does not contain any code. + +"#]]); +}); + +// Asserts that the script runs with expected non-output using `--quiet` flag +forgetest_async!(adheres_to_quiet_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--broadcast", + "--unlocked", + "--non-interactive", + "--quiet", + ]) + .assert_empty_stdout(); +}); + +// Asserts that the script runs with expected non-output using `--quiet` flag +forgetest_async!(adheres_to_json_flag, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "--broadcast", + "--unlocked", + "--non-interactive", + "--json", + ]) + .assert_success() + .stdout_eq(str![[r#" +{"logs":[],"returns":{"success":{"internal_type":"bool","value":"true"}},"success":true,"raw_logs":[],"traces":[["Deployment",{"arena":[{"parent":null,"children":[],"idx":0,"trace":{"depth":0,"success":true,"caller":"0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":false,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CREATE","value":"0x0","data":"[..]","output":"[..]","gas_used":"{...}","gas_limit":"{...}","status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}],["Execution",{"arena":[{"parent":null,"children":[1,2],"idx":0,"trace":{"depth":0,"success":true,"caller":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","address":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0xc0406226","output":"0x0000000000000000000000000000000000000000000000000000000000000001","gas_used":"{...}","gas_limit":1073720760,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[{"Call":0},{"Call":1}]},{"parent":0,"children":[],"idx":1,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x7109709ecfa91a80626ff3989d68f67f5b1dd12d","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x7fb5297f","output":"0x","gas_used":"{...}","gas_limit":1056940994,"status":"Return","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]},{"parent":0,"children":[],"idx":2,"trace":{"depth":1,"success":true,"caller":"0x5b73c5498c1e3b4dba84de0f1833c4a029d90519","address":"0x0000000000000000000000000000000000000000","maybe_precompile":null,"selfdestruct_address":null,"selfdestruct_refund_target":null,"selfdestruct_transferred_value":null,"kind":"CALL","value":"0x0","data":"0x","output":"0x","gas_used":"{...}","gas_limit":1056940645,"status":"Stop","steps":[],"decoded":{"label":null,"return_data":null,"call_data":null}},"logs":[],"ordering":[]}]}]],"gas_used":"{...}","labeled_addresses":{},"returned":"0x0000000000000000000000000000000000000000000000000000000000000001","address":null} +{"chain":31337,"estimated_gas_price":"{...}","estimated_total_gas_used":"{...}","estimated_amount_required":"{...}"} +{"chain":"anvil-hardhat","status":"success","tx_hash":"0x4f78afe915fceb282c7625a68eb350bc0bf78acb59ad893e5c62b710a37f3156","contract_address":null,"block_number":1,"gas_used":"{...}","gas_price":"{...}"} +{"status":"success","transactions":"[..]/broadcast/Foo.sol/31337/run-latest.json","sensitive":"[..]/cache/Foo.sol/31337/run-latest.json"} + +"#]].is_jsonlines()); +}); + +// https://github.com/foundry-rs/foundry/pull/7742 +forgetest_async!(unlocked_no_sender, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external returns (bool success) { + vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + (success, ) = address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--broadcast", + "--unlocked", + "--non-interactive", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +... +Script ran successfully. + +== Return == +success: bool true + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]).stderr_eq(str![[r#" +Warning: Script contains a transaction to 0x0000000000000000000000000000000000000000 which does not contain any code. + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/7833 +forgetest_async!(error_no_create2, |prj, cmd| { + let (_api, handle) = + spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleContract {} + +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + new SimpleContract{salt: bytes32(0)}(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--broadcast", + "--unlocked", + ]); + + cmd.assert_failure().stderr_eq(str![[r#" +Error: script failed: missing CREATE2 deployer: 0x4e59b44847b379578588920cA78FbF26c0B4956C + +"#]]); +}); + +forgetest_async!(can_switch_forks_in_setup, |prj, cmd| { + let (_api, handle) = + spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + let url = handle.http_endpoint(); + + prj.add_script( + "Foo", + &r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function setUp() external { + uint256 initialFork = vm.activeFork(); + vm.createSelectFork(""); + vm.selectFork(initialFork); + } + + function run() external { + assert(vm.getNonce(msg.sender) == 0); + } +} + "# + .replace("", &url), + ) + .unwrap(); + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &url, + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful with warnings: +Warning (2018): Function state mutability can be restricted to view + [FILE]:13:5: + | +13 | function run() external { + | ^ (Relevant source part starts here and spans across multiple lines). + +Script ran successfully. + +"#]]); +}); + +// Asserts that running the same script twice only deploys library once. +forgetest_async!(can_deploy_library_create2, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("BroadcastTest", "deploy()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 2), (1, 1)]) + .await; + + tester.clear(); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("BroadcastTest", "deploy()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 1), (1, 1)]) + .await; +}); + +// Asserts that running the same script twice only deploys library once when using different +// senders. +forgetest_async!(can_deploy_library_create2_different_sender, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("BroadcastTest", "deploy()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 2), (1, 1)]) + .await; + + tester.clear(); + + // Run different script from the same contract (which requires the same library). + tester + .load_private_keys(&[2]) + .await + .add_sig("BroadcastTest", "deployNoArgs()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(2, 2)]) + .await; +}); + +// +forgetest_async!(test_broadcast_raw_create2_deployer, |prj, cmd| { + let (api, handle) = spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external { + // send funds to create2 factory deployer + vm.startBroadcast(); + payable(0x3fAB184622Dc19b6109349B94811493BF2a45362).transfer(10000000 gwei); + // deploy create2 factory + vm.broadcastRawTransaction( + hex"f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222" + ); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "--broadcast", + "--slow", + "SimpleScript", + ]); + + cmd.assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. + +## Setting up 1 EVM. + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + + +========================== + +ONCHAIN EXECUTION COMPLETE & SUCCESSFUL. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); + + assert!(!api + .get_code(address!("4e59b44847b379578588920cA78FbF26c0B4956C"), Default::default()) + .await + .unwrap() + .is_empty()); +}); + +forgetest_init!(can_get_script_wallets, |prj, cmd| { + let script = prj + .add_source( + "Foo", + r#" +import "forge-std/Script.sol"; + +interface Vm { + function getWallets() external returns (address[] memory wallets); +} + +contract WalletScript is Script { + function run() public { + address[] memory wallets = Vm(address(vm)).getWallets(); + console.log(wallets[0]); + } +}"#, + ) + .unwrap(); + cmd.arg("script") + .arg(script) + .args([ + "--private-key", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + "-v", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 + +"#]]); +}); + +forgetest_init!(can_remeber_keys, |prj, cmd| { + let script = prj + .add_source( + "Foo", + r#" +import "forge-std/Script.sol"; + +interface Vm { + function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs); +} + +contract WalletScript is Script { + function run() public { + string memory mnemonic = "test test test test test test test test test test test junk"; + string memory derivationPath = "m/44'/60'/0'/0/"; + address[] memory wallets = Vm(address(vm)).rememberKeys(mnemonic, derivationPath, 3); + for (uint256 i = 0; i < wallets.length; i++) { + console.log(wallets[i]); + } + } +}"#, + ) + .unwrap(); + cmd.arg("script").arg(script).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Script ran successfully. +[GAS] + +== Logs == + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC + +"#]]); +}); + +forgetest_async!(can_simulate_with_default_sender, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Script.s.sol", + r#" +import "forge-std/Script.sol"; +contract A { + function getValue() external pure returns (uint256) { + return 100; + } +} +contract B { + constructor(A a) { + require(a.getValue() == 100); + } +} +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(); + A a = new A(); + new B(a); + } +} + "#, + ) + .unwrap(); + + cmd.arg("script").args(["SimpleScript", "--fork-url", &handle.http_endpoint(), "-vvvv"]); + cmd.assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] SimpleScript::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 + │ └─ ← [Return] 175 bytes of code + ├─ [..] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 + │ ├─ [..] A::getValue() [staticcall] + │ │ └─ ← [Return] 100 + │ └─ ← [Return] 62 bytes of code + └─ ← [Stop] + + +Script ran successfully. + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [..] → new A@0x5b73C5498c1E3b4dbA84de0F1833c4a029d90519 + └─ ← [Return] 175 bytes of code + + [..] → new B@0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 + ├─ [..] A::getValue() [staticcall] + │ └─ ← [Return] 100 + └─ ← [Return] 62 bytes of code +... +"#]]); +}); + +forgetest_async!(should_detect_additional_contracts, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract Simple {} + +contract Deployer { + function deploy() public { + new Simple(); + } +} + +contract ContractScript is Script { + function run() public { + vm.startBroadcast(); + Deployer deployer = new Deployer(); + deployer.deploy(); + } +} + "#, + ) + .unwrap(); + cmd.arg("script") + .args([ + "ContractScript", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + ]) + .assert_success(); + + let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast")) + .find(|file| file.ends_with("run-latest.json")) + .expect("No broadcast artifacts"); + + let sequence: ScriptSequence = foundry_common::fs::read_json_file(&run_latest).unwrap(); + + assert_eq!(sequence.transactions.len(), 2); + assert_eq!(sequence.transactions[1].additional_contracts.len(), 1); +}); + +// +forgetest_async!(should_set_correct_sender_nonce_via_cli, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "MyScript.s.sol", + r#" + import {Script, console} from "forge-std/Script.sol"; + + contract MyScript is Script { + function run() public view { + console.log("sender nonce", vm.getNonce(msg.sender)); + } + } + "#, + ) + .unwrap(); + + let rpc_url = next_http_rpc_endpoint(); + + let fork_bn = 21614115; + + cmd.forge_fuse() + .args([ + "script", + "MyScript", + "--sender", + "0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97", + "--fork-block-number", + &fork_bn.to_string(), + "--rpc-url", + &rpc_url, + ]) + .assert_success() + .stdout_eq(str![[r#"[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +... +== Logs == + sender nonce 1124703[..]"#]]); +}); + +forgetest_async!(dryrun_without_broadcast, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_source( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract Called { + event log_string(string); + uint256 public x; + uint256 public y; + function run(uint256 _x, uint256 _y) external { + x = _x; + y = _y; + emit log_string("script ran"); + } +} + +contract DryRunTest is Script { + function run() external { + vm.startBroadcast(); + Called called = new Called(); + called.run(123, 456); + } +} + "#, + ) + .unwrap(); + + cmd.arg("script") + .args([ + "DryRunTest", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "--rpc-url", + &handle.http_endpoint(), + "-vvvv", + ]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +Traces: + [..] DryRunTest::run() + ├─ [0] VM::startBroadcast() + │ └─ ← [Return] + ├─ [..] → new Called@0x5FbDB2315678afecb367f032d93F642f64180aa3 + │ └─ ← [Return] 567 bytes of code + ├─ [..] Called::run(123, 456) + │ ├─ emit log_string(val: "script ran") + │ └─ ← [Stop] + └─ ← [Stop] + + +Script ran successfully. + +== Logs == + script ran + +## Setting up 1 EVM. +========================== +Simulated On-chain Traces: + + [113557] → new Called@0x5FbDB2315678afecb367f032d93F642f64180aa3 + └─ ← [Return] 567 bytes of code + + [46595] Called::run(123, 456) + ├─ emit log_string(val: "script ran") + └─ ← [Stop] + + +========================== + +Chain 31337 + +[ESTIMATED_GAS_PRICE] + +[ESTIMATED_TOTAL_GAS_USED] + +[ESTIMATED_AMOUNT_REQUIRED] + +========================== + +=== Transactions that will be broadcast === + + +Chain 31337 + +### Transaction 1 ### + +accessList [] +chainId 31337 +gasLimit 228247 +gasPrice +input [..] +maxFeePerBlobGas +maxFeePerGas +maxPriorityFeePerGas +nonce 0 +to +type 0 +value 0 + +### Transaction 2 ### + +accessList [] +chainId 31337 +gasLimit 93856 +gasPrice +input 0x7357f5d2000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000001c8 +maxFeePerBlobGas +maxFeePerGas +maxPriorityFeePerGas +nonce 1 +to 0x5FbDB2315678afecb367f032d93F642f64180aa3 +type 0 +value 0 +contract: Called(0x5FbDB2315678afecb367f032d93F642f64180aa3) +data (decoded): run(uint256,uint256)( + 123, + 456 +) + + +SIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more. + +[SAVED_TRANSACTIONS] + +[SAVED_SENSITIVE_VALUES] + + +"#]]); +}); diff --git a/crates/forge/tests/cli/soldeer.rs b/crates/forge/tests/cli/soldeer.rs new file mode 100644 index 0000000000000..30b0c49577a2b --- /dev/null +++ b/crates/forge/tests/cli/soldeer.rs @@ -0,0 +1,359 @@ +//! Contains various tests related to `forge soldeer`. + +use std::{fs, path::Path}; + +use foundry_test_utils::forgesoldeer; +forgesoldeer!(install_dependency, |prj, cmd| { + let command = "install"; + let dependency = "forge-std~1.8.1"; + + let mut foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[dependencies] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_contents).unwrap(); + + cmd.arg("soldeer").args([command, dependency]).assert_success(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // Making sure the foundry contents are the right ones + foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[dependencies] +forge-std = "1.8.1" + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +"#; + + 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 = "https://gitlab.com/mario4582928/Mario.git"; + + let mut foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[dependencies] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_contents).unwrap(); + + cmd.arg("soldeer").args([command, dependency, git]).assert_success(); + + // 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 actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // Making sure the foundry contents are the right ones + foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[dependencies] +forge-std = { version = "1.8.1", git = "https://gitlab.com/mario4582928/Mario.git", rev = "22868f426bd4dd0e682b5ec5f9bd55507664240c" } + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +"#; + + 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 = "https://gitlab.com/mario4582928/Mario.git"; + let rev_flag = "--rev"; + let commit = "7a0663eaf7488732f39550be655bad6694974cb3"; + + let mut foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[dependencies] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_contents).unwrap(); + + cmd.arg("soldeer").args([command, dependency, git, rev_flag, commit]).assert_success(); + + // 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 actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // Making sure the foundry contents are the right ones + foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +[dependencies] +forge-std = { version = "1.8.1", git = "https://gitlab.com/mario4582928/Mario.git", rev = "7a0663eaf7488732f39550be655bad6694974cb3" } + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +"#; + + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); +}); + +forgesoldeer!(update_dependencies, |prj, cmd| { + let command = "update"; + + // We need to write this into the foundry.toml to make the update install the dependency + let foundry_updates = 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] +"@tt" = {version = "1.6.1", url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/3_3_0-rc_2_22-01-2024_13:12:57_contracts.zip"} +forge-std = { version = "1.8.1" } +solmate = "6.7.0" +mario = { version = "1.0", git = "https://gitlab.com/mario4582928/Mario.git", rev = "22868f426bd4dd0e682b5ec5f9bd55507664240c" } +mario-custom-tag = { version = "1.0", git = "https://gitlab.com/mario4582928/Mario.git", tag = "custom-tag" } +mario-custom-branch = { version = "1.0", git = "https://gitlab.com/mario4582928/Mario.git", tag = "custom-branch" } +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_updates).unwrap(); + + cmd.arg("soldeer").arg(command).assert_success(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let dep1 = prj.root().join("dependencies").join("@tt-1.6.1"); + let dep2 = prj.root().join("dependencies").join("forge-std-1.8.1"); + let dep3 = prj.root().join("dependencies").join("mario-1.0"); + let dep4 = prj.root().join("dependencies").join("solmate-6.7.0"); + let dep5 = prj.root().join("dependencies").join("mario-custom-tag-1.0"); + let dep6 = prj.root().join("dependencies").join("mario-custom-branch-1.0"); + + assert!(dep1.exists()); + assert!(dep2.exists()); + assert!(dep3.exists()); + assert!(dep4.exists()); + assert!(dep5.exists()); + assert!(dep6.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + + // assert_data_eq!(lock_contents, read_file_to_string(&path_lock_file)); + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // 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] +"@tt" = {version = "1.6.1", url = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/3_3_0-rc_2_22-01-2024_13:12:57_contracts.zip"} +forge-std = { version = "1.8.1" } +solmate = "6.7.0" +mario = { version = "1.0", git = "https://gitlab.com/mario4582928/Mario.git", rev = "22868f426bd4dd0e682b5ec5f9bd55507664240c" } +mario-custom-tag = { version = "1.0", git = "https://gitlab.com/mario4582928/Mario.git", tag = "custom-tag" } +mario-custom-branch = { version = "1.0", git = "https://gitlab.com/mario4582928/Mario.git", tag = "custom-branch" } +"#; + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); +}); + +forgesoldeer!(update_dependencies_simple_version, |prj, cmd| { + let command = "update"; + + // We need to write this into the foundry.toml to make the update install the dependency, this + // is he simplified version of version specification + let foundry_updates = 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" +"#; + let foundry_file = prj.root().join("foundry.toml"); + + fs::write(&foundry_file, foundry_updates).unwrap(); + + cmd.arg("soldeer").arg(command).assert_success(); + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // 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_with_remappings_config, |prj, cmd| { + let command = "install"; + let dependency = "forge-std~1.8.1"; + let foundry_updates = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib", "dependencies"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[soldeer] +remappings_generate = true +remappings_prefix = "@custom-f@" +remappings_location = "config" +remappings_regenerate = true + +[dependencies] +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_updates).unwrap(); + + cmd.arg("soldeer").args([command, dependency]).assert_success(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // Making sure the foundry contents are the right ones + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib", "dependencies"] +remappings = ["@custom-f@forge-std-1.8.1/=dependencies/forge-std-1.8.1/"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[soldeer] +remappings_generate = true +remappings_prefix = "@custom-f@" +remappings_location = "config" +remappings_regenerate = true + +[dependencies] +forge-std = "1.8.1" +"#; + + assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); +}); + +forgesoldeer!(install_dependency_with_remappings_txt, |prj, cmd| { + let command = "install"; + let dependency = "forge-std~1.8.1"; + let foundry_updates = r#" +[dependencies] + +[soldeer] +remappings_generate = true +remappings_prefix = "@custom-f@" +remappings_location = "txt" +remappings_regenerate = true +"#; + let foundry_file = prj.root().join("foundry.toml"); + fs::write(&foundry_file, foundry_updates).unwrap(); + + cmd.arg("soldeer").args([command, dependency]).assert_success(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert!(actual_lock_contents.contains("forge-std")); + + // Making sure the foundry contents are the right ones + let remappings_content = r#"@custom-f@forge-std-1.8.1/=dependencies/forge-std-1.8.1/ +"#; + let remappings_file = prj.root().join("remappings.txt"); + assert_data_eq!(read_file_to_string(&remappings_file), remappings_content); +}); + +forgesoldeer!(login, |prj, cmd| { + let command = "login"; + + let _ = cmd.arg("soldeer").arg(command).assert_failure(); +}); + +fn read_file_to_string(path: &Path) -> String { + let contents: String = fs::read_to_string(path).unwrap_or_default(); + contents +} diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index be761193d4954..e0c10a052e936 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -1,27 +1,25 @@ //! svm sanity checks -use foundry_test_utils::{forgetest_init, TestCommand, TestProject}; use semver::Version; -use svm::{self, Platform}; +use svm::Platform; -/// The latest solc release +/// The latest Solc release. /// -/// solc to foundry release process: -/// 1. new solc release -/// 2. svm updated with all build info -/// 3. svm bumped in ethers-rs -/// 4. ethers bumped in foundry + update the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 21); +/// Solc to Foundry release process: +/// 1. new solc release +/// 2. svm updated with all build info +/// 3. svm bumped in foundry-compilers +/// 4. foundry-compilers update with any breaking changes +/// 5. upgrade the `LATEST_SOLC` +const LATEST_SOLC: Version = Version::new(0, 8, 27); macro_rules! ensure_svm_releases { - ($($test:ident => $platform:ident),*) => { - $( + ($($test:ident => $platform:ident),* $(,)?) => {$( #[tokio::test(flavor = "multi_thread")] async fn $test() { ensure_latest_release(Platform::$platform).await } - )* - }; + )*}; } async fn ensure_latest_release(platform: Platform) { @@ -30,7 +28,7 @@ async fn ensure_latest_release(platform: Platform) { .unwrap_or_else(|err| panic!("Could not fetch releases for {platform}: {err:?}")); assert!( releases.releases.contains_key(&LATEST_SOLC), - "platform {platform:?} is missing solc info {LATEST_SOLC}" + "platform {platform:?} is missing solc info for v{LATEST_SOLC}" ); } @@ -44,27 +42,33 @@ ensure_svm_releases!( ); // Ensures we can always test with the latest solc build -forgetest_init!(can_test_with_latest_solc, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_test( - "Counter", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity =; +forgetest_init!(can_test_with_latest_solc, |prj, cmd| { + let src = format!( + r#" +pragma solidity ={LATEST_SOLC}; import "forge-std/Test.sol"; -contract CounterTest is Test { +contract CounterTest is Test {{ + function testAssert() public {{ + assert(true); + }} +}} + "# + ); + prj.add_test("Counter", &src).unwrap(); + cmd.arg("test").assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Counter.sol:CounterTest +[PASS] testAssert() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] +... +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] - function testAssert() public { - assert(true); - } -} - "# - .replace("", &LATEST_SOLC.to_string()), - ) - .unwrap(); +Ran 2 test suites [ELAPSED]: 3 tests passed, 0 failed, 0 skipped (3 total tests) - cmd.args(["test"]); - cmd.stdout().contains("[PASS]") +"#]]); }); diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 7cf7da5216dd2..c0e9750163504 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -1,28 +1,29 @@ -//! Contains various tests for checking `forge test` -use foundry_config::Config; +//! Contains various tests for `forge test`. + +use alloy_primitives::U256; +use anvil::{spawn, NodeConfig}; use foundry_test_utils::{ - forgetest, forgetest_init, - util::{OutputExt, TestCommand, TestProject}, + rpc, str, + util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}, }; -use foundry_utils::rpc; +use similar_asserts::assert_eq; use std::{path::PathBuf, str::FromStr}; // tests that test filters are handled correctly -forgetest!(can_set_filter_values, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_set_filter_values, |prj, cmd| { let patt = regex::Regex::new("test*").unwrap(); let glob = globset::Glob::from_str("foo/bar/baz*").unwrap(); // explicitly set patterns - let config = Config { - test_pattern: Some(patt.clone().into()), - test_pattern_inverse: None, - contract_pattern: Some(patt.clone().into()), - contract_pattern_inverse: None, - path_pattern: Some(glob.clone()), - path_pattern_inverse: None, - ..Default::default() - }; - prj.write_config(config); + prj.update_config(|config| { + config.test_pattern = Some(patt.clone().into()); + config.test_pattern_inverse = None; + config.contract_pattern = Some(patt.clone().into()); + config.contract_pattern_inverse = None; + config.path_pattern = Some(glob.clone()); + config.path_pattern_inverse = None; + config.coverage_pattern_inverse = None; + }); let config = cmd.config(); @@ -32,43 +33,37 @@ forgetest!(can_set_filter_values, |prj: TestProject, mut cmd: TestCommand| { 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 -forgetest!(warn_no_tests, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "dummy", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.13; - +forgetest!(warn_no_tests, |prj, cmd| { + prj.add_source( + "dummy", + r" contract Dummy {} -"#, - ) - .unwrap(); +", + ) + .unwrap(); // set up command cmd.args(["test"]); // run command and assert - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/warn_no_tests.stdout"), - ); + cmd.assert_failure().stdout_eq(str![[r#" +No tests found in project! Forge looks for functions that starts with `test`. + +"#]]); }); // tests that warning is displayed with pattern when no tests match -forgetest!(warn_no_tests_match, |prj: TestProject, mut cmd: TestCommand| { - prj.inner() - .add_source( - "dummy", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.13; - +forgetest!(warn_no_tests_match, |prj, cmd| { + prj.add_source( + "dummy", + r" contract Dummy {} -"#, - ) - .unwrap(); +", + ) + .unwrap(); // set up command cmd.args(["test", "--match-test", "testA.*", "--no-match-test", "testB.*"]); @@ -76,28 +71,31 @@ contract Dummy {} cmd.args(["--match-path", "*TestE*", "--no-match-path", "*TestF*"]); // run command and assert - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/warn_no_tests_match.stdout"), - ); + cmd.assert_failure().stdout_eq(str![[r#" +No tests match the provided pattern: + match-test: `testA.*` + no-match-test: `testB.*` + match-contract: `TestC.*` + no-match-contract: `TestD.*` + match-path: `*TestE*` + no-match-path: `*TestF*` + +"#]]); }); // tests that suggestion is provided with pattern when no tests match -forgetest!(suggest_when_no_tests_match, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(suggest_when_no_tests_match, |prj, cmd| { // set up project - prj.inner() - .add_source( - "TestE.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; - + prj.add_source( + "TestE.t.sol", + r" contract TestC { function test1() public { } } - "#, - ) - .unwrap(); + ", + ) + .unwrap(); // set up command cmd.args(["test", "--match-test", "testA.*", "--no-match-test", "testB.*"]); @@ -105,87 +103,104 @@ contract TestC { cmd.args(["--match-path", "*TestE*", "--no-match-path", "*TestF*"]); // run command and assert - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/suggest_when_no_tests_match.stdout"), - ); + cmd.assert_failure().stdout_eq(str![[r#" +No tests match the provided pattern: + match-test: `testA.*` + no-match-test: `testB.*` + match-contract: `TestC.*` + no-match-contract: `TestD.*` + match-path: `*TestE*` + no-match-path: `*TestF*` + +Did you mean `test1`? + +"#]]); }); // tests that direct import paths are handled correctly -forgetest!(can_fuzz_array_params, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_fuzz_array_params, |prj, cmd| { prj.insert_ds_test(); - prj.inner() - .add_source( - "ATest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "ATest.t.sol", + r#" import "./test.sol"; contract ATest is DSTest { - function testArray(uint64[2] calldata values) external { + function testArray(uint64[2] calldata) external { assertTrue(true); } } "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testArray(uint64[2]) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - cmd.arg("test"); - cmd.stdout().contains("[PASS]") +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); }); // tests that `bytecode_hash` will be sanitized -forgetest!(can_test_pre_bytecode_hash, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_test_pre_bytecode_hash, |prj, cmd| { prj.insert_ds_test(); - prj.inner() - .add_source( - "ATest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED + prj.add_source( + "ATest.t.sol", + r#" // pre bytecode hash version, was introduced in 0.6.0 pragma solidity 0.5.17; import "./test.sol"; contract ATest is DSTest { - function testArray(uint64[2] calldata values) external { + function testArray(uint64[2] calldata) external { assertTrue(true); } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("test"); - cmd.stdout().contains("[PASS]") + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testArray(uint64[2]) (runs: 256, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); }); // tests that using the --match-path option only runs files matching the path -forgetest!(can_test_with_match_path, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_test_with_match_path, |prj, cmd| { prj.insert_ds_test(); - prj.inner() - .add_source( - "ATest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "ATest.t.sol", + r#" import "./test.sol"; contract ATest is DSTest { - function testArray(uint64[2] calldata values) external { + function testPass() external { assertTrue(true); } } "#, - ) - .unwrap(); + ) + .unwrap(); - prj.inner() - .add_source( - "FailTest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "FailTest.t.sol", + r#" import "./test.sol"; contract FailTest is DSTest { function testNothing() external { @@ -193,29 +208,128 @@ contract FailTest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); + + cmd.args(["test", "--match-path", "*src/ATest.t.sol"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testPass() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// 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", "*src/ATest.t.sol"]); - cmd.stdout().contains("[PASS]") && !cmd.stdout().contains("[FAIL]") + cmd.args(["test", "--match-path", test_path.as_ref()]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/ATest.t.sol:ATest +[PASS] testPass() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +const SIMPLE_CONTRACT: &str = r#" +import "./test.sol"; +import "./console.sol"; + +contract SimpleContract { + uint256 public num; + + function setValues(uint256 _num) public { + num = _num; + } +} + +contract SimpleContractTest is DSTest { + function test() public { + SimpleContract c = new SimpleContract(); + c.setValues(100); + console.logUint(100); + } +} + "#; + +forgetest!(can_run_test_with_json_output_verbose, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_console(); + + prj.add_source("Simple.t.sol", SIMPLE_CONTRACT).unwrap(); + + // Assert that with verbose output the json output includes the traces + cmd.args(["test", "-vvv", "--json"]) + .assert_success() + .stdout_eq(file!["../fixtures/SimpleContractTestVerbose.json": Json]); +}); + +forgetest!(can_run_test_with_json_output_non_verbose, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_console(); + + prj.add_source("Simple.t.sol", SIMPLE_CONTRACT).unwrap(); + + // Assert that without verbose output the json output does not include the traces + cmd.args(["test", "--json"]) + .assert_success() + .stdout_eq(file!["../fixtures/SimpleContractTestNonVerbose.json": Json]); }); // tests that `forge test` will pick up tests that are stored in the `test = ` config value -forgetest!(can_run_test_in_custom_test_folder, |prj: TestProject, mut cmd: TestCommand| { +forgetest!(can_run_test_in_custom_test_folder, |prj, cmd| { prj.insert_ds_test(); // explicitly set the test folder - let config = Config { test: "nested/forge-tests".into(), ..Default::default() }; - prj.write_config(config); + prj.update_config(|config| config.test = "nested/forge-tests".into()); + let config = cmd.config(); assert_eq!(config.test, PathBuf::from("nested/forge-tests")); - prj.inner() - .add_source( - "nested/forge-tests/MyTest.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; + prj.add_source( + "nested/forge-tests/MyTest.t.sol", + r#" import "../../test.sol"; contract MyTest is DSTest { function testTrue() public { @@ -223,42 +337,68 @@ contract MyTest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); - cmd.arg("test"); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_run_test_in_custom_test_folder.stdout"), - ); + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/nested/forge-tests/MyTest.t.sol:MyTest +[PASS] testTrue() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); }); // checks that forge test repeatedly produces the same output -forgetest_init!(can_test_repeatedly, |_prj: TestProject, mut cmd: TestCommand| { - cmd.arg("test"); - cmd.assert_non_empty_stdout(); +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(can_test_repeatedly, |prj, cmd| { + prj.clear(); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); for _ in 0..5 { - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_test_repeatedly.stdout"), - ); + cmd.assert_success().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 2 tests for test/Counter.t.sol:CounterTest +[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS]) +[PASS] test_Increment() ([GAS]) +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); } }); // tests that `forge test` will run a test only once after changing the version -forgetest!( - runs_tests_exactly_once_with_changed_versions, - |prj: TestProject, mut cmd: TestCommand| { - prj.insert_ds_test(); - - prj.inner() - .add_source( - "Contract.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.10; +forgetest!(runs_tests_exactly_once_with_changed_versions, |prj, cmd| { + prj.insert_ds_test(); + + prj.add_source( + "Contract.t.sol", + r#" +pragma solidity *; + import "./test.sol"; + contract ContractTest is DSTest { function setUp() public {} @@ -267,56 +407,54 @@ contract ContractTest is DSTest { } } "#, - ) - .unwrap(); + ) + .unwrap(); - // pin version - let config = Config { solc: Some("0.8.10".into()), ..Default::default() }; - prj.write_config(config); + // pin version + prj.update_config(|config| { + config.solc = Some(SOLC_VERSION.into()); + }); - cmd.arg("test"); - cmd.unchecked_output() - .stdout_matches_path(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( - "tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.10.stdout", - )); + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! - // pin version - let config = Config { solc: Some("0.8.13".into()), ..Default::default() }; - prj.write_config(config); +Ran 1 test for src/Contract.t.sol:ContractTest +[PASS] testExample() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] - cmd.unchecked_output() - .stdout_matches_path(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( - "tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.13.stdout", - )); - } -); +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) -// checks that we can test forge std successfully -// `forgetest_init!` will install with `forge-std` under `lib/forge-std` -forgetest_init!( - #[serial_test::serial] - can_test_forge_std, - |prj: TestProject, mut cmd: TestCommand| { - let forge_std_dir = prj.root().join("lib/forge-std"); - // execute in subdir - cmd.cmd().current_dir(forge_std_dir); - cmd.args(["test", "--root", "."]); - let stdout = cmd.stdout(); - assert!(stdout.contains("[PASS]"), "No tests passed:\n{stdout}"); - assert!(!stdout.contains("[FAIL]"), "Tests failed :\n{stdout}"); - } -); +"#]]); + + // pin version + prj.update_config(|config| { + config.solc = Some(OTHER_SOLC_VERSION.into()); + }); + + cmd.forge_fuse().arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Contract.t.sol:ContractTest +[PASS] testExample() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); // tests that libraries are handled correctly in multiforking mode -forgetest_init!(can_use_libs_in_multi_fork, |prj: TestProject, mut cmd: TestCommand| { +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { prj.wipe_contracts(); - prj.inner() - .add_source( - "Contract.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.13; + prj.add_source( + "Contract.sol", + r" library Library { function f(uint256 a, uint256 b) public pure returns (uint256) { return a + b; @@ -330,19 +468,15 @@ contract Contract { c = Library.f(1, 2); } } - "#, - ) - .unwrap(); - - let endpoint = rpc::next_http_archive_rpc_endpoint(); + ", + ) + .unwrap(); - prj.inner() - .add_test( - "Contract.t.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.13; + let endpoint = rpc::next_http_archive_rpc_url(); + prj.add_test( + "Contract.t.sol", + &r#" import "forge-std/Test.sol"; import "src/Contract.sol"; @@ -356,21 +490,25 @@ contract ContractTest is Test { } } "# - .replace("", &endpoint), - ) - .unwrap(); + .replace("", &endpoint), + ) + .unwrap(); - cmd.arg("test"); - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/fixtures/can_use_libs_in_multi_fork.stdout"), - ); + cmd.arg("test").assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:ContractTest +[PASS] test() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); }); static FAILING_TEST: &str = r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.17; - import "forge-std/Test.sol"; contract FailingTest is Test { @@ -380,27 +518,2653 @@ contract FailingTest is Test { } "#; -forgetest_init!(exit_code_error_on_fail_fast, |prj: TestProject, mut cmd: TestCommand| { +forgetest_init!(exit_code_error_on_fail_fast, |prj, cmd| { prj.wipe_contracts(); - prj.inner().add_source("failing_test", FAILING_TEST).unwrap(); + prj.add_source("failing_test", FAILING_TEST).unwrap(); // set up command cmd.args(["test", "--fail-fast"]); // run command and assert error exit code - cmd.assert_err(); + cmd.assert_empty_stderr(); +}); + +forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_source("failing_test", FAILING_TEST).unwrap(); + // set up command + cmd.args(["test", "--fail-fast", "--json"]); + + // run command and assert error exit code + cmd.assert_empty_stderr(); +}); + +// https://github.com/foundry-rs/foundry/pull/6531 +forgetest_init!(fork_traces, |prj, cmd| { + prj.wipe_contracts(); + + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_test( + "Contract.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +interface IERC20 { + function name() external view returns (string memory); +} + +contract USDTCallingTest is Test { + function test() public { + vm.createSelectFork(""); + IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).name(); + } +} + "# + .replace("", &endpoint), + ) + .unwrap(); + + cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:USDTCallingTest +[PASS] test() ([GAS]) +Traces: + [..] USDTCallingTest::test() + ├─ [0] VM::createSelectFork("[..]") + │ └─ ← [Return] 0 + ├─ [3110] 0xdAC17F958D2ee523a2206206994597C13D831ec7::name() [staticcall] + │ └─ ← [Return] "Tether USD" + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/6579 +forgetest_init!(include_custom_types_in_traces, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +error PoolNotInitialized(); +event MyEvent(uint256 a); + +contract CustomTypesTest is Test { + function testErr() public pure { + revert PoolNotInitialized(); + } + function testEvent() public { + emit MyEvent(100); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/Contract.t.sol:CustomTypesTest +[FAIL: PoolNotInitialized()] testErr() ([GAS]) +Traces: + [247] CustomTypesTest::testErr() + └─ ← [Revert] PoolNotInitialized() + +[PASS] testEvent() ([GAS]) +Traces: + [1524] CustomTypesTest::testEvent() + ├─ emit MyEvent(a: 100) + └─ ← [Stop] + +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/Contract.t.sol:CustomTypesTest +[FAIL: PoolNotInitialized()] testErr() ([GAS]) + +Encountered a total of 1 failing tests, 1 tests succeeded + +"#]]); +}); + +forgetest_init!(can_test_transient_storage_with_isolation, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract TransientTester { + function locked() public view returns (bool isLocked) { + assembly { + isLocked := tload(0) + } + } + + modifier lock() { + require(!locked(), "locked"); + assembly { + tstore(0, 1) + } + _; + } + + function maybeReentrant(address target, bytes memory data) public lock { + (bool success, bytes memory ret) = target.call(data); + if (!success) { + // forwards revert reason + assembly { + let ret_size := mload(ret) + revert(add(32, ret), ret_size) + } + } + } +} + +contract TransientTest is Test { + function test() public { + TransientTester t = new TransientTester(); + vm.expectRevert(bytes("locked")); + t.maybeReentrant(address(t), abi.encodeCall(TransientTester.maybeReentrant, (address(0), new bytes(0)))); + + t.maybeReentrant(address(0), new bytes(0)); + assertEq(t.locked(), false); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate", "--evm-version", "cancun"]).assert_success(); }); forgetest_init!( - exit_code_error_on_fail_fast_with_json, - |prj: TestProject, mut cmd: TestCommand| { + #[ignore = "Too slow"] + can_disable_block_gas_limit, + |prj, cmd| { prj.wipe_contracts(); - prj.inner().add_source("failing_test", FAILING_TEST).unwrap(); - // set up command - cmd.args(["test", "--fail-fast", "--json"]); + let endpoint = rpc::next_http_archive_rpc_url(); + + prj.add_test( + "Contract.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +contract C is Test {} + +contract GasWaster { + function waste() public { + for (uint256 i = 0; i < 100; i++) { + new C(); + } + } +} + +contract GasLimitTest is Test { + function test() public { + vm.createSelectFork(""); + + GasWaster waster = new GasWaster(); + waster.waste(); + } +} + "# + .replace("", &endpoint), + ) + .unwrap(); - // run command and assert error exit code - cmd.assert_err(); + cmd.args(["test", "-vvvv", "--isolate", "--disable-block-gas-limit"]).assert_success(); } ); + +forgetest!(test_match_path, |prj, cmd| { + prj.add_source( + "dummy", + r" +contract Dummy { + function testDummy() public {} +} +", + ) + .unwrap(); + + cmd.args(["test", "--match-path", "src/dummy.sol"]); + cmd.assert_success(); +}); + +forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| { + prj.wipe_contracts(); + + // deterministic test so we always have 54 runs until test fails with overflow + prj.update_config(|config| { + config.fuzz.runs = 256; + config.fuzz.seed = Some(U256::from(100)); + }); + + prj.add_test( + "CounterFuzz.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract Counter { + uint256 public number = 0; + + function addOne(uint256 x) external pure returns (uint256) { + return x + 100_000_000; + } +} + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function testAddOne(uint256 x) public view { + assertEq(counter.addOne(x), x + 100_000_000); + } +} + "#, + ) + .unwrap(); + + // make sure there are only 61 runs (with proptest shrinking same test results in 298 runs) + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/CounterFuzz.t.sol:CounterTest +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]] testAddOne(uint256) (runs: 61, [AVG_GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/CounterFuzz.t.sol:CounterTest +[FAIL: panic: arithmetic underflow or overflow (0x11); counterexample: calldata=0xa76d58f5ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]] testAddOne(uint256) (runs: 61, [AVG_GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest_init!(should_exit_early_on_invariant_failure, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "CounterInvariant.t.sol", + r#" +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(); + + // make sure invariant test exit early with 0 runs + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/CounterInvariant.t.sol:CounterTest +[FAIL: failed to set up invariant testing environment: wrong count] invariant_early_exit() (runs: 0, calls: 0, reverts: 0) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 1 failed, 0 skipped (1 total tests) + +Failing tests: +Encountered 1 failing test in test/CounterInvariant.t.sol:CounterTest +[FAIL: failed to set up invariant testing environment: wrong count] invariant_early_exit() (runs: 0, calls: 0, reverts: 0) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +forgetest_init!(should_replay_failures_only, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "ReplayFailures.t.sol", + r#" +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"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 4 tests for test/ReplayFailures.t.sol:ReplayFailuresTest +[PASS] testA() ([GAS]) +[FAIL: revert: testB failed] testB() ([GAS]) +[PASS] testC() ([GAS]) +[FAIL: revert: testD failed] testD() ([GAS]) +Suite result: FAILED. 2 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 2 failed, 0 skipped (4 total tests) + +Failing tests: +Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest +[FAIL: revert: testB failed] testB() ([GAS]) +[FAIL: revert: testD failed] testD() ([GAS]) + +Encountered a total of 2 failing tests, 2 tests succeeded + +"#]]); + + // 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().args(["test", "--rerun"]).assert_failure().stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 2 tests for test/ReplayFailures.t.sol:ReplayFailuresTest +[FAIL: revert: testB failed] testB() ([GAS]) +[FAIL: revert: testD failed] testD() ([GAS]) +Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 2 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 2 failing tests in test/ReplayFailures.t.sol:ReplayFailuresTest +[FAIL: revert: testB failed] testB() ([GAS]) +[FAIL: revert: testD failed] testD() ([GAS]) + +Encountered a total of 2 failing tests, 0 tests succeeded + +"#]]); +}); + +// +forgetest_init!(should_not_record_setup_failures, |prj, cmd| { + prj.add_test( + "ReplayFailures.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract SetupFailureTest is Test { + function setUp() public { + require(2 > 1); + } + + function testA() public pure { + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_success(); + // Test failure filter should not be persisted if `setUp` failed. + assert!(!prj.root().join("cache/test-failures").exists()); +}); + +// https://github.com/foundry-rs/foundry/issues/7530 +forgetest_init!(should_show_precompile_labels, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +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(); + + cmd.args(["test", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:PrecompileLabelsTest +[PASS] testPrecompileLabels() ([GAS]) +Traces: + [14048] PrecompileLabelsTest::testPrecompileLabels() + ├─ [0] VM::deal(VM: [0x7109709ECfa91a80626fF3989D68f67F5b1DD12D], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(console: [0x000000000000000000636F6e736F6c652e6c6f67], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(Create2Deployer: [0x4e59b44847b379578588920cA78FbF26c0B4956C], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(DefaultSender: [0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(DefaultTestContract: [0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(ECRecover: [0x0000000000000000000000000000000000000001], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(SHA-256: [0x0000000000000000000000000000000000000002], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(RIPEMD-160: [0x0000000000000000000000000000000000000003], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(Identity: [0x0000000000000000000000000000000000000004], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(ModExp: [0x0000000000000000000000000000000000000005], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(ECAdd: [0x0000000000000000000000000000000000000006], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(ECMul: [0x0000000000000000000000000000000000000007], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(ECPairing: [0x0000000000000000000000000000000000000008], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(Blake2F: [0x0000000000000000000000000000000000000009], 1000000000000000000 [1e18]) + │ └─ ← [Return] + ├─ [0] VM::deal(PointEvaluation: [0x000000000000000000000000000000000000000A], 1000000000000000000 [1e18]) + │ └─ ← [Return] + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// tests that `forge test` with config `show_logs: true` for fuzz tests will +// display `console.log` info +forgetest_init!(should_show_logs_when_fuzz_test, |prj, cmd| { + prj.wipe_contracts(); + + // run fuzz test 3 times + prj.update_config(|config| { + config.fuzz.runs = 3; + config.fuzz.show_logs = true; + }); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract ContractFuzz is Test { + function testFuzzConsoleLog(uint256 x) public pure { + console.log("inside fuzz test, x is:", x); + } +} + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz +[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS]) +Logs: + inside fuzz test, x is: [..] + inside fuzz test, x is: [..] + inside fuzz test, x is: [..] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// tests that `forge test` with inline config `show_logs = true` for fuzz tests will +// display `console.log` info +forgetest_init!(should_show_logs_when_fuzz_test_inline_config, |prj, cmd| { + prj.wipe_contracts(); + + // run fuzz test 3 times + prj.update_config(|config| { + config.fuzz.runs = 3; + }); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract ContractFuzz is Test { + /// forge-config: default.fuzz.show-logs = true + function testFuzzConsoleLog(uint256 x) public pure { + console.log("inside fuzz test, x is:", x); + } +} + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz +[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS]) +Logs: + inside fuzz test, x is: [..] + inside fuzz test, x is: [..] + inside fuzz test, x is: [..] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// 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 + prj.update_config(|config| { + config.fuzz.runs = 3; + config.fuzz.show_logs = false; + }); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#" + import {Test, console} from "forge-std/Test.sol"; + contract ContractFuzz is Test { + + function testFuzzConsoleLog(uint256 x) public pure { + console.log("inside fuzz test, x is:", x); + } + } + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz +[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// 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 + prj.update_config(|config| { + config.fuzz.runs = 3; + }); + let config = cmd.config(); + assert_eq!(config.fuzz.runs, 3); + + prj.add_test( + "ContractFuzz.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract ContractFuzz is Test { + /// forge-config: default.fuzz.show-logs = false + function testFuzzConsoleLog(uint256 x) public pure { + console.log("inside fuzz test, x is:", x); + } +} + "#, + ) + .unwrap(); + cmd.args(["test", "-vv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/ContractFuzz.t.sol:ContractFuzz +[PASS] testFuzzConsoleLog(uint256) (runs: 3, [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// tests internal functions trace +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(internal_functions_trace, |prj, cmd| { + prj.wipe_contracts(); + prj.clear(); + + prj.add_test( + "Simple", + r#" +import {Test, console} 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#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Simple.sol:SimpleContractTest +[PASS] test() ([GAS]) +Traces: + [..] SimpleContractTest::test() + ├─ [165406] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 826 bytes of code + ├─ [22630] SimpleContract::increment() + │ ├─ [20147] SimpleContract::_setNum(1) + │ │ └─ ← 0 + │ └─ ← [Stop] + ├─ [23204] SimpleContract::setValues(100, 0x0000000000000000000000000000000000000123) + │ ├─ [247] SimpleContract::_setNum(100) + │ │ └─ ← 1 + │ ├─ [22336] SimpleContract::_setAddr(0x0000000000000000000000000000000000000123) + │ │ └─ ← 0x0000000000000000000000000000000000000000 + │ └─ ← [Stop] + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// tests internal functions trace with memory decoding +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(internal_functions_trace_memory, |prj, cmd| { + prj.wipe_contracts(); + prj.clear(); + + prj.add_test( + "Simple", + r#" +import {Test, console} 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"]).assert_success().stdout_eq(str![[r#" +... +Traces: + [..] SimpleContractTest::test() + ├─ [370554] → new SimpleContract@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 1737 bytes of code + ├─ [2511] SimpleContract::setStr("new value") + │ ├─ [1588] SimpleContract::_setStr("new value") + │ │ └─ ← "initial value" + │ └─ ← [Stop] + └─ ← [Stop] +... +"#]]); +}); + +// tests that `forge test` with a seed produces deterministic random values for uint and addresses. +forgetest_init!(deterministic_randomness_with_seed, |prj, cmd| { + prj.wipe_contracts(); + prj.add_test( + "DeterministicRandomnessTest.t.sol", + r#" +import {Test, console} from "forge-std/Test.sol"; + +contract DeterministicRandomnessTest is Test { + + function testDeterministicRandomUint() public { + console.log(vm.randomUint()); + console.log(vm.randomUint()); + console.log(vm.randomUint()); + } + + function testDeterministicRandomUintRange() public { + uint256 min = 0; + uint256 max = 1000000000; + console.log(vm.randomUint(min, max)); + console.log(vm.randomUint(min, max)); + console.log(vm.randomUint(min, max)); + } + + function testDeterministicRandomAddress() public { + console.log(vm.randomAddress()); + console.log(vm.randomAddress()); + console.log(vm.randomAddress()); + } +} +"#, + ) + .unwrap(); + + // Extracts the test result section from the DeterministicRandomnessTest contract output. + fn extract_test_result(out: &str) -> &str { + let start = out + .find("for test/DeterministicRandomnessTest.t.sol:DeterministicRandomnessTest") + .unwrap(); + let end = out.find("Suite result: ok.").unwrap(); + &out[start..end] + } + + // Run the test twice with the same seed and verify the outputs are the same. + let seed1 = "0xa1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"; + let out1 = cmd + .args(["test", "--fuzz-seed", seed1, "-vv"]) + .assert_success() + .get_output() + .stdout_lossy(); + let res1 = extract_test_result(&out1); + + let out2 = cmd + .forge_fuse() + .args(["test", "--fuzz-seed", seed1, "-vv"]) + .assert_success() + .get_output() + .stdout_lossy(); + let res2 = extract_test_result(&out2); + + assert_eq!(res1, res2); + + // Run the test with another seed and verify the output differs. + let seed2 = "0xb1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"; + let out3 = cmd + .forge_fuse() + .args(["test", "--fuzz-seed", seed2, "-vv"]) + .assert_success() + .get_output() + .stdout_lossy(); + let res3 = extract_test_result(&out3); + assert_ne!(res3, res1); + + // Run the test without a seed and verify the outputs differs once again. + cmd.forge_fuse(); + let out4 = cmd.args(["test", "-vv"]).assert_success().get_output().stdout_lossy(); + let res4 = extract_test_result(&out4); + assert_ne!(res4, res1); + assert_ne!(res4, res3); +}); + +// Tests that `pauseGasMetering` used at the end of test does not produce meaningless values. +// https://github.com/foundry-rs/foundry/issues/5491 +forgetest_init!(gas_metering_pause_last_call, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "ATest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract ATest is Test { + function testWeirdGas1() public { + vm.pauseGasMetering(); + } + + function testWeirdGas2() public { + uint256 a = 1; + uint256 b = a + 1; + require(b == 2, "b is not 2"); + vm.pauseGasMetering(); + } + + function testNormalGas() public { + vm.pauseGasMetering(); + vm.resumeGasMetering(); + } + + function testWithAssembly() public { + vm.pauseGasMetering(); + assembly { + return(0, 0) + } + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +[PASS] testNormalGas() (gas: 3153) +[PASS] testWeirdGas1() (gas: 2991) +[PASS] testWeirdGas2() (gas: 3218) +[PASS] testWithAssembly() (gas: 3034) +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/5564 +forgetest_init!(gas_metering_expect_revert, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "ATest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +contract ATest is Test { + error MyError(); + function testSelfMeteringRevert() public { + vm.pauseGasMetering(); + vm.expectRevert(MyError.selector); + this.selfReverts(); + } + function selfReverts() external { + vm.resumeGasMetering(); + revert MyError(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/ATest.t.sol:ATest +[PASS] testSelfMeteringRevert() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/4523 +forgetest_init!(gas_metering_gasleft, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "ATest.t.sol", + r#" +import "forge-std/Test.sol"; + +contract ATest is Test { + mapping(uint256 => bytes32) map; + + function test_GasMeter() public { + vm.pauseGasMetering(); + consumeGas(); + vm.resumeGasMetering(); + + consumeGas(); + } + + function test_GasLeft() public { + consumeGas(); + + uint256 start = gasleft(); + consumeGas(); + console.log("Gas cost:", start - gasleft()); + } + + function consumeGas() private { + for (uint256 i = 0; i < 100; i++) { + map[i] = keccak256(abi.encode(i)); + } + } +} + "#, + ) + .unwrap(); + + // Log and test gas cost should be similar. + cmd.args(["test", "-vvvv"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +Logs: + Gas cost: 50068 + +Traces: + [2303684] ATest::test_GasLeft() + ├─ [0] console::log("Gas cost:", 50068 [5.006e4]) [staticcall] + │ └─ ← [Stop] + └─ ← [Stop] + +[PASS] test_GasMeter() (gas: 53102) +Traces: + [53102] ATest::test_GasMeter() + ├─ [0] VM::pauseGasMetering() + │ └─ ← [Return] + ├─ [0] VM::resumeGasMetering() + │ └─ ← [Return] + └─ ← [Stop] +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/4370 +forgetest_init!(pause_gas_metering_with_delete, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "ATest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +contract ATest is Test { + uint a; + function test_negativeGas () public { + vm.pauseGasMetering(); + a = 100; + vm.resumeGasMetering(); + delete a; + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +[PASS] test_negativeGas() (gas: 0) +... +"#]]); +}); + +// tests `pauseTracing` and `resumeTracing` functions +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(pause_tracing, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + prj.add_source( + "Pause.t.sol", + r#" +import {Vm} from "./Vm.sol"; +import {DSTest} from "./test.sol"; +contract TraceGenerator is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + event DummyEvent(uint256 i); + function call(uint256 i) public { + emit DummyEvent(i); + } + function generate() public { + for (uint256 i = 0; i < 10; i++) { + if (i == 3) { + vm.pauseTracing(); + } + this.call(i); + if (i == 7) { + vm.resumeTracing(); + } + } + } +} +contract PauseTracingTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + event DummyEvent(uint256 i); + function setUp() public { + emit DummyEvent(1); + vm.pauseTracing(); + emit DummyEvent(2); + } + function test() public { + emit DummyEvent(3); + TraceGenerator t = new TraceGenerator(); + vm.resumeTracing(); + t.generate(); + } +} + "#, + ) + .unwrap(); + cmd.args(["test", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Traces: + [7757] PauseTracingTest::setUp() + ├─ emit DummyEvent(i: 1) + ├─ [0] VM::pauseTracing() [staticcall] + │ └─ ← [Return] + └─ ← [Stop] + + [449649] PauseTracingTest::test() + ├─ [0] VM::resumeTracing() [staticcall] + │ └─ ← [Return] + ├─ [22896] TraceGenerator::generate() + │ ├─ [1589] TraceGenerator::call(0) + │ │ ├─ emit DummyEvent(i: 0) + │ │ └─ ← [Stop] + │ ├─ [1589] TraceGenerator::call(1) + │ │ ├─ emit DummyEvent(i: 1) + │ │ └─ ← [Stop] + │ ├─ [1589] TraceGenerator::call(2) + │ │ ├─ emit DummyEvent(i: 2) + │ │ └─ ← [Stop] + │ ├─ [0] VM::pauseTracing() [staticcall] + │ │ └─ ← [Return] + │ ├─ [0] VM::resumeTracing() [staticcall] + │ │ └─ ← [Return] + │ ├─ [1589] TraceGenerator::call(8) + │ │ ├─ emit DummyEvent(i: 8) + │ │ └─ ← [Stop] + │ ├─ [1589] TraceGenerator::call(9) + │ │ ├─ emit DummyEvent(i: 9) + │ │ └─ ← [Stop] + │ └─ ← [Stop] + └─ ← [Stop] +... +"#]]); +}); + +forgetest_init!(gas_metering_reset, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + prj.add_source( + "ATest.t.sol", + r#" +import {Vm} from "./Vm.sol"; +import {DSTest} from "./test.sol"; +contract B { + function a() public returns (uint256) { + return 100; + } +} +contract ATest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + B b; + uint256 a; + + function testResetGas() public { + vm.resetGasMetering(); + } + + function testResetGas1() public { + vm.resetGasMetering(); + b = new B(); + vm.resetGasMetering(); + } + + function testResetGas2() public { + b = new B(); + b = new B(); + vm.resetGasMetering(); + } + + function testResetGas3() public { + vm.resetGasMetering(); + b = new B(); + b = new B(); + } + + function testResetGas4() public { + vm.resetGasMetering(); + b = new B(); + vm.resetGasMetering(); + b = new B(); + } + + function testResetGas5() public { + vm.resetGasMetering(); + b = new B(); + vm.resetGasMetering(); + b = new B(); + vm.resetGasMetering(); + } + + function testResetGas6() public { + vm.resetGasMetering(); + b = new B(); + b = new B(); + _reset(); + vm.resetGasMetering(); + } + + function testResetGas7() public { + vm.resetGasMetering(); + b = new B(); + b = new B(); + _reset(); + } + + function testResetGas8() public { + this.resetExternal(); + } + + function testResetGas9() public { + this.resetExternal(); + vm.resetGasMetering(); + } + + function testResetNegativeGas() public { + a = 100; + vm.resetGasMetering(); + + delete a; + } + + function _reset() internal { + vm.resetGasMetering(); + } + + function resetExternal() external { + b = new B(); + b = new B(); + vm.resetGasMetering(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).with_no_redact().assert_success().stdout_eq(str![[r#" +... +[PASS] testResetGas() (gas: 40) +[PASS] testResetGas1() (gas: 40) +[PASS] testResetGas2() (gas: 40) +[PASS] testResetGas3() (gas: [..]) +[PASS] testResetGas4() (gas: [..]) +[PASS] testResetGas5() (gas: 40) +[PASS] testResetGas6() (gas: 40) +[PASS] testResetGas7() (gas: 49) +[PASS] testResetGas8() (gas: [..]) +[PASS] testResetGas9() (gas: 40) +[PASS] testResetNegativeGas() (gas: 0) +... +"#]]); +}); + +// https://github.com/foundry-rs/foundry/issues/8705 +forgetest_init!(test_expect_revert_decode, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Counter.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +contract Counter { + uint256 public number; + error NumberNotEven(uint256 number); + error RandomError(); + function setNumber(uint256 newNumber) public { + if (newNumber % 2 != 0) { + revert NumberNotEven(newNumber); + } + number = newNumber; + } +} +contract CounterTest is Test { + Counter public counter; + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + function test_decode() public { + vm.expectRevert(Counter.RandomError.selector); + counter.setNumber(1); + } + function test_decode_with_args() public { + vm.expectRevert(abi.encodePacked(Counter.NumberNotEven.selector, uint(2))); + counter.setNumber(1); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: Error != expected error: NumberNotEven(1) != RandomError()] test_decode() ([GAS]) +[FAIL: Error != expected error: NumberNotEven(1) != NumberNotEven(2)] test_decode_with_args() ([GAS]) +... +"#]]); +}); + +// Tests that `expectPartialRevert` cheatcode partially matches revert data. +forgetest_init!(test_expect_partial_revert, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + prj.add_source( + "Counter.t.sol", + r#" +import {Vm} from "./Vm.sol"; +import {DSTest} from "./test.sol"; +contract Counter { + error WrongNumber(uint256 number); + function count() public pure { + revert WrongNumber(0); + } +} +contract CounterTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + function testExpectPartialRevertWithSelector() public { + Counter counter = new Counter(); + vm.expectPartialRevert(Counter.WrongNumber.selector); + counter.count(); + } + function testExpectPartialRevertWith4Bytes() public { + Counter counter = new Counter(); + vm.expectPartialRevert(bytes4(0x238ace70)); + counter.count(); + } + function testExpectRevert() public { + Counter counter = new Counter(); + vm.expectRevert(Counter.WrongNumber.selector); + counter.count(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +[PASS] testExpectPartialRevertWith4Bytes() ([GAS]) +[PASS] testExpectPartialRevertWithSelector() ([GAS]) +[FAIL: Error != expected error: WrongNumber(0) != custom error 0x238ace70] testExpectRevert() ([GAS]) +... +"#]]); +}); + +forgetest_init!(test_assume_no_revert, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + prj.update_config(|config| { + config.fuzz.runs = 100; + config.fuzz.seed = Some(U256::from(100)); + }); + + prj.add_source( + "Counter.t.sol", + r#" +import {Vm} from "./Vm.sol"; +import {DSTest} from "./test.sol"; +contract CounterWithRevert { + error CountError(); + error CheckError(); + + function count(uint256 a) public pure returns (uint256) { + if (a > 1000 || a < 10) { + revert CountError(); + } + return 99999999; + } + function check(uint256 a) public pure { + if (a == 99999999) { + revert CheckError(); + } + } + function dummy() public pure {} +} + +contract CounterRevertTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_assume_no_revert_pass(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + assertEq(a, 99999999); + } + function test_assume_no_revert_fail_assert(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + // Test should fail on next assertion. + assertEq(a, 1); + } + function test_assume_no_revert_fail_in_2nd_call(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + // Test should revert here (not in scope of `assumeNoRevert` cheatcode). + counter.check(a); + assertEq(a, 99999999); + } + function test_assume_no_revert_fail_in_3rd_call(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + // Test `assumeNoRevert` applied to non reverting call should not be available for next reverting call. + vm.assumeNoRevert(); + counter.dummy(); + // Test will revert here (not in scope of `assumeNoRevert` cheatcode). + counter.check(a); + assertEq(a, 99999999); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).with_no_redact().assert_failure().stdout_eq(str![[r#" +... +[FAIL; counterexample: [..]] test_assume_no_revert_fail_assert(uint256) [..] +[FAIL: CheckError(); counterexample: [..]] test_assume_no_revert_fail_in_2nd_call(uint256) [..] +[FAIL: CheckError(); counterexample: [..]] test_assume_no_revert_fail_in_3rd_call(uint256) [..] +[PASS] test_assume_no_revert_pass(uint256) [..] +... +"#]]); +}); + +forgetest_init!(skip_output, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + prj.add_source( + "Counter.t.sol", + r#" + import {Vm} from "./Vm.sol"; + import {DSTest} from "./test.sol"; + + contract Skips is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_skipUnit() public { + vm.skip(true); + } + function test_skipUnitReason() public { + vm.skip(true, "unit"); + } + + function test_skipFuzz(uint) public { + vm.skip(true); + } + function test_skipFuzzReason(uint) public { + vm.skip(true, "fuzz"); + } + + function invariant_skipInvariant() public { + vm.skip(true); + } + function invariant_skipInvariantReason() public { + vm.skip(true, "invariant"); + } + } + "#, + ) + .unwrap(); + + cmd.arg("test").assert_success().stdout_eq(str![[r#" +... +Ran 6 tests for src/Counter.t.sol:Skips +[SKIP] invariant_skipInvariant() (runs: 1, calls: 1, reverts: 1) +[SKIP: invariant] invariant_skipInvariantReason() (runs: 1, calls: 1, reverts: 1) +[SKIP] test_skipFuzz(uint256) (runs: 0, [AVG_GAS]) +[SKIP: fuzz] test_skipFuzzReason(uint256) (runs: 0, [AVG_GAS]) +[SKIP] test_skipUnit() ([GAS]) +[SKIP: unit] test_skipUnitReason() ([GAS]) +Suite result: ok. 0 passed; 0 failed; 6 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 6 skipped (6 total tests) + +"#]]); +}); + +forgetest_init!(skip_setup, |prj, cmd| { + prj.add_test( + "Counter.t.sol", + r#" +import "forge-std/Test.sol"; + +contract SkipCounterSetup is Test { + + function setUp() public { + vm.skip(true, "skip counter test"); + } + + function test_require1() public pure { + require(1 > 2); + } + + function test_require2() public pure { + require(1 > 2); + } + + function test_require3() public pure { + require(1 > 2); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mc", "SkipCounterSetup"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Counter.t.sol:SkipCounterSetup +[SKIP: skipped: skip counter test] setUp() ([GAS]) +Suite result: ok. 0 passed; 0 failed; 1 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 0 tests passed, 0 failed, 1 skipped (1 total tests) + +"#]]); +}); + +forgetest_init!(should_generate_junit_xml_report, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + prj.add_source( + "JunitReportTest.t.sol", + r#" + import {Vm} from "./Vm.sol"; + import {DSTest} from "./test.sol"; + + contract AJunitReportTest is DSTest { + function test_junit_assert_fail() public { + assert(1 > 2); + } + + function test_junit_revert_fail() public { + require(1 > 2, "Revert"); + } + } + + contract BJunitReportTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + function test_junit_pass() public { + require(1 < 2, "Revert"); + } + + function test_junit_skip() public { + vm.skip(true); + } + + function test_junit_skip_with_message() public { + vm.skip(true, "skipped test"); + } + + function test_junit_pass_fuzz(uint256 a) public { + } + } + "#, + ) + .unwrap(); + + cmd.args(["test", "--junit"]).assert_failure().stdout_eq(str![[r#" + + + + + + [FAIL: panic: assertion failed (0x01)] test_junit_assert_fail() ([GAS]) + + + + [FAIL: revert: Revert] test_junit_revert_fail() ([GAS]) + + Suite result: FAILED. 0 passed; 2 failed; 0 skipped; [ELAPSED] + + + + [PASS] test_junit_pass() ([GAS]) + + + [PASS] test_junit_pass_fuzz(uint256) (runs: 256, [AVG_GAS]) + + + + [SKIP] test_junit_skip() ([GAS]) + + + + [SKIP: skipped test] test_junit_skip_with_message() ([GAS]) + + Suite result: ok. 2 passed; 0 failed; 2 skipped; [ELAPSED] + + + + +"#]]); +}); + +forgetest_init!(should_generate_junit_xml_report_with_logs, |prj, cmd| { + prj.wipe_contracts(); + prj.add_source( + "JunitReportTest.t.sol", + r#" +import "forge-std/Test.sol"; +contract JunitReportTest is Test { + function test_junit_with_logs() public { + console.log("Step1"); + console.log("Step2"); + console.log("Step3"); + assert(2 > 1); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--junit", "-vvvv"]).assert_success().stdout_eq(str![[r#" + + + + + [PASS] test_junit_with_logs() ([GAS])/nLogs:/n Step1/n Step2/n Step3/n + + Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + + + + +"#]]); +}); + +forgetest_init!( + // Enable this if no cheatcodes are deprecated. + // #[ignore = "no cheatcodes are deprecated"] + test_deprecated_cheatcode_warning, + |prj, cmd| { + prj.add_test( + "DeprecatedCheatcodeTest.t.sol", + r#" + import "forge-std/Test.sol"; + contract DeprecatedCheatcodeTest is Test { + function test_deprecated_cheatcode() public view { + vm.keyExists('{"a": 123}', ".a"); + vm.keyExists('{"a": 123}', ".a"); + } + } + + contract DeprecatedCheatcodeFuzzTest is Test { + function test_deprecated_cheatcode(uint256 a) public view { + vm.keyExists('{"a": 123}', ".a"); + } + } + + contract Counter { + uint256 a; + + function count() public { + a++; + } + } + + contract DeprecatedCheatcodeInvariantTest is Test { + function setUp() public { + Counter counter = new Counter(); + } + + /// forge-config: default.invariant.runs = 1 + function invariant_deprecated_cheatcode() public { + vm.keyExists('{"a": 123}', ".a"); + } + } + "#, + ) + .unwrap(); + + // Tests deprecated cheatcode warning for unit tests. + cmd.args(["test", "--mc", "DeprecatedCheatcodeTest"]).assert_success().stderr_eq(str![[ + r#" +Warning: the following cheatcode(s) are deprecated and will be removed in future versions: + keyExists(string,string): replaced by `keyExistsJson` + +"# + ]]); + + // Tests deprecated cheatcode warning for fuzz tests. + cmd.forge_fuse() + .args(["test", "--mc", "DeprecatedCheatcodeFuzzTest"]) + .assert_success() + .stderr_eq(str![[r#" +Warning: the following cheatcode(s) are deprecated and will be removed in future versions: + keyExists(string,string): replaced by `keyExistsJson` + +"#]]); + + // Tests deprecated cheatcode warning for invariant tests. + cmd.forge_fuse() + .args(["test", "--mc", "DeprecatedCheatcodeInvariantTest"]) + .assert_success() + .stderr_eq(str![[r#" +Warning: the following cheatcode(s) are deprecated and will be removed in future versions: + keyExists(string,string): replaced by `keyExistsJson` + +"#]]); + } +); + +forgetest_init!(requires_single_test, |prj, cmd| { + cmd.args(["test", "--debug"]).assert_failure().stderr_eq(str![[r#" +Error: 2 tests matched your criteria, but exactly 1 test must match in order to run the debugger. + +Use --match-contract and --match-path to further limit the search. + +"#]]); + cmd.forge_fuse().args(["test", "--flamegraph"]).assert_failure().stderr_eq(str![[r#" +Error: 2 tests matched your criteria, but exactly 1 test must match in order to generate a flamegraph. + +Use --match-contract and --match-path to further limit the search. + +"#]]); + cmd.forge_fuse().args(["test", "--flamechart"]).assert_failure().stderr_eq(str![[r#" +Error: 2 tests matched your criteria, but exactly 1 test must match in order to generate a flamechart. + +Use --match-contract and --match-path to further limit the search. + +"#]]); +}); + +// Test a script that calls vm.rememberKeys +forgetest_init!(script_testing, |prj, cmd| { + prj + .add_source( + "Foo", + r#" +import "forge-std/Script.sol"; + +interface Vm { +function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs); +} + +contract WalletScript is Script { +function run() public { + string memory mnemonic = "test test test test test test test test test test test junk"; + string memory derivationPath = "m/44'/60'/0'/0/"; + address[] memory wallets = Vm(address(vm)).rememberKeys(mnemonic, derivationPath, 3); + for (uint256 i = 0; i < wallets.length; i++) { + console.log(wallets[i]); + } +} +} + +contract FooTest { + WalletScript public script; + + + function setUp() public { + script = new WalletScript(); + } + + function testWalletScript() public { + script.run(); + } +} + +"#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "testWalletScript", "-vvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Foo.sol:FooTest +[PASS] testWalletScript() ([GAS]) +Logs: + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 + 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC +... +"#]]); +}); + +// +forgetest_init!(metadata_bytecode_traces, |prj, cmd| { + prj.add_source( + "ParentProxy.sol", + r#" +import {Counter} from "./Counter.sol"; + +abstract contract ParentProxy { + Counter impl; + bytes data; + + constructor(Counter _implementation, bytes memory _data) { + impl = _implementation; + data = _data; + } +} + "#, + ) + .unwrap(); + prj.add_source( + "Proxy.sol", + r#" +import {ParentProxy} from "./ParentProxy.sol"; +import {Counter} from "./Counter.sol"; + +contract Proxy is ParentProxy { + constructor(Counter _implementation, bytes memory _data) + ParentProxy(_implementation, _data) + {} +} + "#, + ) + .unwrap(); + + prj.add_test( + "MetadataTraceTest.t.sol", + r#" +import {Counter} from "src/Counter.sol"; +import {Proxy} from "src/Proxy.sol"; + +import {Test} from "forge-std/Test.sol"; + +contract MetadataTraceTest is Test { + function test_proxy_trace() public { + Counter counter = new Counter(); + new Proxy(counter, ""); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_proxy_trace", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest +[PASS] test_proxy_trace() ([GAS]) +Traces: + [..] MetadataTraceTest::test_proxy_trace() + ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [..] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ └─ ← [Return] 62 bytes of code + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); + + // Check consistent traces for running with no metadata. + cmd.forge_fuse() + .args(["test", "--mt", "test_proxy_trace", "-vvvv", "--no-metadata"]) + .assert_success() + .stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/MetadataTraceTest.t.sol:MetadataTraceTest +[PASS] test_proxy_trace() ([GAS]) +Traces: + [..] MetadataTraceTest::test_proxy_trace() + ├─ [..] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 427 bytes of code + ├─ [..] → new Proxy@0x2e234DAe75C793f67A35089C9d99245E1C58470b + │ └─ ← [Return] 8 bytes of code + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Tests if dump of execution was created. +forgetest!(test_debug_with_dump, |prj, cmd| { + prj.add_source( + "dummy", + r" +contract Dummy { + function testDummy() public {} +} +", + ) + .unwrap(); + + let dump_path = prj.root().join("dump.json"); + + cmd.args(["test", "--mt", "testDummy", "--debug", "--dump", dump_path.to_str().unwrap()]); + cmd.assert_success(); + + assert!(dump_path.exists()); +}); + +forgetest_init!(test_assume_no_revert_with_data, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.runs = 60; + config.fuzz.seed = Some(U256::from(100)); + }); + + prj.add_source( + "AssumeNoRevertTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +interface Vm { + struct PotentialRevert { + address reverter; + bool partialMatch; + bytes revertData; + } + function expectRevert() external; + function assumeNoRevert() external pure; + function assumeNoRevert(PotentialRevert calldata revertData) external pure; + function assumeNoRevert(PotentialRevert[] calldata revertData) external pure; + function expectRevert(bytes4 revertData, uint64 count) external; +} + +contract ReverterB { + /// @notice has same error selectors as contract below to test the `reverter` param + error MyRevert(); + error SpecialRevertWithData(uint256 x); + + function revertIf2(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithData() public pure returns (bool) { + revert SpecialRevertWithData(2); + } +} + +contract Reverter { + error MyRevert(); + error RevertWithData(uint256 x); + error UnusedError(); + error ExpectedRevertCountZero(); + + ReverterB public immutable subReverter; + + constructor() { + subReverter = new ReverterB(); + } + + function myFunction() public pure returns (bool) { + revert MyRevert(); + } + + function revertIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithDataIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert RevertWithData(2); + } + return true; + } + + function twoPossibleReverts(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } else if (x == 3) { + revert RevertWithData(3); + } + return true; + } + + function revertIf2Or3ExpectedRevertZero(uint256 x) public pure returns (bool) { + if (x == 2) { + revert ExpectedRevertCountZero(); + } else if (x == 3) { + revert MyRevert(); + } + return true; + } +} + +contract ReverterTest is Test { + Reverter reverter; + Vm _vm = Vm(VM_ADDRESS); + + function setUp() public { + reverter = new Reverter(); + } + + /// @dev Test that `assumeNoRevert` does not reject an unanticipated error selector + function testAssume_wrongSelector_fails(uint256 x) public view { + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.UnusedError.selector), partialMatch: false, reverter: address(0)})); + reverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` does not reject an unanticipated error with extra data + function testAssume_wrongData_fails(uint256 x) public view { + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 3), partialMatch: false, reverter: address(0)})); + reverter.revertWithDataIf2(x); + } + + /// @dev Test that `assumeNoRevert` correctly rejects an error selector from a different contract + function testAssumeWithReverter_fails(uint256 x) public view { + ReverterB subReverter = (reverter.subReverter()); + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(reverter)})); + subReverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` correctly rejects one of two different error selectors when supplying a specific reverter + function testMultipleAssumes_OneWrong_fails(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(reverter)}); + revertData[1] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 4), partialMatch: false, reverter: address(reverter)}); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + } + + /// @dev Test that `assumeNoRevert` assumptions are cleared after the first non-cheatcode external call + function testMultipleAssumesClearAfterCall_fails(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), partialMatch: false, reverter: address(0)}); + revertData[1] = Vm.PotentialRevert({revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 4), partialMatch: false, reverter: address(reverter)}); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + + reverter.twoPossibleReverts(2); + } + + /// @dev Test that `assumeNoRevert` correctly rejects a generic assumeNoRevert call after any specific reason is provided + function testMultipleAssumes_ThrowOnGenericNoRevert_AfterSpecific_fails(bytes4 selector) public view { + _vm.assumeNoRevert(Vm.PotentialRevert({revertData: abi.encode(selector), partialMatch: false, reverter: address(0)})); + _vm.assumeNoRevert(); + reverter.twoPossibleReverts(2); + } + + function testAssumeThenExpectCountZeroFails(uint256 x) public { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + _vm.expectRevert(Reverter.ExpectedRevertCountZero.selector, 0); + reverter.revertIf2Or3ExpectedRevertZero(x); + } + + function testExpectCountZeroThenAssumeFails(uint256 x) public { + _vm.expectRevert(Reverter.ExpectedRevertCountZero.selector, 0); + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + reverter.revertIf2Or3ExpectedRevertZero(x); + } + +}"#, + ) + .unwrap(); + cmd.args(["test", "--mc", "ReverterTest"]).assert_failure().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 8 tests for src/AssumeNoRevertTest.t.sol:ReverterTest +[FAIL: expected 0 reverts with reason: 0x92fa317b, but got one; counterexample: [..]] testAssumeThenExpectCountZeroFails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: MyRevert(); counterexample: calldata=[..]] testAssumeWithReverter_fails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: RevertWithData(2); counterexample: [..]] testAssume_wrongData_fails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: MyRevert(); counterexample: [..]] testAssume_wrongSelector_fails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: expected 0 reverts with reason: 0x92fa317b, but got one; counterexample: [..]] testExpectCountZeroThenAssumeFails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: MyRevert(); counterexample: [..]] testMultipleAssumesClearAfterCall_fails(uint256) (runs: 0, [AVG_GAS]) +[FAIL: RevertWithData(3); counterexample: [..]] testMultipleAssumes_OneWrong_fails(uint256) (runs: [..], [AVG_GAS]) +[FAIL: vm.assumeNoRevert: you must make another external call prior to calling assumeNoRevert again; counterexample: [..]] testMultipleAssumes_ThrowOnGenericNoRevert_AfterSpecific_fails(bytes4) (runs: [..], [AVG_GAS]) +... + +"#]]); +}); + +forgetest_async!(can_get_broadcast_txs, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let (_api, handle) = spawn(NodeConfig::test().silent()).await; + + prj.insert_vm(); + prj.insert_ds_test(); + prj.insert_console(); + + prj.add_source( + "Counter.sol", + r#" + contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + "#, + ) + .unwrap(); + + prj.add_script( + "DeployCounter", + r#" + import "forge-std/Script.sol"; + import "src/Counter.sol"; + + contract DeployCounter is Script { + function run() public { + vm.startBroadcast(); + + Counter counter = new Counter(); + + counter.increment(); + + counter.setNumber(10); + + vm.stopBroadcast(); + } + } + "#, + ) + .unwrap(); + + prj.add_script( + "DeployCounterWithCreate2", + r#" + import "forge-std/Script.sol"; + import "src/Counter.sol"; + + contract DeployCounterWithCreate2 is Script { + function run() public { + vm.startBroadcast(); + + bytes32 salt = bytes32(uint256(1337)); + Counter counter = new Counter{salt: salt}(); + + counter.increment(); + + counter.setNumber(20); + + vm.stopBroadcast(); + } + } + "#, + ) + .unwrap(); + + let test = r#" + import {Vm} from "../src/Vm.sol"; + import {DSTest} from "../src/test.sol"; + import {console} from "../src/console.sol"; + + contract GetBroadcastTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_getLatestBroacast() external { + // Gets the latest create + Vm.BroadcastTxSummary memory broadcast = vm.getBroadcast( + "Counter", + 31337, + Vm.BroadcastTxType.Create + ); + + console.log("latest create"); + console.log(broadcast.blockNumber); + + assertEq(broadcast.blockNumber, 1); + + // Gets the latest create2 + Vm.BroadcastTxSummary memory broadcast2 = vm.getBroadcast( + "Counter", + 31337, + Vm.BroadcastTxType.Create2 + ); + + console.log("latest create2"); + console.log(broadcast2.blockNumber); + assertEq(broadcast2.blockNumber, 4); + + // Gets the latest call + Vm.BroadcastTxSummary memory broadcast3 = vm.getBroadcast( + "Counter", + 31337, + Vm.BroadcastTxType.Call + ); + + console.log("latest call"); + assertEq(broadcast3.blockNumber, 6); + } + + function test_getBroadcasts() public { + // Gets all calls + Vm.BroadcastTxSummary[] memory broadcasts = vm.getBroadcasts( + "Counter", + 31337, + Vm.BroadcastTxType.Call + ); + + assertEq(broadcasts.length, 4); + } + + function test_getAllBroadcasts() public { + // Gets all broadcasts + Vm.BroadcastTxSummary[] memory broadcasts2 = vm.getBroadcasts( + "Counter", + 31337 + ); + + assertEq(broadcasts2.length, 6); + } + + function test_getLatestDeployment() public { + address deployedAddress = vm.getDeployment( + "Counter", + 31337 + ); + + assertEq(deployedAddress, address(0x90d4E26f2e78feDf488c7F3C46B8053a0515c71F)); + } + + function test_getDeployments() public { + address[] memory deployments = vm.getDeployments( + "Counter", + 31337 + ); + + assertEq(deployments.length, 2); + assertEq(deployments[0], address(0x90d4E26f2e78feDf488c7F3C46B8053a0515c71F)); // Create2 address - latest deployment + assertEq(deployments[1], address(0x5FbDB2315678afecb367f032d93F642f64180aa3)); // Create address - oldest deployment + } +} + "#; + + prj.add_test("GetBroadcast", test).unwrap(); + + let sender = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; + + cmd.args([ + "script", + "DeployCounter", + "--rpc-url", + &handle.http_endpoint(), + "--sender", + sender, + "--unlocked", + "--broadcast", + "--slow", + ]) + .assert_success(); + + cmd.forge_fuse() + .args([ + "script", + "DeployCounterWithCreate2", + "--rpc-url", + &handle.http_endpoint(), + "--sender", + sender, + "--unlocked", + "--broadcast", + "--slow", + ]) + .assert_success(); + + let broadcast_path = prj.root().join("broadcast"); + + // Check if the broadcast folder exists + assert!(broadcast_path.exists() && broadcast_path.is_dir()); + + cmd.forge_fuse().args(["test", "--mc", "GetBroadcastTest", "-vvv"]).assert_success(); +}); + +// See +forgetest_init!(test_roll_scroll_fork_with_cancun, |prj, cmd| { + prj.add_test( + "ScrollForkTest.t.sol", + r#" + +import {Test} from "forge-std/Test.sol"; + +contract ScrollForkTest is Test { + function test_roll_scroll_fork_to_tx() public { + vm.createSelectFork("https://scroll-mainnet.chainstacklabs.com/"); + bytes32 targetTxHash = 0xf94774a1f69bba76892141190293ffe85dd8d9ac90a0a2e2b114b8c65764014c; + vm.rollFork(targetTxHash); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_roll_scroll_fork_to_tx", "--evm-version", "cancun"]) + .assert_success(); +}); + +// Test that only provider is included in failed fork error. +forgetest_init!(test_display_provider_on_error, |prj, cmd| { + prj.add_test( + "ForkTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract ForkTest is Test { + function test_fork_err_message() public { + vm.createSelectFork("https://eth-mainnet.g.alchemy.com/v2/DUMMY_KEY"); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_fork_err_message"]).assert_failure().stdout_eq(str![[r#" +... +Ran 1 test for test/ForkTest.t.sol:ForkTest +[FAIL: vm.createSelectFork: could not instantiate forked environment with provider eth-mainnet.g.alchemy.com; failed to get latest block number; [..]] test_fork_err_message() ([GAS]) +Suite result: FAILED. 0 passed; 1 failed; 0 skipped; [ELAPSED] +... + +"#]]); +}); + +// Tests that test traces display state changes when running with verbosity. +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(should_show_state_changes, |prj, cmd| { + cmd.args(["test", "--mt", "test_Increment", "-vvvvv"]).assert_success().stdout_eq(str![[r#" +... +Ran 1 test for test/Counter.t.sol:CounterTest +[PASS] test_Increment() ([GAS]) +Traces: + [137242] CounterTest::setUp() + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [2592] Counter::setNumber(0) + │ └─ ← [Stop] + └─ ← [Stop] + + [31851] CounterTest::test_Increment() + ├─ [22418] Counter::increment() + │ ├─ storage changes: + │ │ @ 0: 0 → 1 + │ └─ ← [Stop] + ├─ [424] Counter::number() [staticcall] + │ └─ ← [Return] 1 + ├─ [0] VM::assertEq(1, 1) [staticcall] + │ └─ ← [Return] + ├─ storage changes: + │ @ 0: 0 → 1 + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Tests that chained errors are properly displayed. +// +forgetest!(displays_chained_error, |prj, cmd| { + prj.add_test( + "Foo.t.sol", + r#" +contract ContractTest { + function test_anything(uint) public {} +} + "#, + ) + .unwrap(); + + cmd.arg("test").arg("--gas-limit=100").assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/Foo.t.sol:ContractTest +[FAIL: EVM error; transaction validation error: call gas cost exceeds the gas limit] setUp() ([GAS]) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]]); +}); + +// Tests that `start/stopAndReturn` debugTraceRecording does not panic when running with +// verbosity > 3. +forgetest_init!(should_not_panic_on_debug_trace_verbose, |prj, cmd| { + prj.add_test( + "DebugTraceRecordingTest.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract DebugTraceRecordingTest is Test { + function test_start_stop_recording() public { + vm.startDebugTraceRecording(); + Counter counter = new Counter(); + counter.increment(); + vm.stopAndReturnDebugTraceRecording(); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "test_start_stop_recording", "-vvvv"]).assert_success().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/DebugTraceRecordingTest.t.sol:DebugTraceRecordingTest +[PASS] test_start_stop_recording() ([GAS]) +Traces: + [..] DebugTraceRecordingTest::test_start_stop_recording() + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]], + ); +}); + +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(colored_traces, |prj, cmd| { + cmd.args(["test", "--mt", "test_Increment", "--color", "always", "-vvvvv"]) + .assert_success() + .stdout_eq(file!["../fixtures/colored_traces.svg": TermSvg]); +}); + +// Tests that traces for successful tests can be suppressed by using `-s` flag. +// +#[cfg(not(feature = "isolate-by-default"))] +forgetest_init!(should_only_show_failed_tests_trace, |prj, cmd| { + prj.add_test( + "SuppressTracesTest.t.sol", + r#" +import "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract SuppressTracesTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_increment_success() public { + console.log("test increment success"); + counter.increment(); + assertEq(counter.number(), 1); + } + + function test_increment_failure() public { + console.log("test increment failure"); + counter.increment(); + assertEq(counter.number(), 100); + } +} + "#, + ) + .unwrap(); + + // Show traces and logs for failed test only. + cmd.args(["test", "--mc", "SuppressTracesTest", "-vvvv", "-s"]).assert_failure().stdout_eq( + str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 2 tests for test/SuppressTracesTest.t.sol:SuppressTracesTest +[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS]) +Logs: + test increment failure + +Traces: + [137242] SuppressTracesTest::setUp() + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [2592] Counter::setNumber(0) + │ └─ ← [Stop] + └─ ← [Stop] + + [35178] SuppressTracesTest::test_increment_failure() + ├─ [0] console::log("test increment failure") [staticcall] + │ └─ ← [Stop] + ├─ [22418] Counter::increment() + │ └─ ← [Stop] + ├─ [424] Counter::number() [staticcall] + │ └─ ← [Return] 1 + ├─ [0] VM::assertEq(1, 100) [staticcall] + │ └─ ← [Revert] assertion failed: 1 != 100 + └─ ← [Revert] assertion failed: 1 != 100 + +[PASS] test_increment_success() ([GAS]) +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/SuppressTracesTest.t.sol:SuppressTracesTest +[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS]) + +Encountered a total of 1 failing tests, 1 tests succeeded + +"#]], + ); + + // Show traces and logs for all tests. + cmd.forge_fuse() + .args(["test", "--mc", "SuppressTracesTest", "-vvvv"]) + .assert_failure() + .stdout_eq(str![[r#" +No files changed, compilation skipped + +Ran 2 tests for test/SuppressTracesTest.t.sol:SuppressTracesTest +[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS]) +Logs: + test increment failure + +Traces: + [137242] SuppressTracesTest::setUp() + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + │ └─ ← [Return] 481 bytes of code + ├─ [2592] Counter::setNumber(0) + │ └─ ← [Stop] + └─ ← [Stop] + + [35178] SuppressTracesTest::test_increment_failure() + ├─ [0] console::log("test increment failure") [staticcall] + │ └─ ← [Stop] + ├─ [22418] Counter::increment() + │ └─ ← [Stop] + ├─ [424] Counter::number() [staticcall] + │ └─ ← [Return] 1 + ├─ [0] VM::assertEq(1, 100) [staticcall] + │ └─ ← [Revert] assertion failed: 1 != 100 + └─ ← [Revert] assertion failed: 1 != 100 + +[PASS] test_increment_success() ([GAS]) +Logs: + test increment success + +Traces: + [35229] SuppressTracesTest::test_increment_success() + ├─ [0] console::log("test increment success") [staticcall] + │ └─ ← [Stop] + ├─ [22418] Counter::increment() + │ └─ ← [Stop] + ├─ [424] Counter::number() [staticcall] + │ └─ ← [Return] 1 + ├─ [0] VM::assertEq(1, 1) [staticcall] + │ └─ ← [Return] + └─ ← [Stop] + +Suite result: FAILED. 1 passed; 1 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 1 failed, 0 skipped (2 total tests) + +Failing tests: +Encountered 1 failing test in test/SuppressTracesTest.t.sol:SuppressTracesTest +[FAIL: assertion failed: 1 != 100] test_increment_failure() ([GAS]) + +Encountered a total of 1 failing tests, 1 tests succeeded + +"#]]); +}); + +forgetest_init!(catch_test_deployment_failure, |prj, cmd| { + prj.add_test( + "TestDeploymentFailure.t.sol", + r#" +import "forge-std/Test.sol"; +contract TestDeploymentFailure is Test { + + constructor() { + require(false); + } + + function setUp() public { + require(true); + } + + function test_something() public { + require(1 == 1); + } +} + "#, + ) + .unwrap(); + + cmd.args(["t", "--mt", "test_something"]).assert_failure().stdout_eq(str![[r#" +... +Failing tests: +Encountered 1 failing test in test/TestDeploymentFailure.t.sol:TestDeploymentFailure +[FAIL: EvmError: Revert] constructor() ([GAS]) +..."#]]); +}); diff --git a/crates/forge/tests/cli/utils.rs b/crates/forge/tests/cli/utils.rs index 2c6fbe3bdb0f1..cdd93f7abeeea 100644 --- a/crates/forge/tests/cli/utils.rs +++ b/crates/forge/tests/cli/utils.rs @@ -1,6 +1,8 @@ //! Various helper functions -use ethers::prelude::{Address, Chain, LocalWallet, Signer}; +use alloy_chains::NamedChain; +use alloy_primitives::Address; +use alloy_signer_local::PrivateKeySigner; /// Returns the current millis since unix epoch. /// @@ -12,12 +14,12 @@ pub fn millis_since_epoch() -> u128 { .as_millis() } -pub fn etherscan_key(chain: Chain) -> Option { +pub fn etherscan_key(chain: NamedChain) -> Option { match chain { - Chain::Fantom | Chain::FantomTestnet => { + NamedChain::Fantom | NamedChain::FantomTestnet => { std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY")).ok() } - Chain::OptimismKovan => std::env::var("OP_KOVAN_API_KEY").ok(), + NamedChain::OptimismKovan => std::env::var("OP_KOVAN_API_KEY").ok(), _ => std::env::var("ETHERSCAN_API_KEY").ok(), } } @@ -34,80 +36,89 @@ pub fn network_private_key(chain: &str) -> Option { /// Represents external input required for executing verification requests pub struct EnvExternalities { - pub chain: Chain, + pub chain: NamedChain, pub rpc: String, pub pk: String, pub etherscan: String, pub verifier: String, } -#[allow(dead_code)] impl EnvExternalities { pub fn address(&self) -> Option
{ - let pk: LocalWallet = self.pk.parse().ok()?; + let pk: PrivateKeySigner = self.pk.parse().ok()?; Some(pk.address()) } pub fn goerli() -> Option { Some(Self { - chain: Chain::Goerli, + chain: NamedChain::Goerli, rpc: network_rpc_key("goerli")?, pk: network_private_key("goerli")?, - etherscan: etherscan_key(Chain::Goerli)?, + etherscan: etherscan_key(NamedChain::Goerli)?, verifier: "etherscan".to_string(), }) } pub fn ftm_testnet() -> Option { Some(Self { - chain: Chain::FantomTestnet, + chain: NamedChain::FantomTestnet, rpc: network_rpc_key("ftm_testnet")?, pk: network_private_key("ftm_testnet")?, - etherscan: etherscan_key(Chain::FantomTestnet)?, + etherscan: etherscan_key(NamedChain::FantomTestnet)?, verifier: "etherscan".to_string(), }) } pub fn optimism_kovan() -> Option { Some(Self { - chain: Chain::OptimismKovan, + chain: NamedChain::OptimismKovan, rpc: network_rpc_key("op_kovan")?, pk: network_private_key("op_kovan")?, - etherscan: etherscan_key(Chain::OptimismKovan)?, + etherscan: etherscan_key(NamedChain::OptimismKovan)?, verifier: "etherscan".to_string(), }) } pub fn arbitrum_goerli() -> Option { Some(Self { - chain: Chain::ArbitrumGoerli, + chain: NamedChain::ArbitrumGoerli, rpc: network_rpc_key("arbitrum-goerli")?, pk: network_private_key("arbitrum-goerli")?, - etherscan: etherscan_key(Chain::ArbitrumGoerli)?, + etherscan: etherscan_key(NamedChain::ArbitrumGoerli)?, verifier: "blockscout".to_string(), }) } pub fn mumbai() -> Option { Some(Self { - chain: Chain::PolygonMumbai, + chain: NamedChain::PolygonMumbai, rpc: network_rpc_key("mumbai")?, pk: network_private_key("mumbai")?, - etherscan: etherscan_key(Chain::PolygonMumbai)?, + etherscan: etherscan_key(NamedChain::PolygonMumbai)?, verifier: "etherscan".to_string(), }) } pub fn sepolia() -> Option { Some(Self { - chain: Chain::Sepolia, + chain: NamedChain::Sepolia, rpc: network_rpc_key("sepolia")?, pk: network_private_key("sepolia")?, - etherscan: etherscan_key(Chain::Sepolia)?, + etherscan: etherscan_key(NamedChain::Sepolia)?, verifier: "etherscan".to_string(), }) } + pub fn sepolia_empty_verifier() -> Option { + Some(Self { + chain: NamedChain::Sepolia, + rpc: network_rpc_key("sepolia")?, + pk: network_private_key("sepolia")?, + etherscan: String::new(), + verifier: String::new(), + }) + } + /// Returns the arguments required to deploy the contract pub fn create_args(&self) -> Vec { vec![ @@ -125,7 +136,7 @@ impl EnvExternalities { pub fn parse_deployed_address(out: &str) -> Option { for line in out.lines() { if line.starts_with("Deployed to") { - return Some(line.trim_start_matches("Deployed to: ").to_string()) + return Some(line.trim_start_matches("Deployed to: ").to_string()); } } None @@ -134,8 +145,28 @@ pub fn parse_deployed_address(out: &str) -> Option { pub fn parse_verification_guid(out: &str) -> Option { for line in out.lines() { if line.contains("GUID") { - return Some(line.replace("GUID:", "").replace('`', "").trim().to_string()) + return Some(line.replace("GUID:", "").replace('`', "").trim().to_string()); } } None } + +/// Generates a string containing the code of a Solidity contract. +/// +/// This contract compiles to a large init bytecode size, but small runtime size. +pub fn generate_large_init_contract(n: usize) -> String { + let data = vec![0xff; n]; + let hex = alloy_primitives::hex::encode(data); + format!( + "\ +contract LargeContract {{ + constructor() {{ + bytes memory data = hex\"{hex}\"; + assembly {{ + pop(mload(data)) + }} + }} +}} +" + ) +} diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index bdd35d5914e65..f4465834eb2d8 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -1,56 +1,85 @@ -//! Contains various tests for checking forge commands related to verifying contracts on etherscan -//! and sourcify +//! Contains various tests for checking forge commands related to verifying contracts on Etherscan +//! and Sourcify. use crate::utils::{self, EnvExternalities}; +use foundry_common::retry::Retry; use foundry_test_utils::{ forgetest, - util::{TestCommand, TestProject}, + util::{OutputExt, TestCommand, TestProject}, }; -use foundry_utils::Retry; +use std::time::Duration; /// Adds a `Unique` contract to the source directory of the project that can be imported as /// `import {Unique} from "./unique.sol";` fn add_unique(prj: &TestProject) { let timestamp = utils::millis_since_epoch(); - prj.inner() - .add_source( - "unique", - format!( - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.4.0; - + prj.add_source( + "unique", + &format!( + r#" contract Unique {{ uint public _timestamp = {timestamp}; }} "# - ), - ) - .unwrap(); + ), + ) + .unwrap(); } fn add_verify_target(prj: &TestProject) { - prj.inner() - .add_source( - "Verify.sol", - r#" -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.10; + prj.add_source( + "Verify.sol", + r#" import {Unique} from "./unique.sol"; contract Verify is Unique { function doStuff() external {} } "#, - ) - .unwrap(); + ) + .unwrap(); } +fn add_single_verify_target_file(prj: &TestProject) { + let timestamp = utils::millis_since_epoch(); + let contract = format!( + r#" +contract Unique {{ + uint public _timestamp = {timestamp}; +}} +contract Verify is Unique {{ +function doStuff() external {{}} +}} +"# + ); + + prj.add_source("Verify.sol", &contract).unwrap(); +} + +fn add_verify_target_with_constructor(prj: &TestProject) { + prj.add_source( + "Verify.sol", + r#" +import {Unique} from "./unique.sol"; +contract Verify is Unique { + struct SomeStruct { + uint256 a; + string str; + } + + constructor(SomeStruct memory st, address owner) {} +} +"#, + ) + .unwrap(); +} + +#[allow(clippy::disallowed_macros)] fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Result<()> { - // give etherscan some time to verify the contract - let retry = Retry::new(retries, Some(30)); - retry.run(|| -> eyre::Result<()> { - let output = cmd.unchecked_output(); + // Give Etherscan some time to verify the contract. + Retry::new(retries, Duration::from_secs(30)).run(|| -> eyre::Result<()> { + let output = cmd.execute(); let out = String::from_utf8_lossy(&output.stdout); + println!("{out}"); if out.contains("Contract successfully verified") { return Ok(()) } @@ -62,70 +91,196 @@ fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Resul }) } +fn verify_check( + guid: String, + chain: String, + etherscan_api_key: Option, + verifier: Option, + mut cmd: TestCommand, +) { + let mut args = vec!["verify-check", &guid, "--chain-id", &chain]; + + if let Some(etherscan_api_key) = ðerscan_api_key { + args.push("--etherscan-api-key"); + args.push(etherscan_api_key); + } + + if let Some(verifier) = &verifier { + args.push("--verifier"); + args.push(verifier); + } + cmd.forge_fuse().args(args); + + parse_verification_result(&mut cmd, 6).expect("Failed to verify check") +} + +fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { + let guid = { + // Give Etherscan some time to detect the transaction. + Retry::new(5, Duration::from_secs(60)) + .run(|| -> eyre::Result { + let output = cmd.execute(); + let out = String::from_utf8_lossy(&output.stdout); + utils::parse_verification_guid(&out).ok_or_else(|| { + eyre::eyre!( + "Failed to get guid, stdout: {}, stderr: {}", + out, + String::from_utf8_lossy(&output.stderr) + ) + }) + }) + .expect("Failed to get verify guid") + }; + + // verify-check + let etherscan = (!info.etherscan.is_empty()).then_some(info.etherscan.clone()); + let verifier = (!info.verifier.is_empty()).then_some(info.verifier.clone()); + verify_check(guid, info.chain.to_string(), etherscan, verifier, cmd); +} + +fn deploy_contract( + info: &EnvExternalities, + contract_path: &str, + prj: TestProject, + cmd: &mut TestCommand, +) -> String { + add_unique(&prj); + add_verify_target(&prj); + let output = cmd + .forge_fuse() + .arg("create") + .args(info.create_args()) + .arg(contract_path) + .assert_success() + .get_output() + .stdout_lossy(); + utils::parse_deployed_address(output.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {output}")) +} + +#[allow(clippy::disallowed_macros)] fn verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { + // only execute if keys present + if let Some(info) = info { + println!("verifying on {}", info.chain); + + let contract_path = "src/Verify.sol:Verify"; + let address = deploy_contract(&info, contract_path, prj, &mut cmd); + + let mut args = vec![ + "--chain-id".to_string(), + info.chain.to_string(), + address, + contract_path.to_string(), + ]; + + if !info.etherscan.is_empty() { + args.push("--etherscan-api-key".to_string()); + args.push(info.etherscan.clone()); + } + + if !info.verifier.is_empty() { + args.push("--verifier".to_string()); + args.push(info.verifier.clone()); + } + cmd.forge_fuse().arg("verify-contract").root_arg().args(args); + + await_verification_response(info, cmd) + } +} + +#[allow(clippy::disallowed_macros)] +fn guess_constructor_args(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { println!("verifying on {}", info.chain); add_unique(&prj); - add_verify_target(&prj); + add_verify_target_with_constructor(&prj); let contract_path = "src/Verify.sol:Verify"; - cmd.arg("create").args(info.create_args()).arg(contract_path); + let output = cmd + .arg("create") + .args(info.create_args()) + .arg(contract_path) + .args(vec![ + "--constructor-args", + "(239,SomeString)", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + ]) + .assert_success() + .get_output() + .stdout_lossy(); - let out = cmd.stdout_lossy(); - let address = utils::parse_deployed_address(out.as_str()) - .unwrap_or_else(|| panic!("Failed to parse deployer {out}")); + let address = utils::parse_deployed_address(output.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {output}")); cmd.forge_fuse().arg("verify-contract").root_arg().args([ - "--chain-id".to_string(), - info.chain.to_string(), + "--rpc-url".to_string(), + info.rpc.to_string(), address, contract_path.to_string(), + "--etherscan-api-key".to_string(), info.etherscan.to_string(), "--verifier".to_string(), info.verifier.to_string(), + "--guess-constructor-args".to_string(), ]); - // `verify-contract` - let guid = { - // give etherscan some time to detect the transaction - let retry = Retry::new(5, Some(60)); - retry - .run(|| -> eyre::Result { - let output = cmd.unchecked_output(); - let out = String::from_utf8_lossy(&output.stdout); - utils::parse_verification_guid(&out).ok_or_else(|| { - eyre::eyre!( - "Failed to get guid, stdout: {}, stderr: {}", - out, - String::from_utf8_lossy(&output.stderr) - ) - }) - }) - .expect("Failed to get verify guid") - }; - - // verify-check - cmd.forge_fuse() - .arg("verify-check") - .arg(guid) - .arg("--chain-id") - .arg(info.chain.to_string()) - .arg("--etherscan-key") - .arg(info.etherscan) - .arg("--verifier") - .arg(info.verifier); - - parse_verification_result(&mut cmd, 6).expect("Failed to verify check") + await_verification_response(info, cmd) + } +} + +#[allow(clippy::disallowed_macros)] +/// Executes create --verify on the given chain +fn create_verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { + // only execute if keys present + if let Some(info) = info { + println!("verifying on {}", info.chain); + add_single_verify_target_file(&prj); + + let contract_path = "src/Verify.sol:Verify"; + let output = cmd + .arg("create") + .args(info.create_args()) + .args([contract_path, "--etherscan-api-key", info.etherscan.as_str(), "--verify"]) + .assert_success() + .get_output() + .stdout_lossy(); + + assert!(output.contains("Contract successfully verified"), "{}", output); } } // tests `create && contract-verify && verify-check` on Fantom testnet if correct env vars are set -forgetest!(can_verify_random_contract_fantom_testnet, |prj: TestProject, cmd: TestCommand| { +forgetest!(can_verify_random_contract_fantom_testnet, |prj, cmd| { verify_on_chain(EnvExternalities::ftm_testnet(), prj, cmd); }); // tests `create && contract-verify && verify-check` on Optimism kovan if correct env vars are set -forgetest!(can_verify_random_contract_optimism_kovan, |prj: TestProject, cmd: TestCommand| { +forgetest!(can_verify_random_contract_optimism_kovan, |prj, cmd| { verify_on_chain(EnvExternalities::optimism_kovan(), prj, cmd); }); + +// tests `create && contract-verify && verify-check` on Sepolia testnet if correct env vars are set +forgetest!(can_verify_random_contract_sepolia, |prj, cmd| { + verify_on_chain(EnvExternalities::sepolia(), prj, cmd); +}); + +// tests `create --verify on Sepolia testnet if correct env vars are set +// SEPOLIA_RPC_URL=https://rpc.sepolia.org +// TEST_PRIVATE_KEY=0x... +// ETHERSCAN_API_KEY= +forgetest!(can_create_verify_random_contract_sepolia, |prj, cmd| { + create_verify_on_chain(EnvExternalities::sepolia(), prj, cmd); +}); + +// tests `create && contract-verify --guess-constructor-args && verify-check` on Goerli testnet if +// correct env vars are set +forgetest!(can_guess_constructor_args, |prj, cmd| { + guess_constructor_args(EnvExternalities::goerli(), prj, cmd); +}); + +// tests `create && verify-contract && verify-check` on sepolia with default sourcify verifier +forgetest!(can_verify_random_contract_sepolia_default_sourcify, |prj, cmd| { + verify_on_chain(EnvExternalities::sepolia_empty_verifier(), prj, cmd); +}); diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs new file mode 100644 index 0000000000000..127765bbfdccb --- /dev/null +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -0,0 +1,313 @@ +use foundry_compilers::artifacts::{BytecodeHash, EvmVersion}; +use foundry_config::Config; +use foundry_test_utils::{ + forgetest_async, + rpc::{next_http_archive_rpc_url, next_mainnet_etherscan_api_key}, + util::OutputExt, + TestCommand, TestProject, +}; + +#[allow(clippy::too_many_arguments)] +fn test_verify_bytecode( + prj: TestProject, + mut cmd: TestCommand, + addr: &str, + contract_name: &str, + constructor_args: Option>, + config: Config, + verifier: &str, + verifier_url: &str, + expected_matches: (&str, &str), +) { + let etherscan_key = next_mainnet_etherscan_api_key(); + let rpc_url = next_http_archive_rpc_url(); + + // fetch and flatten source code + let source_code = cmd + .cast_fuse() + .args(["source", addr, "--flatten", "--etherscan-api-key", ðerscan_key]) + .assert_success() + .get_output() + .stdout_lossy(); + + prj.add_source(contract_name, &source_code).unwrap(); + prj.write_config(config); + + let etherscan_key = next_mainnet_etherscan_api_key(); + let mut args = vec![ + "verify-bytecode", + addr, + contract_name, + "--etherscan-api-key", + ðerscan_key, + "--verifier", + verifier, + "--verifier-url", + verifier_url, + "--rpc-url", + &rpc_url, + ]; + + if let Some(constructor_args) = constructor_args { + args.push("--constructor-args"); + args.extend(constructor_args.iter()); + } + + let output = cmd.forge_fuse().args(args).assert_success().get_output().stdout_lossy(); + + assert!(output + .contains(format!("Creation code matched with status {}", expected_matches.0).as_str())); + assert!(output + .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str())); +} + +#[allow(clippy::too_many_arguments)] +fn test_verify_bytecode_with_ignore( + prj: TestProject, + mut cmd: TestCommand, + addr: &str, + contract_name: &str, + config: Config, + verifier: &str, + verifier_url: &str, + expected_matches: (&str, &str), + ignore: &str, + chain: &str, +) { + let etherscan_key = next_mainnet_etherscan_api_key(); + let rpc_url = next_http_archive_rpc_url(); + + // fetch and flatten source code + let source_code = cmd + .cast_fuse() + .args([ + "source", + addr, + "--flatten", + "--etherscan-api-key", + ðerscan_key, + "--chain", + chain, + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + prj.add_source(contract_name, &source_code).unwrap(); + prj.write_config(config); + + let output = cmd + .forge_fuse() + .args([ + "verify-bytecode", + addr, + contract_name, + "--etherscan-api-key", + ðerscan_key, + "--verifier", + verifier, + "--verifier-url", + verifier_url, + "--rpc-url", + &rpc_url, + "--ignore", + ignore, + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + if ignore == "creation" { + assert!(!output.contains( + format!("Creation code matched with status {}", expected_matches.0).as_str() + )); + } else { + assert!(output.contains( + format!("Creation code matched with status {}", expected_matches.0).as_str() + )); + } + + if ignore == "runtime" { + assert!(!output + .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str())); + } else { + assert!(output + .contains(format!("Runtime code matched with status {}", expected_matches.1).as_str())); + } +} +forgetest_async!(can_verify_bytecode_no_metadata, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + None, + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/api", + ("partial", "partial"), + ); +}); + +forgetest_async!(can_verify_bytecode_with_metadata, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0xb8901acb165ed027e32754e0ffe830802919727f", + "L1_ETH_Bridge", + None, + Config { + evm_version: EvmVersion::Paris, + optimizer_runs: Some(50000), + optimizer: Some(true), + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/api", + ("partial", "partial"), + ); +}); + +// Test non-CREATE2 deployed contract with blockscout +forgetest_async!(can_verify_bytecode_with_blockscout, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b", + "StrategyManager", + None, + Config { + evm_version: EvmVersion::London, + optimizer: Some(true), + optimizer_runs: Some(200), + ..Default::default() + }, + "blockscout", + "https://eth.blockscout.com/api", + ("partial", "partial"), + ); +}); + +// Test CREATE2 deployed contract with blockscout +forgetest_async!(can_vb_create2_with_blockscout, |prj, cmd| { + test_verify_bytecode( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + None, + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "blockscout", + "https://eth.blockscout.com/api", + ("partial", "partial"), + ); +}); + +// Test `--constructor-args` +forgetest_async!(can_verify_bytecode_with_constructor_args, |prj, cmd| { + let constructor_args = vec![ + "0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A", + "0x91E677b07F7AF907ec9a428aafA9fc14a0d3A338", + "0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd", + ]; + test_verify_bytecode( + prj, + cmd, + "0x70f44C13944d49a236E3cD7a94f48f5daB6C619b", + "StrategyManager", + Some(constructor_args), + Config { + evm_version: EvmVersion::London, + optimizer: Some(true), + optimizer_runs: Some(200), + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/api", + ("partial", "partial"), + ); +}); + +// `--ignore` tests +forgetest_async!(can_ignore_creation, |prj, cmd| { + test_verify_bytecode_with_ignore( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/api", + ("ignored", "partial"), + "creation", + "1", + ); +}); + +forgetest_async!(can_ignore_runtime, |prj, cmd| { + test_verify_bytecode_with_ignore( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/api", + ("partial", "ignored"), + "runtime", + "1", + ); +}); + +// Test predeploy contracts +// TODO: Add test utils for base such as basescan keys and alchemy keys. +// WETH9 Predeploy +// forgetest_async!(can_verify_predeploys, |prj, cmd| { +// test_verify_bytecode_with_ignore( +// prj, +// cmd, +// "0x4200000000000000000000000000000000000006", +// "WETH9", +// Config { +// evm_version: EvmVersion::default(), +// optimizer: Some(true), +// optimizer_runs: 10000, +// cbor_metadata: true, +// bytecode_hash: BytecodeHash::Bzzr1, +// ..Default::default() +// }, +// "etherscan", +// "https://api.basescan.org/api", +// ("ignored", "partial"), +// "creation", +// "base", +// ); +// }); diff --git a/crates/forge/tests/cli/version.rs b/crates/forge/tests/cli/version.rs new file mode 100644 index 0000000000000..0df88b83a501d --- /dev/null +++ b/crates/forge/tests/cli/version.rs @@ -0,0 +1,18 @@ +use foundry_test_utils::{forgetest, str}; + +forgetest!(print_short_version, |_prj, cmd| { + cmd.arg("-V").assert_success().stdout_eq(str![[r#" +forge [..]-[..] ([..] [..]) + +"#]]); +}); + +forgetest!(print_long_version, |_prj, cmd| { + cmd.arg("--version").assert_success().stdout_eq(str![[r#" +forge Version: [..] +Commit SHA: [..] +Build Timestamp: [..] +Build Profile: [..] + +"#]]); +}); diff --git a/crates/forge/tests/fixtures/ExpectCallFailures.t.sol b/crates/forge/tests/fixtures/ExpectCallFailures.t.sol new file mode 100644 index 0000000000000..a917e5de1565f --- /dev/null +++ b/crates/forge/tests/fixtures/ExpectCallFailures.t.sol @@ -0,0 +1,220 @@ +// Note Used in forge-cli tests to assert failures. +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "./test.sol"; +import "./Vm.sol"; + +contract Contract { + function numberA() public pure returns (uint256) { + return 1; + } + + function numberB() public pure returns (uint256) { + return 2; + } + + function add(uint256 a, uint256 b) public pure returns (uint256) { + return a + b; + } + + function pay(uint256 a) public payable returns (uint256) { + return a; + } +} + +contract NestedContract { + Contract private inner; + + constructor(Contract _inner) { + inner = _inner; + } + + function sum() public view returns (uint256) { + return inner.numberA() + inner.numberB(); + } + + function forwardPay() public payable returns (uint256) { + return inner.pay{gas: 50_000, value: 1}(1); + } + + function addHardGasLimit() public view returns (uint256) { + return inner.add{gas: 50_000}(1, 1); + } + + function hello() public pure returns (string memory) { + return "hi"; + } + + function sumInPlace(uint256 a, uint256 b) public view returns (uint256) { + return a + b + 42; + } +} + +contract ExpectCallFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public pure { + for (uint256 i = 0; i < times; i++) { + target.add(a, b); + } + } + + function exposed_failExpectInnerCall(NestedContract target) public { + // this function does not call inner + target.hello(); + } + + function testShouldFailExpectMultipleCallsWithDataAdditive() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + // Not enough calls to satisfy the additive expectCall, which expects 3 calls. + this.exposed_callTargetNTimes(target, 1, 2, 2); + } + + function testShouldFailExpectCallWithData() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); + this.exposed_callTargetNTimes(target, 3, 3, 1); + } + + function testShouldFailExpectInnerCall() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector)); + + this.exposed_failExpectInnerCall(target); + } + + function testShouldFailExpectSelectorCall() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + } + + function testShouldFailExpectCallWithMoreParameters() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 3, 3, 3)); + target.add(3, 3); + this.exposed_callTargetNTimes(target, 3, 3, 1); + } + + function testShouldFailExpectCallValue() public { + Contract target = new Contract(); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); + } + + function exposed_addHardGasLimit(NestedContract target) public { + target.addHardGasLimit(); + } + + function testShouldFailExpectCallWithNoValueAndWrongGas() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + this.exposed_addHardGasLimit(target); + } + + function testShouldFailExpectCallWithNoValueAndWrongMinGas() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1)); + this.exposed_addHardGasLimit(target); + } + + /// Ensure that you cannot use expectCall with an expectRevert. + function testShouldFailExpectCallWithRevertDisallowed() public { + Contract target = new Contract(); + vm.expectRevert(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + this.exposed_callTargetNTimes(target, 5, 5, 1); + } +} + +contract ExpectCallCountFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testShouldFailExpectCallCountWithWrongCount() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + target.add(1, 2); + } + + function testShouldFailExpectCountInnerCall() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 1); + + // this function does not call inner + target.hello(); + } + + function exposed_pay(Contract target, uint256 value, uint256 amount) public payable { + target.pay{value: value}(amount); + } + + function testShouldFailExpectCallCountValue() public { + Contract target = new Contract(); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 1); + this.exposed_pay{value: 2}(target, 2, 2); + } + + function exposed_addHardGasLimit(NestedContract target, uint256 times) public { + for (uint256 i = 0; i < times; i++) { + target.addHardGasLimit(); + } + } + + function testShouldFailExpectCallCountWithNoValueAndWrongGas() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); + this.exposed_addHardGasLimit(target, 2); + } + + function testShouldFailExpectCallCountWithNoValueAndWrongMinGas() public { + Contract inner = new Contract(); + NestedContract target = new NestedContract(inner); + vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); + this.exposed_addHardGasLimit(target, 1); + } +} + +contract ExpectCallMixedFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { + for (uint256 i = 0; i < times; i++) { + target.add(1, 2); + } + } + + function testShouldFailOverrideNoCountWithCount() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + // You should not be able to overwrite a expectCall that had no count with some count. + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + this.exposed_callTargetNTimes(target, 1, 2, 2); + } + + function testShouldFailOverrideCountWithCount() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + // You should not be able to overwrite a expectCall that had a count with some count. + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); + target.add(1, 2); + target.add(1, 2); + } + + function testShouldFailOverrideCountWithNoCount() public { + Contract target = new Contract(); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + // You should not be able to overwrite a expectCall that had a count with no count. + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + target.add(1, 2); + target.add(1, 2); + } +} diff --git a/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol b/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol new file mode 100644 index 0000000000000..8d7918e364766 --- /dev/null +++ b/crates/forge/tests/fixtures/ExpectEmitFailures.t.sol @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "./test.sol"; +import "./Vm.sol"; + +contract Emitter { + uint256 public thing; + + event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); + event A(uint256 indexed topic1); + event B(uint256 indexed topic1); + event C(uint256 indexed topic1); + event D(uint256 indexed topic1); + event E(uint256 indexed topic1); + + /// This event has 0 indexed topics, but the one in our tests + /// has exactly one indexed topic. Even though both of these + /// events have the same topic 0, they are different and should + /// be non-comparable. + /// + /// Ref: issue #760 + event SomethingElse(uint256 data); + + event SomethingNonIndexed(uint256 data); + + function emitEvent(uint256 topic1, uint256 topic2, uint256 topic3, uint256 data) public { + emit Something(topic1, topic2, topic3, data); + } + + function emitNEvents(uint256 topic1, uint256 topic2, uint256 topic3, uint256 data, uint256 n) public { + for (uint256 i = 0; i < n; i++) { + emit Something(topic1, topic2, topic3, data); + } + } + + function emitMultiple( + uint256[2] memory topic1, + uint256[2] memory topic2, + uint256[2] memory topic3, + uint256[2] memory data + ) public { + emit Something(topic1[0], topic2[0], topic3[0], data[0]); + emit Something(topic1[1], topic2[1], topic3[1], data[1]); + } + + function emitAndNest() public { + emit Something(1, 2, 3, 4); + emitNested(Emitter(address(this)), 1, 2, 3, 4); + } + + function emitOutOfExactOrder() public { + emit SomethingNonIndexed(1); + emit Something(1, 2, 3, 4); + emit Something(1, 2, 3, 4); + emit Something(1, 2, 3, 4); + } + + function emitNested(Emitter inner, uint256 topic1, uint256 topic2, uint256 topic3, uint256 data) public { + inner.emitEvent(topic1, topic2, topic3, data); + } + + function getVar() public pure returns (uint256) { + return 1; + } + + /// Used to test matching of consecutive different events, + /// even if they're not emitted right after the other. + function emitWindow() public { + emit A(1); + emit B(2); + emit C(3); + emit D(4); + emit E(5); + } + + function emitNestedWindow() public { + emit A(1); + emit C(3); + emit E(5); + this.emitWindow(); + } + + // Used to test matching of consecutive different events + // split across subtree calls. + function emitSplitWindow() public { + this.emitWindow(); + this.emitWindow(); + } + + function emitWindowAndOnTest(ExpectEmitFailureTest t) public { + this.emitWindow(); + t.emitLocal(); + } + + /// Ref: issue #1214 + function doesNothing() public pure {} + + function changeThing(uint256 num) public { + thing = num; + } + + /// Ref: issue #760 + function emitSomethingElse(uint256 data) public { + emit SomethingElse(data); + } +} + +/// Emulates `Emitter` in #760 +contract LowLevelCaller { + function f() external { + address(this).call(abi.encodeWithSignature("g()")); + } + + function g() public {} +} + +contract ExpectEmitFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Emitter emitter; + + event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); + + event SomethingElse(uint256 indexed topic1); + + event A(uint256 indexed topic1); + event B(uint256 indexed topic1); + event C(uint256 indexed topic1); + event D(uint256 indexed topic1); + event E(uint256 indexed topic1); + + function setUp() public { + emitter = new Emitter(); + } + + function emitLocal() public { + emit A(1); + } + + function testShouldFailExpectEmitDanglingNoReference() public { + vm.expectEmit(false, false, false, false); + } + + function testShouldFailExpectEmitDanglingWithReference() public { + vm.expectEmit(false, false, false, false); + emit Something(1, 2, 3, 4); + } + + /// The topics that are checked are altered to be incorrect + /// compared to the reference. + function testShouldFailExpectEmit( + bool checkTopic1, + bool checkTopic2, + bool checkTopic3, + bool checkData, + uint128 topic1, + uint128 topic2, + uint128 topic3, + uint128 data + ) public { + vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); + + uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); + uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); + uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); + uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); + + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + + emit Something(topic1, topic2, topic3, data); + emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); + } + + /// The topics that are checked are altered to be incorrect + /// compared to the reference. + function testShouldFailExpectEmitNested( + bool checkTopic1, + bool checkTopic2, + bool checkTopic3, + bool checkData, + uint128 topic1, + uint128 topic2, + uint128 topic3, + uint128 data + ) public { + vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); + Emitter inner = new Emitter(); + + uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); + uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); + uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); + uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); + + vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); + + emit Something(topic1, topic2, topic3, data); + emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); + } + + function testShouldFailExpectEmitCanMatchWithoutExactOrder() public { + vm.expectEmit(true, true, true, true); + emit Something(1, 2, 3, 4); + // This should fail, as this event is never emitted + // in between the other two Something events. + vm.expectEmit(true, true, true, true); + emit SomethingElse(1); + vm.expectEmit(true, true, true, true); + emit Something(1, 2, 3, 4); + + emitter.emitOutOfExactOrder(); + } + + function testShouldFailExpectEmitAddress() public { + vm.expectEmit(address(0)); + emit Something(1, 2, 3, 4); + + emitter.emitEvent(1, 2, 3, 4); + } + + function testShouldFailExpectEmitAddressWithArgs() public { + vm.expectEmit(true, true, true, true, address(0)); + emit Something(1, 2, 3, 4); + + emitter.emitEvent(1, 2, 3, 4); + } + + /// Ref: issue #760 + function testShouldFailLowLevelWithoutEmit() public { + LowLevelCaller caller = new LowLevelCaller(); + + vm.expectEmit(true, true, true, true); + emit Something(1, 2, 3, 4); + + // This does not emit an event, so this test should fail + caller.f(); + } + + function testShouldFailNoEmitDirectlyOnNextCall() public { + LowLevelCaller caller = new LowLevelCaller(); + + vm.expectEmit(true, true, true, true); + emit Something(1, 2, 3, 4); + + // This call does not emit. As emit expects the next call to emit, this should fail. + caller.f(); + // This call does emit, but it is a call later than expected. + emitter.emitEvent(1, 2, 3, 4); + } + + /// Ref: issue #760 + function testShouldFailDifferentIndexedParameters() public { + vm.expectEmit(true, false, false, false); + emit SomethingElse(1); + + // This should fail since `SomethingElse` in the test + // and in the `Emitter` contract have differing + // amounts of indexed topics. + emitter.emitSomethingElse(1); + } + + /// This test should fail, as the call to `changeThing` is not a static call. + /// While we can ignore static calls, we cannot ignore normal calls. + function testShouldFailEmitOnlyAppliesToNextCall() public { + vm.expectEmit(true, true, true, true); + emit Something(1, 2, 3, 4); + // This works because it's a staticcall. + emitter.doesNothing(); + // This should make the test fail as it's a normal call. + emitter.changeThing(block.timestamp); + + emitter.emitEvent(1, 2, 3, 4); + } + + /// emitWindow() emits events A, B, C, D, E. + /// We should not be able to match [B, A, C, D, E] as B and A are flipped. + function testShouldFailCanMatchConsecutiveEvents() public { + vm.expectEmit(true, false, false, true); + emit B(2); + vm.expectEmit(true, false, false, true); + emit A(1); + vm.expectEmit(true, false, false, true); + emit C(3); + vm.expectEmit(true, false, false, true); + emit D(4); + vm.expectEmit(true, false, false, true); + emit E(5); + + emitter.emitWindow(); + } + + /// emitWindowNested() emits events A, C, E, A, B, C, D, E, the last 5 on an external call. + /// We should NOT be able to match [A, A, E, E], as while we're matching the correct amount + /// of events, they're not in the correct order. It should be [A, E, A, E]. + function testShouldFailMatchRepeatedEventsOutOfOrder() public { + vm.expectEmit(true, false, false, true); + emit A(1); + vm.expectEmit(true, false, false, true); + emit A(1); + vm.expectEmit(true, false, false, true); + emit E(5); + vm.expectEmit(true, false, false, true); + emit E(5); + + emitter.emitNestedWindow(); + } + + /// emitWindow() emits events A, B, C, D, E. + /// We should not be able to match [A, A] even if emitWindow() is called twice, + /// as expectEmit() only works for the next call. + function testShouldFailEventsOnTwoCalls() public { + vm.expectEmit(true, false, false, true); + emit A(1); + vm.expectEmit(true, false, false, true); + emit A(1); + emitter.emitWindow(); + emitter.emitWindow(); + } + + /// We should not be able to expect emits if we're expecting the function reverts, no matter + /// if the function reverts or not. + function testShouldFailEmitWindowWithRevertDisallowed() public { + vm.expectRevert(); + vm.expectEmit(true, false, false, true); + emit A(1); + emitter.emitWindow(); + } +} + +contract ExpectEmitCountFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Emitter emitter; + + event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); + + function setUp() public { + emitter = new Emitter(); + } + + function testShouldFailNoEmit() public { + vm.expectEmit(0); + emit Something(1, 2, 3, 4); + emitter.emitEvent(1, 2, 3, 4); + } + + function testShouldFailCountLessEmits() public { + uint64 count = 2; + vm.expectEmit(count); + emit Something(1, 2, 3, 4); + emitter.emitNEvents(1, 2, 3, 4, count - 1); + } + + function testShouldFailNoEmitFromAddress() public { + vm.expectEmit(address(emitter), 0); + emit Something(1, 2, 3, 4); + emitter.emitEvent(1, 2, 3, 4); + } + + function testShouldFailCountEmitsFromAddress() public { + uint64 count = 3; + vm.expectEmit(address(emitter), count); + emit Something(1, 2, 3, 4); + emitter.emitNEvents(1, 2, 3, 4, count - 1); + } + + function testShouldFailEmitSomethingElse() public { + uint64 count = 2; + vm.expectEmit(count); + emit Something(1, 2, 3, 4); + emitter.emitSomethingElse(23214); + } +} diff --git a/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol new file mode 100644 index 0000000000000..4eca811f0c465 --- /dev/null +++ b/crates/forge/tests/fixtures/ExpectRevertFailures.t.sol @@ -0,0 +1,317 @@ +// Note Used in forge-cli tests to assert failures. +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "./test.sol"; +import "./Vm.sol"; + +contract Reverter { + error CustomError(); + + function revertWithMessage(string memory message) public pure { + revert(message); + } + + function doNotRevert() public pure {} + + function panic() public pure returns (uint256) { + return uint256(100) - uint256(101); + } + + function revertWithCustomError() public pure { + revert CustomError(); + } + + function nestedRevert(Reverter inner, string memory message) public pure { + inner.revertWithMessage(message); + } + + function callThenRevert(Dummy dummy, string memory message) public pure { + dummy.callMe(); + revert(message); + } + + function callThenNoRevert(Dummy dummy) public pure { + dummy.callMe(); + } + + function revertWithoutReason() public pure { + revert(); + } +} + +contract ConstructorReverter { + constructor(string memory message) { + revert(message); + } +} + +/// Used to ensure that the dummy data from `vm.expectRevert` +/// is large enough to decode big structs. +/// +/// The struct is based on issue #2454 +struct LargeDummyStruct { + address a; + uint256 b; + bool c; + address d; + address e; + string f; + address[8] g; + address h; + uint256 i; +} + +contract Dummy { + function callMe() public pure returns (string memory) { + return "thanks for calling"; + } + + function largeReturnType() public pure returns (LargeDummyStruct memory) { + revert("reverted with large return type"); + } +} + +contract ExpectRevertFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testShouldFailExpectRevertErrorDoesNotMatch() public { + Reverter reverter = new Reverter(); + vm.expectRevert("should revert with this message"); + reverter.revertWithMessage("but reverts with this message"); + } + + function testShouldFailRevertNotOnImmediateNextCall() public { + Reverter reverter = new Reverter(); + // expectRevert should only work for the next call. However, + // we do not immediately revert, so, + // we fail. + vm.expectRevert("revert"); + reverter.doNotRevert(); + reverter.revertWithMessage("revert"); + } + + function testShouldFailExpectRevertDidNotRevert() public { + Reverter reverter = new Reverter(); + vm.expectRevert("does not revert, but we think it should"); + reverter.doNotRevert(); + } + + function testShouldFailExpectRevertAnyRevertDidNotRevert() public { + Reverter reverter = new Reverter(); + vm.expectRevert(); + reverter.doNotRevert(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testShouldFailExpectRevertDangling() public { + vm.expectRevert("dangling"); + } + + function testShouldFailexpectCheatcodeRevertForExtCall() public { + Reverter reverter = new Reverter(); + vm._expectCheatcodeRevert(); + reverter.revertWithMessage("revert"); + } + + function testShouldFailexpectCheatcodeRevertForCreate() public { + vm._expectCheatcodeRevert(); + new ConstructorReverter("some message"); + } +} + +contract AContract { + BContract bContract; + CContract cContract; + + constructor(BContract _bContract, CContract _cContract) { + bContract = _bContract; + cContract = _cContract; + } + + function callAndRevert() public pure { + require(1 > 2, "Reverted by AContract"); + } + + function callAndRevertInBContract() public { + bContract.callAndRevert(); + } + + function callAndRevertInCContract() public { + cContract.callAndRevert(); + } + + function callAndRevertInCContractThroughBContract() public { + bContract.callAndRevertInCContract(); + } + + function createDContract() public { + new DContract(); + } + + function createDContractThroughBContract() public { + bContract.createDContract(); + } + + function createDContractThroughCContract() public { + cContract.createDContract(); + } + + function doNotRevert() public {} +} + +contract BContract { + CContract cContract; + + constructor(CContract _cContract) { + cContract = _cContract; + } + + function callAndRevert() public pure { + require(1 > 2, "Reverted by BContract"); + } + + function callAndRevertInCContract() public { + this.doNotRevert(); + cContract.doNotRevert(); + cContract.callAndRevert(); + } + + function createDContract() public { + this.doNotRevert(); + cContract.doNotRevert(); + new DContract(); + } + + function createDContractThroughCContract() public { + this.doNotRevert(); + cContract.doNotRevert(); + cContract.createDContract(); + } + + function doNotRevert() public {} +} + +contract CContract { + error CContractError(string reason); + + function callAndRevert() public pure { + revert CContractError("Reverted by CContract"); + } + + function createDContract() public { + new DContract(); + } + + function doNotRevert() public {} +} + +contract DContract { + constructor() { + require(1 > 2, "Reverted by DContract"); + } +} + +contract ExpectRevertWithReverterFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + error CContractError(string reason); + + AContract aContract; + BContract bContract; + CContract cContract; + + function setUp() public { + cContract = new CContract(); + bContract = new BContract(cContract); + aContract = new AContract(bContract, cContract); + } + + function testShouldFailExpectRevertsNotOnImmediateNextCall() public { + // Test expect revert with reverter fails if next call doesn't revert. + vm.expectRevert(address(aContract)); + aContract.doNotRevert(); + aContract.callAndRevert(); + } +} + +contract ExpectRevertCountFailureTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testShouldFailRevertCountAny() public { + uint64 count = 3; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert2"); + } + + function testShouldFailNoRevert() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.revertWithMessage("revert"); + } + + function testShouldFailReverCountSpecifc() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("second-revert"); + } + + function testShouldFailNoRevertSpecific() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert"); + } + + function testShouldFailRevertCountCallsThenReverts() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + + vm.expectRevert("called a function and then reverted", count); + reverter.callThenRevert(dummy, "called a function and then reverted"); + reverter.callThenRevert(dummy, "wrong revert"); + } +} + +contract ExpectRevertCountWithReverterFailures is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testShouldFailRevertCountWithReverter() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.revertWithMessage("revert"); + reverter2.revertWithMessage("revert"); + } + + function testShouldFailNoRevertWithReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.revertWithMessage("revert"); + } + + function testShouldFailReverterCountWithWrongData() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("wrong revert"); + } + + function testShouldFailWrongReverterCountWithData() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert"); + reverter2.revertWithMessage("revert"); + } +} diff --git a/crates/forge/tests/fixtures/MemSafetyFailures.t.sol b/crates/forge/tests/fixtures/MemSafetyFailures.t.sol new file mode 100644 index 0000000000000..e73033eec1d63 --- /dev/null +++ b/crates/forge/tests/fixtures/MemSafetyFailures.t.sol @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "./test.sol"; +import "./Vm.sol"; + +contract MemSafetyFailureTest is DSTest { + Vm constant vm = Vm(address(HEVM_ADDRESS)); + + /// @dev Tests that writing to memory before the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MSTORE` opcode. + function testShouldFailExpectSafeMemory_MSTORE_Low() public { + // Allow memory writes in the range of [0x80, 0xA0) within this context + vm.expectSafeMemory(0x80, 0xA0); + + // Attempt to write to memory outside of the range using `MSTORE` + assembly { + mstore(0x60, 0xc0ffee) + } + } + + /// @dev Tests that writing to memory after the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MSTORE` opcode. + function testShouldFailExpectSafeMemory_MSTORE_High() public { + // Allow memory writes in the range of [0x80, 0xA0) within this context + vm.expectSafeMemory(0x80, 0xA0); + + // Attempt to write to memory outside of the range using `MSTORE` + assembly { + mstore(0xA0, 0xc0ffee) + } + } + + /// @dev Tests that writing to memory before the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MSTORE8` opcode. + function testShouldFailExpectSafeMemory_MSTORE8_Low() public { + // Allow memory writes in the range of [0x80, 0x81) within this context + vm.expectSafeMemory(0x80, 0x81); + + // Attempt to write to memory outside of the range using `MSTORE8` + assembly { + mstore8(0x60, 0xFF) + } + } + + /// @dev Tests that writing to memory after the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MSTORE8` opcode. + function testShouldFailExpectSafeMemory_MSTORE8_High() public { + // Allow memory writes in the range of [0x80, 0x81) within this context + vm.expectSafeMemory(0x80, 0x81); + + // Attempt to write to memory outside of the range using `MSTORE8` + assembly { + mstore8(0x81, 0xFF) + } + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `CALLDATACOPY` opcode. + function testShouldFailExpectSafeMemory_CALLDATACOPY(uint256 _x) public { + // Allow memory writes in the range of [0x80, 0xA0) within this context + vm.expectSafeMemory(0x80, 0xA0); + + // Write to memory outside the range using `CALLDATACOPY` + assembly { + calldatacopy(0xA0, 0x04, 0x20) + } + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `CODECOPY` opcode. + function testShouldFailExpectSafeMemory_CODECOPY() public { + // Allow memory writes in the range of [0x80, 0xA0) within this context + vm.expectSafeMemory(0x80, 0xA0); + + // Attempt to write to memory outside of the range using `CODECOPY` + assembly { + let size := extcodesize(address()) + codecopy(0x80, 0x00, size) + } + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `RETURNDATACOPY` opcode. + function testShouldFailExpectSafeMemory_RETURNDATACOPY() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + + // Create a payload to call `giveReturndata` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); + + // Allow memory writes in the range of [0x80, 0x100) within this context + vm.expectSafeMemory(0x80, 0x100); + + // Create a new SubContext contract and call `giveReturndata` on it. + _doCallReturnData(address(sc), payload, 0x80, 0x60); + + // Write to memory outside of the range using `RETURNDATACOPY` + assembly { + returndatacopy(0x100, 0x00, 0x60) + } + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will not cause the test to fail while using the `EXTCODECOPY` opcode. + function testShouldFailExpectSafeMemory_EXTCODECOPY() public { + // Allow memory writes in the range of [0x80, 0xA0) within this context + vm.expectSafeMemory(0x80, 0xA0); + + // Attempt to write to memory outside of the range using `EXTCODECOPY` + assembly { + let size := extcodesize(address()) + extcodecopy(address(), 0xA0, 0x00, 0x20) + } + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `CALL` opcode. + function testShouldFailExpectSafeMemory_CALL() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + + // Create a payload to call `giveReturndata` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); + + // Allow memory writes in the range of [0x80, 0x100) within this context + vm.expectSafeMemory(0x80, 0x100); + + // Create a new SubContext contract and call `giveReturndata` on it. + _doCallReturnData(address(sc), payload, 0x100, 0x60); + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `CALLCODE` opcode. + function testShouldFailExpectSafeMemory_CALLCODE() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + + // Create a payload to call `giveReturndata` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); + + // Allow memory writes in the range of [0x80, 0x100) within this context + vm.expectSafeMemory(0x80, 0x100); + + // Create a new SubContext contract and call `giveReturndata` on it. + _doCallCodeReturnData(address(sc), payload, 0x100, 0x60); + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `STATICCALL` opcode. + function testShouldFailExpectSafeMemory_STATICCALL() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + + // Create a payload to call `giveReturndata` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); + + // Allow memory writes in the range of [0x80, 0x100) within this context + vm.expectSafeMemory(0x80, 0x100); + + // Create a new SubContext contract and call `giveReturndata` on it. + _doStaticCallReturnData(address(sc), payload, 0x100, 0x60); + } + + /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `DELEGATECALL` opcode. + function testShouldFailExpectSafeMemory_DELEGATECALL() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + + // Create a payload to call `giveReturndata` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); + + // Allow memory writes in the range of [0x80, 0x100) within this context + vm.expectSafeMemory(0x80, 0x100); + + // Create a new SubContext contract and call `giveReturndata` on it. + _doDelegateCallReturnData(address(sc), payload, 0x100, 0x60); + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MLOAD` opcode. + function testShouldFailExpectSafeMemory_MLOAD() public { + vm.expectSafeMemory(0x80, 0x100); + + // This should revert. Ugly hack to make sure the mload isn't optimized + // out. + uint256 a; + assembly { + a := mload(0x100) + } + uint256 b = a + 1; + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `SHA3` opcode. + function testShouldFailExpectSafeMemory_SHA3() public { + vm.expectSafeMemory(0x80, 0x100); + + // This should revert. Ugly hack to make sure the sha3 isn't optimized + // out. + uint256 a; + assembly { + a := keccak256(0x100, 0x20) + } + uint256 b = a + 1; + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `LOG0` opcode. + function testShouldFailExpectSafeMemory_LOG0() public { + vm.expectSafeMemory(0x80, 0x100); + + // This should revert. + assembly { + log0(0x100, 0x20) + } + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `CREATE` opcode. + function testShouldFailExpectSafeMemory_CREATE() public { + vm.expectSafeMemory(0x80, 0x100); + + // This should revert. + assembly { + pop(create(0, 0x100, 0x20)) + } + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `CREATE2` opcode. + function testShouldFailExpectSafeMemory_CREATE2() public { + vm.expectSafeMemory(0x80, 0x100); + + // This should revert. + assembly { + pop(create2(0, 0x100, 0x20, 0x00)) + } + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `RETURN` opcode. + function testShouldFailExpectSafeMemory_RETURN() public { + vm.expectSafeMemory(0x80, 0x100); + + // This should revert. + assembly { + return(0x100, 0x20) + } + } + + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `REVERT` opcode. + function testShouldFailExpectSafeMemory_REVERT() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + + // Create a payload to call `doRevert` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.doRevert.selector, 0x120, 0x20); + + // Expect memory in the range of [0x00, 0x120] to be safe in the next subcontext + vm.expectSafeMemoryCall(0x00, 0x120); + + // Call `doRevert` on the SubContext contract and ensure it did not revert with + // zero data. + _doCallReturnData(address(sc), payload, 0x200, 0x20); + assembly { + if iszero(eq(keccak256(0x60, 0x20), keccak256(0x200, returndatasize()))) { revert(0x00, 0x00) } + } + } + + /// @dev Tests that the `expectSafeMemoryCall` cheatcode works as expected. + function testShouldFailExpectSafeMemoryCall() public { + // Create a new SubContext contract + SubContext sc = new SubContext(); + // Create a payload to call `doMstore8` on the SubContext contract + bytes memory payload = abi.encodeWithSelector(SubContext.doMstore.selector, 0xA0, 0xc0ffee); + + // Allow memory writes in the range of [0x80, 0xA0) within the next created subcontext + vm.expectSafeMemoryCall(0x80, 0xA0); + + // Should revert. The memory write in this subcontext is outside of the allowed range. + if (!_doCall(address(sc), payload)) { + revert("Expected call to fail"); + } + } + + /// @dev Tests that the `stopExpectSafeMemory` cheatcode does not cause violations not being noticed. + function testShouldFailStopExpectSafeMemory() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } + + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write outside of allowed range, this should revert + mstore(add(initPtr, 0x20), 0x01) + } + + vm.stopExpectSafeMemory(); + } + + // Helpers + + /// @dev Performs a call without copying any returndata. + function _doCall(address _target, bytes memory _payload) internal returns (bool _success) { + assembly { + _success := call(gas(), _target, 0x00, add(_payload, 0x20), mload(_payload), 0x00, 0x00) + } + } + + /// @dev Performs a call and copies returndata to memory. + function _doCallReturnData(address _target, bytes memory _payload, uint256 returnDataDest, uint256 returnDataSize) + internal + { + assembly { + pop(call(gas(), _target, 0x00, add(_payload, 0x20), mload(_payload), returnDataDest, returnDataSize)) + } + } + + /// @dev Performs a staticcall and copies returndata to memory. + function _doStaticCallReturnData( + address _target, + bytes memory _payload, + uint256 returnDataDest, + uint256 returnDataSize + ) internal { + assembly { + pop(staticcall(gas(), _target, add(_payload, 0x20), mload(_payload), returnDataDest, returnDataSize)) + } + } + + /// @dev Performs a delegatecall and copies returndata to memory. + function _doDelegateCallReturnData( + address _target, + bytes memory _payload, + uint256 returnDataDest, + uint256 returnDataSize + ) internal { + assembly { + pop(delegatecall(gas(), _target, add(_payload, 0x20), mload(_payload), returnDataDest, returnDataSize)) + } + } + + /// @dev Performs a callcode and copies returndata to memory. + function _doCallCodeReturnData( + address _target, + bytes memory _payload, + uint256 returnDataDest, + uint256 returnDataSize + ) internal { + assembly { + pop(callcode(gas(), _target, 0x00, add(_payload, 0x20), mload(_payload), returnDataDest, returnDataSize)) + } + } +} + +/// @dev A simple contract for testing the `expectSafeMemory` & `expectSafeMemoryCall` cheatcodes. +contract SubContext { + function doMstore(uint256 offset, uint256 val) external { + assembly { + mstore(offset, val) + } + } + + function doMstore8(uint256 offset, uint8 val) external { + assembly { + mstore8(offset, val) + } + } + + function giveReturndata() external view returns (bytes memory _returndata) { + return hex"7dc4acc68d77c9c85b5cb0f53ab9ceea175f7964390758e4409013ce80643f84"; + } + + function doRevert(uint256 offset, uint256 size) external { + assembly { + revert(offset, size) + } + } +} diff --git a/crates/forge/tests/fixtures/ScriptVerify.sol b/crates/forge/tests/fixtures/ScriptVerify.sol index e282f9e5c3871..8dc611f5e531a 100644 --- a/crates/forge/tests/fixtures/ScriptVerify.sol +++ b/crates/forge/tests/fixtures/ScriptVerify.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity =0.8.16; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.16; import {Unique} from "./unique.sol"; diff --git a/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json new file mode 100644 index 0000000000000..b4e396863dc68 --- /dev/null +++ b/crates/forge/tests/fixtures/SimpleContractTestNonVerbose.json @@ -0,0 +1,28 @@ +{ + "src/Simple.t.sol:SimpleContractTest": { + "duration": "{...}", + "test_results": { + "test()": { + "status": "Success", + "reason": null, + "counterexample": null, + "logs": [], + "decoded_logs": [], + "kind": { + "Unit": { + "gas": "{...}" + } + }, + "traces": [], + "labeled_addresses": {}, + "duration": { + "secs": "{...}", + "nanos": "{...}" + }, + "breakpoints": {}, + "gas_snapshots": {} + } + }, + "warnings": [] + } +} \ No newline at end of file diff --git a/crates/forge/tests/fixtures/SimpleContractTestVerbose.json b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json new file mode 100644 index 0000000000000..20324950fc240 --- /dev/null +++ b/crates/forge/tests/fixtures/SimpleContractTestVerbose.json @@ -0,0 +1,217 @@ +{ + "src/Simple.t.sol:SimpleContractTest": { + "duration": "{...}", + "test_results": { + "test()": { + "status": "Success", + "reason": null, + "counterexample": null, + "logs": [ + { + "address": "0x000000000000000000636f6e736f6c652e6c6f67", + "topics": [ + "0x41304facd9323d75b11bcdd609cb38effffdb05710f7caf0e9b16c6d9d709f50" + ], + "data": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000033130300000000000000000000000000000000000000000000000000000000000" + } + ], + "decoded_logs": [ + "100" + ], + "kind": { + "Unit": { + "gas": "{...}" + } + }, + "traces": [ + [ + "Deployment", + { + "arena": [ + { + "parent": null, + "children": [], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "address": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", + "maybe_precompile": false, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CREATE", + "value": "0x0", + "data": "{...}", + "output": "{...}", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Return", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + } + ] + } + ], + [ + "Execution", + { + "arena": [ + { + "parent": null, + "children": [ + 1, + 2, + 3 + ], + "idx": 0, + "trace": { + "depth": 0, + "success": true, + "caller": "0x1804c8ab1f12e6bbf3894d4083f33e07309d1f38", + "address": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0xf8a8fd6d", + "output": "0x", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [ + { + "Call": 0 + }, + { + "Call": 1 + }, + { + "Call": 2 + } + ] + }, + { + "parent": 0, + "children": [], + "idx": 1, + "trace": { + "depth": 1, + "success": true, + "caller": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", + "address": "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", + "maybe_precompile": false, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CREATE", + "value": "0x0", + "data": "{...}", + "output": "{...}", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Return", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + }, + { + "parent": 0, + "children": [], + "idx": 2, + "trace": { + "depth": 1, + "success": true, + "caller": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", + "address": "0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "CALL", + "value": "0x0", + "data": "0xe26d14740000000000000000000000000000000000000000000000000000000000000064", + "output": "0x", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + }, + { + "parent": 0, + "children": [], + "idx": 3, + "trace": { + "depth": 1, + "success": true, + "caller": "0x7fa9385be102ac3eac297483dd6233d62b3e1496", + "address": "0x000000000000000000636f6e736f6c652e6c6f67", + "maybe_precompile": null, + "selfdestruct_address": null, + "selfdestruct_refund_target": null, + "selfdestruct_transferred_value": null, + "kind": "STATICCALL", + "value": "0x0", + "data": "{...}", + "output": "0x", + "gas_used": "{...}", + "gas_limit": "{...}", + "status": "Stop", + "steps": [], + "decoded": { + "label": null, + "return_data": null, + "call_data": null + } + }, + "logs": [], + "ordering": [] + } + ] + } + ] + ], + "labeled_addresses": {}, + "duration": { + "secs": "{...}", + "nanos": "{...}" + }, + "breakpoints": {}, + "gas_snapshots": {} + } + }, + "warnings": [] + } +} \ No newline at end of file diff --git a/crates/forge/tests/fixtures/can_build_skip_contracts.stdout b/crates/forge/tests/fixtures/can_build_skip_contracts.stdout deleted file mode 100644 index 74ddb6b4e49f3..0000000000000 --- a/crates/forge/tests/fixtures/can_build_skip_contracts.stdout +++ /dev/null @@ -1,3 +0,0 @@ -Compiling 1 files with 0.8.17 -Solc 0.8.17 finished in 34.45ms -Compiler run successful! diff --git a/crates/forge/tests/fixtures/can_build_skip_glob.stdout b/crates/forge/tests/fixtures/can_build_skip_glob.stdout deleted file mode 100644 index 522beb3e2b6c0..0000000000000 --- a/crates/forge/tests/fixtures/can_build_skip_glob.stdout +++ /dev/null @@ -1,3 +0,0 @@ -Compiling 1 files with 0.8.17 -Solc 0.8.17 finished in 33.25ms -Compiler run successful! diff --git a/crates/forge/tests/fixtures/can_check_snapshot.stdout b/crates/forge/tests/fixtures/can_check_snapshot.stdout deleted file mode 100644 index 1cd927836ae65..0000000000000 --- a/crates/forge/tests/fixtures/can_check_snapshot.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 2 files with 0.8.10 -Solc 0.8.10 finished in 424.55ms -Compiler run successful! - -Running 1 test for src/ATest.t.sol:ATest -[PASS] testExample() (gas: 168) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.42ms -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) 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 c04ae5bd577d2..0000000000000 --- 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 a67e4dd96aadf..0000000000000 --- a/crates/forge/tests/fixtures/can_create_template_contract.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 22 files with 0.8.15 -Solc 0.8.15 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 c04ae5bd577d2..0000000000000 --- 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 ab309752469be..0000000000000 --- a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout +++ /dev/null @@ -1,6 +0,0 @@ -Compiling 22 files with 0.8.15 -Solc 0.8.15 finished in 1.95s -Compiler run successful! -Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -Deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 -Transaction hash: 0x4c3d9f7c4cc26876b43a11ba7ff218374471786a8ae8bf5574deb1d97fc1e851 diff --git a/crates/forge/tests/fixtures/can_detect_dirty_git_status_on_init.stderr b/crates/forge/tests/fixtures/can_detect_dirty_git_status_on_init.stderr deleted file mode 100644 index de86b264db43d..0000000000000 --- a/crates/forge/tests/fixtures/can_detect_dirty_git_status_on_init.stderr +++ /dev/null @@ -1,10 +0,0 @@ -Error: -The target directory is a part of or on its own an already initialized git repository, -and it requires clean working and staging areas, including no untracked files. - -Check the current git repository's status with `git status`. -Then, you can track files with `git add ...` and then commit them with `git commit`, -ignore them in the `.gitignore` file, or run this command again with the `--no-commit` flag. - -If none of the previous steps worked, please open an issue at: -https://github.com/foundry-rs/foundry/issues/new/choose diff --git a/crates/forge/tests/fixtures/can_execute_script_and_skip_contracts.stdout b/crates/forge/tests/fixtures/can_execute_script_and_skip_contracts.stdout deleted file mode 100644 index 46f580d03a5b4..0000000000000 --- a/crates/forge/tests/fixtures/can_execute_script_and_skip_contracts.stdout +++ /dev/null @@ -1,12 +0,0 @@ -Compiling 1 files with 0.8.17 -Solc 0.8.17 -Compiler run successful! -Script ran successfully. -Gas used: 22900 - -== Return == -result: uint256 255 -1: uint8 3 - -== Logs == - script ran diff --git a/crates/forge/tests/fixtures/can_execute_script_command.stdout b/crates/forge/tests/fixtures/can_execute_script_command.stdout deleted file mode 100644 index 459bf7369bf9a..0000000000000 --- a/crates/forge/tests/fixtures/can_execute_script_command.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 1 files with 0.8.10 -Solc 0.8.10 finished in 23.34ms -Compiler run successful! -Script ran successfully. -Gas used: 22815 - -== Logs == - script ran diff --git a/crates/forge/tests/fixtures/can_execute_script_command_fqn.stdout b/crates/forge/tests/fixtures/can_execute_script_command_fqn.stdout deleted file mode 100644 index 6b2a06b307488..0000000000000 --- a/crates/forge/tests/fixtures/can_execute_script_command_fqn.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 1 files with 0.8.10 -Solc 0.8.10 finished in 23.70ms -Compiler run successful! -Script ran successfully. -Gas used: 22815 - -== Logs == - script ran diff --git a/crates/forge/tests/fixtures/can_execute_script_command_with_args.stdout b/crates/forge/tests/fixtures/can_execute_script_command_with_args.stdout deleted file mode 100644 index 882fe3a47c515..0000000000000 --- a/crates/forge/tests/fixtures/can_execute_script_command_with_args.stdout +++ /dev/null @@ -1,10 +0,0 @@ -Compiling 1 files with 0.8.10 -Solc 0.8.10 finished in 35.28ms -Compiler run successful! -Script ran successfully. -Gas used: 25301 - -== Logs == - script ran - 1 - 2 diff --git a/crates/forge/tests/fixtures/can_execute_script_command_with_returned.stdout b/crates/forge/tests/fixtures/can_execute_script_command_with_returned.stdout deleted file mode 100644 index 0cc0e7f97b1c2..0000000000000 --- a/crates/forge/tests/fixtures/can_execute_script_command_with_returned.stdout +++ /dev/null @@ -1,12 +0,0 @@ -Compiling 1 files with 0.8.10 -Solc 0.8.10 finished in 1.27s -Compiler run successful! -Script ran successfully. -Gas used: 22900 - -== Return == -result: uint256 255 -1: uint8 3 - -== Logs == - script ran diff --git a/crates/forge/tests/fixtures/can_execute_script_command_with_sig.stdout b/crates/forge/tests/fixtures/can_execute_script_command_with_sig.stdout deleted file mode 100644 index 54f954f36daaf..0000000000000 --- a/crates/forge/tests/fixtures/can_execute_script_command_with_sig.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 1 files with 0.8.10 -Solc 0.8.10 finished in 24.49ms -Compiler run successful! -Script ran successfully. -Gas used: 22815 - -== Logs == - script ran diff --git a/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout b/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout deleted file mode 100644 index 4becd823dbbea..0000000000000 --- a/crates/forge/tests/fixtures/can_run_test_in_custom_test_folder.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 2 files with 0.8.10 -Solc 0.8.10 finished in 185.25ms -Compiler run successful! - -Running 1 test for src/nested/forge-tests/MyTest.t.sol:MyTest -[PASS] testTrue() (gas: 168) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.93ms -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr b/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr deleted file mode 100644 index c7c847bf96e18..0000000000000 --- a/crates/forge/tests/fixtures/can_set_yul_optimizer.stderr +++ /dev/null @@ -1,9 +0,0 @@ -Error: -Compiler run failed: -Error (6553): SyntaxError: The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction. - --> src/Foo.sol:6:8: - | -6 | assembly { - | ^ (Relevant source part starts here and spans across multiple lines). - - diff --git a/crates/forge/tests/fixtures/can_test_repeatedly.stdout b/crates/forge/tests/fixtures/can_test_repeatedly.stdout deleted file mode 100644 index 6cec559ab0430..0000000000000 --- a/crates/forge/tests/fixtures/can_test_repeatedly.stdout +++ /dev/null @@ -1,7 +0,0 @@ -No files changed, compilation skipped - -Running 2 tests for test/Counter.t.sol:CounterTest -[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 26521, ~: 28387) -[PASS] test_Increment() (gas: 28357) -Test result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms -Ran 1 test suites: 2 tests passed, 0 failed, 0 skipped (2 total tests) diff --git a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout b/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout deleted file mode 100644 index 4d4022a5a4faf..0000000000000 --- a/crates/forge/tests/fixtures/can_use_libs_in_multi_fork.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 20 files with 0.8.13 -Solc 0.8.13 finished in 1.95s -Compiler run successful! - -Running 1 test for test/Contract.t.sol:ContractTest -[PASS] test() (gas: 70373) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.21s -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/colored_traces.svg b/crates/forge/tests/fixtures/colored_traces.svg new file mode 100644 index 0000000000000..4b2be87d03df7 --- /dev/null +++ b/crates/forge/tests/fixtures/colored_traces.svg @@ -0,0 +1,82 @@ + + + + + + + No files changed, compilation skipped + + + + Ran 1 test for test/Counter.t.sol:CounterTest + + [PASS] test_Increment() ([GAS]) + + Traces: + + [137242] CounterTest::setUp() + + ├─ [96345] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f + + │ └─ ← [Return] 481 bytes of code + + ├─ [2592] Counter::setNumber(0) + + │ └─ ← [Stop] + + └─ ← [Stop] + + + + [31851] CounterTest::test_Increment() + + ├─ [22418] Counter::increment() + + │ ├─ storage changes: + + │ │ @ 0: 0 → 1 + + │ └─ ← [Stop] + + ├─ [424] Counter::number() [staticcall] + + │ └─ ← [Return] 1 + + ├─ [0] VM::assertEq(1, 1) [staticcall] + + │ └─ ← [Return] + + ├─ storage changes: + + │ @ 0: 0 → 1 + + └─ ← [Stop] + + + + Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + + + + Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + + + + + + diff --git a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.10.stdout b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.10.stdout deleted file mode 100644 index 478d164a65b45..0000000000000 --- a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.10.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 2 files with 0.8.10 -Solc 0.8.10 finished in 185.25ms -Compiler run successful! - -Running 1 test for src/Contract.t.sol:ContractTest -[PASS] testExample() (gas: 190) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.13.stdout b/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.13.stdout deleted file mode 100644 index 16e259e943075..0000000000000 --- a/crates/forge/tests/fixtures/runs_tests_exactly_once_with_changed_versions.0.8.13.stdout +++ /dev/null @@ -1,8 +0,0 @@ -Compiling 2 files with 0.8.13 -Solc 0.8.13 finished in 185.25ms -Compiler run successful! - -Running 1 test for src/Contract.t.sol:ContractTest -[PASS] testExample() (gas: 190) -Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.89ms -Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests) diff --git a/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout b/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout deleted file mode 100644 index 3c58603b91bc8..0000000000000 --- a/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout +++ /dev/null @@ -1,13 +0,0 @@ -Compiling 1 files with 0.8.10 -Solc 0.8.10 finished in 185.25ms -Compiler run successful! - -No tests match the provided pattern: - match-test: `testA.*` - no-match-test: `testB.*` - match-contract: `TestC.*` - no-match-contract: `TestD.*` - match-path: `*TestE*` - no-match-path: `*TestF*` - -Did you mean `test1`? diff --git a/crates/forge/tests/fixtures/warn_no_tests.stdout b/crates/forge/tests/fixtures/warn_no_tests.stdout deleted file mode 100644 index 3af59c7af14e7..0000000000000 --- a/crates/forge/tests/fixtures/warn_no_tests.stdout +++ /dev/null @@ -1,5 +0,0 @@ -Compiling 1 files with 0.8.13 -Solc 0.8.13 -Compiler run successful! - -No tests found in project! Forge looks for functions that starts with `test`. diff --git a/crates/forge/tests/fixtures/warn_no_tests_match.stdout b/crates/forge/tests/fixtures/warn_no_tests_match.stdout deleted file mode 100644 index 6eede42748583..0000000000000 --- a/crates/forge/tests/fixtures/warn_no_tests_match.stdout +++ /dev/null @@ -1,11 +0,0 @@ -Compiling 1 files with 0.8.13 -Solc 0.8.13 -Compiler run successful! - -No tests match the provided pattern: - match-test: `testA.*` - no-match-test: `testB.*` - match-contract: `TestC.*` - no-match-contract: `TestD.*` - match-path: `*TestE*` - no-match-path: `*TestF*` diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index f2769793bddd1..11fcdbcfd5c1b 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -1,19 +1,82 @@ -//! forge tests for cheat codes +//! Forge tests for cheatcodes. use crate::{ config::*, - test_helpers::{filter::Filter, RE_PATH_SEPARATOR}, + test_helpers::{ + ForgeTestData, RE_PATH_SEPARATOR, TEST_DATA_DEFAULT, TEST_DATA_MULTI_VERSION, + TEST_DATA_PARIS, + }, }; +use alloy_primitives::U256; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; +use foundry_test_utils::Filter; + +/// Executes all cheat code tests but not fork cheat codes or tests that require isolation mode or +/// specific seed. +async fn test_cheats_local(test_data: &ForgeTestData) { + let mut filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")) + .exclude_paths("Fork") + .exclude_contracts("(Isolated|WithSeed)"); + + // Exclude FFI tests on Windows because no `echo`, and file tests that expect certain file paths + if cfg!(windows) { + filter = filter.exclude_tests("(Ffi|File|Line|Root)"); + } + + if cfg!(feature = "isolate-by-default") { + filter = filter.exclude_contracts("(LastCallGasDefaultTest|MockFunctionTest|WithSeed)"); + } + + let runner = test_data.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); + }); + + TestConfig::with_filter(runner, filter).run().await; +} + +/// Executes subset of all cheat code tests in isolation mode. +async fn test_cheats_local_isolated(test_data: &ForgeTestData) { + let filter = Filter::new(".*", ".*(Isolated)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); + + let runner = test_data.runner_with(|config| { + config.isolate = true; + }); + + TestConfig::with_filter(runner, filter).run().await; +} + +/// Executes subset of all cheat code tests using a specific seed. +async fn test_cheats_local_with_seed(test_data: &ForgeTestData) { + let filter = Filter::new(".*", ".*(WithSeed)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); + + let runner = test_data.runner_with(|config| { + config.fuzz.seed = Some(U256::from(100)); + }); + + TestConfig::with_filter(runner, filter).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_default() { + test_cheats_local(&TEST_DATA_DEFAULT).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_default_isolated() { + test_cheats_local_isolated(&TEST_DATA_DEFAULT).await +} -/// Executes all cheat code tests but not fork cheat codes #[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local() { - let filter = - Filter::new(".*", "Skip*", &format!(".*cheats{RE_PATH_SEPARATOR}*")).exclude_paths("Fork"); +async fn test_cheats_local_default_with_seed() { + test_cheats_local_with_seed(&TEST_DATA_DEFAULT).await +} - // on windows exclude ffi tests since no echo and file test that expect a certain file path - #[cfg(windows)] - let filter = filter.exclude_tests("(Ffi|File|Line|Root)"); +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_multi_version() { + test_cheats_local(&TEST_DATA_MULTI_VERSION).await +} - TestConfig::filter(filter).await.run().await; +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_paris() { + test_cheats_local(&TEST_DATA_PARIS).await } diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs index f81472045d69a..655fae4db254b 100644 --- a/crates/forge/tests/it/config.rs +++ b/crates/forge/tests/it/config.rs @@ -1,49 +1,38 @@ -//! Test setup +//! Test config. -use crate::test_helpers::{ - filter::Filter, COMPILED, COMPILED_WITH_LIBS, EVM_OPTS, LIBS_PROJECT, PROJECT, -}; use forge::{ result::{SuiteResult, TestStatus}, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, -}; -use foundry_config::{ - fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, - InvariantConfig, RpcEndpoint, RpcEndpoints, + MultiContractRunner, }; use foundry_evm::{ - decode::decode_console_logs, executor::inspector::CheatsConfig, revm::primitives::SpecId, -}; -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, + decode::decode_console_logs, + revm::primitives::SpecId, + traces::{decode_trace_arena, render_trace_arena, CallTraceDecoderBuilder}, }; +use foundry_test_utils::{init_tracing, Filter}; +use futures::future::join_all; +use itertools::Itertools; +use std::collections::BTreeMap; -/// How to execute a a test run +/// How to execute a test run. pub struct TestConfig { pub runner: MultiContractRunner, pub should_fail: bool, pub filter: Filter, - pub opts: TestOptions, } -// === impl TestConfig === - impl TestConfig { pub fn new(runner: MultiContractRunner) -> Self { Self::with_filter(runner, Filter::matches_all()) } pub fn with_filter(runner: MultiContractRunner, filter: Filter) -> Self { - Self { runner, should_fail: false, filter, opts: test_opts() } + init_tracing(); + Self { runner, should_fail: false, filter } } - pub async fn filter(filter: Filter) -> Self { - Self::with_filter(runner().await, filter) - } - - pub fn evm_spec(mut self, spec: SpecId) -> Self { - self.runner.evm_spec = spec; + pub fn spec_id(mut self, spec: SpecId) -> Self { + self.runner.spec_id = spec; self } @@ -57,8 +46,8 @@ impl TestConfig { } /// Executes the test runner - pub async fn test(&mut self) -> BTreeMap { - self.runner.test(&self.filter, None, self.opts.clone()).await + pub fn test(&mut self) -> BTreeMap { + self.runner.test_collect(&self.filter) } pub async fn run(&mut self) { @@ -71,24 +60,39 @@ impl TestConfig { /// * filter matched 0 test cases /// * a test results deviates from the configured `should_fail` setting pub async fn try_run(&mut self) -> eyre::Result<()> { - let suite_result = self.runner.test(&self.filter, None, self.opts.clone()).await; + let suite_result = self.test(); if suite_result.is_empty() { eyre::bail!("empty test result"); } for (_, SuiteResult { test_results, .. }) in suite_result { - for (test_name, 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() + .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() + .collect::>(); eyre::bail!( - "Test {} did not {} as expected.\nReason: {:?}\nLogs:\n{}", + "Test {} did not {} as expected.\nReason: {:?}\nLogs:\n{}\n\nTraces:\n{}", test_name, outcome, result.reason, - logs.join("\n") + logs.join("\n"), + decoded_traces.into_iter().format("\n"), ) } } @@ -98,121 +102,6 @@ impl TestConfig { } } -pub fn test_opts() -> TestOptions { - TestOptions { - fuzz: FuzzConfig { - runs: 256, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig { - include_storage: true, - include_push_bytes: true, - dictionary_weight: 40, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - }, - invariant: InvariantConfig { - runs: 256, - depth: 15, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { - dictionary_weight: 80, - include_storage: true, - include_push_bytes: true, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - shrink_sequence: true, - }, - inline_fuzz: Default::default(), - inline_invariant: Default::default(), - } -} - -pub fn manifest_root() -> PathBuf { - let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); - // need to check here where we're executing the test from, if in `forge` we need to also allow - // `testdata` - if root.ends_with("forge") { - root = root.parent().unwrap(); - } - root.to_path_buf() -} - -/// Builds a base runner -pub fn base_runner() -> MultiContractRunnerBuilder { - MultiContractRunnerBuilder::default().sender(EVM_OPTS.sender) -} - -/// Builds a non-tracing runner -pub async fn runner() -> MultiContractRunner { - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); - runner_with_config(config).await -} - -/// Builds a non-tracing runner -pub async fn runner_with_config(mut config: Config) -> MultiContractRunner { - config.rpc_endpoints = rpc_endpoints(); - config.allow_paths.push(manifest_root()); - - base_runner() - .with_cheats_config(CheatsConfig::new(&config, &EVM_OPTS)) - .sender(config.sender) - .build( - &PROJECT.paths.root, - (*COMPILED).clone(), - EVM_OPTS.evm_env().await.expect("Could not instantiate fork environment"), - EVM_OPTS.clone(), - ) - .unwrap() -} - -/// Builds a tracing runner -pub async fn tracing_runner() -> MultiContractRunner { - let mut opts = EVM_OPTS.clone(); - opts.verbosity = 5; - base_runner() - .build( - &PROJECT.paths.root, - (*COMPILED).clone(), - EVM_OPTS.evm_env().await.expect("Could not instantiate fork environment"), - opts, - ) - .unwrap() -} - -// Builds a runner that runs against forked state -pub async fn forked_runner(rpc: &str) -> MultiContractRunner { - let mut opts = EVM_OPTS.clone(); - - opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC - opts.fork_url = Some(rpc.to_string()); - - let env = opts.evm_env().await.expect("Could not instantiate fork environment"); - let fork = opts.get_fork(&Default::default(), env.clone()); - - base_runner() - .with_fork(fork) - .build(&LIBS_PROJECT.paths.root, (*COMPILED_WITH_LIBS).clone(), env, opts) - .unwrap() -} - -/// the RPC endpoints used during tests -pub fn rpc_endpoints() -> RpcEndpoints { - RpcEndpoints::new([ - ( - "rpcAlias", - RpcEndpoint::Url( - "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), - ), - ), - ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), - ]) -} - /// A helper to assert the outcome of multiple tests with helpful assert messages #[track_caller] #[allow(clippy::type_complexity)] @@ -236,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(); @@ -262,8 +151,9 @@ pub fn assert_multiple( } if let Some(expected_logs) = expected_logs { - assert!( - logs.iter().eq(expected_logs.iter()), + assert_eq!( + logs, + expected_logs, "Logs did not match for test {}.\nExpected:\n{}\n\nGot:\n{}", test_name, expected_logs.join("\n"), diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs index fd2f22812cdcb..392cb90998bcd 100644 --- a/crates/forge/tests/it/core.rs +++ b/crates/forge/tests/it/core.rs @@ -1,100 +1,100 @@ -//! forge tests for core functionality +//! Forge tests for core functionality. -use crate::{config::*, test_helpers::filter::Filter}; +use crate::{ + config::*, + test_helpers::{TEST_DATA_DEFAULT, TEST_DATA_PARIS}, +}; use forge::result::SuiteResult; -use foundry_evm::trace::TraceKind; +use foundry_evm::traces::TraceKind; +use foundry_test_utils::Filter; use std::{collections::BTreeMap, env}; #[tokio::test(flavor = "multi_thread")] async fn test_core() { - let mut runner = runner().await; - let results = runner.test(&Filter::new(".*", ".*", ".*core"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*core"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "core/FailingSetup.t.sol:FailingSetupTest", - vec![( - "setUp()", - false, - Some("Setup failed: setup failed predictably".to_string()), - None, - None, - )], + "default/core/Reverting.t.sol:RevertingTest", + vec![("testRevert()", true, None, None, None)], ), ( - "core/MultipleSetup.t.sol:MultipleSetup", - vec![( - "setUp()", - false, - Some("Multiple setUp functions".to_string()), - None, - Some(1), - )], - ), - ( - "core/Reverting.t.sol:RevertingTest", - vec![("testFailRevert()", true, None, None, None)], - ), - ( - "core/SetupConsistency.t.sol:SetupConsistencyCheck", + "default/core/SetupConsistency.t.sol:SetupConsistencyCheck", vec![ ("testAdd()", true, None, None, None), ("testMultiply()", true, None, None, None), ], ), ( - "core/DSStyle.t.sol:DSStyleTest", - vec![("testFailingAssertions()", true, None, None, None)], - ), - ( - "core/ContractEnvironment.t.sol:ContractEnvironmentTest", + "default/core/ContractEnvironment.t.sol:ContractEnvironmentTest", vec![ ("testAddresses()", true, None, None, None), ("testEnvironment()", true, None, None, None), ], ), ( - "core/PaymentFailure.t.sol:PaymentFailureTest", + "default/core/PaymentFailure.t.sol:PaymentFailureTest", vec![("testCantPay()", false, Some("EvmError: Revert".to_string()), None, None)], ), - ("core/Abstract.t.sol:AbstractTest", vec![("testSomething()", true, None, None, None)]), ( - "core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", + "default/core/Abstract.t.sol:AbstractTest", + vec![("testSomething()", true, None, None, None)], + ), + ( + "default/core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", + vec![("setUp()", false, Some("execution error".to_string()), None, None)], + ), + ( + "default/core/BadSigAfterInvariant.t.sol:BadSigAfterInvariant", + vec![("testShouldPassWithWarning()", true, None, None, None)], + ), + ( + "default/core/LegacyAssertions.t.sol:NoAssertionsRevertTest", vec![( - "setUp()", + "testMultipleAssertFailures()", false, - Some("Setup failed: execution error".to_string()), + Some("assertion failed: 1 != 2".to_string()), None, None, )], ), + ( + "default/core/LegacyAssertions.t.sol:LegacyAssertionsTest", + vec![ + ("testFlagNotSetSuccess()", true, None, None, None), + ("testFlagSetFailure()", true, None, None, None), + ], + ), ]), ); } #[tokio::test(flavor = "multi_thread")] async fn test_linking() { - let mut runner = runner().await; - let results = runner.test(&Filter::new(".*", ".*", ".*linking"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*linking"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", vec![("testCall()", true, None, None, None)], ), ( - "linking/nested/Nested.t.sol:NestedLibraryLinkingTest", + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", vec![ ("testDirect()", true, None, None, None), ("testNested()", true, None, None, None), ], ), ( - "linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", vec![ ("testA()", true, None, None, None), ("testB()", true, None, None, None), @@ -109,38 +109,27 @@ async fn test_linking() { #[tokio::test(flavor = "multi_thread")] async fn test_logs() { - let mut runner = runner().await; - let results = runner.test(&Filter::new(".*", ".*", ".*logs"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*logs"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "logs/DebugLogs.t.sol:DebugLogsTest", + "default/logs/DebugLogs.t.sol:DebugLogsTest", vec![ + ("test1()", true, None, Some(vec!["0".into(), "1".into(), "2".into()]), None), + ("test2()", true, None, Some(vec!["0".into(), "1".into(), "3".into()]), None), ( - "test1()", - true, - None, - Some(vec!["0".into(), "1".into(), "2".into()]), - None, - ), - ( - "test2()", - true, - None, - Some(vec!["0".into(), "1".into(), "3".into()]), - None, - ), - ( - "testFailWithRequire()", + "testRevertIfWithRequire()", true, None, Some(vec!["0".into(), "1".into(), "5".into()]), None, ), ( - "testFailWithRevert()", + "testRevertIfWithRevert()", true, None, Some(vec!["0".into(), "1".into(), "4".into(), "100".into()]), @@ -178,7 +167,9 @@ async fn test_logs() { Some(vec![ "0".into(), "1".into(), - "0x6162636400000000000000000000000000000000000000000000000000000000".into()]), + "0x6162636400000000000000000000000000000000000000000000000000000000" + .into(), + ]), None, ), ( @@ -209,7 +200,8 @@ async fn test_logs() { Some(vec![ "0".into(), "1".into(), - "address: 0x0000000000000000000000000000000000000001".into()]), + "address: 0x0000000000000000000000000000000000000001".into(), + ]), None, ), ( @@ -217,75 +209,59 @@ async fn test_logs() { true, None, Some(vec![ - "0".into(), - "1".into(), - "abcd: 0x6162636400000000000000000000000000000000000000000000000000000000".into()]), + "0".into(), + "1".into(), + "abcd: 0x6162636400000000000000000000000000000000000000000000000000000000" + .into(), + ]), None, ), ( "testLogNamedDecimalInt()", true, None, - Some(vec![ - "0".into(), - "1".into(), - "amount: -0.000000000000031337".into()]), + Some(vec!["0".into(), "1".into(), "amount: -0.000000000000031337".into()]), None, ), ( "testLogNamedDecimalUint()", true, None, - Some(vec![ - "0".into(), - "1".into(), - "amount: 1.000000000000000000".into()]), + Some(vec!["0".into(), "1".into(), "amount: 1.000000000000000000".into()]), None, ), ( "testLogNamedInt()", true, None, - Some(vec![ - "0".into(), - "1".into(), - "amount: -31337".into()]), + Some(vec!["0".into(), "1".into(), "amount: -31337".into()]), None, ), ( "testLogNamedUint()", true, None, - Some(vec![ - "0".into(), - "1".into(), - "amount: 1000000000000000000".into()]), + Some(vec!["0".into(), "1".into(), "amount: 1000000000000000000".into()]), None, ), ( "testLogNamedBytes()", true, None, - Some(vec![ - "0".into(), - "1".into(), - "abcd: 0x61626364".into()]), + Some(vec!["0".into(), "1".into(), "abcd: 0x61626364".into()]), None, ), ( "testLogNamedString()", true, None, - Some(vec![ - "0".into(), - "1".into(), - "key: val".into()]), + Some(vec!["0".into(), "1".into(), "key: val".into()]), None, ), ], ), ( - "logs/HardhatLogs.t.sol:HardhatLogsTest", + "default/logs/HardhatLogs.t.sol:HardhatLogsTest", vec![ ( "testInts()", @@ -357,7 +333,10 @@ async fn test_logs() { "testLogAddress()", true, None, - Some(vec!["constructor".into(), "0x0000000000000000000000000000000000000001".into()]), + Some(vec![ + "constructor".into(), + "0x0000000000000000000000000000000000000001".into(), + ]), None, ), ( @@ -476,119 +455,172 @@ async fn test_logs() { "testLogBytes16()", true, None, - Some(vec!["constructor".into(), "0x61000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x61000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes17()", true, None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x6100000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes18()", true, None, - Some(vec!["constructor".into(), "0x610000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x610000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes19()", true, None, - Some(vec!["constructor".into(), "0x61000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x61000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes20()", true, None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x6100000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes21()", true, None, - Some(vec!["constructor".into(), "0x610000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x610000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes22()", true, None, - Some(vec!["constructor".into(), "0x61000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x61000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes23()", true, None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x6100000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes24()", true, None, - Some(vec!["constructor".into(), "0x610000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x610000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes25()", true, None, - Some(vec!["constructor".into(), "0x61000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x61000000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes26()", true, None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x6100000000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes27()", true, None, - Some(vec!["constructor".into(), "0x610000000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x610000000000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes28()", true, None, - Some(vec!["constructor".into(), "0x61000000000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x61000000000000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes29()", true, None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x6100000000000000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes30()", true, None, - Some(vec!["constructor".into(), "0x610000000000000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x610000000000000000000000000000000000000000000000000000000000".into(), + ]), None, ), ( "testLogBytes31()", true, None, - Some(vec!["constructor".into(), "0x61000000000000000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x61000000000000000000000000000000000000000000000000000000000000" + .into(), + ]), None, ), ( "testLogBytes32()", true, None, - Some(vec!["constructor".into(), "0x6100000000000000000000000000000000000000000000000000000000000000".into()]), + Some(vec![ + "constructor".into(), + "0x6100000000000000000000000000000000000000000000000000000000000000" + .into(), + ]), None, ), ( @@ -616,7 +648,10 @@ async fn test_logs() { "testConsoleLogAddress()", true, None, - Some(vec!["constructor".into(), "0x0000000000000000000000000000000000000001".into()]), + Some(vec![ + "constructor".into(), + "0x0000000000000000000000000000000000000001".into(), + ]), None, ), ( @@ -637,7 +672,10 @@ async fn test_logs() { "testConsoleLogFormatAddress()", true, None, - Some(vec!["constructor".into(), "formatted log addr=0x0000000000000000000000000000000000000001".into()]), + Some(vec![ + "constructor".into(), + "formatted log addr=0x0000000000000000000000000000000000000001".into(), + ]), None, ), ( @@ -669,35 +707,31 @@ async fn test_logs() { #[tokio::test(flavor = "multi_thread")] async fn test_env_vars() { - let mut runner = runner().await; - - // test `setEnv` first, and confirm that it can correctly set environment variables, - // so that we can use it in subsequent `env*` tests - runner.test(&Filter::new("testSetEnv", ".*", ".*"), None, test_opts()).await; let env_var_key = "_foundryCheatcodeSetEnvTestKey"; let env_var_val = "_foundryCheatcodeSetEnvTestVal"; - let res = env::var(env_var_key); - assert!( - res.is_ok() && res.unwrap() == env_var_val, - "Test `testSetEnv` did not pass as expected. -Reason: `setEnv` failed to set an environment variable `{env_var_key}={env_var_val}`" - ); + env::remove_var(env_var_key); + + let filter = Filter::new("testSetEnv", ".*", ".*"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let _ = runner.test_collect(&filter); + + assert_eq!(env::var(env_var_key).unwrap(), env_var_val); } #[tokio::test(flavor = "multi_thread")] async fn test_doesnt_run_abstract_contract() { - let mut runner = runner().await; - let results = runner - .test(&Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()), None, test_opts()) - .await; - assert!(results.get("core/Abstract.t.sol:AbstractTestBase").is_none()); - assert!(results.get("core/Abstract.t.sol:AbstractTest").is_some()); + let filter = Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); + assert!(!results.contains_key("default/core/Abstract.t.sol:AbstractTestBase")); + assert!(results.contains_key("default/core/Abstract.t.sol:AbstractTest")); } #[tokio::test(flavor = "multi_thread")] async fn test_trace() { - let mut runner = tracing_runner().await; - let suite_result = runner.test(&Filter::new(".*", ".*", ".*trace"), None, test_opts()).await; + let filter = Filter::new(".*", ".*", ".*trace"); + let mut runner = TEST_DATA_DEFAULT.tracing_runner(); + let suite_result = runner.test_collect(&filter); // TODO: This trace test is very basic - it is probably a good candidate for snapshot // testing. @@ -707,12 +741,12 @@ async fn test_trace() { result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Deployment); let setup_traces = result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Setup); let execution_traces = - result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Deployment); + result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Execution); assert_eq!( deployment_traces.count(), - 1, - "Test {test_name} did not have exactly 1 deployment trace." + 13, + "Test {test_name} did not have exactly 13 deployment trace." ); assert!(setup_traces.count() <= 1, "Test {test_name} had more than 1 setup trace."); assert_eq!( @@ -723,3 +757,71 @@ async fn test_trace() { } } } + +#[tokio::test(flavor = "multi_thread")] +async fn test_assertions_revert_false() { + let filter = Filter::new(".*", ".*NoAssertionsRevertTest", ".*"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.assertions_revert = false; + }); + let results = runner.test_collect(&filter); + + assert_multiple( + &results, + BTreeMap::from([( + "default/core/LegacyAssertions.t.sol:NoAssertionsRevertTest", + vec![( + "testMultipleAssertFailures()", + false, + None, + Some(vec![ + "assertion failed: 1 != 2".to_string(), + "assertion failed: 5 >= 4".to_string(), + ]), + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_legacy_assertions() { + let filter = Filter::new(".*", ".*LegacyAssertions", ".*"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.legacy_assertions = true; + }); + let results = runner.test_collect(&filter); + + assert_multiple( + &results, + BTreeMap::from([( + "default/core/LegacyAssertions.t.sol:LegacyAssertionsTest", + vec![ + ("testFlagNotSetSuccess()", true, None, None, None), + ("testFlagSetFailure()", false, None, None, None), + ], + )]), + ); +} + +/// Test `beforeTest` functionality and `selfdestruct`. +/// See +#[tokio::test(flavor = "multi_thread")] +async fn test_before_setup_with_selfdestruct() { + let filter = Filter::new(".*", ".*BeforeTestSelfDestructTest", ".*"); + let results = TEST_DATA_PARIS.runner().test_collect(&filter); + + assert_multiple( + &results, + BTreeMap::from([( + "paris/core/BeforeTest.t.sol:BeforeTestSelfDestructTest", + vec![ + ("testKill()", true, None, None, None), + ("testA()", true, None, None, None), + ("testSimpleA()", true, None, None, None), + ("testB()", true, None, None, None), + ("testC(uint256)", true, None, None, None), + ], + )]), + ); +} diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs index 79ef6a9b0805f..d84309275e393 100644 --- a/crates/forge/tests/it/fork.rs +++ b/crates/forge/tests/it/fork.rs @@ -1,26 +1,25 @@ -//! forge tests for cheat codes +//! Forge forking tests. use crate::{ config::*, - test_helpers::{filter::Filter, RE_PATH_SEPARATOR}, + test_helpers::{RE_PATH_SEPARATOR, TEST_DATA_DEFAULT, TEST_DATA_PARIS}, }; +use alloy_chains::Chain; use forge::result::SuiteResult; +use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_test_utils::Filter; +use std::fs; /// Executes reverting fork test #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork_revert() { - let mut runner = runner().await; - let suite_result = runner - .test( - &Filter::new( - "testNonExistingContractRevert", - ".*", - &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), - ), - None, - test_opts(), - ) - .await; + let filter = Filter::new( + "testNonExistingContractRevert", + ".*", + &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), + ); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert_eq!(suite_result.len(), 1); for (_, SuiteResult { test_results, .. }) in suite_result { @@ -36,16 +35,50 @@ async fn test_cheats_fork_revert() { /// Executes all non-reverting fork cheatcodes #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork() { + let runner = TEST_DATA_PARIS.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); let filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; +} + +/// Executes eth_getLogs cheatcode +#[tokio::test(flavor = "multi_thread")] +async fn test_get_logs_fork() { + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); + let filter = Filter::new("testEthGetLogs", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + TestConfig::with_filter(runner, filter).run().await; +} + +/// Executes rpc cheatcode +#[tokio::test(flavor = "multi_thread")] +async fn test_rpc_fork() { + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); + let filter = Filter::new("testRpc", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + TestConfig::with_filter(runner, filter).run().await; } /// Tests that we can launch in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork() { - let rpc_url = foundry_utils::rpc::next_http_archive_rpc_endpoint(); - let runner = forked_runner(&rpc_url).await; + let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_url(); + let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; + let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); + TestConfig::with_filter(runner, filter).run().await; +} + +/// Smoke test that forking workings with websockets +#[tokio::test(flavor = "multi_thread")] +async fn test_launch_fork_ws() { + let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_url(); + let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; } @@ -53,13 +86,44 @@ async fn test_launch_fork() { /// Tests that we can transact transactions in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_transact_fork() { + let runner = TEST_DATA_PARIS.runner(); let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Transact")); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; } -/// Tests that we can create the same fork (provider,block) concurretnly in different tests +/// Tests that we can create the same fork (provider,block) concurrently in different tests #[tokio::test(flavor = "multi_thread")] async fn test_create_same_fork() { + let runner = TEST_DATA_DEFAULT.runner(); let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}ForkSame")); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; +} + +/// Test that `no_storage_caching` config is properly applied +#[tokio::test(flavor = "multi_thread")] +async fn test_storage_caching_config() { + let filter = + Filter::new("testStorageCaching", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.no_storage_caching = true; + }); + + // no_storage_caching set to true: storage should not be cached + TestConfig::with_filter(runner, filter.clone()).run().await; + let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); + let _ = fs::remove_file(cache_dir); + + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.no_storage_caching = false; + }); + TestConfig::with_filter(runner, filter).run().await; + + // no_storage_caching set to false: storage should be cached + let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); + assert!(cache_dir.exists()); + + // cleanup cached storage so subsequent tests does not fail + let _ = fs::remove_file(cache_dir); } diff --git a/crates/forge/tests/it/fs.rs b/crates/forge/tests/it/fs.rs index bb0c007450715..5733ec5849b99 100644 --- a/crates/forge/tests/it/fs.rs +++ b/crates/forge/tests/it/fs.rs @@ -1,25 +1,23 @@ -//! Tests for reproducing issues +//! Filesystem tests. -use crate::{ - config::*, - test_helpers::{filter::Filter, PROJECT}, -}; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; +use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_fs_disabled() { - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); - let runner = runner_with_config(config).await; + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); + }); let filter = Filter::new(".*", ".*", ".*fs/Disabled"); TestConfig::with_filter(runner, filter).run().await; } #[tokio::test(flavor = "multi_thread")] async fn test_fs_default() { - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); + }); let filter = Filter::new(".*", ".*", ".*fs/Default"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 9643bf5c15f05..2855a7f25ced1 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -1,23 +1,22 @@ -//! Tests for invariants +//! Fuzz tests. -use crate::{config::*, test_helpers::filter::Filter}; -use ethers::types::U256; -use forge::result::{SuiteResult, TestStatus}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use alloy_primitives::{Bytes, U256}; +use forge::{ + decode::decode_console_logs, + fuzz::CounterExample, + result::{SuiteResult, TestStatus}, +}; +use foundry_test_utils::{forgetest_init, str, Filter}; use std::collections::BTreeMap; #[tokio::test(flavor = "multi_thread")] async fn test_fuzz() { - let mut runner = runner().await; - - let suite_result = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") - .exclude_paths("invariant"), - None, - test_opts(), - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)|testSuccessChecker\(uint256\)|testSuccessChecker2\(int256\)|testSuccessChecker3\(uint32\)|testStorageOwner\(address\)|testImmutableOwner\(address\)") + .exclude_paths("invariant"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert!(!suite_result.is_empty()); @@ -27,46 +26,74 @@ async fn test_fuzz() { "testPositive(uint256)" | "testPositive(int256)" | "testSuccessfulFuzz(uint128,uint128)" | - "testToStringFuzz(bytes32)" => assert!( - result.status == TestStatus::Success, + "testToStringFuzz(bytes32)" => assert_eq!( + result.status, + TestStatus::Success, "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!( - result.status == TestStatus::Failure, + _ => assert_eq!( + result.status, + TestStatus::Failure, "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") ), } } } } +#[tokio::test(flavor = "multi_thread")] +async fn test_successful_fuzz_cases() { + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzPositive") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") + .exclude_paths("invariant"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); + + assert!(!suite_result.is_empty()); + + for (_, SuiteResult { test_results, .. }) in suite_result { + for (test_name, result) in test_results { + match test_name.as_str() { + "testSuccessChecker(uint256)" | + "testSuccessChecker2(int256)" | + "testSuccessChecker3(uint32)" => assert_eq!( + result.status, + TestStatus::Success, + "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", + test_name, + result.reason, + decode_console_logs(&result.logs).join("\n") + ), + _ => {} + } + } + } +} + /// Test that showcases PUSH collection on normal fuzzing. Ignored until we collect them in a /// smarter way. #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_fuzz_collection() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.depth = 100; - opts.invariant.runs = 1000; - opts.fuzz.runs = 1000; - opts.fuzz.seed = Some(U256::from(6u32)); - runner.test_options = opts.clone(); - - let results = - runner.test(&Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"), None, opts).await; + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.depth = 100; + config.invariant.runs = 1000; + config.fuzz.runs = 1000; + config.fuzz.seed = Some(U256::from(6u32)); + }); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/FuzzCollection.t.sol:SampleContractTest", + "default/fuzz/FuzzCollection.t.sol:SampleContractTest", vec![ ("invariantCounter", false, Some("broken counter.".into()), None, None), ( @@ -81,3 +108,171 @@ async fn test_fuzz_collection() { )]), ); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_persist_fuzz_failure() { + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzFailurePersist.t.sol"); + + macro_rules! run_fail { + () => { run_fail!(|config| {}) }; + (|$config:ident| $e:expr) => {{ + let mut runner = TEST_DATA_DEFAULT.runner_with(|$config| { + $config.fuzz.runs = 1000; + $e + }); + runner + .test_collect(&filter) + .get("default/fuzz/FuzzFailurePersist.t.sol:FuzzFailurePersistTest") + .unwrap() + .test_results + .get("test_persist_fuzzed_failure(uint256,int256,address,bool,string,(address,uint256),address[])") + .unwrap() + .counterexample + .clone() + }}; + } + + // record initial counterexample calldata + let initial_counterexample = run_fail!(); + let initial_calldata = match initial_counterexample { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + + // run several times and compare counterexamples calldata + for i in 0..10 { + let new_calldata = match run_fail!() { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + // calldata should be the same with the initial one + assert_eq!(initial_calldata, new_calldata, "run {i}"); + } + + // write new failure in different file + let new_calldata = match run_fail!(|config| { + config.fuzz.failure_persist_file = Some("failure1".to_string()); + }) { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + // empty file is used to load failure so new calldata is generated + assert_ne!(initial_calldata, new_calldata); +} + +forgetest_init!(test_can_scrape_bytecode, |prj, cmd| { + prj.update_config(|config| config.optimizer = Some(true)); + prj.add_source( + "FuzzerDict.sol", + r#" +// https://github.com/foundry-rs/foundry/issues/1168 +contract FuzzerDict { + // Immutables should get added to the dictionary. + address public immutable immutableOwner; + // Regular storage variables should also get added to the dictionary. + address public storageOwner; + + constructor(address _immutableOwner, address _storageOwner) { + immutableOwner = _immutableOwner; + storageOwner = _storageOwner; + } +} + "#, + ) + .unwrap(); + + prj.add_test( + "FuzzerDictTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/FuzzerDict.sol"; + +contract FuzzerDictTest is Test { + FuzzerDict fuzzerDict; + + function setUp() public { + fuzzerDict = new FuzzerDict(address(100), address(200)); + } + + /// forge-config: default.fuzz.runs = 2000 + function testImmutableOwner(address who) public { + assertTrue(who != fuzzerDict.immutableOwner()); + } + + /// forge-config: default.fuzz.runs = 2000 + function testStorageOwner(address who) public { + assertTrue(who != fuzzerDict.storageOwner()); + } +} + "#, + ) + .unwrap(); + + // Test that immutable address is used as fuzzed input, causing test to fail. + cmd.args(["test", "--fuzz-seed", "119", "--mt", "testImmutableOwner"]).assert_failure(); + // Test that storage address is used as fuzzed input, causing test to fail. + cmd.forge_fuse() + .args(["test", "--fuzz-seed", "119", "--mt", "testStorageOwner"]) + .assert_failure(); +}); + +// tests that inline max-test-rejects config is properly applied +forgetest_init!(test_inline_max_test_rejects, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InlineMaxRejectsTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 1 + function test_fuzz_bound(uint256 a) public { + vm.assume(false); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (1 allowed)] test_fuzz_bound(uint256) (runs: 0, [AVG_GAS]) +... +"#]]); +}); + +// Tests that test timeout config is properly applied. +// If test doesn't timeout after one second, then test will fail with `rejected too many inputs`. +forgetest_init!(test_fuzz_timeout, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract FuzzTimeoutTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 10000 + /// forge-config: default.fuzz.timeout = 1 + function test_fuzz_bound(uint256 a) public pure { + vm.assume(a == 0); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/Contract.t.sol:FuzzTimeoutTest +[PASS] test_fuzz_bound(uint256) (runs: [..], [AVG_GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 4f76391373a76..eab7f9ec1bb16 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -1,110 +1,70 @@ -use crate::{ - config::runner, - test_helpers::{filter::Filter, COMPILED, PROJECT}, -}; -use forge::{ - result::{SuiteResult, TestKind, TestResult}, - TestOptions, TestOptionsBuilder, -}; -use foundry_config::{FuzzConfig, InvariantConfig}; +//! Inline configuration tests. + +use crate::test_helpers::TEST_DATA_DEFAULT; +use forge::result::TestKind; +use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_fuzz() { - let opts = test_options(); - let filter = Filter::new(".*", ".*", ".*inline/FuzzInlineConf.t.sol"); - - let mut runner = runner().await; - runner.test_options = opts.clone(); - - let result = runner.test(&filter, None, opts).await; - let suite_result: &SuiteResult = - result.get("inline/FuzzInlineConf.t.sol:FuzzInlineConf").unwrap(); - let test_result: &TestResult = - suite_result.test_results.get("testInlineConfFuzz(uint8)").unwrap(); - match &test_result.kind { - TestKind::Fuzz { runs, .. } => { - assert_eq!(runs, &1024); - } - _ => { - unreachable!() - } - } + let mut runner = TEST_DATA_DEFAULT.runner(); + let result = runner.test_collect(&filter); + let results = result + .into_iter() + .flat_map(|(path, r)| { + r.test_results.into_iter().map(move |(name, t)| { + let runs = match t.kind { + TestKind::Fuzz { runs, .. } => runs, + _ => unreachable!(), + }; + (path.clone(), name, runs) + }) + }) + .collect::>(); + + assert_eq!( + results, + vec![ + ( + "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(), + "testInlineConfFuzz(uint8)".to_string(), + 1024 + ), + ( + "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf2".to_string(), + "testInlineConfFuzz1(uint8)".to_string(), + 1 + ), + ( + "default/inline/FuzzInlineConf.t.sol:FuzzInlineConf2".to_string(), + "testInlineConfFuzz2(uint8)".to_string(), + 10 + ), + ] + ); } #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_invariant() { - const ROOT: &str = "inline/InvariantInlineConf.t.sol"; + const ROOT: &str = "default/inline/InvariantInlineConf.t.sol"; - let opts = test_options(); let filter = Filter::new(".*", ".*", ".*inline/InvariantInlineConf.t.sol"); - let mut runner = runner().await; - runner.test_options = opts.clone(); - - let result = runner.test(&filter, None, opts).await; + let mut runner = TEST_DATA_DEFAULT.runner(); + let result = runner.test_collect(&filter); let suite_result_1 = result.get(&format!("{ROOT}:InvariantInlineConf")).expect("Result exists"); let suite_result_2 = result.get(&format!("{ROOT}:InvariantInlineConf2")).expect("Result exists"); let test_result_1 = suite_result_1.test_results.get("invariant_neverFalse()").unwrap(); - let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); - - match &test_result_1.kind { - TestKind::Invariant { runs, .. } => { - assert_eq!(runs, &333); - } - _ => { - unreachable!() - } + match test_result_1.kind { + TestKind::Invariant { runs, .. } => assert_eq!(runs, 333), + _ => unreachable!(), } - match &test_result_2.kind { - TestKind::Invariant { runs, .. } => { - assert_eq!(runs, &42); - } - _ => { - unreachable!() - } + let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); + match test_result_2.kind { + TestKind::Invariant { runs, .. } => assert_eq!(runs, 42), + _ => unreachable!(), } } - -#[test] -fn build_test_options() { - let root = &PROJECT.paths.root; - let profiles = vec!["default".to_string(), "ci".to_string()]; - let build_result = TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) - .profiles(profiles) - .build(root); - - assert!(build_result.is_ok()); -} - -#[test] -fn build_test_options_just_one_valid_profile() { - let root = &PROJECT.paths.root; - let valid_profiles = vec!["profile-sheldon-cooper".to_string()]; - let build_result = TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) - .profiles(valid_profiles) - .build(root); - - // We expect an error, since COMPILED contains in-line - // per-test configs for "default" and "ci" profiles - assert!(build_result.is_err()); -} - -fn test_options() -> TestOptions { - let root = &PROJECT.paths.root; - TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .compile_output(&COMPILED) - .build(root) - .expect("Config loaded") -} diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 8b50f84acaf0a..a88ed3db21c49 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -1,84 +1,170 @@ -//! Tests for invariants +//! Invariant tests. -use crate::{config::*, test_helpers::filter::Filter}; -use ethers::types::U256; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use alloy_primitives::U256; use forge::fuzz::CounterExample; +use foundry_test_utils::{forgetest_init, str, Filter}; use std::collections::BTreeMap; +macro_rules! get_counterexample { + ($runner:ident, $filter:expr) => { + $runner + .test_collect($filter) + .values() + .last() + .expect("Invariant contract should be testable.") + .test_results + .values() + .last() + .expect("Invariant contract should be testable.") + .counterexample + .as_ref() + .expect("Invariant contract should have failed with a counterexample.") + }; +} + #[tokio::test(flavor = "multi_thread")] -async fn test_invariant() { - let mut runner = runner().await; +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/InvariantTest1.t.sol:InvariantTest", + vec![ + ("invariant_neverFalse()", false, Some("revert: false".into()), None, None), + ( + "statefulFuzz_neverFalseWithInvariantAlias()", + false, + Some("revert: false".into()), + None, + None, + ), + ], + )]), + ); +} - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/(target|targetAbi|common)"), - None, - test_opts(), - ) - .await; +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_filters() { + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.runs = 10; + }); + // Contracts filter tests. assert_multiple( - &results, + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/target/(ExcludeContracts|TargetContracts).t.sol", + )), BTreeMap::from([ ( - "fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", - vec![("invariantHideJesus()", false, Some("jesus betrayed.".into()), None, None)], - ), - ( - "fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", - vec![("invariantNotStolen()", true, None, None, None)], - ), - ( - "fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", - vec![ - ("invariant_neverFalse()", false, Some("false.".into()), None, None), - ( - "statefulFuzz_neverFalseWithInvariantAlias()", - false, - Some("false.".into()), - None, - None, - ), - ], + "default/fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", + vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", + "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([ ( - "fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", + "default/fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", - vec![("invariantTrueWorld()", false, Some("false world.".into()), None, None)], + "default/fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", + vec![( + "invariantTrueWorld()", + false, + Some("revert: false world".into()), + None, + 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([ ( - "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)], ), ( - "fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", + "default/fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", vec![("invariantTrueWorld()", true, None, None, None)], ), + ]), + ); + + // Artifacts filter tests. + assert_multiple( + &runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/targetAbi/(ExcludeArtifacts|TargetArtifacts|TargetArtifactSelectors|TargetArtifactSelectors2).t.sol", + )), + BTreeMap::from([ ( - "fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", + "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", vec![("invariantShouldPass()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", + "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", vec![ ("invariantShouldPass()", true, None, None, None), - ("invariantShouldFail()", false, Some("false world.".into()), None, None), + ( + "invariantShouldFail()", + false, + Some("revert: false world".into()), + None, + None, + ), ], ), ( - "fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", vec![("invariantShouldPass()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", - vec![("invariantShouldFail()", false, Some("its false.".into()), None, None)], + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", + vec![( + "invariantShouldFail()", + false, + Some("revert: it's false".into()), + None, + None, + )], ), ]), ); @@ -86,95 +172,1004 @@ async fn test_invariant() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_override() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.call_override = true; - runner.test_options = opts.clone(); - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"), - None, - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = false; + config.invariant.call_override = true; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", + vec![("invariantNotStolen()", false, Some("revert: stolen".into()), None, None)], + )]), + ); +} +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fail_on_revert() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 10; + }); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", - vec![("invariantNotStolen()", false, Some("stolen.".into()), None, None)], + "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", + vec![( + "statefulFuzz_BrokenInvariant()", + false, + Some("revert: failed on revert".into()), + None, + None, + )], )]), ); } #[tokio::test(flavor = "multi_thread")] +#[ignore] async fn test_invariant_storage() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.depth = 100; - opts.fuzz.seed = Some(U256::from(6u32)); - runner.test_options = opts.clone(); - - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"), - None, - opts, - ) - .await; - + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.depth = 100; + if cfg!(windows) { + config.invariant.depth += 50; + } + config.fuzz.seed = Some(U256::from(6u32)); + }); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", + "default/fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", vec![ - ("invariantChangeAddress()", false, Some("changedAddr".into()), None, None), - ("invariantChangeString()", false, Some("changedStr".into()), None, None), - ("invariantChangeUint()", false, Some("changedUint".into()), None, None), - ("invariantPush()", false, Some("pushUint".into()), None, None), + ("invariantChangeAddress()", false, Some("changedAddr".to_string()), None, None), + ("invariantChangeString()", false, Some("changedString".to_string()), None, None), + ("invariantChangeUint()", false, Some("changedUint".to_string()), None, None), + ("invariantPush()", false, Some("pushUint".to_string()), None, None), ], )]), ); } #[tokio::test(flavor = "multi_thread")] -// for some reason there's different rng -#[cfg(not(windows))] +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() { - let mut runner = runner().await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); + let mut runner = + TEST_DATA_DEFAULT.runner_with(|config| config.fuzz.seed = Some(U256::from(119u32))); + + match get_counterexample!(runner, &filter) { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. + CounterExample::Sequence(_, sequence) => { + assert!(sequence.len() <= 3); + + if sequence.len() == 2 { + // call order should always be preserved + let create_fren_sequence = sequence[0].clone(); + assert_eq!( + create_fren_sequence.contract_name.unwrap(), + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Jesus" + ); + assert_eq!(create_fren_sequence.signature.unwrap(), "create_fren()"); + + let betray_sequence = sequence[1].clone(); + assert_eq!( + betray_sequence.contract_name.unwrap(), + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Judas" + ); + assert_eq!(betray_sequence.signature.unwrap(), "betray()"); + } + } + }; +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_invariant_assert_shrink() { + // ensure assert shrinks to same sequence of 2 as require + check_shrink_sequence("invariant_with_assert", 2).await; +} - let mut opts = test_opts(); - opts.fuzz.seed = Some(U256::from(102u32)); - runner.test_options = opts.clone(); +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_invariant_require_shrink() { + // ensure require shrinks to same sequence of 2 as assert + check_shrink_sequence("invariant_with_require", 2).await; +} - let results = runner - .test( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"), - None, - opts, - ) - .await; +async fn check_shrink_sequence(test_pattern: &str, expected_len: usize) { + let filter = + Filter::new(test_pattern, ".*", ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + config.invariant.runs = 1; + config.invariant.depth = 15; + }); - let results = - results.values().last().expect("`InvariantInnerContract.t.sol` should be testable."); + match get_counterexample!(runner, &filter) { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(_, sequence) => { + assert_eq!(sequence.len(), expected_len); + } + }; +} - let result = - results.test_results.values().last().expect("`InvariantInnerContract` should be testable."); +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_shrink_big_sequence() { + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkBigSequence.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.runs = 1; + config.invariant.depth = 1000; + }); - let counter = result + let initial_counterexample = runner + .test_collect(&filter) + .values() + .last() + .expect("Invariant contract should be testable.") + .test_results + .values() + .last() + .expect("Invariant contract should be testable.") .counterexample - .as_ref() - .expect("`InvariantInnerContract` should have failed with a counterexample."); + .clone() + .unwrap(); + + let initial_sequence = match initial_counterexample { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(_, sequence) => sequence, + }; + // ensure shrinks to same sequence of 77 + assert_eq!(initial_sequence.len(), 77); + + // test failure persistence + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest", + vec![( + "invariant_shrink_big_sequence()", + false, + Some("invariant_shrink_big_sequence replay failure".into()), + None, + None, + )], + )]), + ); + let new_sequence = match results + .values() + .last() + .expect("Invariant contract should be testable.") + .test_results + .values() + .last() + .expect("Invariant contract should be testable.") + .counterexample + .clone() + .unwrap() + { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(_, sequence) => sequence, + }; + // ensure shrinks to same sequence of 77 + assert_eq!(new_sequence.len(), 77); + // ensure calls within failed sequence are the same as initial one + for index in 0..77 { + let new_call = new_sequence.get(index).unwrap(); + let initial_call = initial_sequence.get(index).unwrap(); + assert_eq!(new_call.sender, initial_call.sender); + assert_eq!(new_call.addr, initial_call.addr); + assert_eq!(new_call.calldata, initial_call.calldata); + } +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_shrink_fail_on_revert() { + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + config.invariant.fail_on_revert = true; + config.invariant.runs = 1; + config.invariant.depth = 200; + }); + + match get_counterexample!(runner, &filter) { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(_, sequence) => { + // ensure shrinks to sequence of 10 + assert_eq!(sequence.len(), 10); + } + }; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_preserve_state() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantPreserveState.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", + vec![( + "invariant_preserve_state()", + false, + Some("EvmError: Revert".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_with_address_fixture() { + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/common/InvariantCalldataDictionary.t.sol", + )); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary", + vec![( + "invariant_owner_never_changes()", + false, + Some("".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_assume_does_not_revert() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + // Should not treat vm.assume as revert. + config.invariant.fail_on_revert = true; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![("invariant_dummy()", true, None, None, None)], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_assume_respects_restrictions() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + config.invariant.max_assume_rejects = 1; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![( + "invariant_dummy()", + false, + Some("`vm.assume` rejected too many inputs (1 allowed)".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_decode_custom_error() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantCustomError.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantCustomError.t.sol:InvariantCustomError", + vec![( + "invariant_decode_error()", + false, + Some("InvariantCustomError(111, \"custom\")".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fuzzed_selected_targets() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/target/FuzzedTargetContracts.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:ExplicitTargetContract", + vec![("invariant_explicit_target()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:DynamicTargetContract", + vec![( + "invariant_dynamic_targets()", + false, + Some("revert: wrong target selector called".into()), + None, + None, + )], + ), + ]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fixtures() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantFixtures.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.runs = 1; + config.invariant.depth = 100; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantFixtures.t.sol:InvariantFixtures", + vec![( + "invariant_target_not_compromised()", + false, + Some("".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_scrape_values() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantScrapeValues.t.sol"); + let results = TEST_DATA_DEFAULT.runner().test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromReturnValueTest", + vec![( + "invariant_value_not_found()", + false, + Some("revert: value from return found".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromLogValueTest", + vec![( + "invariant_value_not_found()", + false, + Some("revert: value from logs found".into()), + None, + None, + )], + ), + ]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_roll_fork_handler() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantRollFork.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fuzz.seed = Some(U256::from(119u32)); + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkBlockTest", + vec![( + "invariant_fork_handler_block()", + false, + Some("revert: too many blocks mined".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkStateTest", + vec![( + "invariant_fork_handler_state()", + false, + Some("revert: wrong supply".into()), + None, + None, + )], + ), + ]), + ); +} - match counter { +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_excluded_senders() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantExcludedSenders.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = true; + }); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest", + vec![("invariant_check_sender()", true, None, None, None)], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_after_invariant() { + // Check failure on passing invariant and failed `afterInvariant` condition + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAfterInvariant.t.sol"); + let results = TEST_DATA_DEFAULT.runner().test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest", + vec![ + ( + "invariant_after_invariant_failure()", + false, + Some("revert: afterInvariant failure".into()), + None, + None, + ), + ( + "invariant_failure()", + false, + Some("revert: invariant failure".into()), + None, + None, + ), + ("invariant_success()", true, None, None, None), + ], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_no_reverts_in_counterexample() { + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSequenceNoReverts.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.invariant.fail_on_revert = false; + // Use original counterexample to test sequence len. + config.invariant.shrink_run_limit = 0; + }); + + match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - // `fuzz_seed` at 100 makes this sequence shrinkable from 4 to 2. - CounterExample::Sequence(sequence) => { - // there some diff across platforms for some reason, either 3 or 2 - assert!(sequence.len() <= 3) + CounterExample::Sequence(_, sequence) => { + // ensure original counterexample len is 10 (even without shrinking) + assert_eq!(sequence.len(), 10); } }; } + +// Tests that a persisted failure doesn't fail due to assume revert if test driver is changed. +forgetest_init!(should_not_fail_replay_assume, |prj, cmd| { + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + config.invariant.max_assume_rejects = 10; + }); + + // Add initial test that breaks invariant. + prj.add_test( + "AssumeTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract AssumeHandler is Test { + function fuzzMe(uint256 a) public { + require(false, "Invariant failure"); + } +} + +contract AssumeTest is Test { + function setUp() public { + AssumeHandler handler = new AssumeHandler(); + } + function invariant_assume() public {} +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_assume"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: revert: Invariant failure] +... +"#]]); + + // Change test to use assume instead require. Same test should fail with too many inputs + // rejected message instead persisted failure revert. + prj.add_test( + "AssumeTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract AssumeHandler is Test { + function fuzzMe(uint256 a) public { + vm.assume(false); + } +} + +contract AssumeTest is Test { + function setUp() public { + AssumeHandler handler = new AssumeHandler(); + } + function invariant_assume() public {} +} + "#, + ) + .unwrap(); + + cmd.assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (10 allowed)] invariant_assume() (runs: 0, calls: 0, reverts: 0) +... +"#]]); +}); + +// Test too many inputs rejected for `assumePrecompile`/`assumeForgeAddress`. +// +forgetest_init!(should_revert_with_assume_code, |prj, cmd| { + prj.update_config(|config| { + config.invariant.fail_on_revert = true; + config.invariant.max_assume_rejects = 10; + config.fuzz.seed = Some(U256::from(100u32)); + }); + + // Add initial test that breaks invariant. + prj.add_test( + "AssumeTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract BalanceTestHandler is Test { + address public ref = address(1412323); + address alice; + + constructor(address _alice) { + alice = _alice; + } + + function increment(uint256 amount_, address addr) public { + assumeNotPrecompile(addr); + assumeNotForgeAddress(addr); + assertEq(alice.balance, 100_000 ether); + } +} + +contract BalanceAssumeTest is Test { + function setUp() public { + address alice = makeAddr("alice"); + vm.deal(alice, 100_000 ether); + targetSender(alice); + BalanceTestHandler handler = new BalanceTestHandler(alice); + targetContract(address(handler)); + } + + function invariant_balance() public {} +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_balance"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: `vm.assume` rejected too many inputs (10 allowed)] invariant_balance() (runs: 1, calls: 500, reverts: 0) +... +"#]]); +}); + +// Test proper message displayed if `targetSelector`/`excludeSelector` called with empty selectors. +// +forgetest_init!(should_not_panic_if_no_selectors, |prj, cmd| { + prj.add_test( + "NoSelectorTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract TestHandler is Test {} + +contract NoSelectorTest is Test { + bytes4[] selectors; + + function setUp() public { + TestHandler handler = new TestHandler(); + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + excludeSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + } + + function invariant_panic() public {} +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_panic"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: failed to set up invariant testing environment: No contracts to fuzz.] invariant_panic() (runs: 0, calls: 0, reverts: 0) +... +"#]]); +}); + +// +forgetest_init!(should_show_invariant_metrics, |prj, cmd| { + prj.add_test( + "SelectorMetricsTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract CounterTest is Test { + function setUp() public { + CounterHandler handler = new CounterHandler(); + AnotherCounterHandler handler1 = new AnotherCounterHandler(); + // targetContract(address(handler1)); + } + + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.show-metrics = true + function invariant_counter() public {} + + /// forge-config: default.invariant.runs = 10 + /// forge-config: default.invariant.show-metrics = true + function invariant_counter2() public {} +} + +contract CounterHandler is Test { + function doSomething(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } + + function doAnotherThing(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } +} + +contract AnotherCounterHandler is Test { + function doWork(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } + + function doWorkThing(uint256 a) public { + vm.assume(a < 10_000_000); + require(a < 100_000); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_"]).assert_success().stdout_eq(str![[r#" +... +[PASS] invariant_counter() (runs: 10, calls: 5000, reverts: [..]) + +╭-----------------------+----------------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=====================================================================+ +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doSomething | [..] | [..] | [..] | +╰-----------------------+----------------+-------+---------+----------╯ + +[PASS] invariant_counter2() (runs: 10, calls: 5000, reverts: [..]) + +╭-----------------------+----------------+-------+---------+----------╮ +| Contract | Selector | Calls | Reverts | Discards | ++=====================================================================+ +| AnotherCounterHandler | doWork | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| AnotherCounterHandler | doWorkThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doAnotherThing | [..] | [..] | [..] | +|-----------------------+----------------+-------+---------+----------| +| CounterHandler | doSomething | [..] | [..] | [..] | +╰-----------------------+----------------+-------+---------+----------╯ + +Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests) + +"#]]); +}); + +// Tests that invariant exists with success after configured timeout. +forgetest_init!(should_apply_configured_timeout, |prj, cmd| { + // Add initial test that breaks invariant. + prj.add_test( + "TimeoutTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract TimeoutHandler is Test { + uint256 public count; + + function increment() public { + count++; + } +} + +contract TimeoutTest is Test { + TimeoutHandler handler; + + function setUp() public { + handler = new TimeoutHandler(); + } + + /// forge-config: default.invariant.runs = 10000 + /// forge-config: default.invariant.depth = 20000 + /// forge-config: default.invariant.timeout = 1 + function invariant_counter_timeout() public view { + // Invariant will fail if more than 10000 increments. + // Make sure test timeouts after one second and remaining runs are canceled. + require(handler.count() < 10000); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_counter_timeout"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/TimeoutTest.t.sol:TimeoutTest +[PASS] invariant_counter_timeout() (runs: 0, calls: 0, reverts: 0) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +// Tests that selector hits are uniformly distributed +// +forgetest_init!(invariant_selectors_weight, |prj, cmd| { + prj.update_config(|config| { + config.invariant.runs = 1; + config.invariant.depth = 10; + }); + prj.add_source( + "InvariantHandlers.sol", + r#" +contract HandlerOne { + uint256 public hit1; + + function selector1() external { + hit1 += 1; + } +} + +contract HandlerTwo { + uint256 public hit2; + uint256 public hit3; + uint256 public hit4; + uint256 public hit5; + + function selector2() external { + hit2 += 1; + } + + function selector3() external { + hit3 += 1; + } + + function selector4() external { + hit4 += 1; + } + + function selector5() external { + hit5 += 1; + } +} + "#, + ) + .unwrap(); + + prj.add_test( + "InvariantSelectorsWeightTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/InvariantHandlers.sol"; + +contract InvariantSelectorsWeightTest is Test { + HandlerOne handlerOne; + HandlerTwo handlerTwo; + + function setUp() public { + handlerOne = new HandlerOne(); + handlerTwo = new HandlerTwo(); + } + + function afterInvariant() public { + assertEq(handlerOne.hit1(), 2); + assertEq(handlerTwo.hit2(), 2); + assertEq(handlerTwo.hit3(), 3); + assertEq(handlerTwo.hit4(), 1); + assertEq(handlerTwo.hit5(), 2); + } + + function invariant_selectors_weight() public view {} +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--fuzz-seed", "119", "--mt", "invariant_selectors_weight"]).assert_success(); +}); + +// Tests original and new counterexample lengths are displayed on failure. +// Tests switch from regular sequence output to solidity. +forgetest_init!(invariant_sequence_len, |prj, cmd| { + prj.update_config(|config| { + config.fuzz.seed = Some(U256::from(100u32)); + }); + + prj.add_test( + "InvariantSequenceLenTest.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; +import "src/Counter.sol"; + +contract InvariantSequenceLenTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + targetContract(address(counter)); + } + + function invariant_increment() public { + require(counter.number() / 2 < 100000000000000000000000000000000, "invariant increment failure"); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL: revert: invariant increment failure] + [Sequence] (original: 4, shrunk: 1) +... +"#]]); + + // Check regular sequence output. Shrink disabled to show several lines. + cmd.forge_fuse().arg("clean").assert_success(); + prj.update_config(|config| { + config.invariant.shrink_run_limit = 0; + }); + cmd.forge_fuse().args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq( + str![[r#" +... +Failing tests: +Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest +[FAIL: revert: invariant increment failure] + [Sequence] (original: 4, shrunk: 4) + sender=0x00000000000000000000000000000000000018dE addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[1931387396117645594923 [1.931e21]] + sender=0x00000000000000000000000000000000000009d5 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x0000000000000000000000000000000000000105 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x00000000000000000000000000000000000009B2 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[996881781832960761274744263729582347 [9.968e35]] + invariant_increment() (runs: 0, calls: 0, reverts: 0) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]], + ); + + // Check solidity sequence output on same failure. + cmd.forge_fuse().arg("clean").assert_success(); + prj.update_config(|config| { + config.invariant.show_solidity = true; + }); + cmd.forge_fuse().args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq( + str![[r#" +... +Failing tests: +Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest +[FAIL: revert: invariant increment failure] + [Sequence] (original: 4, shrunk: 4) + vm.prank(0x00000000000000000000000000000000000018dE); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(1931387396117645594923); + vm.prank(0x00000000000000000000000000000000000009d5); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.prank(0x0000000000000000000000000000000000000105); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).increment(); + vm.prank(0x00000000000000000000000000000000000009B2); + Counter(0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f).setNumber(996881781832960761274744263729582347); + invariant_increment() (runs: 0, calls: 0, reverts: 0) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]], + ); + + // Persisted failures should be able to switch output. + prj.update_config(|config| { + config.invariant.show_solidity = false; + }); + cmd.forge_fuse().args(["test", "--mt", "invariant_increment"]).assert_failure().stdout_eq( + str![[r#" +... +Failing tests: +Encountered 1 failing test in test/InvariantSequenceLenTest.t.sol:InvariantSequenceLenTest +[FAIL: invariant_increment replay failure] + [Sequence] (original: 4, shrunk: 4) + sender=0x00000000000000000000000000000000000018dE addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[1931387396117645594923 [1.931e21]] + sender=0x00000000000000000000000000000000000009d5 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x0000000000000000000000000000000000000105 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=increment() args=[] + sender=0x00000000000000000000000000000000000009B2 addr=[src/Counter.sol:Counter]0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f calldata=setNumber(uint256) args=[996881781832960761274744263729582347 [9.968e35]] + invariant_increment() (runs: 1, calls: 1, reverts: 1) + +Encountered a total of 1 failing tests, 0 tests succeeded + +"#]], + ); +}); diff --git a/crates/forge/tests/it/main.rs b/crates/forge/tests/it/main.rs index f8197c99d42a5..aaa129796a39a 100644 --- a/crates/forge/tests/it/main.rs +++ b/crates/forge/tests/it/main.rs @@ -1,5 +1,7 @@ -mod cheats; pub mod config; +pub mod test_helpers; + +mod cheats; mod core; mod fork; mod fs; @@ -8,4 +10,4 @@ mod inline; mod invariant; mod repros; mod spec; -pub mod test_helpers; +mod vyper; diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 54cd9e4f84a64..c2ebb32518d65 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -1,291 +1,400 @@ -//! Tests for reproducing issues - -use crate::{ - config::*, - test_helpers::{filter::Filter, PROJECT}, +//! Regression tests for previous issues. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; +use alloy_json_abi::Event; +use alloy_primitives::{address, b256, Address, U256}; +use forge::{ + decode::decode_console_logs, + result::{TestKind, TestStatus}, }; -use ethers::abi::{Address, Event, EventParam, Log, LogParam, ParamType, RawLog, Token}; use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; -use std::str::FromStr; +use foundry_evm::{ + constants::HARDHAT_CONSOLE_ADDRESS, + traces::{CallKind, CallTraceDecoder, DecodedCallData, TraceKind}, +}; +use foundry_test_utils::Filter; +use std::sync::Arc; -/// A macro that tests a single pattern (".*/repros/") +/// Creates a test that runs `testdata/repros/Issue{issue}.t.sol`. macro_rules! test_repro { - ($issue:expr) => { - test_repro!($issue, false, None) + ($(#[$attr:meta])* $issue_number:literal $(,)?) => { + test_repro!($(#[$attr])* $issue_number, false, None); }; - ($issue:expr, $should_fail:expr, $sender:expr) => { - let pattern = concat!(".*repros/", $issue); - let filter = Filter::path(pattern); - - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - if let Some(sender) = $sender { - config.sender = sender; + ($(#[$attr:meta])* $issue_number:literal, $should_fail:expr $(,)?) => { + test_repro!($(#[$attr])* $issue_number, $should_fail, None); + }; + ($(#[$attr:meta])* $issue_number:literal, $should_fail:expr, $sender:expr $(,)?) => { + paste::paste! { + #[tokio::test(flavor = "multi_thread")] + $(#[$attr])* + async fn [< issue_ $issue_number >]() { + repro_config($issue_number, $should_fail, $sender.into()).await.run().await; + } } - - let mut config = TestConfig::with_filter(runner_with_config(config).await, filter) - .set_should_fail($should_fail); - config.run().await; }; -} - -macro_rules! test_repro_fail { - ($issue:expr) => { - test_repro!($issue, true, None) + ($(#[$attr:meta])* $issue_number:literal, $should_fail:expr, $sender:expr, |$res:ident| $e:expr $(,)?) => { + paste::paste! { + #[tokio::test(flavor = "multi_thread")] + $(#[$attr])* + async fn [< issue_ $issue_number >]() { + let mut $res = repro_config($issue_number, $should_fail, $sender.into()).await.test(); + $e + } + } }; -} - -macro_rules! test_repro_with_sender { - ($issue:expr, $sender:expr) => { - test_repro!($issue, false, Some($sender)) + ($(#[$attr:meta])* $issue_number:literal; |$config:ident| $e:expr $(,)?) => { + paste::paste! { + #[tokio::test(flavor = "multi_thread")] + $(#[$attr])* + async fn [< issue_ $issue_number >]() { + let mut $config = repro_config($issue_number, false, None).await; + $e + $config.run().await; + } + } }; } -macro_rules! run_test_repro { - ($issue:expr) => { - run_test_repro!($issue, false, None) - }; - ($issue:expr, $should_fail:expr, $sender:expr) => {{ - let pattern = concat!(".*repros/", $issue); - let filter = Filter::path(pattern); +async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { + foundry_test_utils::init_tracing(); + let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); - let mut config = Config::default(); - if let Some(sender) = $sender { + let runner = TEST_DATA_DEFAULT.runner_with(|config| { + config.fs_permissions = FsPermissions::new(vec![ + PathPermission::read("./fixtures"), + PathPermission::read("out"), + ]); + if let Some(sender) = sender { config.sender = sender; } - - let mut config = TestConfig::with_filter(runner_with_config(config).await, filter) - .set_should_fail($should_fail); - config.test().await - }}; -} - -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_2623() { - test_repro!("Issue2623"); -} - -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_2629() { - test_repro!("Issue2629"); + }); + TestConfig::with_filter(runner, filter).set_should_fail(should_fail) } -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_2723() { - test_repro!("Issue2723"); -} +// https://github.com/foundry-rs/foundry/issues/2623 +test_repro!(2623); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_2898() { - test_repro!("Issue2898"); -} +// https://github.com/foundry-rs/foundry/issues/2629 +test_repro!(2629); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_2956() { - test_repro!("Issue2956"); -} +// https://github.com/foundry-rs/foundry/issues/2723 +test_repro!(2723); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_2984() { - test_repro!("Issue2984"); -} +// https://github.com/foundry-rs/foundry/issues/2898 +test_repro!(2898); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_4640() { - test_repro!("Issue4640"); -} +// https://github.com/foundry-rs/foundry/issues/2956 +test_repro!(2956); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3077() { - test_repro!("Issue3077"); -} +// https://github.com/foundry-rs/foundry/issues/2984 +test_repro!(2984); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3055() { - test_repro_fail!("Issue3055"); -} +// https://github.com/foundry-rs/foundry/issues/3055 +test_repro!(3055, true); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3192() { - test_repro!("Issue3192"); -} +// https://github.com/foundry-rs/foundry/issues/3077 +test_repro!(3077); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3110() { - test_repro!("Issue3110"); -} +// https://github.com/foundry-rs/foundry/issues/3110 +test_repro!(3110); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3189() { - test_repro_fail!("Issue3189"); -} +// https://github.com/foundry-rs/foundry/issues/3119 +test_repro!(3119); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3119() { - test_repro!("Issue3119"); -} +// https://github.com/foundry-rs/foundry/issues/3189 +test_repro!(3189, true); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3190() { - test_repro!("Issue3190"); -} +// https://github.com/foundry-rs/foundry/issues/3190 +test_repro!(3190); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3221() { - test_repro!("Issue3221"); -} +// https://github.com/foundry-rs/foundry/issues/3192 +test_repro!(3192); -// -// 1.0 related -// #[tokio::test(flavor = "multi_thread")] -// async fn test_issue_3437() { -// test_repro!("Issue3437"); -// } - -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3708() { - test_repro!("Issue3708"); -} +// https://github.com/foundry-rs/foundry/issues/3220 +test_repro!(3220); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3223() { - test_repro_with_sender!( - "Issue3223", - Address::from_str("0xF0959944122fb1ed4CfaBA645eA06EED30427BAA").unwrap() - ); -} +// https://github.com/foundry-rs/foundry/issues/3221 +test_repro!(3221); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3220() { - test_repro!("Issue3220"); -} +// https://github.com/foundry-rs/foundry/issues/3223 +test_repro!(3223, false, address!("F0959944122fb1ed4CfaBA645eA06EED30427BAA")); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3347() { - let mut res = run_test_repro!("Issue3347"); - let mut res = res.remove("repros/Issue3347.sol:Issue3347Test").unwrap(); +// https://github.com/foundry-rs/foundry/issues/3347 +test_repro!(3347, false, None, |res| { + let mut res = res.remove("default/repros/Issue3347.t.sol:Issue3347Test").unwrap(); let test = res.test_results.remove("test()").unwrap(); assert_eq!(test.logs.len(), 1); - let event = Event { - name: "log2".to_string(), - inputs: vec![ - EventParam { name: "x".to_string(), kind: ParamType::Uint(256), indexed: false }, - EventParam { name: "y".to_string(), kind: ParamType::Uint(256), indexed: false }, - ], - anonymous: false, - }; - let raw_log = - RawLog { topics: test.logs[0].topics.clone(), data: test.logs[0].data.clone().to_vec() }; - let log = event.parse_log(raw_log).unwrap(); + let event = Event::parse("event log2(uint256, uint256)").unwrap(); + let decoded = event.decode_log(&test.logs[0].data, false).unwrap(); assert_eq!( - log, - Log { - params: vec![ - LogParam { name: "x".to_string(), value: Token::Uint(1u64.into()) }, - LogParam { name: "y".to_string(), value: Token::Uint(2u64.into()) } + decoded, + DecodedEvent { + selector: Some(b256!( + "78b9a1f3b55d6797ab2c4537e83ee04ff0c65a1ca1bb39d79a62e0a78d5a8a57" + )), + indexed: vec![], + body: vec![ + DynSolValue::Uint(U256::from(1), 256), + DynSolValue::Uint(U256::from(2), 256) ] } ); -} +}); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3685() { - test_repro!("Issue3685"); -} +// https://github.com/foundry-rs/foundry/issues/3437 +// 1.0 related +// test_repro!(3437); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3653() { - test_repro!("Issue3653"); -} +// https://github.com/foundry-rs/foundry/issues/3596 +test_repro!(3596, true, None); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3596() { - test_repro!("Issue3596", true, None); -} +// https://github.com/foundry-rs/foundry/issues/3653 +test_repro!(3653); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3661() { - test_repro!("Issue3661"); -} +// https://github.com/foundry-rs/foundry/issues/3661 +test_repro!(3661); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3674() { - test_repro_with_sender!( - "Issue3674", - Address::from_str("0xF0959944122fb1ed4CfaBA645eA06EED30427BAA").unwrap() - ); -} +// https://github.com/foundry-rs/foundry/issues/3674 +test_repro!(3674, false, address!("F0959944122fb1ed4CfaBA645eA06EED30427BAA")); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3703() { - test_repro!("Issue3703"); -} +// https://github.com/foundry-rs/foundry/issues/3685 +test_repro!(3685); + +// https://github.com/foundry-rs/foundry/issues/3703 +test_repro!( + #[ignore = "flaky polygon RPCs"] + 3703 +); + +// https://github.com/foundry-rs/foundry/issues/3708 +test_repro!(3708); -// +// https://github.com/foundry-rs/foundry/issues/3723 // 1.0 related -// #[tokio::test(flavor = "multi_thread")] -// async fn test_issue_3723() { -// test_repro!("Issue3723"); -// } - -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_3753() { - test_repro!("Issue3753"); -} +// test_repro!(3723); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_4630() { - test_repro!("Issue4630"); -} +// https://github.com/foundry-rs/foundry/issues/3753 +test_repro!(3753); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_4586() { - test_repro!("Issue4586"); -} +// https://github.com/foundry-rs/foundry/issues/3792 +test_repro!(3792); + +// https://github.com/foundry-rs/foundry/issues/4232 +test_repro!(4232); + +// https://github.com/foundry-rs/foundry/issues/4402 +test_repro!(4402); + +// https://github.com/foundry-rs/foundry/issues/4586 +test_repro!(4586); + +// https://github.com/foundry-rs/foundry/issues/4630 +test_repro!(4630); + +// https://github.com/foundry-rs/foundry/issues/4640 +test_repro!(4640); // https://github.com/foundry-rs/foundry/issues/4832 // 1.0 related -// #[tokio::test(flavor = "multi_thread")] -// async fn test_issue_4832() { -// test_repro!("Issue4832"); -// } - -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue_5038() { - test_repro!("Issue5038"); -} +// test_repro!(4832); -// -#[tokio::test(flavor = "multi_thread")] -async fn test_issue3792() { - test_repro!("Issue3792"); -} +// https://github.com/foundry-rs/foundry/issues/5038 +test_repro!(5038); + +// https://github.com/foundry-rs/foundry/issues/5808 +test_repro!(5808); + +// +test_repro!(5929); + +// +test_repro!(5935); + +// +test_repro!(5948); + +// https://github.com/foundry-rs/foundry/issues/6006 +test_repro!(6006); + +// https://github.com/foundry-rs/foundry/issues/6032 +test_repro!(6032); + +// https://github.com/foundry-rs/foundry/issues/6070 +test_repro!(6070); + +// https://github.com/foundry-rs/foundry/issues/6115 +test_repro!(6115); + +// https://github.com/foundry-rs/foundry/issues/6170 +test_repro!(6170, false, None, |res| { + let mut res = res.remove("default/repros/Issue6170.t.sol:Issue6170Test").unwrap(); + let test = res.test_results.remove("test()").unwrap(); + assert_eq!(test.status, TestStatus::Failure); + assert_eq!(test.reason, Some("log != expected log".to_string())); +}); + +// +test_repro!(6293); + +// https://github.com/foundry-rs/foundry/issues/6180 +test_repro!(6180); + +// https://github.com/foundry-rs/foundry/issues/6355 +test_repro!(6355, false, None, |res| { + let mut res = res.remove("default/repros/Issue6355.t.sol:Issue6355Test").unwrap(); + let test = res.test_results.remove("test_shouldFail()").unwrap(); + assert_eq!(test.status, TestStatus::Failure); + + let test = res.test_results.remove("test_shouldFailWithRevertToState()").unwrap(); + assert_eq!(test.status, TestStatus::Failure); +}); + +// https://github.com/foundry-rs/foundry/issues/6437 +test_repro!(6437); + +// Test we decode Hardhat console logs AND traces correctly. +// https://github.com/foundry-rs/foundry/issues/6501 +test_repro!(6501, false, None, |res| { + let mut res = res.remove("default/repros/Issue6501.t.sol:Issue6501Test").unwrap(); + let test = res.test_results.remove("test_hhLogs()").unwrap(); + assert_eq!(test.status, TestStatus::Success); + assert_eq!( + decode_console_logs(&test.logs), + ["a".to_string(), "1".to_string(), "b 2".to_string()] + ); + + let (kind, traces) = test.traces.last().unwrap().clone(); + let nodes = traces.arena.into_nodes(); + assert_eq!(kind, TraceKind::Execution); + + let test_call = nodes.first().unwrap(); + assert_eq!(test_call.idx, 0); + assert_eq!(test_call.children, [1, 2, 3]); + assert_eq!(test_call.trace.depth, 0); + assert!(test_call.trace.success); + + let expected = [ + ("log(string)", vec!["\"a\""]), + ("log(uint256)", vec!["1"]), + ("log(string,uint256)", vec!["\"b\"", "2"]), + ]; + for (node, expected) in nodes[1..=3].iter().zip(expected) { + let trace = &node.trace; + let decoded = CallTraceDecoder::new().decode_function(trace).await; + assert_eq!(trace.kind, CallKind::StaticCall); + assert_eq!(trace.address, HARDHAT_CONSOLE_ADDRESS); + assert_eq!(decoded.label, Some("console".into())); + assert_eq!(trace.depth, 1); + assert!(trace.success); + assert_eq!( + decoded.call_data, + Some(DecodedCallData { + signature: expected.0.into(), + args: expected.1.into_iter().map(ToOwned::to_owned).collect(), + }) + ); + } +}); + +// https://github.com/foundry-rs/foundry/issues/6538 +test_repro!(6538); + +// https://github.com/foundry-rs/foundry/issues/6554 +test_repro!(6554; |config| { + let path = config.runner.config.root.join("out/default/Issue6554.t.sol"); + + let mut prj_config = Config::clone(&config.runner.config); + prj_config.fs_permissions.add(PathPermission::read_write(path)); + config.runner.config = Arc::new(prj_config); + +}); + +// https://github.com/foundry-rs/foundry/issues/6759 +test_repro!(6759); + +// https://github.com/foundry-rs/foundry/issues/6966 +test_repro!(6966); + +// https://github.com/foundry-rs/foundry/issues/6616 +test_repro!(6616); + +// https://github.com/foundry-rs/foundry/issues/5529 +test_repro!(5529; |config| { + let mut prj_config = Config::clone(&config.runner.config); + prj_config.always_use_create_2_factory = true; + config.runner.evm_opts.always_use_create_2_factory = true; + config.runner.config = Arc::new(prj_config); +}); + +// https://github.com/foundry-rs/foundry/issues/6634 +test_repro!(6634; |config| { + let mut prj_config = Config::clone(&config.runner.config); + prj_config.always_use_create_2_factory = true; + config.runner.evm_opts.always_use_create_2_factory = true; + config.runner.config = Arc::new(prj_config); +}); + +// https://github.com/foundry-rs/foundry/issues/7457 +test_repro!(7457); + +// https://github.com/foundry-rs/foundry/issues/7481 +test_repro!(7481); + +// https://github.com/foundry-rs/foundry/issues/5739 +test_repro!(5739); + +// https://github.com/foundry-rs/foundry/issues/8004 +test_repro!(8004); + +// https://github.com/foundry-rs/foundry/issues/2851 +test_repro!(2851, false, None, |res| { + let mut res = res.remove("default/repros/Issue2851.t.sol:Issue2851Test").unwrap(); + let test = res.test_results.remove("invariantNotZero()").unwrap(); + assert_eq!(test.status, TestStatus::Failure); +}); + +// https://github.com/foundry-rs/foundry/issues/8006 +test_repro!(8006); + +// https://github.com/foundry-rs/foundry/issues/8277 +test_repro!(8277); + +// https://github.com/foundry-rs/foundry/issues/8287 +test_repro!(8287); + +// https://github.com/foundry-rs/foundry/issues/8168 +test_repro!(8168); + +// https://github.com/foundry-rs/foundry/issues/8383 +test_repro!(8383, false, None, |res| { + let mut res = res.remove("default/repros/Issue8383.t.sol:Issue8383Test").unwrap(); + let test = res.test_results.remove("testP256VerifyOutOfBounds()").unwrap(); + assert_eq!(test.status, TestStatus::Success); + match test.kind { + TestKind::Unit { gas } => assert_eq!(gas, 3101), + _ => panic!("not a unit test kind"), + } +}); + +// https://github.com/foundry-rs/foundry/issues/6643 +test_repro!(6643); + +// https://github.com/foundry-rs/foundry/issues/8971 +test_repro!(8971; |config| { + let mut prj_config = Config::clone(&config.runner.config); + prj_config.isolate = true; + config.runner.config = Arc::new(prj_config); +}); + +// https://github.com/foundry-rs/foundry/issues/8639 +test_repro!(8639); + +// https://github.com/foundry-rs/foundry/issues/8566 +test_repro!(8566); + +// https://github.com/foundry-rs/foundry/issues/9643 +test_repro!(9643); + +// https://github.com/foundry-rs/foundry/issues/7238 +test_repro!(7238); diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/it/spec.rs index 8afe3b57a69df..52e581c33c921 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/it/spec.rs @@ -1,8 +1,11 @@ -use crate::{config::*, test_helpers::filter::Filter}; +//! Integration tests for EVM specifications. + +use crate::{config::*, test_helpers::TEST_DATA_PARIS}; use foundry_evm::revm::primitives::SpecId; +use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_shanghai_compat() { let filter = Filter::new("", "ShanghaiCompat", ".*spec"); - TestConfig::filter(filter).await.evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(TEST_DATA_PARIS.runner(), filter).spec_id(SpecId::SHANGHAI).run().await; } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index b28a3cca0c958..0712ea73bd976 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -1,180 +1,360 @@ -#![allow(unused)] +//! Test helpers for Forge integration tests. -use super::*; -use ethers::{ - prelude::{artifacts::Settings, Lazy, ProjectCompileOutput, SolcConfig}, - solc::{artifacts::Libraries, Project, ProjectPathsConfig}, - types::{Address, U256}, +use alloy_chains::NamedChain; +use alloy_primitives::U256; +use forge::{revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder}; +use foundry_compilers::{ + artifacts::{EvmVersion, Libraries, Settings}, + compilers::multi::MultiCompiler, + utils::RuntimeOrHandle, + Project, ProjectCompileOutput, SolcConfig, Vyper, }; -use foundry_config::Config; -use foundry_evm::{ - executor::{ - backend::Backend, - opts::{Env, EvmOpts}, - DatabaseRef, Executor, ExecutorBuilder, - }, - fuzz::FuzzedExecutor, - CALLER, +use foundry_config::{ + fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, + InvariantConfig, RpcEndpointUrl, RpcEndpoints, +}; +use foundry_evm::{constants::CALLER, opts::EvmOpts}; +use foundry_test_utils::{fd_lock, init_tracing, rpc::next_rpc_endpoint}; +use std::{ + env, fmt, + io::Write, + path::{Path, PathBuf}, + sync::{Arc, LazyLock}, }; -use std::{path::PathBuf, str::FromStr}; - -pub static PROJECT: Lazy = Lazy::new(|| { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata"); - let paths = ProjectPathsConfig::builder().root(root.clone()).sources(root).build().unwrap(); - Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap() -}); - -pub static LIBS_PROJECT: Lazy = Lazy::new(|| { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata"); - let paths = ProjectPathsConfig::builder().root(root.clone()).sources(root).build().unwrap(); - let libs = - ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - - let settings = Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; - - let solc_config = SolcConfig::builder().settings(settings).build(); - Project::builder() - .paths(paths) - .ephemeral() - .no_artifacts() - .solc_config(solc_config) - .build() - .unwrap() -}); - -pub static COMPILED: Lazy = Lazy::new(|| { - let out = (*PROJECT).compile().unwrap(); - if out.has_compiler_errors() { - eprintln!("{out}"); - panic!("Compiled with errors"); - } - out -}); -pub static COMPILED_WITH_LIBS: Lazy = Lazy::new(|| { - let out = (*LIBS_PROJECT).compile().unwrap(); - if out.has_compiler_errors() { - eprintln!("{out}"); - panic!("Compiled with errors"); +pub const RE_PATH_SEPARATOR: &str = "/"; +const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); +static VYPER: LazyLock = LazyLock::new(|| std::env::temp_dir().join("vyper")); + +/// Profile for the tests group. Used to configure separate configurations for test runs. +pub enum ForgeTestProfile { + Default, + Paris, + MultiVersion, +} + +impl fmt::Display for ForgeTestProfile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => write!(f, "default"), + Self::Paris => write!(f, "paris"), + Self::MultiVersion => write!(f, "multi-version"), + } } - out -}); - -pub static EVM_OPTS: Lazy = Lazy::new(|| EvmOpts { - env: Env { - gas_limit: 18446744073709551615, - chain_id: None, - tx_origin: Config::DEFAULT_SENDER, - block_number: 1, - block_timestamp: 1, - ..Default::default() - }, - sender: Config::DEFAULT_SENDER, - initial_balance: U256::MAX, - ffi: true, - memory_limit: 2u64.pow(24), - ..Default::default() -}); - -pub fn fuzz_executor(executor: &Executor) -> FuzzedExecutor { - let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; - - FuzzedExecutor::new( - executor, - proptest::test_runner::TestRunner::new(cfg), - CALLER, - config::test_opts().fuzz, - ) } -pub const RE_PATH_SEPARATOR: &str = "/"; +impl ForgeTestProfile { + /// Returns true if the profile is Paris. + pub fn is_paris(&self) -> bool { + matches!(self, Self::Paris) + } -pub mod filter { - use super::*; - use foundry_common::TestFilter; - use regex::Regex; - - pub struct Filter { - test_regex: Regex, - contract_regex: Regex, - path_regex: Regex, - exclude_tests: Option, - exclude_paths: Option, - } - - impl Filter { - pub fn new(test_pattern: &str, contract_pattern: &str, path_pattern: &str) -> Self { - Filter { - test_regex: Regex::new(test_pattern) - .unwrap_or_else(|_| panic!("Failed to parse test pattern: `{test_pattern}`")), - contract_regex: Regex::new(contract_pattern).unwrap_or_else(|_| { - panic!("Failed to parse contract pattern: `{contract_pattern}`") - }), - path_regex: Regex::new(path_pattern) - .unwrap_or_else(|_| panic!("Failed to parse path pattern: `{path_pattern}`")), - exclude_tests: None, - exclude_paths: None, - } - } + pub fn root(&self) -> PathBuf { + PathBuf::from(TESTDATA) + } - pub fn contract(contract_pattern: &str) -> Self { - Self::new(".*", contract_pattern, ".*") - } + /// Configures the solc settings for the test profile. + pub fn solc_config(&self) -> SolcConfig { + let libs = + ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - pub fn path(path_pattern: &str) -> Self { - Self::new(".*", ".*", path_pattern) - } + let mut settings = + Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; - /// All tests to also exclude - /// - /// This is a workaround since regex does not support negative look aheads - pub fn exclude_tests(mut self, pattern: &str) -> Self { - self.exclude_tests = Some(Regex::new(pattern).unwrap()); - self + if matches!(self, Self::Paris) { + settings.evm_version = Some(EvmVersion::Paris); } - /// All paths to also exclude - /// - /// This is a workaround since regex does not support negative look aheads - pub fn exclude_paths(mut self, pattern: &str) -> Self { - self.exclude_paths = Some(Regex::new(pattern).unwrap()); - self - } + let settings = SolcConfig::builder().settings(settings).build(); + SolcConfig { settings } + } + + /// Build [Config] for test profile. + /// + /// Project source files are read from testdata/{profile_name} + /// Project output files are written to testdata/out/{profile_name} + /// Cache is written to testdata/cache/{profile_name} + /// + /// AST output is enabled by default to support inline configs. + pub fn config(&self) -> Config { + let mut config = Config::with_root(self.root()); + + config.ast = true; + config.src = self.root().join(self.to_string()); + config.out = self.root().join("out").join(self.to_string()); + config.cache_path = self.root().join("cache").join(self.to_string()); + config.libraries = vec![ + "fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(), + ]; - pub fn matches_all() -> Self { - Filter { - test_regex: Regex::new(".*").unwrap(), - contract_regex: Regex::new(".*").unwrap(), - path_regex: Regex::new(".*").unwrap(), - exclude_tests: None, - exclude_paths: None, - } + config.prompt_timeout = 0; + + config.optimizer = Some(true); + config.optimizer_runs = Some(200); + + config.gas_limit = u64::MAX.into(); + config.chain = None; + config.tx_origin = CALLER; + config.block_number = 1; + config.block_timestamp = 1; + + config.sender = CALLER; + config.initial_balance = U256::MAX; + config.ffi = true; + config.verbosity = 3; + config.memory_limit = 1 << 26; + + if self.is_paris() { + config.evm_version = EvmVersion::Paris; } + + config.fuzz = FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig { + include_storage: true, + include_push_bytes: true, + dictionary_weight: 40, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + gas_report_samples: 256, + failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), + failure_persist_file: Some("testfailure".to_string()), + show_logs: false, + timeout: None, + }; + config.invariant = InvariantConfig { + runs: 256, + depth: 15, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { + dictionary_weight: 80, + include_storage: true, + include_push_bytes: true, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: Some( + tempfile::Builder::new() + .prefix(&format!("foundry-{self}")) + .tempdir() + .unwrap() + .into_path(), + ), + show_metrics: false, + timeout: None, + show_solidity: false, + }; + + config.sanitized() } +} - impl TestFilter for Filter { - fn matches_test(&self, test_name: impl AsRef) -> bool { - let test_name = test_name.as_ref(); - if let Some(ref exclude) = self.exclude_tests { - if exclude.is_match(test_name) { - return false - } - } - self.test_regex.is_match(test_name) - } +/// Container for test data for a specific test profile. +pub struct ForgeTestData { + pub project: Project, + pub output: ProjectCompileOutput, + pub config: Arc, + pub profile: ForgeTestProfile, +} + +impl ForgeTestData { + /// Builds [ForgeTestData] for the given [ForgeTestProfile]. + /// + /// Uses [get_compiled] to lazily compile the project. + pub fn new(profile: ForgeTestProfile) -> Self { + init_tracing(); + let config = Arc::new(profile.config()); + let mut project = config.project().unwrap(); + let output = get_compiled(&mut project); + Self { project, output, config, profile } + } - fn matches_contract(&self, contract_name: impl AsRef) -> bool { - self.contract_regex.is_match(contract_name.as_ref()) + /// Builds a base runner + pub fn base_runner(&self) -> MultiContractRunnerBuilder { + init_tracing(); + let config = self.config.clone(); + let mut runner = MultiContractRunnerBuilder::new(config).sender(self.config.sender); + if self.profile.is_paris() { + runner = runner.evm_spec(SpecId::MERGE); } + runner + } + + /// Builds a non-tracing runner + pub fn runner(&self) -> MultiContractRunner { + self.runner_with(|_| {}) + } + + /// Builds a non-tracing runner + pub fn runner_with(&self, modify: impl FnOnce(&mut Config)) -> MultiContractRunner { + let mut config = (*self.config).clone(); + modify(&mut config); + self.runner_with_config(config) + } + + fn runner_with_config(&self, mut config: Config) -> MultiContractRunner { + config.rpc_endpoints = rpc_endpoints(); + config.allow_paths.push(manifest_root().to_path_buf()); - fn matches_path(&self, path: impl AsRef) -> bool { - let path = path.as_ref(); - if let Some(ref exclude) = self.exclude_paths { - if exclude.is_match(path) { - return false - } - } - self.path_regex.is_match(path) + if config.fs_permissions.is_empty() { + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); } + + let opts = config_evm_opts(&config); + + let mut builder = self.base_runner(); + let config = Arc::new(config); + let root = self.project.root(); + builder.config = config.clone(); + builder + .enable_isolation(opts.isolate) + .sender(config.sender) + .build::(root, &self.output, opts.local_evm_env(), opts) + .unwrap() + } + + /// Builds a tracing runner + pub fn tracing_runner(&self) -> MultiContractRunner { + let mut opts = config_evm_opts(&self.config); + opts.verbosity = 5; + self.base_runner() + .build::(self.project.root(), &self.output, opts.local_evm_env(), opts) + .unwrap() + } + + /// Builds a runner that runs against forked state + pub async fn forked_runner(&self, rpc: &str) -> MultiContractRunner { + let mut opts = config_evm_opts(&self.config); + + opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC + opts.fork_url = Some(rpc.to_string()); + + let env = opts.evm_env().await.expect("Could not instantiate fork environment"); + let fork = opts.get_fork(&Default::default(), env.clone()); + + self.base_runner() + .with_fork(fork) + .build::(self.project.root(), &self.output, env, opts) + .unwrap() + } +} + +/// 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. + // This is similar to [`foundry_test_utils::util::initialize`], see its comments for more + // details. + let mut lock = fd_lock::new_lock(&lock_file_path); + let read = lock.read().unwrap(); + let out; + + let mut write = None; + if !project.cache_path().exists() || std::fs::read(&lock_file_path).unwrap() != b"1" { + drop(read); + write = Some(lock.write().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 +} + +/// Default data for the tests group. +pub static TEST_DATA_DEFAULT: LazyLock = + LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::Default)); + +/// Data for tests requiring Paris support on Solc and EVM level. +pub static TEST_DATA_PARIS: LazyLock = + LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::Paris)); + +/// Data for tests requiring Cancun support on Solc and EVM level. +pub static TEST_DATA_MULTI_VERSION: LazyLock = + LazyLock::new(|| ForgeTestData::new(ForgeTestProfile::MultiVersion)); + +pub fn manifest_root() -> &'static Path { + let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); + // need to check here where we're executing the test from, if in `forge` we need to also allow + // `testdata` + if root.ends_with("forge") { + root = root.parent().unwrap(); + } + root +} + +/// the RPC endpoints used during tests +pub fn rpc_endpoints() -> RpcEndpoints { + RpcEndpoints::new([ + ("mainnet", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Mainnet))), + ("mainnet2", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Mainnet))), + ("sepolia", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Sepolia))), + ("optimism", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Optimism))), + ("arbitrum", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Arbitrum))), + ("polygon", RpcEndpointUrl::Url(next_rpc_endpoint(NamedChain::Polygon))), + ("avaxTestnet", RpcEndpointUrl::Url("https://api.avax-test.network/ext/bc/C/rpc".into())), + ("moonbeam", RpcEndpointUrl::Url("https://moonbeam-rpc.publicnode.com".into())), + ("rpcEnvAlias", RpcEndpointUrl::Env("${RPC_ENV_ALIAS}".into())), + ]) +} + +fn config_evm_opts(config: &Config) -> EvmOpts { + config.to_figment(foundry_config::FigmentProviders::None).extract().unwrap() } diff --git a/crates/forge/tests/it/vyper.rs b/crates/forge/tests/it/vyper.rs new file mode 100644 index 0000000000000..c40b87541bfb9 --- /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/Cargo.toml b/crates/linking/Cargo.toml new file mode 100644 index 0000000000000..15d0d113b74d8 --- /dev/null +++ b/crates/linking/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "foundry-linking" +description = "Smart contract linking tools" + +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-compilers = { workspace = true, features = ["full"] } +semver.workspace = true +alloy-primitives = { workspace = true, features = ["rlp"] } +thiserror.workspace = true diff --git a/crates/linking/src/lib.rs b/crates/linking/src/lib.rs new file mode 100644 index 0000000000000..e44ee77489f2f --- /dev/null +++ b/crates/linking/src/lib.rs @@ -0,0 +1,688 @@ +//! # foundry-linking +//! +//! EVM bytecode linker. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_primitives::{Address, Bytes, B256}; +use foundry_compilers::{ + artifacts::{CompactContractBytecodeCow, Libraries}, + contracts::ArtifactContracts, + Artifact, ArtifactId, +}; +use semver::Version; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// Errors that can occur during linking. +#[derive(Debug, thiserror::Error)] +pub enum LinkerError { + #[error("wasn't able to find artifact for library {name} at {file}")] + MissingLibraryArtifact { file: String, name: String }, + #[error("target artifact is not present in provided artifacts set")] + MissingTargetArtifact, + #[error(transparent)] + InvalidAddress(
::Err), + #[error("cyclic dependency found, can't link libraries via CREATE2")] + CyclicDependency, +} + +pub struct Linker<'a> { + /// Root of the project, used to determine whether artifact/library path can be stripped. + pub root: PathBuf, + /// Compilation artifacts. + pub contracts: ArtifactContracts>, +} + +/// Output of the `link_with_nonce_or_address` +pub struct LinkOutput { + /// Resolved library addresses. Contains both user-provided and newly deployed libraries. + /// It will always contain library paths with stripped path prefixes. + pub libraries: Libraries, + /// Vector of libraries that need to be deployed from sender address. + /// The order in which they appear in the vector is the order in which they should be deployed. + pub libs_to_deploy: Vec, +} + +impl<'a> Linker<'a> { + pub fn new( + root: impl Into, + contracts: ArtifactContracts>, + ) -> Self { + Linker { root: root.into(), contracts } + } + + /// Helper method to convert [ArtifactId] to the format in which libraries are stored in + /// [Libraries] object. + /// + /// Strips project root path from source file path. + fn convert_artifact_id_to_lib_path(&self, id: &ArtifactId) -> (PathBuf, String) { + let path = id.source.strip_prefix(self.root.as_path()).unwrap_or(&id.source); + // name is either {LibName} or {LibName}.{version} + let name = id.name.split('.').next().unwrap(); + + (path.to_path_buf(), name.to_owned()) + } + + /// Finds an [ArtifactId] object in the given [ArtifactContracts] keys which corresponds to the + /// library path in the form of "./path/to/Lib.sol:Lib" + /// + /// Optionally accepts solc version, and if present, only compares artifacts with given version. + fn find_artifact_id_by_library_path( + &'a self, + file: &str, + name: &str, + version: Option<&Version>, + ) -> Option<&'a ArtifactId> { + for id in self.contracts.keys() { + if let Some(version) = version { + if id.version != *version { + continue; + } + } + let (artifact_path, artifact_name) = self.convert_artifact_id_to_lib_path(id); + + if artifact_name == *name && artifact_path == Path::new(file) { + return Some(id); + } + } + + None + } + + /// Performs DFS on the graph of link references, and populates `deps` with all found libraries. + fn collect_dependencies( + &'a self, + target: &'a ArtifactId, + deps: &mut BTreeSet<&'a ArtifactId>, + ) -> Result<(), LinkerError> { + let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; + + let mut references = BTreeMap::new(); + if let Some(bytecode) = &contract.bytecode { + references.extend(bytecode.link_references.clone()); + } + if let Some(deployed_bytecode) = &contract.deployed_bytecode { + if let Some(bytecode) = &deployed_bytecode.bytecode { + references.extend(bytecode.link_references.clone()); + } + } + + for (file, libs) in &references { + for contract in libs.keys() { + let id = self + .find_artifact_id_by_library_path(file, contract, Some(&target.version)) + .ok_or_else(|| LinkerError::MissingLibraryArtifact { + file: file.to_string(), + name: contract.to_string(), + })?; + if deps.insert(id) { + self.collect_dependencies(id, deps)?; + } + } + } + + Ok(()) + } + + /// Links given artifact with either given library addresses or address computed from sender and + /// nonce. + /// + /// Each key in `libraries` should either be a global path or relative to project root. All + /// remappings should be resolved. + /// + /// When calling for `target` being an external library itself, you should check that `target` + /// does not appear in `libs_to_deploy` to avoid deploying it twice. It may happen in cases + /// when there is a dependency cycle including `target`. + pub fn link_with_nonce_or_address( + &'a self, + libraries: Libraries, + sender: Address, + mut nonce: u64, + targets: impl IntoIterator, + ) -> Result { + // Library paths in `link_references` keys are always stripped, so we have to strip + // user-provided paths to be able to match them correctly. + let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path()); + + let mut needed_libraries = BTreeSet::new(); + for target in targets { + self.collect_dependencies(target, &mut needed_libraries)?; + } + + let mut libs_to_deploy = Vec::new(); + + // If `libraries` does not contain needed dependency, compute its address and add to + // `libs_to_deploy`. + for id in needed_libraries { + let (lib_path, lib_name) = self.convert_artifact_id_to_lib_path(id); + + libraries.libs.entry(lib_path).or_default().entry(lib_name).or_insert_with(|| { + let address = sender.create(nonce); + libs_to_deploy.push((id, address)); + nonce += 1; + + address.to_checksum(None) + }); + } + + // Link and collect bytecodes for `libs_to_deploy`. + let libs_to_deploy = libs_to_deploy + .into_iter() + .map(|(id, _)| { + Ok(self.link(id, &libraries)?.get_bytecode_bytes().unwrap().into_owned()) + }) + .collect::, LinkerError>>()?; + + Ok(LinkOutput { libraries, libs_to_deploy }) + } + + pub fn link_with_create2( + &'a self, + libraries: Libraries, + sender: Address, + salt: B256, + target: &'a ArtifactId, + ) -> Result { + // Library paths in `link_references` keys are always stripped, so we have to strip + // user-provided paths to be able to match them correctly. + let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path()); + + let mut needed_libraries = BTreeSet::new(); + self.collect_dependencies(target, &mut needed_libraries)?; + + let mut needed_libraries = needed_libraries + .into_iter() + .filter(|id| { + // Filter out already provided libraries. + let (file, name) = self.convert_artifact_id_to_lib_path(id); + !libraries.libs.contains_key(&file) || !libraries.libs[&file].contains_key(&name) + }) + .map(|id| { + // Link library with provided libs and extract bytecode object (possibly unlinked). + let bytecode = self.link(id, &libraries).unwrap().bytecode.unwrap(); + (id, bytecode) + }) + .collect::>(); + + let mut libs_to_deploy = Vec::new(); + + // Iteratively compute addresses and link libraries until we have no unlinked libraries + // left. + while !needed_libraries.is_empty() { + // Find any library which is fully linked. + let deployable = needed_libraries + .iter() + .enumerate() + .find(|(_, (_, bytecode))| !bytecode.object.is_unlinked()); + + // If we haven't found any deployable library, it means we have a cyclic dependency. + let Some((index, &(id, _))) = deployable else { + return Err(LinkerError::CyclicDependency); + }; + let (_, bytecode) = needed_libraries.swap_remove(index); + let code = bytecode.bytes().unwrap(); + let address = sender.create2_from_code(salt, code); + libs_to_deploy.push(code.clone()); + + 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, address); + } + + libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None)); + } + + Ok(LinkOutput { libraries, libs_to_deploy }) + } + + /// Links given artifact with given libraries. + pub fn link( + &self, + target: &ArtifactId, + libraries: &Libraries, + ) -> Result, LinkerError> { + let mut contract = + self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone(); + for (file, libs) in &libraries.libs { + 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); + } + 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); + } + } + } + Ok(contract) + } + + pub fn get_linked_artifacts( + &self, + libraries: &Libraries, + ) -> Result { + self.contracts.keys().map(|id| Ok((id.clone(), self.link(id, libraries)?))).collect() + } + + pub fn get_linked_artifacts_cow( + &self, + libraries: &Libraries, + ) -> Result>, LinkerError> { + self.contracts.keys().map(|id| Ok((id.clone(), self.link(id, libraries)?))).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{fixed_bytes, map::HashMap}; + use foundry_compilers::{ + multi::MultiCompiler, + solc::{Solc, SolcCompiler}, + Project, ProjectCompileOutput, ProjectPathsConfig, + }; + + struct LinkerTest { + project: Project, + output: ProjectCompileOutput, + dependency_assertions: HashMap>, + } + + impl LinkerTest { + fn new(path: impl Into, strip_prefixes: bool) -> Self { + let path = path.into(); + let paths = ProjectPathsConfig::builder() + .root("../../testdata") + .lib("../../testdata/lib") + .sources(path.clone()) + .tests(path) + .build() + .unwrap(); + + let solc = Solc::find_or_install(&Version::new(0, 8, 18)).unwrap(); + let project = Project::builder() + .paths(paths) + .ephemeral() + .no_artifacts() + .build(MultiCompiler { solc: Some(SolcCompiler::Specific(solc)), vyper: None }) + .unwrap(); + + let mut output = project.compile().unwrap(); + + if strip_prefixes { + output = output.with_stripped_file_prefixes(project.root()); + } + + Self { project, output, dependency_assertions: HashMap::default() } + } + + fn assert_dependencies( + mut self, + artifact_id: String, + deps: Vec<(String, Address)>, + ) -> Self { + self.dependency_assertions.insert(artifact_id, deps); + self + } + + fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) { + let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect()); + for (id, identifier) in self.iter_linking_targets(&linker) { + let output = linker + .link_with_nonce_or_address(Default::default(), sender, initial_nonce, [id]) + .expect("Linking failed"); + self.validate_assertions(identifier, output); + } + } + + fn test_with_create2(self, sender: Address, salt: B256) { + let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect()); + for (id, identifier) in self.iter_linking_targets(&linker) { + let output = linker + .link_with_create2(Default::default(), sender, salt, id) + .expect("Linking failed"); + self.validate_assertions(identifier, output); + } + } + + fn iter_linking_targets<'a>( + &'a self, + linker: &'a Linker<'_>, + ) -> impl IntoIterator + 'a { + linker.contracts.keys().filter_map(move |id| { + // If we didn't strip paths, artifacts will have absolute paths. + // That's expected and we want to ensure that only `libraries` object has relative + // paths, artifacts should be kept as is. + let source = id + .source + .strip_prefix(self.project.root()) + .unwrap_or(&id.source) + .to_string_lossy(); + let identifier = format!("{source}:{}", id.name); + + // Skip ds-test as it always has no dependencies etc. (and the path is outside root + // so is not sanitized) + if identifier.contains("DSTest") { + return None; + } + + Some((id, identifier)) + }) + } + + fn validate_assertions(&self, identifier: String, output: LinkOutput) { + let LinkOutput { libs_to_deploy, libraries } = output; + + let assertions = self + .dependency_assertions + .get(&identifier) + .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); + + assert_eq!( + libs_to_deploy.len(), + assertions.len(), + "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}", + libs_to_deploy.len(), + assertions.len(), + libs_to_deploy + ); + + for (dep_identifier, address) in assertions { + let (file, name) = dep_identifier.split_once(':').unwrap(); + if let Some(lib_address) = + libraries.libs.get(Path::new(file)).and_then(|libs| libs.get(name)) + { + assert_eq!( + *lib_address, + address.to_string(), + "incorrect library address for dependency {dep_identifier} of {identifier}" + ); + } else { + panic!("Library {dep_identifier} not found"); + } + } + } + } + + fn link_test(path: impl Into, test_fn: impl Fn(LinkerTest)) { + let path = path.into(); + test_fn(LinkerTest::new(path.clone(), true)); + test_fn(LinkerTest::new(path, false)); + } + + #[test] + fn link_simple() { + link_test("../../testdata/default/linking/simple", |linker| { + linker + .assert_dependencies("default/linking/simple/Simple.t.sol:Lib".to_string(), vec![]) + .assert_dependencies( + "default/linking/simple/Simple.t.sol:LibraryConsumer".to_string(), + vec![( + "default/linking/simple/Simple.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), + vec![( + "default/linking/simple/Simple.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_nested() { + link_test("../../testdata/default/linking/nested", |linker| { + linker + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + vec![( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + vec![ + // Lib shows up here twice, because the linker sees it twice, but it should + // have the same address and nonce. + ( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + vec![ + ( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_duplicate() { + link_test("../../testdata/default/linking/duplicate", |linker| { + linker + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + vec![( + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + vec![( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), + )], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + vec![ + ( + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), + vec![ + ( + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest" + .to_string(), + vec![ + ( + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), + Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), + Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") + .unwrap(), + ), + ( + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), + Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_cycle() { + link_test("../../testdata/default/linking/cycle", |linker| { + linker + .assert_dependencies( + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + vec![ + ( + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") + .unwrap(), + ), + ( + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + vec![ + ( + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), + Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") + .unwrap(), + ), + ( + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), + Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") + .unwrap(), + ), + ], + ) + .test_with_sender_and_nonce(Address::default(), 1); + }); + } + + #[test] + fn link_create2_nested() { + link_test("../../testdata/default/linking/nested", |linker| { + linker + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + vec![( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74").unwrap(), + )], + ) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + vec![ + // Lib shows up here twice, because the linker sees it twice, but it should + // have the same address and nonce. + ( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") + .unwrap(), + ), + ( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + vec![ + ( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0xddb1Cd2497000DAeA687CEa3dc34Af44084BEa74") + .unwrap(), + ), + ( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0xfebE2F30641170642f317Ff6F644Cee60E7Ac369") + .unwrap(), + ), + ], + ) + .test_with_create2( + Address::default(), + fixed_bytes!( + "19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9" + ), + ); + }); + } +} diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index 41ac67dc495ea..98dc669f137e8 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -9,9 +9,17 @@ license.workspace = true homepage.workspace = true repository.workspace = true -[dependencies] -foundry-macros-impl.path = "impl" +[lints] +workspace = true + +[lib] +proc-macro = true +# proc-macro tests aren't fully supported by cargo-nextest archives +test = false +doc = false -ethers-core.workspace = true -serde = "1.0" -serde_json = "1.0" +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true +proc-macro-error = "1" diff --git a/crates/macros/impl/Cargo.toml b/crates/macros/impl/Cargo.toml deleted file mode 100644 index aea855be63a48..0000000000000 --- a/crates/macros/impl/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "foundry-macros-impl" -publish = false - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = "2.0" diff --git a/crates/macros/impl/src/console_fmt.rs b/crates/macros/impl/src/console_fmt.rs deleted file mode 100644 index 9817fdef57bf8..0000000000000 --- a/crates/macros/impl/src/console_fmt.rs +++ /dev/null @@ -1,116 +0,0 @@ -use proc_macro2::{Delimiter, Group, Ident, TokenStream}; -use quote::{format_ident, quote}; -use syn::{ - punctuated::Punctuated, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Path, Token, - Type, -}; - -pub fn console_fmt(input: DeriveInput) -> TokenStream { - let krate = crate::krate(); - let name = input.ident; - let tokens = match input.data { - Data::Struct(s) => derive_struct(s, &krate), - Data::Enum(e) => derive_enum(e, &krate), - Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), - }; - quote! { - impl #krate::ConsoleFmt for #name { - #tokens - } - } -} - -fn derive_struct(s: DataStruct, krate: &Path) -> TokenStream { - let imp = impl_struct(s, krate).unwrap_or_else(|| quote!(String::new())); - quote! { - fn fmt(&self, _spec: #krate::FormatSpec) -> String { - #imp - } - } -} - -fn impl_struct(s: DataStruct, krate: &Path) -> Option { - let fields: Punctuated = match s.fields { - Fields::Named(fields) => fields.named.into_iter(), - Fields::Unnamed(fields) => fields.unnamed.into_iter(), - Fields::Unit => return None, - } - .collect(); - - let n = fields.len(); - if n == 0 { - return None - } - - let first_ty = match &fields.first().unwrap().ty { - Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), - _ => String::new(), - }; - - let args: Punctuated = fields - .into_iter() - .enumerate() - .map(|(i, field)| { - let ident = field.ident.unwrap_or_else(|| format_ident!("{i}")); - quote!(&self.#ident) - }) - .collect(); - - let imp = if first_ty == "String" { - // console_format(arg1, [...rest]) - let mut args = args.pairs(); - let first = args.next().unwrap(); - let first = first.value(); - let n = n - 1; - quote! { - let args: [&dyn #krate::ConsoleFmt; #n] = [#(#args)*]; - #krate::console_format((#first).as_str(), args) - } - } else { - // console_format("", [...args]) - quote! { - let args: [&dyn #krate::ConsoleFmt; #n] = [#args]; - #krate::console_format("", args) - } - }; - - Some(imp) -} - -/// Delegates to variants. -fn derive_enum(e: DataEnum, krate: &Path) -> TokenStream { - let arms = e.variants.into_iter().map(|variant| { - let name = variant.ident; - let (fields, delimiter) = match variant.fields { - Fields::Named(fields) => (fields.named.into_iter(), Delimiter::Brace), - Fields::Unnamed(fields) => (fields.unnamed.into_iter(), Delimiter::Parenthesis), - Fields::Unit => return quote!(), - }; - - let fields: Punctuated = fields - .enumerate() - .map(|(i, field)| field.ident.unwrap_or_else(|| format_ident!("__var_{i}"))) - .collect(); - - if fields.len() != 1 { - unimplemented!("Enum variant with more than 1 field") - } - - let field = fields.into_iter().next().unwrap(); - let fields = Group::new(delimiter, quote!(#field)); - quote! { - Self::#name #fields => #krate::ConsoleFmt::fmt(#field, spec), - } - }); - - quote! { - fn fmt(&self, spec: #krate::FormatSpec) -> String { - match self { - #(#arms)* - - #[allow(unreachable_code)] - _ => String::new(), - } - } - } -} diff --git a/crates/macros/impl/src/lib.rs b/crates/macros/impl/src/lib.rs deleted file mode 100644 index c56e51060bc4c..0000000000000 --- a/crates/macros/impl/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![warn(unused_crate_dependencies)] - -mod console_fmt; -mod utils; - -pub(crate) use utils::*; - -use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput}; - -#[proc_macro_derive(ConsoleFmt)] -pub fn console_fmt(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - console_fmt::console_fmt(input).into() -} diff --git a/crates/macros/impl/src/utils.rs b/crates/macros/impl/src/utils.rs deleted file mode 100644 index cfcd35a09804a..0000000000000 --- a/crates/macros/impl/src/utils.rs +++ /dev/null @@ -1,12 +0,0 @@ -#[inline] -pub fn krate() -> syn::Path { - syn::parse_str(crate_str()).unwrap() -} - -#[inline] -pub fn crate_str() -> &'static str { - match std::env::var("CARGO_MANIFEST_DIR") { - Ok(var) if var.ends_with("macros") => "crate", - _ => "::foundry_macros", - } -} diff --git a/crates/macros/src/cheatcodes.rs b/crates/macros/src/cheatcodes.rs new file mode 100644 index 0000000000000..4d0f260c294b8 --- /dev/null +++ b/crates/macros/src/cheatcodes.rs @@ -0,0 +1,422 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::quote; +use syn::{Attribute, Data, DataStruct, DeriveInput, Error, Result}; + +pub fn derive_cheatcode(input: &DeriveInput) -> Result { + let name = &input.ident; + let name_s = name.to_string(); + match &input.data { + Data::Struct(s) if name_s.ends_with("Call") => derive_call(name, s, &input.attrs), + Data::Struct(_) if name_s.ends_with("Return") => Ok(TokenStream::new()), + Data::Struct(s) => derive_struct(name, s, &input.attrs), + Data::Enum(e) if name_s.ends_with("Calls") => derive_calls_enum(name, e), + Data::Enum(e) if name_s.ends_with("Errors") => derive_errors_events_enum(name, e, false), + Data::Enum(e) if name_s.ends_with("Events") => derive_errors_events_enum(name, e, true), + Data::Enum(e) => derive_enum(name, e, &input.attrs), + Data::Union(_) => Err(Error::new(name.span(), "unions are not supported")), + } +} + +/// Implements `CheatcodeDef` for a function call struct. +fn derive_call(name: &Ident, data: &DataStruct, attrs: &[Attribute]) -> Result { + let mut group = None::; + let mut status = None::; + let mut safety = None::; + for attr in attrs.iter().filter(|a| a.path().is_ident("cheatcode")) { + attr.meta.require_list()?.parse_nested_meta(|meta| { + let path = meta.path.get_ident().ok_or_else(|| meta.error("expected ident"))?; + let path_s = path.to_string(); + match path_s.as_str() { + "group" if group.is_none() => group = Some(meta.value()?.parse()?), + "status" if status.is_none() => status = Some(meta.value()?.parse()?), + "safety" if safety.is_none() => safety = Some(meta.value()?.parse()?), + _ => return Err(meta.error("unexpected attribute")), + }; + Ok(()) + })?; + } + let group = group.ok_or_else(|| { + syn::Error::new(name.span(), "missing #[cheatcode(group = ...)] attribute") + })?; + let status = status.unwrap_or_else(|| quote!(Stable)); + let safety = if let Some(safety) = safety { + quote!(Safety::#safety) + } else { + quote! { + match Group::#group.safety() { + Some(s) => s, + None => panic_unknown_safety(), + } + } + }; + + check_named_fields(data, name); + + let id = name.to_string(); + let id = id.strip_suffix("Call").expect("function struct ends in Call"); + + let doc = get_docstring(attrs); + let (signature, selector, declaration, description) = func_docstring(&doc); + + let mut params = declaration; + if let Some(ret) = params.find(" returns ") { + params = ¶ms[..ret]; + } + if params.contains(" memory ") { + emit_warning!( + name.span(), + "parameter data locations must be `calldata` instead of `memory`" + ); + } + + let (visibility, mutability) = parse_function_attrs(declaration, name.span())?; + let visibility = Ident::new(visibility, Span::call_site()); + let mutability = Ident::new(mutability, Span::call_site()); + + if description.is_empty() { + emit_warning!(name.span(), "missing documentation for a cheatcode") + } + let description = description.replace("\n ", "\n"); + + Ok(quote! { + impl CheatcodeDef for #name { + const CHEATCODE: &'static Cheatcode<'static> = &Cheatcode { + func: Function { + id: #id, + description: #description, + declaration: #declaration, + visibility: Visibility::#visibility, + mutability: Mutability::#mutability, + signature: #signature, + selector: #selector, + selector_bytes: ::SELECTOR, + }, + group: Group::#group, + status: Status::#status, + safety: #safety, + }; + } + }) +} + +/// Generates the `CHEATCODES` constant and implements `CheatcodeImpl` dispatch for an enum. +fn derive_calls_enum(name: &Ident, input: &syn::DataEnum) -> Result { + if input.variants.iter().any(|v| v.fields.len() != 1) { + return Err(syn::Error::new(name.span(), "expected all variants to have a single field")) + } + + // keep original order for matching + let variant_names = input.variants.iter().map(|v| &v.ident); + + let mut variants = input.variants.iter().collect::>(); + variants.sort_by(|a, b| a.ident.cmp(&b.ident)); + let variant_tys = variants.iter().map(|v| { + assert_eq!(v.fields.len(), 1); + &v.fields.iter().next().unwrap().ty + }); + Ok(quote! { + /// All the cheatcodes in [this contract](self). + pub const CHEATCODES: &'static [&'static Cheatcode<'static>] = &[#(<#variant_tys as CheatcodeDef>::CHEATCODE,)*]; + + /// Internal macro to implement the `Cheatcode` trait for the Vm calls enum. + #[doc(hidden)] + #[macro_export] + macro_rules! vm_calls { + ($mac:ident) => { + $mac!(#(#variant_names),*) + }; + } + }) +} + +fn derive_errors_events_enum( + name: &Ident, + input: &syn::DataEnum, + events: bool, +) -> Result { + if input.variants.iter().any(|v| v.fields.len() != 1) { + return Err(syn::Error::new(name.span(), "expected all variants to have a single field")) + } + + let (ident, ty_assoc_name, ty, doc) = if events { + ("VM_EVENTS", "EVENT", "Event", "events") + } else { + ("VM_ERRORS", "ERROR", "Error", "custom errors") + }; + let ident = Ident::new(ident, Span::call_site()); + let ty_assoc_name = Ident::new(ty_assoc_name, Span::call_site()); + let ty = Ident::new(ty, Span::call_site()); + let doc = format!("All the {doc} in [this contract](self)."); + + let mut variants = input.variants.iter().collect::>(); + variants.sort_by(|a, b| a.ident.cmp(&b.ident)); + let variant_tys = variants.iter().map(|v| { + assert_eq!(v.fields.len(), 1); + &v.fields.iter().next().unwrap().ty + }); + Ok(quote! { + #[doc = #doc] + pub const #ident: &'static [&'static #ty<'static>] = &[#(#variant_tys::#ty_assoc_name,)*]; + }) +} + +fn derive_struct( + name: &Ident, + input: &syn::DataStruct, + attrs: &[Attribute], +) -> Result { + let name_s = name.to_string(); + + let doc = get_docstring(attrs); + let doc = doc.trim(); + let kind = match () { + () if doc.contains("Custom error ") => StructKind::Error, + () if doc.contains("Event ") => StructKind::Event, + _ => StructKind::Struct, + }; + + let (doc, def) = doc.split_once("```solidity\n").expect("bad docstring"); + let mut doc = doc.trim_end(); + let def_end = def.rfind("```").expect("bad docstring"); + let def = def[..def_end].trim(); + + match kind { + StructKind::Error => doc = &doc[..doc.find("Custom error ").expect("bad doc")], + StructKind::Event => doc = &doc[..doc.find("Event ").expect("bad doc")], + StructKind::Struct => {} + } + let doc = doc.trim(); + + if doc.is_empty() { + let n = match kind { + StructKind::Error => "n", + StructKind::Event => "n", + StructKind::Struct => "", + }; + emit_warning!(name.span(), "missing documentation for a{n} {}", kind.as_str()); + } + + if kind == StructKind::Struct { + check_named_fields(input, name); + } + + let def = match kind { + StructKind::Struct => { + let fields = input.fields.iter().map(|f| { + let name = f.ident.as_ref().expect("field has no name").to_string(); + + let to_find = format!("{name};"); + let ty_end = def.find(&to_find).expect("field not found in def"); + let ty = &def[..ty_end]; + let ty_start = ty.rfind(';').or_else(|| ty.find('{')).expect("bad struct def") + 1; + let ty = ty[ty_start..].trim(); + if ty.is_empty() { + panic!("bad struct def: {def:?}") + } + + let doc = get_docstring(&f.attrs); + let doc = doc.trim(); + quote! { + StructField { + name: #name, + ty: #ty, + description: #doc, + } + } + }); + quote! { + /// The struct definition. + pub const STRUCT: &'static Struct<'static> = &Struct { + name: #name_s, + description: #doc, + fields: Cow::Borrowed(&[#(#fields),*]), + }; + } + } + StructKind::Error => { + quote! { + /// The custom error definition. + pub const ERROR: &'static Error<'static> = &Error { + name: #name_s, + description: #doc, + declaration: #def, + }; + } + } + StructKind::Event => { + quote! { + /// The event definition. + pub const EVENT: &'static Event<'static> = &Event { + name: #name_s, + description: #doc, + declaration: #def, + }; + } + } + }; + Ok(quote! { + impl #name { + #def + } + }) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum StructKind { + Struct, + Error, + Event, +} + +impl StructKind { + fn as_str(self) -> &'static str { + match self { + Self::Struct => "struct", + Self::Error => "error", + Self::Event => "event", + } + } +} + +fn derive_enum(name: &Ident, input: &syn::DataEnum, attrs: &[Attribute]) -> Result { + let name_s = name.to_string(); + let doc = get_docstring(attrs); + let doc_end = doc.find("```solidity").expect("bad docstring"); + let doc = doc[..doc_end].trim(); + if doc.is_empty() { + emit_warning!(name.span(), "missing documentation for an enum"); + } + let variants = input.variants.iter().filter(|v| v.discriminant.is_none()).map(|v| { + let name = v.ident.to_string(); + let doc = get_docstring(&v.attrs); + let doc = doc.trim(); + if doc.is_empty() { + emit_warning!(v.ident.span(), "missing documentation for a variant"); + } + quote! { + EnumVariant { + name: #name, + description: #doc, + } + } + }); + Ok(quote! { + impl #name { + /// The enum definition. + pub const ENUM: &'static Enum<'static> = &Enum { + name: #name_s, + description: #doc, + variants: Cow::Borrowed(&[#(#variants),*]), + }; + } + }) +} + +fn check_named_fields(data: &DataStruct, ident: &Ident) { + for field in data.fields.iter() { + if field.ident.is_none() { + emit_warning!(ident, "all params must be named"); + } + } +} + +/// Flattens all the `#[doc = "..."]` attributes into a single string. +fn get_docstring(attrs: &[Attribute]) -> String { + let mut doc = String::new(); + for attr in attrs { + if !attr.path().is_ident("doc") { + continue + } + let syn::Meta::NameValue(syn::MetaNameValue { + value: syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(s), .. }), + .. + }) = &attr.meta + else { + continue + }; + + let value = s.value(); + if !value.is_empty() { + if !doc.is_empty() { + doc.push('\n'); + } + doc.push_str(&value); + } + } + doc +} + +/// Returns `(signature, hex_selector, declaration, description)` from a given `sol!`-generated +/// docstring for a function. +/// +/// # Examples +/// +/// The following docstring (string literals are joined with newlines): +/// ```text +/// "Function with signature `foo(uint256)` and selector `0x1234abcd`." +/// "```solidity" +/// "function foo(uint256 x) external view returns (bool y);" +/// "```" +/// "Description of the function." +/// ``` +/// +/// Will return: +/// ```text +/// ( +/// "foo(uint256)", +/// "0x1234abcd", +/// "function foo(uint256 x) external view returns (bool y);", +/// "Description of the function." +/// ) +/// ``` +fn func_docstring(doc: &str) -> (&str, &str, &str, &str) { + let expected_start = "Function with signature `"; + let start = doc.find(expected_start).expect("no auto docstring"); + let (descr_before, auto) = doc.split_at(start); + + let mut lines = auto.lines(); + let mut next = || lines.next().expect("unexpected end of docstring"); + + let sig_line = next(); + let example_start = next(); + assert_eq!(example_start, "```solidity"); + let declaration = next(); + let example_end = next(); + assert_eq!(example_end, "```"); + + let n = expected_start.len(); + let mut sig_end = n; + sig_end += sig_line[n..].find('`').unwrap(); + let sig = &sig_line[n..sig_end]; + assert!(!sig.starts_with('`') && !sig.ends_with('`')); + + let selector_end = sig_line.rfind('`').unwrap(); + let selector = sig_line[sig_end..selector_end].strip_prefix("` and selector `").unwrap(); + assert!(!selector.starts_with('`') && !selector.ends_with('`')); + assert!(selector.starts_with("0x")); + + let description = match doc.find("```\n") { + Some(i) => &doc[i + 4..], + None => descr_before, + }; + + (sig, selector, declaration, description.trim()) +} + +/// Returns `(visibility, mutability)` from a given Solidity function declaration. +fn parse_function_attrs(f: &str, span: Span) -> Result<(&str, &str)> { + let Some(ext_start) = f.find("external") else { + return Err(Error::new(span, "functions must have `external` visibility")) + }; + let visibility = "External"; + + let f = &f[ext_start..]; + let mutability = if f.contains("view") { + "View" + } else if f.contains("pure") { + "Pure" + } else { + "None" + }; + Ok((visibility, mutability)) +} diff --git a/crates/macros/src/console_fmt.rs b/crates/macros/src/console_fmt.rs new file mode 100644 index 0000000000000..2522afb2c5b92 --- /dev/null +++ b/crates/macros/src/console_fmt.rs @@ -0,0 +1,109 @@ +use proc_macro2::{Delimiter, Group, Ident, TokenStream}; +use quote::{format_ident, quote}; +use syn::{punctuated::Punctuated, Data, DataEnum, DataStruct, DeriveInput, Fields, Token, Type}; + +pub fn console_fmt(input: &DeriveInput) -> TokenStream { + let name = &input.ident; + let tokens = match &input.data { + Data::Struct(s) => derive_struct(s), + Data::Enum(e) => derive_enum(e), + Data::Union(_) => return quote!(compile_error!("Unions are unsupported");), + }; + quote! { + impl ConsoleFmt for #name { + #tokens + } + } +} + +fn derive_struct(s: &DataStruct) -> TokenStream { + let imp = impl_struct(s).unwrap_or_else(|| quote!(String::new())); + quote! { + fn fmt(&self, _spec: FormatSpec) -> String { + #imp + } + } +} + +fn impl_struct(s: &DataStruct) -> Option { + let fields = match &s.fields { + Fields::Named(fields) => &fields.named, + Fields::Unnamed(fields) => &fields.unnamed, + Fields::Unit => return None, + }; + + if fields.is_empty() { + return None + } + + let first_ty = match &fields.first().unwrap().ty { + Type::Path(path) => path.path.segments.last().unwrap().ident.to_string(), + _ => String::new(), + }; + + let args: Punctuated = fields + .into_iter() + .enumerate() + .map(|(i, field)| { + let ident = field.ident.as_ref().cloned().unwrap_or_else(|| format_ident!("{i}")); + quote!(&self.#ident) + }) + .collect(); + + let imp = if first_ty == "String" { + // console_format(arg1, [...rest]) + let mut args = args.pairs(); + let first = args.next().unwrap(); + let first = first.value(); + quote! { + console_format((#first).as_str(), &[#(#args)*]) + } + } else { + // console_format("", [...args]) + quote! { + console_format("", &[#args]) + } + }; + + Some(imp) +} + +/// Delegates to variants. +fn derive_enum(e: &DataEnum) -> TokenStream { + let arms = e.variants.iter().map(|variant| { + let name = &variant.ident; + let (fields, delimiter) = match &variant.fields { + Fields::Named(fields) => (fields.named.iter(), Delimiter::Brace), + Fields::Unnamed(fields) => (fields.unnamed.iter(), Delimiter::Parenthesis), + Fields::Unit => return quote!(), + }; + + let fields: Punctuated = fields + .enumerate() + .map(|(i, field)| { + field.ident.as_ref().cloned().unwrap_or_else(|| format_ident!("__var_{i}")) + }) + .collect(); + + if fields.len() != 1 { + unimplemented!("Enum variant with more than 1 field") + } + + let field = fields.into_iter().next().unwrap(); + let fields = Group::new(delimiter, quote!(#field)); + quote! { + Self::#name #fields => ConsoleFmt::fmt(#field, spec), + } + }); + + quote! { + fn fmt(&self, spec: FormatSpec) -> String { + match self { + #(#arms)* + + #[allow(unreachable_code)] + _ => String::new(), + } + } + } +} diff --git a/crates/macros/src/fmt/console_fmt.rs b/crates/macros/src/fmt/console_fmt.rs deleted file mode 100644 index ed5f05b1e5931..0000000000000 --- a/crates/macros/src/fmt/console_fmt.rs +++ /dev/null @@ -1,435 +0,0 @@ -use super::UIfmt; -use ethers_core::types::{Address, Bytes, H256, I256, U256}; - -/// A format specifier. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum FormatSpec { - /// %s format spec - #[default] - String, - /// %d format spec - Number, - /// %i format spec - Integer, - /// %o format spec - Object, - /// %e format spec - Exponential, - /// %x format spec - Hexadecimal, -} - -impl FormatSpec { - fn from_char(ch: char) -> Option { - match ch { - 's' => Some(Self::String), - 'd' => Some(Self::Number), - 'i' => Some(Self::Integer), - 'o' => Some(Self::Object), - 'e' => Some(Self::Exponential), - 'x' => Some(Self::Hexadecimal), - _ => None, - } - } -} - -/// Formats a value using a [FormatSpec]. -pub trait ConsoleFmt { - /// Formats a value using a [FormatSpec]. - fn fmt(&self, spec: FormatSpec) -> String; -} - -impl ConsoleFmt for String { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::String => self.clone(), - FormatSpec::Object => format!("'{}'", self.clone()), - FormatSpec::Number | - FormatSpec::Integer | - FormatSpec::Exponential | - FormatSpec::Hexadecimal => String::from("NaN"), - } - } -} - -impl ConsoleFmt for bool { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::String => self.pretty(), - FormatSpec::Object => format!("'{}'", self.pretty()), - FormatSpec::Number => (*self as i32).to_string(), - FormatSpec::Integer | FormatSpec::Exponential | FormatSpec::Hexadecimal => { - String::from("NaN") - } - } - } -} - -impl ConsoleFmt for U256 { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => { - self.pretty() - } - FormatSpec::Hexadecimal => format!("0x{:x}", *self), - FormatSpec::Exponential => { - let log = self.pretty().len() - 1; - let exp10 = U256::exp10(log); - let amount = *self; - let integer = amount / exp10; - let decimal = (amount % exp10).to_string(); - let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{integer}.{decimal}e{log}") - } else { - format!("{integer}e{log}") - } - } - } - } -} - -impl ConsoleFmt for I256 { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::String | FormatSpec::Object | FormatSpec::Number | FormatSpec::Integer => { - self.pretty() - } - FormatSpec::Hexadecimal => format!("0x{:x}", *self), - FormatSpec::Exponential => { - let amount = *self; - let sign = if amount.is_negative() { "-" } else { "" }; - let log = if amount.is_negative() { - self.pretty().len() - 2 - } else { - self.pretty().len() - 1 - }; - let exp10 = I256::exp10(log); - let integer = (amount / exp10).twos_complement(); - let decimal = (amount % exp10).twos_complement().to_string(); - let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); - if !decimal.is_empty() { - format!("{sign}{integer}.{decimal}e{log}") - } else { - format!("{integer}e{log}") - } - } - } - } -} - -impl ConsoleFmt for H256 { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::Hexadecimal | FormatSpec::String => self.pretty(), - FormatSpec::Object => format!("'{}'", self.pretty()), - FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential => { - String::from("NaN") - } - } - } -} - -impl ConsoleFmt for Address { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::String => self.pretty(), - FormatSpec::Object => format!("'{}'", self.pretty()), - FormatSpec::Number | - FormatSpec::Integer | - FormatSpec::Exponential | - FormatSpec::Hexadecimal => String::from("NaN"), - } - } -} - -impl ConsoleFmt for Bytes { - fn fmt(&self, spec: FormatSpec) -> String { - match spec { - FormatSpec::String => self.pretty(), - FormatSpec::Object => format!("'{}'", self.pretty()), - FormatSpec::Number | - FormatSpec::Integer | - FormatSpec::Exponential | - FormatSpec::Hexadecimal => String::from("NaN"), - } - } -} - -impl ConsoleFmt for [u8; N] { - fn fmt(&self, _spec: FormatSpec) -> String { - self.pretty() - } -} - -/// Formats a string using the input values. -/// -/// Formatting rules are the same as Hardhat. The supported format specifiers are as follows: -/// - %s: Converts the value using its String representation. This is equivalent to applying -/// [`UIfmt::pretty()`] on the format string. -/// - %o: Treats the format value as a javascript "object" and converts it to its string -/// representation. -/// - %d, %i: Converts the value to an integer. If a non-numeric value, such as String or Address, -/// is passed, then the spec is formatted as `NaN`. -/// - %x: Converts the value to a hexadecimal string. If a non-numeric value, such as String or -/// Address, is passed, then the spec is formatted as `NaN`. -/// - %e: Converts the value to an exponential notation string. If a non-numeric value, such as -/// String or Address, is passed, then the spec is formatted as `NaN`. -/// - %%: This is parsed as a single percent sign ('%') without consuming any input value. -/// -/// Unformatted values are appended to the end of the formatted output using [`UIfmt::pretty()`]. -/// If there are more format specifiers than values, then the remaining unparsed format specifiers -/// appended to the formatted output as-is. -/// -/// # Example -/// -/// ```ignore -/// let formatted = console_format("%s has %d characters", ["foo", 3]); -/// assert_eq!(formatted, "foo has 3 characters"); -/// ``` -pub fn console_format<'a>( - spec: &str, - values: impl IntoIterator, -) -> String { - let mut values = values.into_iter(); - let mut result = String::with_capacity(spec.len()); - - // for the first space - let mut write_space = true; - let last_value = if spec.is_empty() { - // we still want to print any remaining values - write_space = false; - values.next() - } else { - format_spec(spec, &mut values, &mut result) - }; - - // append any remaining values with the standard format - if let Some(v) = last_value { - for v in std::iter::once(v).chain(values) { - let fmt = v.fmt(FormatSpec::String); - if write_space { - result.push(' '); - } - result.push_str(&fmt); - write_space = true; - } - } - - result -} - -fn format_spec<'a>( - s: &str, - values: &mut impl Iterator, - result: &mut String, -) -> Option<&'a dyn ConsoleFmt> { - let mut expect_fmt = false; - let mut current_value = values.next(); - - for (i, ch) in s.char_indices() { - // no more values - if current_value.is_none() { - result.push_str(&s[i..].replace("%%", "%")); - break - } - - if expect_fmt { - expect_fmt = false; - if let Some(spec) = FormatSpec::from_char(ch) { - // format and write the value - let string = current_value.unwrap().fmt(spec); - result.push_str(&string); - current_value = values.next(); - } else { - // invalid specifier or a second `%`, in both cases we ignore - result.push(ch); - } - } else { - expect_fmt = ch == '%'; - // push when not a `%` or it's the last char - if !expect_fmt || i == s.len() - 1 { - result.push(ch); - } - } - } - - current_value -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ConsoleFmt; - use std::str::FromStr; - - macro_rules! logf1 { - ($a:ident) => {{ - let args: [&dyn ConsoleFmt; 1] = [&$a.p_1]; - console_format(&$a.p_0, args) - }}; - } - - macro_rules! logf2 { - ($a:ident) => {{ - let args: [&dyn ConsoleFmt; 2] = [&$a.p_1, &$a.p_2]; - console_format(&$a.p_0, args) - }}; - } - - macro_rules! logf3 { - ($a:ident) => {{ - let args: [&dyn ConsoleFmt; 3] = [&$a.p_1, &$a.p_2, &$a.p_3]; - console_format(&$a.p_0, args) - }}; - } - - #[derive(Clone, Debug, ConsoleFmt)] - struct Log1 { - p_0: String, - p_1: U256, - } - - #[derive(Clone, Debug, ConsoleFmt)] - struct Log2 { - p_0: String, - p_1: bool, - p_2: U256, - } - - #[derive(Clone, Debug, ConsoleFmt)] - struct Log3 { - p_0: String, - p_1: Address, - p_2: bool, - p_3: U256, - } - - #[allow(unused)] - #[derive(Clone, Debug, ConsoleFmt)] - enum Logs { - Log1(Log1), - Log2(Log2), - Log3(Log3), - } - - #[test] - fn test_console_log_format_specifiers() { - let console_log_format_1 = |spec: &str, arg: &dyn ConsoleFmt| { - let args: [&dyn ConsoleFmt; 1] = [arg]; - console_format(spec, args) - }; - - assert_eq!("foo", console_log_format_1("%s", &String::from("foo"))); - assert_eq!("NaN", console_log_format_1("%d", &String::from("foo"))); - assert_eq!("NaN", console_log_format_1("%i", &String::from("foo"))); - assert_eq!("NaN", console_log_format_1("%e", &String::from("foo"))); - assert_eq!("NaN", console_log_format_1("%x", &String::from("foo"))); - assert_eq!("'foo'", console_log_format_1("%o", &String::from("foo"))); - assert_eq!("%s foo", console_log_format_1("%%s", &String::from("foo"))); - assert_eq!("% foo", console_log_format_1("%", &String::from("foo"))); - assert_eq!("% foo", console_log_format_1("%%", &String::from("foo"))); - - assert_eq!("true", console_log_format_1("%s", &true)); - assert_eq!("1", console_log_format_1("%d", &true)); - assert_eq!("0", console_log_format_1("%d", &false)); - assert_eq!("NaN", console_log_format_1("%i", &true)); - assert_eq!("NaN", console_log_format_1("%e", &true)); - assert_eq!("NaN", console_log_format_1("%x", &true)); - assert_eq!("'true'", console_log_format_1("%o", &true)); - - let b32 = - H256::from_str("0xdeadbeef00000000000000000000000000000000000000000000000000000000") - .unwrap(); - assert_eq!( - "0xdeadbeef00000000000000000000000000000000000000000000000000000000", - console_log_format_1("%s", &b32) - ); - assert_eq!( - "0xdeadbeef00000000000000000000000000000000000000000000000000000000", - console_log_format_1("%x", &b32) - ); - assert_eq!("NaN", console_log_format_1("%d", &b32)); - assert_eq!("NaN", console_log_format_1("%i", &b32)); - assert_eq!("NaN", console_log_format_1("%e", &b32)); - assert_eq!( - "'0xdeadbeef00000000000000000000000000000000000000000000000000000000'", - console_log_format_1("%o", &b32) - ); - - let addr = Address::from_str("0xdEADBEeF00000000000000000000000000000000").unwrap(); - assert_eq!("0xdEADBEeF00000000000000000000000000000000", console_log_format_1("%s", &addr)); - assert_eq!("NaN", console_log_format_1("%d", &addr)); - assert_eq!("NaN", console_log_format_1("%i", &addr)); - assert_eq!("NaN", console_log_format_1("%e", &addr)); - assert_eq!("NaN", console_log_format_1("%x", &addr)); - assert_eq!( - "'0xdEADBEeF00000000000000000000000000000000'", - console_log_format_1("%o", &addr) - ); - - let bytes = Bytes::from_str("0xdeadbeef").unwrap(); - assert_eq!("0xdeadbeef", console_log_format_1("%s", &bytes)); - assert_eq!("NaN", console_log_format_1("%d", &bytes)); - assert_eq!("NaN", console_log_format_1("%i", &bytes)); - assert_eq!("NaN", console_log_format_1("%e", &bytes)); - assert_eq!("NaN", console_log_format_1("%x", &bytes)); - assert_eq!("'0xdeadbeef'", console_log_format_1("%o", &bytes)); - - assert_eq!("100", console_log_format_1("%s", &U256::from(100))); - assert_eq!("100", console_log_format_1("%d", &U256::from(100))); - assert_eq!("100", console_log_format_1("%i", &U256::from(100))); - assert_eq!("1e2", console_log_format_1("%e", &U256::from(100))); - assert_eq!("1.0023e6", console_log_format_1("%e", &U256::from(1002300))); - assert_eq!("1.23e5", console_log_format_1("%e", &U256::from(123000))); - assert_eq!("0x64", console_log_format_1("%x", &U256::from(100))); - assert_eq!("100", console_log_format_1("%o", &U256::from(100))); - - assert_eq!("100", console_log_format_1("%s", &I256::from(100))); - assert_eq!("100", console_log_format_1("%d", &I256::from(100))); - assert_eq!("100", console_log_format_1("%i", &I256::from(100))); - assert_eq!("1e2", console_log_format_1("%e", &I256::from(100))); - assert_eq!("-1.0023e6", console_log_format_1("%e", &I256::from(-1002300))); - assert_eq!("-1.23e5", console_log_format_1("%e", &I256::from(-123000))); - assert_eq!("1.0023e6", console_log_format_1("%e", &I256::from(1002300))); - assert_eq!("1.23e5", console_log_format_1("%e", &I256::from(123000))); - assert_eq!("0x64", console_log_format_1("%x", &I256::from(100))); - assert_eq!("100", console_log_format_1("%o", &I256::from(100))); - } - - #[test] - fn test_console_log_format() { - let mut log1 = Log1 { p_0: "foo %s".to_string(), p_1: U256::from(100) }; - assert_eq!("foo 100", logf1!(log1)); - log1.p_0 = String::from("foo"); - assert_eq!("foo 100", logf1!(log1)); - log1.p_0 = String::from("%s foo"); - assert_eq!("100 foo", logf1!(log1)); - - let mut log2 = Log2 { p_0: "foo %s %s".to_string(), p_1: true, p_2: U256::from(100) }; - assert_eq!("foo true 100", logf2!(log2)); - log2.p_0 = String::from("foo"); - assert_eq!("foo true 100", logf2!(log2)); - log2.p_0 = String::from("%s %s foo"); - assert_eq!("true 100 foo", logf2!(log2)); - - let log3 = Log3 { - p_0: String::from("foo %s %%s %s and %d foo %%"), - p_1: Address::from_str("0xdEADBEeF00000000000000000000000000000000").unwrap(), - p_2: true, - p_3: U256::from(21), - }; - assert_eq!( - "foo 0xdEADBEeF00000000000000000000000000000000 %s true and 21 foo %", - logf3!(log3) - ); - } - - #[test] - fn test_derive_format() { - let log1 = Log1 { p_0: String::from("foo %s bar"), p_1: U256::from(42) }; - assert_eq!(log1.fmt(Default::default()), "foo 42 bar"); - let call = Logs::Log1(log1); - assert_eq!(call.fmt(Default::default()), "foo 42 bar"); - } -} diff --git a/crates/macros/src/fmt/mod.rs b/crates/macros/src/fmt/mod.rs deleted file mode 100644 index dada2b94e6a34..0000000000000 --- a/crates/macros/src/fmt/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Helpers for formatting ethereum types - -mod ui; -pub use ui::*; - -mod token; -pub use token::*; - -mod console_fmt; -pub use console_fmt::{console_format, ConsoleFmt, FormatSpec}; diff --git a/crates/macros/src/fmt/token.rs b/crates/macros/src/fmt/token.rs deleted file mode 100644 index cc2808d9e7b3d..0000000000000 --- a/crates/macros/src/fmt/token.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Formatting helpers for [`Token`]s. - -use ethers_core::{abi::Token, types::I256, utils, utils::hex}; -use std::{fmt, fmt::Write}; - -/// Wrapper that pretty formats a token -pub struct TokenDisplay<'a>(pub &'a Token); - -impl fmt::Display for TokenDisplay<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_token(f, self.0) - } -} - -/// Recursively formats an ABI token. -fn fmt_token(f: &mut fmt::Formatter, item: &Token) -> fmt::Result { - match item { - Token::Address(inner) => { - write!(f, "{}", utils::to_checksum(inner, None)) - } - // add 0x - Token::Bytes(inner) => write!(f, "0x{}", hex::encode(inner)), - Token::FixedBytes(inner) => write!(f, "0x{}", hex::encode(inner)), - // print as decimal - Token::Uint(inner) => write!(f, "{inner}"), - Token::Int(inner) => write!(f, "{}", I256::from_raw(*inner)), - Token::Array(tokens) | Token::FixedArray(tokens) => { - f.write_char('[')?; - let mut tokens = tokens.iter().peekable(); - while let Some(token) = tokens.next() { - fmt_token(f, token)?; - if tokens.peek().is_some() { - f.write_char(',')? - } - } - f.write_char(']') - } - Token::Tuple(tokens) => { - f.write_char('(')?; - let mut tokens = tokens.iter().peekable(); - while let Some(token) = tokens.next() { - fmt_token(f, token)?; - if tokens.peek().is_some() { - f.write_char(',')? - } - } - f.write_char(')') - } - _ => write!(f, "{item}"), - } -} diff --git a/crates/macros/src/fmt/ui.rs b/crates/macros/src/fmt/ui.rs deleted file mode 100644 index d6d9971a30dba..0000000000000 --- a/crates/macros/src/fmt/ui.rs +++ /dev/null @@ -1,666 +0,0 @@ -//! Helper trait and functions to format ethers types. - -use ethers_core::{ - types::*, - utils::{hex, to_checksum}, -}; -use serde::Deserialize; - -/// length of the name column for pretty formatting `{:>20}{value}` -const NAME_COLUMN_LEN: usize = 20usize; - -/// Helper trait to format ethers types. -/// -/// # Examples -/// -/// ``` -/// use foundry_macros::fmt::UIfmt; -/// let boolean: bool = true; -/// let string = boolean.pretty(); -/// ``` -pub trait UIfmt { - /// Return a prettified string version of the value - fn pretty(&self) -> String; -} - -impl UIfmt for String { - fn pretty(&self) -> String { - self.to_string() - } -} -impl UIfmt for bool { - fn pretty(&self) -> String { - self.to_string() - } -} - -impl UIfmt for U256 { - fn pretty(&self) -> String { - self.to_string() - } -} -impl UIfmt for I256 { - fn pretty(&self) -> String { - self.to_string() - } -} - -impl UIfmt for Address { - fn pretty(&self) -> String { - to_checksum(self, None) - } -} - -impl UIfmt for H64 { - fn pretty(&self) -> String { - format!("{self:#x}") - } -} - -impl UIfmt for H256 { - fn pretty(&self) -> String { - format!("{self:#x}") - } -} - -impl UIfmt for Bytes { - fn pretty(&self) -> String { - format!("{self:#x}") - } -} - -impl UIfmt for [u8; N] { - fn pretty(&self) -> String { - format!("0x{}", hex::encode(&self[..])) - } -} - -impl UIfmt for U64 { - fn pretty(&self) -> String { - self.to_string() - } -} - -impl UIfmt for Bloom { - fn pretty(&self) -> String { - format!("{self:#x}") - } -} - -impl UIfmt for Option { - fn pretty(&self) -> String { - if let Some(ref inner) = self { - inner.pretty() - } else { - "".to_string() - } - } -} - -impl UIfmt for Vec { - fn pretty(&self) -> String { - if !self.is_empty() { - let mut s = String::with_capacity(self.len() * 64); - s.push_str("[\n"); - for item in self { - for line in item.pretty().lines() { - s.push('\t'); - s.push_str(line); - s.push('\n'); - } - } - s.push(']'); - s - } else { - "[]".to_string() - } - } -} - -impl UIfmt for TransactionReceipt { - fn pretty(&self) -> String { - format!( - " -blockHash {} -blockNumber {} -contractAddress {} -cumulativeGasUsed {} -effectiveGasPrice {} -gasUsed {} -logs {} -logsBloom {} -root {} -status {} -transactionHash {} -transactionIndex {} -type {}", - self.block_hash.pretty(), - self.block_number.pretty(), - self.contract_address.pretty(), - self.cumulative_gas_used.pretty(), - self.effective_gas_price.pretty(), - self.gas_used.pretty(), - serde_json::to_string(&self.logs).unwrap(), - self.logs_bloom.pretty(), - self.root.pretty(), - self.status.pretty(), - self.transaction_hash.pretty(), - self.transaction_index.pretty(), - self.transaction_type.pretty() - ) - } -} - -impl UIfmt for Log { - fn pretty(&self) -> String { - format!( - " -address: {} -blockHash: {} -blockNumber: {} -data: {} -logIndex: {} -removed: {} -topics: {} -transactionHash: {} -transactionIndex: {}", - self.address.pretty(), - self.block_hash.pretty(), - self.block_number.pretty(), - self.data.pretty(), - self.log_index.pretty(), - self.removed.pretty(), - self.topics.pretty(), - self.transaction_hash.pretty(), - self.transaction_index.pretty(), - ) - } -} - -impl UIfmt for Block { - fn pretty(&self) -> String { - format!( - " -{} -transactions {}", - pretty_block_basics(self), - self.transactions.pretty() - ) - } -} - -impl UIfmt for Block { - fn pretty(&self) -> String { - format!( - " -{} -transactions: {}", - pretty_block_basics(self), - self.transactions.pretty() - ) - } -} - -fn pretty_block_basics(block: &Block) -> String { - format!( - " -baseFeePerGas {} -difficulty {} -extraData {} -gasLimit {} -gasUsed {} -hash {} -logsBloom {} -miner {} -mixHash {} -nonce {} -number {} -parentHash {} -receiptsRoot {} -sealFields {} -sha3Uncles {} -size {} -stateRoot {} -timestamp {} -withdrawalsRoot {} -totalDifficulty {}{}", - block.base_fee_per_gas.pretty(), - block.difficulty.pretty(), - block.extra_data.pretty(), - block.gas_limit.pretty(), - block.gas_used.pretty(), - block.hash.pretty(), - block.logs_bloom.pretty(), - block.author.pretty(), - block.mix_hash.pretty(), - block.nonce.pretty(), - block.number.pretty(), - block.parent_hash.pretty(), - block.receipts_root.pretty(), - block.seal_fields.pretty(), - block.uncles_hash.pretty(), - block.size.pretty(), - block.state_root.pretty(), - block.timestamp.pretty(), - block.withdrawals_root.pretty(), - block.total_difficulty.pretty(), - block.other.pretty() - ) -} - -impl UIfmt for OtherFields { - fn pretty(&self) -> String { - let mut s = String::with_capacity(self.len() * 30); - if !self.is_empty() { - s.push('\n'); - } - for (key, value) in self.iter() { - let val = EthValue::from(value.clone()).pretty(); - let offset = NAME_COLUMN_LEN.saturating_sub(key.len()); - s.push_str(key); - s.extend(std::iter::repeat(' ').take(offset + 1)); - s.push_str(&val); - s.push('\n'); - } - s - } -} - -/// Various numerical ethereum types used for pretty printing -#[derive(Debug, Clone, Deserialize)] -#[serde(untagged)] -#[allow(missing_docs)] -pub enum EthValue { - U64(U64), - U256(U256), - U64Array(Vec), - U256Array(Vec), - Other(serde_json::Value), -} - -impl From for EthValue { - fn from(val: serde_json::Value) -> Self { - serde_json::from_value(val).expect("infallible") - } -} - -impl UIfmt for EthValue { - fn pretty(&self) -> String { - match self { - EthValue::U64(num) => num.pretty(), - EthValue::U256(num) => num.pretty(), - EthValue::U64Array(arr) => arr.pretty(), - EthValue::U256Array(arr) => arr.pretty(), - EthValue::Other(val) => val.to_string().trim_matches('"').to_string(), - } - } -} - -impl UIfmt for Transaction { - fn pretty(&self) -> String { - format!( - " -blockHash {} -blockNumber {} -from {} -gas {} -gasPrice {} -hash {} -input {} -nonce {} -r {} -s {} -to {} -transactionIndex {} -v {} -value {}{}", - self.block_hash.pretty(), - self.block_number.pretty(), - self.from.pretty(), - self.gas.pretty(), - self.gas_price.pretty(), - self.hash.pretty(), - self.input.pretty(), - self.nonce.pretty(), - to_bytes(self.r).pretty(), - to_bytes(self.s).pretty(), - self.to.pretty(), - self.transaction_index.pretty(), - self.v.pretty(), - self.value.pretty(), - self.other.pretty() - ) - } -} - -/// Convert a U256 to bytes -pub fn to_bytes(uint: U256) -> Bytes { - let mut buffer: [u8; 4 * 8] = [0; 4 * 8]; - uint.to_big_endian(&mut buffer); - Bytes::from(buffer) -} - -/// Returns the `UiFmt::pretty()` formatted attribute of the transactions -pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option { - match attr { - "blockHash" | "block_hash" => Some(transaction.block_hash.pretty()), - "blockNumber" | "block_number" => Some(transaction.block_number.pretty()), - "from" => Some(transaction.from.pretty()), - "gas" => Some(transaction.gas.pretty()), - "gasPrice" | "gas_price" => Some(transaction.gas_price.pretty()), - "hash" => Some(transaction.hash.pretty()), - "input" => Some(transaction.input.pretty()), - "nonce" => Some(transaction.nonce.pretty()), - "s" => Some(to_bytes(transaction.s).pretty()), - "r" => Some(to_bytes(transaction.r).pretty()), - "to" => Some(transaction.to.pretty()), - "transactionIndex" | "transaction_index" => Some(transaction.transaction_index.pretty()), - "v" => Some(transaction.v.pretty()), - "value" => Some(transaction.value.pretty()), - other => { - if let Some(value) = transaction.other.get(other) { - return Some(value.to_string().trim_matches('"').to_string()) - } - None - } - } -} - -/// Returns the `UiFmt::pretty()` formatted attribute of the given block -pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { - match attr { - "baseFeePerGas" | "base_fee_per_gas" => Some(block.base_fee_per_gas.pretty()), - "difficulty" => Some(block.difficulty.pretty()), - "extraData" | "extra_data" => Some(block.extra_data.pretty()), - "gasLimit" | "gas_limit" => Some(block.gas_limit.pretty()), - "gasUsed" | "gas_used" => Some(block.gas_used.pretty()), - "hash" => Some(block.hash.pretty()), - "logsBloom" | "logs_bloom" => Some(block.logs_bloom.pretty()), - "miner" | "author" => Some(block.author.pretty()), - "mixHash" | "mix_hash" => Some(block.mix_hash.pretty()), - "nonce" => Some(block.nonce.pretty()), - "number" => Some(block.number.pretty()), - "parentHash" | "parent_hash" => Some(block.parent_hash.pretty()), - "receiptsRoot" | "receipts_root" => Some(block.receipts_root.pretty()), - "sealFields" | "seal_fields" => Some(block.seal_fields.pretty()), - "sha3Uncles" | "sha_3_uncles" => Some(block.uncles_hash.pretty()), - "size" => Some(block.size.pretty()), - "stateRoot" | "state_root" => Some(block.state_root.pretty()), - "timestamp" => Some(block.timestamp.pretty()), - "totalDifficulty" | "total_difficult" => Some(block.total_difficulty.pretty()), - other => { - if let Some(value) = block.other.get(other) { - let val = EthValue::from(value.clone()); - return Some(val.pretty()) - } - None - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::str::FromStr; - - #[test] - fn can_format_bytes32() { - let val = hex::decode("7465737400000000000000000000000000000000000000000000000000000000") - .unwrap(); - let mut b32 = [0u8; 32]; - b32.copy_from_slice(&val); - - assert_eq!( - b32.pretty(), - "0x7465737400000000000000000000000000000000000000000000000000000000" - ); - let b: Bytes = val.into(); - assert_eq!(b.pretty(), b32.pretty()); - } - - #[test] - fn can_pretty_print_optimism_tx() { - let s = r#" - { - "blockHash": "0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae", - "blockNumber": "0x1b4", - "from": "0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6", - "gas": "0x11cbbdc", - "gasPrice": "0x0", - "hash": "0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f", - "input": "0xd294f093", - "nonce": "0xa2", - "to": "0x4a16A42407AA491564643E1dfc1fd50af29794eF", - "transactionIndex": "0x0", - "value": "0x0", - "v": "0x38", - "r": "0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee", - "s": "0xe804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583", - "queueOrigin": "sequencer", - "txType": "", - "l1TxOrigin": null, - "l1BlockNumber": "0xc1a65c", - "l1Timestamp": "0x60d34b60", - "index": "0x1b3", - "queueIndex": null, - "rawTransaction": "0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583" - } - "#; - - let tx: Transaction = serde_json::from_str(s).unwrap(); - assert_eq!(tx.pretty().trim(), - r#" -blockHash 0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae -blockNumber 436 -from 0x3b179DcfC5fAa677044c27dCe958e4BC0ad696A6 -gas 18660316 -gasPrice 0 -hash 0x2642e960d3150244e298d52b5b0f024782253e6d0b2c9a01dd4858f7b4665a3f -input 0xd294f093 -nonce 162 -r 0x6fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2bee -s 0x0e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 -to 0x4a16A42407AA491564643E1dfc1fd50af29794eF -transactionIndex 0 -v 56 -value 0 -index 435 -l1BlockNumber 12691036 -l1Timestamp 1624460128 -l1TxOrigin null -queueIndex null -queueOrigin sequencer -rawTransaction 0xf86681a28084011cbbdc944a16a42407aa491564643e1dfc1fd50af29794ef8084d294f09338a06fca94073a0cf3381978662d46cf890602d3e9ccf6a31e4b69e8ecbd995e2beea00e804161a2b56a37ca1f6f4c4b8bce926587afa0d9b1acc5165e6556c959d583 -txType -"#.trim() - ); - } - - #[test] - fn print_block_w_txs() { - let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block = serde_json::from_str(block).unwrap(); - let output ="\nblockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 -blockNumber 3 -from 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a -gas 90000 -gasPrice 20000000000 -hash 0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067 -input 0x -nonce 2 -r 0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88 -s 0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e -to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e -transactionIndex 0 -v 37 -value 0".to_string(); - let generated = block.transactions[0].pretty(); - assert_eq!(generated.as_str(), output.as_str()); - } - - #[test] - fn uifmt_option_u64() { - let empty: Option = None; - assert_eq!("".to_string(), empty.pretty()); - assert_eq!("100".to_string(), U64::from_dec_str("100").unwrap().pretty()); - assert_eq!("100".to_string(), Option::from(U64::from_dec_str("100").unwrap()).pretty()) - } - - #[test] - fn uifmt_option_h64() { - let empty: Option = None; - assert_eq!("".to_string(), empty.pretty()); - H256::from_low_u64_be(100); - assert_eq!( - "0x0000000000000000000000000000000000000000000000000000000000000064", - H256::from_low_u64_be(100).pretty() - ); - assert_eq!( - "0x0000000000000000000000000000000000000000000000000000000000000064", - Some(H256::from_low_u64_be(100)).pretty() - ); - } - #[test] - fn uifmt_option_bytes() { - let empty: Option = None; - assert_eq!("".to_string(), empty.pretty()); - assert_eq!( - "0x0000000000000000000000000000000000000000000000000000000000000064".to_string(), - Bytes::from_str("0x0000000000000000000000000000000000000000000000000000000000000064") - .unwrap() - .pretty() - ); - assert_eq!( - "0x0000000000000000000000000000000000000000000000000000000000000064".to_string(), - Some( - Bytes::from_str( - "0x0000000000000000000000000000000000000000000000000000000000000064" - ) - .unwrap() - ) - .pretty() - ); - } - #[test] - fn test_pretty_tx_attr() { - let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block = serde_json::from_str(block).unwrap(); - assert_eq!(None, get_pretty_tx_attr(&block.transactions[0], "")); - assert_eq!( - Some("3".to_string()), - get_pretty_tx_attr(&block.transactions[0], "blockNumber") - ); - assert_eq!( - Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), - get_pretty_tx_attr(&block.transactions[0], "from") - ); - assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&block.transactions[0], "gas")); - assert_eq!( - Some("20000000000".to_string()), - get_pretty_tx_attr(&block.transactions[0], "gasPrice") - ); - assert_eq!( - Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), - get_pretty_tx_attr(&block.transactions[0], "hash") - ); - assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&block.transactions[0], "input")); - assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&block.transactions[0], "nonce")); - assert_eq!( - Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), - get_pretty_tx_attr(&block.transactions[0], "r") - ); - assert_eq!( - Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), - get_pretty_tx_attr(&block.transactions[0], "s") - ); - assert_eq!( - Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), - get_pretty_tx_attr(&block.transactions[0], "to") - ); - assert_eq!( - Some("0".to_string()), - get_pretty_tx_attr(&block.transactions[0], "transactionIndex") - ); - assert_eq!(Some("37".to_string()), get_pretty_tx_attr(&block.transactions[0], "v")); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&block.transactions[0], "value")); - } - #[test] - fn test_pretty_block_attr() { - let json = serde_json::json!( - { - "baseFeePerGas": "0x7", - "miner": "0x0000000000000000000000000000000000000001", - "number": "0x1b4", - "hash": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", - "parentHash": "0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5", - "mixHash": "0x1010101010101010101010101010101010101010101010101010101010101010", - "nonce": "0x0000000000000000", - "sealFields": [ - "0xe04d296d2460cfb8472af2c5fd05b5a214109c25688d3704aed5484f9a7792f2", - "0x0000000000000042" - ], - "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331", - "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "stateRoot": "0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff", - "difficulty": "0x27f07", - "totalDifficulty": "0x27f07", - "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", - "size": "0x27f07", - "gasLimit": "0x9f759", - "minGasPrice": "0x9f759", - "gasUsed": "0x9f759", - "timestamp": "0x54e34e8e", - "transactions": [], - "uncles": [] - } - ); - - let block: Block<()> = serde_json::from_value(json).unwrap(); - - assert_eq!(None, get_pretty_block_attr(&block, "")); - assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "difficulty")); - assert_eq!( - Some("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()), - get_pretty_block_attr(&block, "extraData") - ); - assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasLimit")); - assert_eq!(Some("653145".to_string()), get_pretty_block_attr(&block, "gasUsed")); - assert_eq!( - Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), - get_pretty_block_attr(&block, "hash") - ); - assert_eq!(Some("0x0e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273310e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331".to_string()), get_pretty_block_attr(&block, "logsBloom")); - assert_eq!( - Some("0x0000000000000000000000000000000000000001".to_string()), - get_pretty_block_attr(&block, "miner") - ); - assert_eq!( - Some("0x1010101010101010101010101010101010101010101010101010101010101010".to_string()), - get_pretty_block_attr(&block, "mixHash") - ); - assert_eq!(Some("0x0000000000000000".to_string()), get_pretty_block_attr(&block, "nonce")); - assert_eq!(Some("436".to_string()), get_pretty_block_attr(&block, "number")); - assert_eq!( - Some("0x9646252be9520f6e71339a8df9c55e4d7619deeb018d2a3f2d21fc165dde5eb5".to_string()), - get_pretty_block_attr(&block, "parentHash") - ); - assert_eq!( - Some("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".to_string()), - get_pretty_block_attr(&block, "receiptsRoot") - ); - assert_eq!( - Some("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".to_string()), - get_pretty_block_attr(&block, "sha3Uncles") - ); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "size")); - assert_eq!( - Some("0xd5855eb08b3387c0af375e9cdb6acfc05eb8f519e419b874b6ff2ffda7ed1dff".to_string()), - get_pretty_block_attr(&block, "stateRoot") - ); - assert_eq!(Some("1424182926".to_string()), get_pretty_block_attr(&block, "timestamp")); - assert_eq!(Some("163591".to_string()), get_pretty_block_attr(&block, "totalDifficulty")); - } -} diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index c1b94f5b6a141..dbdaa208ce726 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,11 +1,29 @@ -//! Foundry's procedural macros. +//! # foundry-macros //! -//! Also includes traits and other utilities used by the macros. +//! Internal Foundry proc-macros. -#![warn(unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -pub mod fmt; -pub use fmt::{console_format, ConsoleFmt, FormatSpec, TokenDisplay, UIfmt}; +#[macro_use] +extern crate proc_macro_error; -#[doc(inline)] -pub use foundry_macros_impl::ConsoleFmt; +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +use syn::{parse_macro_input, DeriveInput, Error}; + +mod cheatcodes; +mod console_fmt; + +#[proc_macro_derive(ConsoleFmt)] +pub fn console_fmt(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + console_fmt::console_fmt(&input).into() +} + +#[proc_macro_derive(Cheatcode, attributes(cheatcode))] +#[proc_macro_error] +pub fn cheatcode(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + cheatcodes::derive_cheatcode(&input).unwrap_or_else(Error::into_compile_error).into() +} diff --git a/crates/script-sequence/Cargo.toml b/crates/script-sequence/Cargo.toml new file mode 100644 index 0000000000000..08fd9a695fde9 --- /dev/null +++ b/crates/script-sequence/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "forge-script-sequence" +description = "Script sequence types" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +foundry-common.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } + +serde.workspace = true +eyre.workspace = true +serde_json.workspace = true +tracing.workspace = true +walkdir.workspace = true + +revm-inspectors.workspace = true + +alloy-rpc-types.workspace = true +alloy-primitives.workspace = true +alloy-network.workspace = true diff --git a/crates/script-sequence/src/lib.rs b/crates/script-sequence/src/lib.rs new file mode 100644 index 0000000000000..929f44a724b8c --- /dev/null +++ b/crates/script-sequence/src/lib.rs @@ -0,0 +1,12 @@ +//! Script Sequence and related types. + +#[macro_use] +extern crate foundry_common; + +pub mod reader; +pub mod sequence; +pub mod transaction; + +pub use reader::*; +pub use sequence::*; +pub use transaction::*; diff --git a/crates/script-sequence/src/reader.rs b/crates/script-sequence/src/reader.rs new file mode 100644 index 0000000000000..de2e9faf16389 --- /dev/null +++ b/crates/script-sequence/src/reader.rs @@ -0,0 +1,179 @@ +use crate::{ScriptSequence, TransactionWithMetadata}; +use alloy_network::AnyTransactionReceipt; +use eyre::{bail, Result}; +use foundry_common::fs; +use revm_inspectors::tracing::types::CallKind; +use std::path::{Component, Path, PathBuf}; + +/// This type reads broadcast files in the +/// `project_root/broadcast/{contract_name}.s.sol/{chain_id}/` directory. +/// +/// It consists methods that filter and search for transactions in the broadcast files that match a +/// `transactionType` if provided. +/// +/// Note: +/// +/// It only returns transactions for which there exists a corresponding receipt in the broadcast. +#[derive(Debug, Clone)] +pub struct BroadcastReader { + contract_name: String, + chain_id: u64, + tx_type: Vec, + broadcast_path: PathBuf, +} + +impl BroadcastReader { + /// Create a new `BroadcastReader` instance. + pub fn new(contract_name: String, chain_id: u64, broadcast_path: &Path) -> Result { + if !broadcast_path.exists() && !broadcast_path.is_dir() { + bail!("broadcast dir does not exist"); + } + + Ok(Self { + contract_name, + chain_id, + tx_type: Default::default(), + broadcast_path: broadcast_path.to_path_buf(), + }) + } + + /// Set the transaction type to filter by. + pub fn with_tx_type(mut self, tx_type: CallKind) -> Self { + self.tx_type.push(tx_type); + self + } + + /// Read all broadcast files in the broadcast directory. + /// + /// Example structure: + /// + /// project-root/broadcast/{script_name}.s.sol/{chain_id}/*.json + /// project-root/broadcast/multi/{multichain_script_name}.s.sol-{timestamp}/deploy.json + pub fn read(&self) -> eyre::Result> { + // 1. Recursively read all .json files in the broadcast directory + let mut broadcasts = vec![]; + for entry in walkdir::WalkDir::new(&self.broadcast_path).into_iter() { + let entry = entry?; + let path = entry.path(); + + if path.is_file() && path.extension().is_some_and(|ext| ext == "json") { + // Ignore -latest to avoid duplicating broadcast entries + if path.components().any(|c| c.as_os_str().to_string_lossy().contains("-latest")) { + continue; + } + + // Detect Multichain broadcasts using "multi" in the path + if path.components().any(|c| c == Component::Normal("multi".as_ref())) { + // Parse as MultiScriptSequence + + let broadcast = fs::read_json_file::(path)?; + let multichain_deployments = broadcast + .get("deployments") + .and_then(|deployments| { + serde_json::from_value::>(deployments.clone()).ok() + }) + .unwrap_or_default(); + + broadcasts.extend(multichain_deployments); + continue; + } + + let broadcast = fs::read_json_file::(path)?; + broadcasts.push(broadcast); + } + } + + let broadcasts = self.filter_and_sort(broadcasts); + + Ok(broadcasts) + } + + /// Attempts read the latest broadcast file in the broadcast directory. + /// + /// This may be the `run-latest.json` file or the broadcast file with the latest timestamp. + pub fn read_latest(&self) -> eyre::Result { + let broadcasts = self.read()?; + + // Find the broadcast with the latest timestamp + let target = broadcasts + .into_iter() + .max_by_key(|broadcast| broadcast.timestamp) + .ok_or_else(|| eyre::eyre!("No broadcasts found"))?; + + Ok(target) + } + + /// Applies the filters and sorts the broadcasts by descending timestamp. + pub fn filter_and_sort(&self, broadcasts: Vec) -> Vec { + // Apply the filters + let mut seqs = broadcasts + .into_iter() + .filter(|broadcast| { + if broadcast.chain != self.chain_id { + return false; + } + + broadcast.transactions.iter().any(move |tx| { + let name_filter = + tx.contract_name.clone().is_some_and(|cn| cn == self.contract_name); + + let type_filter = self.tx_type.is_empty() || + self.tx_type.iter().any(|kind| *kind == tx.opcode); + + name_filter && type_filter + }) + }) + .collect::>(); + + // Sort by descending timestamp + seqs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); + + seqs + } + + /// Search for transactions in the broadcast that match the specified `contractName` and + /// `txType`. + /// + /// It cross-checks the transactions with their corresponding receipts in the broadcast and + /// returns the result. + /// + /// Transactions that don't have a corresponding receipt are ignored. + /// + /// Sorts the transactions by descending block number. + pub fn into_tx_receipts( + &self, + broadcast: ScriptSequence, + ) -> Vec<(TransactionWithMetadata, AnyTransactionReceipt)> { + let transactions = broadcast.transactions.clone(); + + let txs = transactions + .into_iter() + .filter(|tx| { + let name_filter = + tx.contract_name.clone().is_some_and(|cn| cn == self.contract_name); + + let type_filter = + self.tx_type.is_empty() || self.tx_type.iter().any(|kind| *kind == tx.opcode); + + name_filter && type_filter + }) + .collect::>(); + + let mut targets = Vec::new(); + for tx in txs.into_iter() { + let maybe_receipt = broadcast + .receipts + .iter() + .find(|receipt| tx.hash.is_some_and(|hash| hash == receipt.transaction_hash)); + + if let Some(receipt) = maybe_receipt { + targets.push((tx, receipt.clone())); + } + } + + // Sort by descending block number + targets.sort_by(|a, b| b.1.block_number.cmp(&a.1.block_number)); + + targets + } +} diff --git a/crates/script-sequence/src/sequence.rs b/crates/script-sequence/src/sequence.rs new file mode 100644 index 0000000000000..4b2b434e11793 --- /dev/null +++ b/crates/script-sequence/src/sequence.rs @@ -0,0 +1,259 @@ +use crate::transaction::TransactionWithMetadata; +use alloy_network::AnyTransactionReceipt; +use alloy_primitives::{hex, map::HashMap, TxHash}; +use eyre::{ContextCompat, Result, WrapErr}; +use foundry_common::{fs, shell, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_compilers::ArtifactId; +use foundry_config::Config; +use serde::{Deserialize, Serialize}; +use std::{ + collections::VecDeque, + io::{BufWriter, Write}, + path::PathBuf, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; + +pub const DRY_RUN_DIR: &str = "dry-run"; + +#[derive(Clone, Serialize, Deserialize)] +pub struct NestedValue { + pub internal_type: String, + pub value: String, +} + +/// Helper that saves the transactions sequence and its state on which transactions have been +/// broadcasted +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct ScriptSequence { + pub transactions: VecDeque, + pub receipts: Vec, + pub libraries: Vec, + pub pending: Vec, + #[serde(skip)] + /// Contains paths to the sequence files + /// None if sequence should not be saved to disk (e.g. part of a multi-chain sequence) + pub paths: Option<(PathBuf, PathBuf)>, + pub returns: HashMap, + pub timestamp: u64, + pub chain: u64, + pub commit: Option, +} + +/// Sensitive values from the transactions in a script sequence +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveTransactionMetadata { + pub rpc: String, +} + +/// Sensitive info from the script sequence which is saved into the cache folder +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveScriptSequence { + pub transactions: VecDeque, +} + +impl From for SensitiveScriptSequence { + fn from(sequence: ScriptSequence) -> Self { + Self { + transactions: sequence + .transactions + .iter() + .map(|tx| SensitiveTransactionMetadata { rpc: tx.rpc.clone() }) + .collect(), + } + } +} + +impl ScriptSequence { + /// Loads The sequence for the corresponding json file + pub fn load( + config: &Config, + sig: &str, + target: &ArtifactId, + chain_id: u64, + dry_run: bool, + ) -> Result { + let (path, sensitive_path) = Self::get_paths(config, sig, target, chain_id, dry_run)?; + + let mut script_sequence: Self = fs::read_json_file(&path) + .wrap_err(format!("Deployment not found for chain `{chain_id}`."))?; + + let sensitive_script_sequence: SensitiveScriptSequence = fs::read_json_file( + &sensitive_path, + ) + .wrap_err(format!("Deployment's sensitive details not found for chain `{chain_id}`."))?; + + script_sequence.fill_sensitive(&sensitive_script_sequence); + + script_sequence.paths = Some((path, sensitive_path)); + + Ok(script_sequence) + } + + /// Saves the transactions as file if it's a standalone deployment. + /// `save_ts` should be set to true for checkpoint updates, which might happen many times and + /// could result in us saving many identical files. + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + self.sort_receipts(); + + if self.transactions.is_empty() { + return Ok(()) + } + + let Some((path, sensitive_path)) = self.paths.clone() else { return Ok(()) }; + + self.timestamp = now().as_secs(); + let ts_name = format!("run-{}.json", self.timestamp); + + let sensitive_script_sequence: SensitiveScriptSequence = self.clone().into(); + + // broadcast folder writes + //../run-latest.json + let mut writer = BufWriter::new(fs::create_file(&path)?); + serde_json::to_writer_pretty(&mut writer, &self)?; + writer.flush()?; + if save_ts { + //../run-[timestamp].json + fs::copy(&path, path.with_file_name(&ts_name))?; + } + + // cache folder writes + //../run-latest.json + let mut writer = BufWriter::new(fs::create_file(&sensitive_path)?); + serde_json::to_writer_pretty(&mut writer, &sensitive_script_sequence)?; + writer.flush()?; + if save_ts { + //../run-[timestamp].json + fs::copy(&sensitive_path, sensitive_path.with_file_name(&ts_name))?; + } + + if !silent { + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ + "status": "success", + "transactions": path.display().to_string(), + "sensitive": sensitive_path.display().to_string(), + }) + )?; + } else { + sh_println!("\nTransactions saved to: {}\n", path.display())?; + sh_println!("Sensitive values saved to: {}\n", sensitive_path.display())?; + } + } + + Ok(()) + } + + pub fn add_receipt(&mut self, receipt: AnyTransactionReceipt) { + self.receipts.push(receipt); + } + + /// Sorts all receipts with ascending transaction index + pub fn sort_receipts(&mut self) { + self.receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); + } + + pub fn add_pending(&mut self, index: usize, tx_hash: TxHash) { + if !self.pending.contains(&tx_hash) { + self.transactions[index].hash = Some(tx_hash); + self.pending.push(tx_hash); + } + } + + pub fn remove_pending(&mut self, tx_hash: TxHash) { + self.pending.retain(|element| element != &tx_hash); + } + + /// Gets paths in the formats + /// `./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json` and + /// `./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json`. + pub fn get_paths( + config: &Config, + sig: &str, + target: &ArtifactId, + chain_id: u64, + dry_run: bool, + ) -> Result<(PathBuf, PathBuf)> { + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); + let mut common = PathBuf::new(); + + let target_fname = target.source.file_name().wrap_err("No filename.")?; + common.push(target_fname); + common.push(chain_id.to_string()); + if dry_run { + common.push(DRY_RUN_DIR); + } + + broadcast.push(common.clone()); + cache.push(common); + + fs::create_dir_all(&broadcast)?; + fs::create_dir_all(&cache)?; + + // TODO: ideally we want the name of the function here if sig is calldata + let filename = sig_to_file_name(sig); + + broadcast.push(format!("{filename}-latest.json")); + cache.push(format!("{filename}-latest.json")); + + Ok((broadcast, cache)) + } + + /// Returns the first RPC URL of this sequence. + pub fn rpc_url(&self) -> &str { + self.transactions.front().expect("empty sequence").rpc.as_str() + } + + /// Returns the list of the transactions without the metadata. + pub fn transactions(&self) -> impl Iterator { + self.transactions.iter().map(|tx| tx.tx()) + } + + pub fn fill_sensitive(&mut self, sensitive: &SensitiveScriptSequence) { + self.transactions + .iter_mut() + .enumerate() + .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); + } +} + +/// Converts the `sig` argument into the corresponding file path. +/// +/// This accepts either the signature of the function or the raw calldata. +pub fn sig_to_file_name(sig: &str) -> String { + if let Some((name, _)) = sig.split_once('(') { + // strip until call argument parenthesis + return name.to_string() + } + // assume calldata if `sig` is hex + if let Ok(calldata) = hex::decode(sig) { + // in which case we return the function signature + return hex::encode(&calldata[..SELECTOR_LEN]) + } + + // return sig as is + sig.to_string() +} + +pub fn now() -> Duration { + SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_convert_sig() { + assert_eq!(sig_to_file_name("run()").as_str(), "run"); + assert_eq!( + sig_to_file_name( + "522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266" + ) + .as_str(), + "522bb704" + ); + } +} diff --git a/crates/script-sequence/src/transaction.rs b/crates/script-sequence/src/transaction.rs new file mode 100644 index 0000000000000..7f72a4d30980c --- /dev/null +++ b/crates/script-sequence/src/transaction.rs @@ -0,0 +1,75 @@ +use alloy_primitives::{Address, Bytes, B256}; +use foundry_common::TransactionMaybeSigned; +use revm_inspectors::tracing::types::CallKind; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalContract { + #[serde(rename = "transactionType")] + pub opcode: CallKind, + pub address: Address, + pub init_code: Bytes, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionWithMetadata { + pub hash: Option, + #[serde(rename = "transactionType")] + pub opcode: CallKind, + #[serde(default = "default_string")] + pub contract_name: Option, + #[serde(default = "default_address")] + pub contract_address: Option
, + #[serde(default = "default_string")] + pub function: Option, + #[serde(default = "default_vec_of_strings")] + pub arguments: Option>, + #[serde(skip)] + pub rpc: String, + pub transaction: TransactionMaybeSigned, + pub additional_contracts: Vec, + pub is_fixed_gas_limit: bool, +} + +fn default_string() -> Option { + Some(String::new()) +} + +fn default_address() -> Option
{ + Some(Address::ZERO) +} + +fn default_vec_of_strings() -> Option> { + Some(vec![]) +} + +impl TransactionWithMetadata { + pub fn from_tx_request(transaction: TransactionMaybeSigned) -> Self { + Self { + transaction, + hash: Default::default(), + opcode: Default::default(), + contract_name: Default::default(), + contract_address: Default::default(), + function: Default::default(), + arguments: Default::default(), + is_fixed_gas_limit: Default::default(), + additional_contracts: Default::default(), + rpc: Default::default(), + } + } + + pub fn tx(&self) -> &TransactionMaybeSigned { + &self.transaction + } + + pub fn tx_mut(&mut self) -> &mut TransactionMaybeSigned { + &mut self.transaction + } + + pub fn is_create2(&self) -> bool { + self.opcode == CallKind::Create2 + } +} diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml new file mode 100644 index 0000000000000..99c99c35fa682 --- /dev/null +++ b/crates/script/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "forge-script" +description = "Solidity scripting" + +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] +forge-verify.workspace = true +foundry-cli.workspace = true +foundry-config.workspace = true +foundry-common.workspace = true +foundry-evm.workspace = true +foundry-debugger.workspace = true +foundry-cheatcodes.workspace = true +foundry-wallets.workspace = true +foundry-linking.workspace = true +forge-script-sequence.workspace = true + +serde.workspace = true +eyre.workspace = true +serde_json.workspace = true +dunce.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } +tracing.workspace = true +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +semver.workspace = true +futures.workspace = true +tokio.workspace = true + +itertools.workspace = true +parking_lot.workspace = true +yansi.workspace = true +revm-inspectors.workspace = true +alloy-rpc-types.workspace = true +alloy-json-abi.workspace = true +dialoguer = { version = "0.11", default-features = false } +indicatif = "0.17" + +alloy-signer.workspace = true +alloy-serde.workspace = true +alloy-network.workspace = true +alloy-provider.workspace = true +alloy-chains.workspace = true +alloy-dyn-abi.workspace = true +alloy-primitives.workspace = true +alloy-eips.workspace = true +alloy-consensus.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs new file mode 100644 index 0000000000000..6e828bc8619f7 --- /dev/null +++ b/crates/script/src/broadcast.rs @@ -0,0 +1,460 @@ +use crate::{ + build::LinkedBuildData, progress::ScriptProgress, sequence::ScriptSequenceKind, + verify::BroadcastedState, ScriptArgs, ScriptConfig, +}; +use alloy_chains::Chain; +use alloy_consensus::TxEnvelope; +use alloy_eips::{eip2718::Encodable2718, BlockId}; +use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; +use alloy_primitives::{ + map::{AddressHashMap, AddressHashSet}, + utils::format_units, + Address, TxHash, +}; +use alloy_provider::{utils::Eip1559Estimation, Provider}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use eyre::{bail, Context, Result}; +use forge_verify::provider::VerificationProviderType; +use foundry_cheatcodes::Wallets; +use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; +use foundry_common::{ + provider::{get_http_provider, try_get_http_provider, RetryProvider}, + shell, TransactionMaybeSigned, +}; +use foundry_config::Config; +use futures::{future::join_all, StreamExt}; +use itertools::Itertools; +use std::{cmp::Ordering, sync::Arc}; + +pub async fn estimate_gas>( + tx: &mut WithOtherFields, + provider: &P, + estimate_multiplier: u64, +) -> Result<()> { + // if already set, some RPC endpoints might simply return the gas value that is already + // set in the request and omit the estimate altogether, so we remove it here + tx.gas = None; + + tx.set_gas_limit( + provider.estimate_gas(tx).await.wrap_err("Failed to estimate gas for tx")? * + estimate_multiplier / + 100, + ); + Ok(()) +} + +pub async fn next_nonce( + caller: Address, + provider_url: &str, + block_number: Option, +) -> eyre::Result { + let provider = try_get_http_provider(provider_url) + .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?; + + let block_id = block_number.map_or(BlockId::latest(), BlockId::number); + Ok(provider.get_transaction_count(caller).block_id(block_id).await?) +} + +pub async fn send_transaction( + provider: Arc, + mut kind: SendTransactionKind<'_>, + sequential_broadcast: bool, + is_fixed_gas_limit: bool, + estimate_via_rpc: bool, + estimate_multiplier: u64, +) -> Result { + if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind { + if sequential_broadcast { + let from = tx.from.expect("no sender"); + + let tx_nonce = tx.nonce.expect("no nonce"); + for attempt in 0..5 { + let nonce = provider.get_transaction_count(from).await?; + match nonce.cmp(&tx_nonce) { + Ordering::Greater => { + bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.") + } + Ordering::Less => { + if attempt == 4 { + bail!("After 5 attempts, provider nonce ({nonce}) is still behind expected nonce ({tx_nonce}).") + } + warn!("Expected nonce ({tx_nonce}) is ahead of provider nonce ({nonce}). Retrying in 1 second..."); + tokio::time::sleep(std::time::Duration::from_millis(1000)).await; + } + Ordering::Equal => { + // Nonces are equal, we can proceed + break; + } + } + } + } + + // Chains which use `eth_estimateGas` are being sent sequentially and require their + // gas to be re-estimated right before broadcasting. + if !is_fixed_gas_limit && estimate_via_rpc { + estimate_gas(tx, &provider, estimate_multiplier).await?; + } + } + + let pending = match kind { + SendTransactionKind::Unlocked(tx) => { + debug!("sending transaction from unlocked account {:?}", tx); + + // Submit the transaction + provider.send_transaction(tx).await? + } + SendTransactionKind::Raw(tx, signer) => { + debug!("sending transaction: {:?}", tx); + let signed = tx.build(signer).await?; + + // Submit the raw transaction + provider.send_raw_transaction(signed.encoded_2718().as_ref()).await? + } + SendTransactionKind::Signed(tx) => { + debug!("sending transaction: {:?}", tx); + provider.send_raw_transaction(tx.encoded_2718().as_ref()).await? + } + }; + + Ok(*pending.tx_hash()) +} + +/// How to send a single transaction +#[derive(Clone)] +pub enum SendTransactionKind<'a> { + Unlocked(WithOtherFields), + Raw(WithOtherFields, &'a EthereumWallet), + Signed(TxEnvelope), +} + +/// Represents how to send _all_ transactions +pub enum SendTransactionsKind { + /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. + Unlocked(AddressHashSet), + /// Send a signed transaction via `eth_sendRawTransaction` + Raw(AddressHashMap), +} + +impl SendTransactionsKind { + /// Returns the [`SendTransactionKind`] for the given address + /// + /// Returns an error if no matching signer is found or the address is not unlocked + pub fn for_sender( + &self, + addr: &Address, + tx: WithOtherFields, + ) -> Result> { + match self { + Self::Unlocked(unlocked) => { + if !unlocked.contains(addr) { + bail!("Sender address {:?} is not unlocked", addr) + } + Ok(SendTransactionKind::Unlocked(tx)) + } + Self::Raw(wallets) => { + if let Some(wallet) = wallets.get(addr) { + Ok(SendTransactionKind::Raw(tx, wallet)) + } else { + bail!("No matching signer for {:?} found", addr) + } + } + } + } +} + +/// State after we have bundled all +/// [`TransactionWithMetadata`](forge_script_sequence::TransactionWithMetadata) objects into a +/// single [`ScriptSequenceKind`] object containing one or more script sequences. +pub struct BundledState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: LinkedBuildData, + pub sequence: ScriptSequenceKind, +} + +impl BundledState { + pub async fn wait_for_pending(mut self) -> Result { + let progress = ScriptProgress::default(); + let progress_ref = &progress; + let futs = self + .sequence + .sequences_mut() + .iter_mut() + .enumerate() + .map(|(sequence_idx, sequence)| async move { + let rpc_url = sequence.rpc_url(); + let provider = Arc::new(get_http_provider(rpc_url)); + progress_ref + .wait_for_pending( + sequence_idx, + sequence, + &provider, + self.script_config.config.transaction_timeout, + ) + .await + }) + .collect::>(); + + let errors = join_all(futs).await.into_iter().filter_map(Result::err).collect::>(); + + self.sequence.save(true, false)?; + + if !errors.is_empty() { + return Err(eyre::eyre!("{}", errors.iter().format("\n"))); + } + + Ok(self) + } + + /// Broadcasts transactions from all sequences. + pub async fn broadcast(mut self) -> Result { + let required_addresses = self + .sequence + .sequences() + .iter() + .flat_map(|sequence| { + sequence + .transactions() + .filter(|tx| tx.is_unsigned()) + .map(|tx| tx.from().expect("missing from")) + }) + .collect::(); + + if required_addresses.contains(&Config::DEFAULT_SENDER) { + eyre::bail!( + "You seem to be using Foundry's default sender. Be sure to set your own --sender." + ); + } + + let send_kind = if self.args.unlocked { + SendTransactionsKind::Unlocked(required_addresses.clone()) + } else { + let signers = self.script_wallets.into_multi_wallet().into_signers()?; + let mut missing_addresses = Vec::new(); + + for addr in &required_addresses { + if !signers.contains_key(addr) { + missing_addresses.push(addr); + } + } + + if !missing_addresses.is_empty() { + eyre::bail!( + "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", + missing_addresses, + signers.keys().collect::>() + ); + } + + let signers = signers + .into_iter() + .map(|(addr, signer)| (addr, EthereumWallet::new(signer))) + .collect(); + + SendTransactionsKind::Raw(signers) + }; + + let progress = ScriptProgress::default(); + + for i in 0..self.sequence.sequences().len() { + let mut sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + let provider = Arc::new(try_get_http_provider(sequence.rpc_url())?); + let already_broadcasted = sequence.receipts.len(); + + let seq_progress = progress.get_sequence_progress(i, sequence); + + if already_broadcasted < sequence.transactions.len() { + let is_legacy = Chain::from(sequence.chain).is_legacy() || self.args.legacy; + // Make a one-time gas price estimation + let (gas_price, eip1559_fees) = match ( + is_legacy, + self.args.with_gas_price, + self.args.priority_gas_price, + ) { + (true, Some(gas_price), _) => (Some(gas_price.to()), None), + (true, None, _) => (Some(provider.get_gas_price().await?), None), + (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => ( + None, + Some(Eip1559Estimation { + max_fee_per_gas: max_fee_per_gas.to(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to(), + }), + ), + (false, _, _) => { + let mut fees = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + + if let Some(gas_price) = self.args.with_gas_price { + fees.max_fee_per_gas = gas_price.to(); + } + + if let Some(priority_gas_price) = self.args.priority_gas_price { + fees.max_priority_fee_per_gas = priority_gas_price.to(); + } + + (None, Some(fees)) + } + }; + + // Iterate through transactions, matching the `from` field with the associated + // wallet. Then send the transaction. Panics if we find a unknown `from` + let transactions = sequence + .transactions + .iter() + .skip(already_broadcasted) + .map(|tx_with_metadata| { + let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit; + + let kind = match tx_with_metadata.tx().clone() { + TransactionMaybeSigned::Signed { tx, .. } => { + SendTransactionKind::Signed(tx) + } + TransactionMaybeSigned::Unsigned(mut tx) => { + let from = tx.from.expect("No sender for onchain transaction!"); + + tx.set_chain_id(sequence.chain); + + // Set TxKind::Create explicitly to satisfy `check_reqd_fields` in + // alloy + if tx.to.is_none() { + tx.set_create(); + } + + if let Some(gas_price) = gas_price { + tx.set_gas_price(gas_price); + } else { + let eip1559_fees = eip1559_fees.expect("was set above"); + tx.set_max_priority_fee_per_gas( + eip1559_fees.max_priority_fee_per_gas, + ); + tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); + } + + send_kind.for_sender(&from, tx)? + } + }; + + Ok((kind, is_fixed_gas_limit)) + }) + .collect::>>()?; + + let estimate_via_rpc = + has_different_gas_calc(sequence.chain) || self.args.skip_simulation; + + // We only wait for a transaction receipt before sending the next transaction, if + // there is more than one signer. There would be no way of assuring + // their order otherwise. + // Or if the chain does not support batched transactions (eg. Arbitrum). + // Or if we need to invoke eth_estimateGas before sending transactions. + let sequential_broadcast = estimate_via_rpc || + self.args.slow || + required_addresses.len() != 1 || + !has_batch_support(sequence.chain); + + // We send transactions and wait for receipts in batches. + let batch_size = if sequential_broadcast { 1 } else { self.args.batch_size }; + let mut index = already_broadcasted; + + for (batch_number, batch) in transactions.chunks(batch_size).enumerate() { + let mut pending_transactions = vec![]; + + seq_progress.inner.write().set_status(&format!( + "Sending transactions [{} - {}]", + batch_number * batch_size, + batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1 + )); + for (kind, is_fixed_gas_limit) in batch { + let fut = send_transaction( + provider.clone(), + kind.clone(), + sequential_broadcast, + *is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + ); + pending_transactions.push(fut); + } + + if !pending_transactions.is_empty() { + let mut buffer = futures::stream::iter(pending_transactions).buffered(7); + + while let Some(tx_hash) = buffer.next().await { + let tx_hash = tx_hash.wrap_err("Failed to send transaction")?; + sequence.add_pending(index, tx_hash); + + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + seq_progress.inner.write().tx_sent(tx_hash); + index += 1; + } + + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + progress + .wait_for_pending( + i, + sequence, + &provider, + self.script_config.config.transaction_timeout, + ) + .await? + } + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + } + } + + let (total_gas, total_gas_price, total_paid) = + sequence.receipts.iter().fold((0, 0, 0), |acc, receipt| { + let gas_used = receipt.gas_used; + let gas_price = receipt.effective_gas_price as u64; + (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price) + }); + let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); + let avg_gas_price = format_units(total_gas_price / sequence.receipts.len() as u64, 9) + .unwrap_or_else(|_| "N/A".to_string()); + + seq_progress.inner.write().set_status(&format!( + "Total Paid: {} ETH ({} gas * avg {} gwei)\n", + paid.trim_end_matches('0'), + total_gas, + avg_gas_price.trim_end_matches('0').trim_end_matches('.') + )); + seq_progress.inner.write().finish(); + } + + if !shell::is_json() { + sh_println!("\n\n==========================")?; + sh_println!("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; + } + + Ok(BroadcastedState { + args: self.args, + script_config: self.script_config, + build_data: self.build_data, + sequence: self.sequence, + }) + } + + pub fn verify_preflight_check(&self) -> Result<()> { + for sequence in self.sequence.sequences() { + if self.args.verifier.verifier == VerificationProviderType::Etherscan && + self.script_config + .config + .get_etherscan_api_key(Some(sequence.chain.into())) + .is_none() + { + eyre::bail!("Missing etherscan key for chain {}", sequence.chain); + } + } + + Ok(()) + } +} diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs new file mode 100644 index 0000000000000..4824cee6b8140 --- /dev/null +++ b/crates/script/src/build.rs @@ -0,0 +1,353 @@ +use crate::{ + broadcast::BundledState, execute::LinkedState, multi_sequence::MultiChainSequence, + sequence::ScriptSequenceKind, ScriptArgs, ScriptConfig, +}; +use alloy_primitives::{Bytes, B256}; +use alloy_provider::Provider; +use eyre::{OptionExt, Result}; +use forge_script_sequence::ScriptSequence; +use foundry_cheatcodes::Wallets; +use foundry_common::{ + compile::ProjectCompiler, provider::try_get_http_provider, ContractData, ContractsByArtifact, +}; +use foundry_compilers::{ + artifacts::{BytecodeObject, Libraries}, + compilers::{multi::MultiCompilerLanguage, Language}, + info::ContractInfo, + utils::source_files_iter, + ArtifactId, ProjectCompileOutput, +}; +use foundry_evm::traces::debug::ContractSources; +use foundry_linking::Linker; +use std::{path::PathBuf, str::FromStr, sync::Arc}; + +/// Container for the compiled contracts. +#[derive(Debug)] +pub struct BuildData { + /// Root of the project. + pub project_root: PathBuf, + /// The compiler output. + pub output: ProjectCompileOutput, + /// ID of target contract artifact. + pub target: ArtifactId, +} + +impl BuildData { + pub fn get_linker(&self) -> Linker<'_> { + Linker::new(self.project_root.clone(), self.output.artifact_ids().collect()) + } + + /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to + /// default linking with sender nonce and address. + pub async fn link(self, script_config: &ScriptConfig) -> Result { + let create2_deployer = script_config.evm_opts.create2_deployer; + let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url { + let provider = try_get_http_provider(fork_url)?; + let deployer_code = provider.get_code_at(create2_deployer).await?; + + !deployer_code.is_empty() + } else { + // If --fork-url is not provided, we are just simulating the script. + true + }; + + let known_libraries = script_config.config.libraries_with_remappings()?; + + let maybe_create2_link_output = can_use_create2 + .then(|| { + self.get_linker() + .link_with_create2( + known_libraries.clone(), + create2_deployer, + script_config.config.create2_library_salt, + &self.target, + ) + .ok() + }) + .flatten(); + + let (libraries, predeploy_libs) = if let Some(output) = maybe_create2_link_output { + ( + output.libraries, + ScriptPredeployLibraries::Create2( + output.libs_to_deploy, + script_config.config.create2_library_salt, + ), + ) + } else { + let output = self.get_linker().link_with_nonce_or_address( + known_libraries, + script_config.evm_opts.sender, + script_config.sender_nonce, + [&self.target], + )?; + + (output.libraries, ScriptPredeployLibraries::Default(output.libs_to_deploy)) + }; + + LinkedBuildData::new(libraries, predeploy_libs, self) + } + + /// Links the build data with the given libraries. Expects supplied libraries set being enough + /// to fully link target contract. + pub fn link_with_libraries(self, libraries: Libraries) -> Result { + LinkedBuildData::new(libraries, ScriptPredeployLibraries::Default(Vec::new()), self) + } +} + +#[derive(Debug)] +pub enum ScriptPredeployLibraries { + Default(Vec), + Create2(Vec, B256), +} + +impl ScriptPredeployLibraries { + pub fn libraries_count(&self) -> usize { + match self { + Self::Default(libs) => libs.len(), + Self::Create2(libs, _) => libs.len(), + } + } +} + +/// Container for the linked contracts and their dependencies +#[derive(Debug)] +pub struct LinkedBuildData { + /// Original build data, might be used to relink this object with different libraries. + pub build_data: BuildData, + /// Known fully linked contracts. + pub known_contracts: ContractsByArtifact, + /// Libraries used to link the contracts. + pub libraries: Libraries, + /// Libraries that need to be deployed by sender before script execution. + pub predeploy_libraries: ScriptPredeployLibraries, + /// Source files of the contracts. Used by debugger. + pub sources: ContractSources, +} + +impl LinkedBuildData { + pub fn new( + libraries: Libraries, + predeploy_libraries: ScriptPredeployLibraries, + build_data: BuildData, + ) -> Result { + let sources = ContractSources::from_project_output( + &build_data.output, + &build_data.project_root, + Some(&libraries), + )?; + + let known_contracts = + ContractsByArtifact::new(build_data.get_linker().get_linked_artifacts(&libraries)?); + + Ok(Self { build_data, known_contracts, libraries, predeploy_libraries, sources }) + } + + /// Fetches target bytecode from linked contracts. + pub fn get_target_contract(&self) -> Result<&ContractData> { + self.known_contracts + .get(&self.build_data.target) + .ok_or_eyre("target not found in linked artifacts") + } +} + +/// First state basically containing only inputs of the user. +pub struct PreprocessedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, +} + +impl PreprocessedState { + /// Parses user input and compiles the contracts depending on script target. + /// After compilation, finds exact [ArtifactId] of the target contract. + pub fn compile(self) -> Result { + let Self { args, script_config, script_wallets } = self; + let project = script_config.config.project()?; + + let mut target_name = args.target_contract.clone(); + + // If we've received correct path, use it as target_path + // Otherwise, parse input as : and use the path from the contract info, if + // present. + let target_path = if let Ok(path) = dunce::canonicalize(&args.path) { + path + } else { + let contract = ContractInfo::from_str(&args.path)?; + target_name = Some(contract.name.clone()); + if let Some(path) = contract.path { + dunce::canonicalize(path)? + } else { + project.find_contract_path(contract.name.as_str())? + } + }; + + #[allow(clippy::redundant_clone)] + let sources_to_compile = source_files_iter( + project.paths.sources.as_path(), + MultiCompilerLanguage::FILE_EXTENSIONS, + ) + .chain([target_path.to_path_buf()]); + + let output = ProjectCompiler::new().files(sources_to_compile).compile(&project)?; + + let mut target_id: Option = None; + + // Find target artfifact id by name and path in compilation artifacts. + for (id, contract) in output.artifact_ids().filter(|(id, _)| id.source == target_path) { + if let Some(name) = &target_name { + if id.name != *name { + continue; + } + } else if contract.abi.as_ref().is_none_or(|abi| abi.is_empty()) || + contract.bytecode.as_ref().is_none_or(|b| match &b.object { + BytecodeObject::Bytecode(b) => b.is_empty(), + BytecodeObject::Unlinked(_) => false, + }) + { + // Ignore contracts with empty abi or linked bytecode of length 0 which are + // interfaces/abstract contracts/libraries. + continue; + } + + if let Some(target) = target_id { + // We might have multiple artifacts for the same contract but with different + // solc versions. Their names will have form of {name}.0.X.Y, so we are + // stripping versions off before comparing them. + let target_name = target.name.split('.').next().unwrap(); + let id_name = id.name.split('.').next().unwrap(); + if target_name != id_name { + eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`") + } + } + target_id = Some(id); + } + + let target = target_id.ok_or_eyre("Could not find target contract")?; + + Ok(CompiledState { + args, + script_config, + script_wallets, + build_data: BuildData { output, target, project_root: project.root().clone() }, + }) + } +} + +/// State after we have determined and compiled target contract to be executed. +pub struct CompiledState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: BuildData, +} + +impl CompiledState { + /// Uses provided sender address to compute library addresses and link contracts with them. + pub async fn link(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; + + let build_data = build_data.link(&script_config).await?; + + Ok(LinkedState { args, script_config, script_wallets, build_data }) + } + + /// Tries loading the resumed state from the cache files, skipping simulation stage. + pub async fn resume(self) -> Result { + let chain = if self.args.multi { + None + } else { + let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --fork-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?; + let provider = Arc::new(try_get_http_provider(fork_url)?); + Some(provider.get_chain_id().await?) + }; + + let sequence = match self.try_load_sequence(chain, false) { + Ok(sequence) => sequence, + Err(_) => { + // If the script was simulated, but there was no attempt to broadcast yet, + // try to read the script sequence from the `dry-run/` folder + let mut sequence = self.try_load_sequence(chain, true)?; + + // If sequence was in /dry-run, Update its paths so it is not saved into /dry-run + // this time as we are about to broadcast it. + sequence.update_paths_to_broadcasted( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + )?; + + sequence.save(true, true)?; + sequence + } + }; + + let (args, build_data, script_wallets, script_config) = if !self.args.unlocked { + let mut froms = sequence.sequences().iter().flat_map(|s| { + s.transactions + .iter() + .skip(s.receipts.len()) + .map(|t| t.transaction.from().expect("from is missing in script artifact")) + }); + + let available_signers = self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; + + if !froms.all(|from| available_signers.contains(&from)) { + // IF we are missing required signers, execute script as we might need to collect + // private keys from the execution. + let executed = self.link().await?.prepare_execution().await?.execute().await?; + ( + executed.args, + executed.build_data.build_data, + executed.script_wallets, + executed.script_config, + ) + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + } + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + }; + + // Collect libraries from sequence and link contracts with them. + let libraries = match sequence { + ScriptSequenceKind::Single(ref seq) => Libraries::parse(&seq.libraries)?, + // Library linking is not supported for multi-chain sequences + ScriptSequenceKind::Multi(_) => Libraries::default(), + }; + + let linked_build_data = build_data.link_with_libraries(libraries)?; + + Ok(BundledState { + args, + script_config, + script_wallets, + build_data: linked_build_data, + sequence, + }) + } + + fn try_load_sequence(&self, chain: Option, dry_run: bool) -> Result { + if let Some(chain) = chain { + let sequence = ScriptSequence::load( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + chain, + dry_run, + )?; + Ok(ScriptSequenceKind::Single(sequence)) + } else { + let sequence = MultiChainSequence::load( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + dry_run, + )?; + Ok(ScriptSequenceKind::Multi(sequence)) + } + } +} diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs new file mode 100644 index 0000000000000..863044f548b0e --- /dev/null +++ b/crates/script/src/execute.rs @@ -0,0 +1,517 @@ +use super::{runner::ScriptRunner, JsonResult, NestedValue, ScriptResult}; +use crate::{ + build::{CompiledState, LinkedBuildData}, + simulate::PreSimulationState, + ScriptArgs, ScriptConfig, +}; +use alloy_dyn_abi::FunctionExt; +use alloy_json_abi::{Function, InternalType, JsonAbi}; +use alloy_primitives::{ + map::{HashMap, HashSet}, + Address, Bytes, +}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionInput; +use eyre::{OptionExt, Result}; +use foundry_cheatcodes::Wallets; +use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; +use foundry_common::{ + fmt::{format_token, format_token_raw}, + provider::get_http_provider, + ContractsByArtifact, +}; +use foundry_config::{Config, NamedChain}; +use foundry_debugger::Debugger; +use foundry_evm::{ + decode::decode_console_logs, + inspectors::cheatcodes::BroadcastableTransactions, + traces::{ + decode_trace_arena, + identifier::{SignaturesIdentifier, TraceIdentifiers}, + render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + }, +}; +use futures::future::join_all; +use itertools::Itertools; +use std::path::Path; +use yansi::Paint; + +/// State after linking, contains the linked build data along with library addresses and optional +/// array of libraries that need to be predeployed. +pub struct LinkedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: LinkedBuildData, +} + +/// Container for data we need for execution which can only be obtained after linking stage. +#[derive(Debug)] +pub struct ExecutionData { + /// Function to call. + pub func: Function, + /// Calldata to pass to the target contract. + pub calldata: Bytes, + /// Bytecode of the target contract. + pub bytecode: Bytes, + /// ABI of the target contract. + pub abi: JsonAbi, +} + +impl LinkedState { + /// Given linked and compiled artifacts, prepares data we need for execution. + /// This includes the function to call and the calldata to pass to it. + pub async fn prepare_execution(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; + + let target_contract = build_data.get_target_contract()?; + + let bytecode = target_contract.bytecode().ok_or_eyre("target contract has no bytecode")?; + + let (func, calldata) = args.get_method_and_calldata(&target_contract.abi)?; + + ensure_clean_constructor(&target_contract.abi)?; + + Ok(PreExecutionState { + args, + script_config, + script_wallets, + execution_data: ExecutionData { + func, + calldata, + bytecode: bytecode.clone(), + abi: target_contract.abi.clone(), + }, + build_data, + }) + } +} + +/// Same as [LinkedState], but also contains [ExecutionData]. +#[derive(Debug)] +pub struct PreExecutionState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, +} + +impl PreExecutionState { + /// Executes the script and returns the state after execution. + /// Might require executing script twice in cases when we determine sender from execution. + pub async fn execute(mut self) -> Result { + let mut runner = self + .script_config + .get_runner_with_cheatcodes( + self.build_data.known_contracts.clone(), + self.script_wallets.clone(), + self.args.debug, + self.build_data.build_data.target.clone(), + ) + .await?; + let result = self.execute_with_runner(&mut runner).await?; + + // If we have a new sender from execution, we need to use it to deploy libraries and relink + // contracts. + if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? { + self.script_config.update_sender(new_sender).await?; + + // Rollback to rerun linking with the new sender. + let state = CompiledState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data.build_data, + }; + + return Box::pin(state.link().await?.prepare_execution().await?.execute()).await; + } + + Ok(ExecutedState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_data: self.execution_data, + execution_result: result, + }) + } + + /// Executes the script using the provided runner and returns the [ScriptResult]. + pub async fn execute_with_runner(&self, runner: &mut ScriptRunner) -> Result { + let (address, mut setup_result) = runner.setup( + &self.build_data.predeploy_libraries, + self.execution_data.bytecode.clone(), + needs_setup(&self.execution_data.abi), + self.script_config.sender_nonce, + self.args.broadcast, + self.script_config.evm_opts.fork_url.is_none(), + )?; + + if setup_result.success { + let script_result = runner.script(address, self.execution_data.calldata.clone())?; + + setup_result.success &= script_result.success; + setup_result.gas_used = script_result.gas_used; + setup_result.logs.extend(script_result.logs); + setup_result.traces.extend(script_result.traces); + setup_result.labeled_addresses.extend(script_result.labeled_addresses); + setup_result.returned = script_result.returned; + setup_result.breakpoints = script_result.breakpoints; + + match (&mut setup_result.transactions, script_result.transactions) { + (Some(txs), Some(new_txs)) => { + txs.extend(new_txs); + } + (None, Some(new_txs)) => { + setup_result.transactions = Some(new_txs); + } + _ => {} + } + } + + Ok(setup_result) + } + + /// It finds the deployer from the running script and uses it to predeploy libraries. + /// + /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy + /// them instead. + fn maybe_new_sender( + &self, + transactions: Option<&BroadcastableTransactions>, + ) -> Result> { + let mut new_sender = None; + + if let Some(txs) = transactions { + // If the user passed a `--sender` don't check anything. + if self.build_data.predeploy_libraries.libraries_count() > 0 && + self.args.evm.sender.is_none() + { + for tx in txs.iter() { + if tx.transaction.to().is_none() { + let sender = tx.transaction.from().expect("no sender"); + if let Some(ns) = new_sender { + if sender != ns { + sh_warn!("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; + return Ok(None); + } + } else if sender != self.script_config.evm_opts.sender { + new_sender = Some(sender); + } + } + } + } + } + Ok(new_sender) + } +} + +/// Container for information about RPC-endpoints used during script execution. +pub struct RpcData { + /// Unique list of rpc urls present. + pub total_rpcs: HashSet, + /// If true, one of the transactions did not have a rpc. + pub missing_rpc: bool, +} + +impl RpcData { + /// Iterates over script transactions and collects RPC urls. + fn from_transactions(txs: &BroadcastableTransactions) -> Self { + let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); + let total_rpcs = + txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); + + Self { total_rpcs, missing_rpc } + } + + /// Returns true if script might be multi-chain. + /// Returns false positive in case when missing rpc is the same as the only rpc present. + pub fn is_multi_chain(&self) -> bool { + self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty()) + } + + /// Checks if all RPCs support EIP-3855. Prints a warning if not. + async fn check_shanghai_support(&self) -> Result<()> { + let chain_ids = self.total_rpcs.iter().map(|rpc| async move { + let provider = get_http_provider(rpc); + let id = provider.get_chain_id().await.ok()?; + NamedChain::try_from(id).ok() + }); + + let chains = join_all(chain_ids).await; + let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c)); + if iter.clone().any(|(s, _)| !s) { + let msg = format!( + "\ +EIP-3855 is not supported in one or more of the RPCs used. +Unsupported Chain IDs: {}. +Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly. +For more information, please see https://eips.ethereum.org/EIPS/eip-3855", + iter.filter(|(supported, _)| !supported) + .map(|(_, chain)| *chain as u64) + .format(", ") + ); + sh_warn!("{msg}")?; + } + Ok(()) + } +} + +/// Container for data being collected after execution. +pub struct ExecutionArtifacts { + /// Trace decoder used to decode traces. + pub decoder: CallTraceDecoder, + /// Return values from the execution result. + pub returns: HashMap, + /// Information about RPC endpoints used during script execution. + pub rpc_data: RpcData, +} + +/// State after the script has been executed. +pub struct ExecutedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, + pub execution_result: ScriptResult, +} + +impl ExecutedState { + /// Collects the data we need for simulation and various post-execution tasks. + pub async fn prepare_simulation(self) -> Result { + let returns = self.get_returns()?; + + let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?; + + let mut txs = self.execution_result.transactions.clone().unwrap_or_default(); + + // Ensure that unsigned transactions have both `data` and `input` populated to avoid + // issues with eth_estimateGas and eth_sendTransaction requests. + for tx in &mut txs { + if let Some(req) = tx.transaction.as_unsigned_mut() { + req.input = + TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input()); + } + } + let rpc_data = RpcData::from_transactions(&txs); + + if rpc_data.is_multi_chain() { + sh_warn!("Multi chain deployment is still under development. Use with caution.")?; + if !self.build_data.libraries.is_empty() { + eyre::bail!( + "Multi chain deployment does not support library linking at the moment." + ) + } + } + rpc_data.check_shanghai_support().await?; + + Ok(PreSimulationState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_data: self.execution_data, + execution_result: self.execution_result, + execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data }, + }) + } + + /// Builds [CallTraceDecoder] from the execution result and known contracts. + async fn build_trace_decoder( + &self, + known_contracts: &ContractsByArtifact, + ) -> Result { + let mut decoder = CallTraceDecoderBuilder::new() + .with_labels(self.execution_result.labeled_addresses.clone()) + .with_verbosity(self.script_config.evm_opts.verbosity) + .with_known_contracts(known_contracts) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + self.script_config.config.offline, + )?) + .build(); + + let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan( + &self.script_config.config, + self.script_config.evm_opts.get_remote_chain_id().await, + )?; + + for (_, trace) in &self.execution_result.traces { + decoder.identify(trace, &mut identifier); + } + + Ok(decoder) + } + + /// Collects the return values from the execution result. + fn get_returns(&self) -> Result> { + let mut returns = HashMap::default(); + let returned = &self.execution_result.returned; + let func = &self.execution_data.func; + + match func.abi_decode_output(returned, false) { + Ok(decoded) => { + for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { + let internal_type = + output.internal_type.clone().unwrap_or(InternalType::Other { + contract: None, + ty: "unknown".to_string(), + }); + + let label = if !output.name.is_empty() { + output.name.to_string() + } else { + index.to_string() + }; + + returns.insert( + label, + NestedValue { + internal_type: internal_type.to_string(), + value: format_token_raw(token), + }, + ); + } + } + Err(_) => { + sh_err!("Failed to decode return value: {:x?}", returned)?; + } + } + + Ok(returns) + } +} + +impl PreSimulationState { + pub fn show_json(&self) -> Result<()> { + let result = &self.execution_result; + + let json_result = JsonResult { + logs: decode_console_logs(&result.logs), + returns: &self.execution_artifacts.returns, + result, + }; + let json = serde_json::to_string(&json_result)?; + sh_println!("{json}")?; + + if !self.execution_result.success { + return Err(eyre::eyre!( + "script failed: {}", + &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + )); + } + + Ok(()) + } + + pub async fn show_traces(&self) -> Result<()> { + let verbosity = self.script_config.evm_opts.verbosity; + let func = &self.execution_data.func; + let result = &self.execution_result; + let decoder = &self.execution_artifacts.decoder; + + if !result.success || verbosity > 3 { + if result.traces.is_empty() { + warn!(verbosity, "no traces"); + } + + sh_println!("Traces:")?; + for (kind, trace) in &result.traces { + let should_include = match kind { + TraceKind::Setup => verbosity >= 5, + TraceKind::Execution => verbosity > 3, + _ => false, + } || !result.success; + + if should_include { + let mut trace = trace.clone(); + decode_trace_arena(&mut trace, decoder).await?; + sh_println!("{}", render_trace_arena(&trace))?; + } + } + sh_println!()?; + } + + if result.success { + sh_println!("{}", "Script ran successfully.".green())?; + } + + if self.script_config.evm_opts.fork_url.is_none() { + sh_println!("Gas used: {}", result.gas_used)?; + } + + if result.success && !result.returned.is_empty() { + sh_println!("\n== Return ==")?; + match func.abi_decode_output(&result.returned, false) { + Ok(decoded) => { + for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { + let internal_type = + output.internal_type.clone().unwrap_or(InternalType::Other { + contract: None, + ty: "unknown".to_string(), + }); + + let label = if !output.name.is_empty() { + output.name.to_string() + } else { + index.to_string() + }; + sh_println!( + "{label}: {internal_type} {value}", + label = label.trim_end(), + value = format_token(token) + )?; + } + } + Err(_) => { + sh_err!("{:x?}", (&result.returned))?; + } + } + } + + let console_logs = decode_console_logs(&result.logs); + if !console_logs.is_empty() { + sh_println!("\n== Logs ==")?; + for log in console_logs { + sh_println!(" {log}")?; + } + } + + if !result.success { + return Err(eyre::eyre!( + "script failed: {}", + &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + )); + } + + Ok(()) + } + + pub fn run_debugger(self) -> Result<()> { + self.create_debugger().try_run_tui()?; + Ok(()) + } + + pub fn dump_debugger(self, path: &Path) -> Result<()> { + self.create_debugger().dump_to_file(path)?; + Ok(()) + } + + fn create_debugger(self) -> Debugger { + Debugger::builder() + .traces( + self.execution_result + .traces + .into_iter() + .filter(|(t, _)| t.is_execution()) + .collect(), + ) + .decoder(&self.execution_artifacts.decoder) + .sources(self.build_data.sources) + .breakpoints(self.execution_result.breakpoints) + .build() + } +} diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs new file mode 100644 index 0000000000000..60a68990bbdf7 --- /dev/null +++ b/crates/script/src/lib.rs @@ -0,0 +1,909 @@ +//! # foundry-script +//! +//! Smart contract scripting. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; + +use crate::runner::ScriptRunner; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{ + hex, + map::{AddressHashMap, HashMap}, + Address, Bytes, Log, TxKind, U256, +}; +use alloy_signer::Signer; +use broadcast::next_nonce; +use build::PreprocessedState; +use clap::{Parser, ValueHint}; +use dialoguer::Confirm; +use eyre::{ContextCompat, Result}; +use forge_script_sequence::{AdditionalContract, NestedValue}; +use forge_verify::{RetryArgs, VerifierArgs}; +use foundry_cli::{ + opts::{BuildOpts, GlobalArgs}, + utils::LoadConfig, +}; +use foundry_common::{ + abi::{encode_function_args, get_func}, + evm::{Breakpoints, EvmArgs}, + shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN, +}; +use foundry_compilers::ArtifactId; +use foundry_config::{ + figment, + figment::{ + value::{Dict, Map}, + Metadata, Profile, Provider, + }, + Config, +}; +use foundry_evm::{ + backend::Backend, + executors::ExecutorBuilder, + inspectors::{ + cheatcodes::{BroadcastableTransactions, Wallets}, + CheatsConfig, + }, + opts::EvmOpts, + traces::{TraceMode, Traces}, +}; +use foundry_wallets::MultiWalletOpts; +use serde::Serialize; +use std::path::PathBuf; + +mod broadcast; +mod build; +mod execute; +mod multi_sequence; +mod progress; +mod providers; +mod receipts; +mod runner; +mod sequence; +mod simulate; +mod transaction; +mod verify; + +// Loads project's figment and merges the build cli arguments into it +foundry_config::merge_impl_figment_convert!(ScriptArgs, build, evm); + +/// CLI arguments for `forge script`. +#[derive(Clone, Debug, Default, Parser)] +pub struct ScriptArgs { + // Include global options for users of this struct. + #[command(flatten)] + pub global: GlobalArgs, + + /// The contract you want to run. Either the file path or contract name. + /// + /// If multiple contracts exist in the same file you must specify the target contract with + /// --target-contract. + #[arg(value_hint = ValueHint::FilePath)] + pub path: String, + + /// Arguments to pass to the script function. + pub args: Vec, + + /// The name of the contract you want to run. + #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] + pub target_contract: Option, + + /// The signature of the function you want to call in the contract, or raw calldata. + #[arg(long, short, default_value = "run()")] + pub sig: String, + + /// Max priority fee per gas for EIP1559 transactions. + #[arg( + long, + env = "ETH_PRIORITY_GAS_PRICE", + value_parser = foundry_cli::utils::parse_ether_value, + value_name = "PRICE" + )] + pub priority_gas_price: Option, + + /// Use legacy transactions instead of EIP1559 ones. + /// + /// This is auto-enabled for common networks without EIP1559. + #[arg(long)] + pub legacy: bool, + + /// Broadcasts the transactions. + #[arg(long)] + pub broadcast: bool, + + /// Batch size of transactions. + /// + /// This is ignored and set to 1 if batching is not available or `--slow` is enabled. + #[arg(long, default_value = "100")] + pub batch_size: usize, + + /// Skips on-chain simulation. + #[arg(long)] + pub skip_simulation: bool, + + /// Relative percentage to multiply gas estimates by. + #[arg(long, short, default_value = "130")] + pub gas_estimate_multiplier: u64, + + /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender + #[arg( + long, + conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], + )] + pub unlocked: bool, + + /// Resumes submitting transactions that failed or timed-out previously. + /// + /// It DOES NOT simulate the script again and it expects nonces to have remained the same. + /// + /// Example: If transaction N has a nonce of 22, then the account should have a nonce of 22, + /// otherwise it fails. + #[arg(long)] + pub resume: bool, + + /// If present, --resume or --verify will be assumed to be a multi chain deployment. + #[arg(long)] + pub multi: bool, + + /// Open the script in the debugger. + /// + /// Takes precedence over broadcast. + #[arg(long)] + pub debug: bool, + + /// Dumps all debugger steps to file. + #[arg( + long, + requires = "debug", + value_hint = ValueHint::FilePath, + value_name = "PATH" + )] + pub dump: Option, + + /// Makes sure a transaction is sent, + /// only after its previous one has been confirmed and succeeded. + #[arg(long)] + pub slow: bool, + + /// Disables interactive prompts that might appear when deploying big contracts. + /// + /// For more info on the contract size limit, see EIP-170: + #[arg(long)] + pub non_interactive: bool, + + /// The Etherscan (or equivalent) API key + #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + pub etherscan_api_key: Option, + + /// Verifies all the contracts found in the receipts of a script, if any. + #[arg(long)] + pub verify: bool, + + /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions, either + /// specified in wei, or as a string with a unit type. + /// + /// Examples: 1ether, 10gwei, 0.01ether + #[arg( + long, + env = "ETH_GAS_PRICE", + value_parser = foundry_cli::utils::parse_ether_value, + value_name = "PRICE", + )] + pub with_gas_price: Option, + + /// Timeout to use for broadcasting transactions. + #[arg(long, env = "ETH_TIMEOUT")] + pub timeout: Option, + + #[command(flatten)] + pub build: BuildOpts, + + #[command(flatten)] + pub wallets: MultiWalletOpts, + + #[command(flatten)] + pub evm: EvmArgs, + + #[command(flatten)] + pub verifier: VerifierArgs, + + #[command(flatten)] + pub retry: RetryArgs, +} + +impl ScriptArgs { + pub async fn preprocess(self) -> Result { + let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); + + let (config, mut evm_opts) = self.load_config_and_evm_opts()?; + + if let Some(sender) = self.maybe_load_private_key()? { + evm_opts.sender = sender; + } + + let script_config = ScriptConfig::new(config, evm_opts).await?; + + Ok(PreprocessedState { args: self, script_config, script_wallets }) + } + + /// Executes the script + pub async fn run_script(self) -> Result<()> { + trace!(target: "script", "executing script command"); + + let state = self.preprocess().await?; + let create2_deployer = state.script_config.evm_opts.create2_deployer; + let compiled = state.compile()?; + + // Move from `CompiledState` to `BundledState` either by resuming or executing and + // simulating script. + let bundled = if compiled.args.resume || (compiled.args.verify && !compiled.args.broadcast) + { + compiled.resume().await? + } else { + // Drive state machine to point at which we have everything needed for simulation. + let pre_simulation = compiled + .link() + .await? + .prepare_execution() + .await? + .execute() + .await? + .prepare_simulation() + .await?; + + if pre_simulation.args.debug { + return match pre_simulation.args.dump.clone() { + Some(path) => pre_simulation.dump_debugger(&path), + None => pre_simulation.run_debugger(), + }; + } + + if shell::is_json() { + pre_simulation.show_json()?; + } else { + pre_simulation.show_traces().await?; + } + + // Ensure that we have transactions to simulate/broadcast, otherwise exit early to avoid + // hard error. + if pre_simulation + .execution_result + .transactions + .as_ref() + .is_none_or(|txs| txs.is_empty()) + { + return Ok(()); + } + + // Check if there are any missing RPCs and exit early to avoid hard error. + if pre_simulation.execution_artifacts.rpc_data.missing_rpc { + if !shell::is_json() { + sh_println!("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; + } + + return Ok(()); + } + + pre_simulation.args.check_contract_sizes( + &pre_simulation.execution_result, + &pre_simulation.build_data.known_contracts, + create2_deployer, + )?; + + pre_simulation.fill_metadata().await?.bundle().await? + }; + + // Exit early in case user didn't provide any broadcast/verify related flags. + if !bundled.args.should_broadcast() { + if !shell::is_json() { + if shell::verbosity() >= 4 { + sh_println!("\n=== Transactions that will be broadcast ===\n")?; + bundled.sequence.show_transactions()?; + } + + sh_println!("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; + } + return Ok(()); + } + + // Exit early if something is wrong with verification options. + if bundled.args.verify { + bundled.verify_preflight_check()?; + } + + // Wait for pending txes and broadcast others. + let broadcasted = bundled.wait_for_pending().await?.broadcast().await?; + + if broadcasted.args.verify { + broadcasted.verify().await?; + } + + Ok(()) + } + + /// In case the user has loaded *only* one private-key, we can assume that he's using it as the + /// `--sender` + fn maybe_load_private_key(&self) -> Result> { + let maybe_sender = self + .wallets + .private_keys()? + .filter(|pks| pks.len() == 1) + .map(|pks| pks.first().unwrap().address()); + Ok(maybe_sender) + } + + /// Returns the Function and calldata based on the signature + /// + /// If the `sig` is a valid human-readable function we find the corresponding function in the + /// `abi` If the `sig` is valid hex, we assume it's calldata and try to find the + /// corresponding function by matching the selector, first 4 bytes in the calldata. + /// + /// Note: We assume that the `sig` is already stripped of its prefix, See [`ScriptArgs`] + fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> { + if let Ok(decoded) = hex::decode(&self.sig) { + let selector = &decoded[..SELECTOR_LEN]; + let func = + abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| { + eyre::eyre!( + "Function selector `{}` not found in the ABI", + hex::encode(selector) + ) + })?; + return Ok((func.clone(), decoded.into())); + } + + let func = if self.sig.contains('(') { + let func = get_func(&self.sig)?; + abi.functions() + .find(|&abi_func| abi_func.selector() == func.selector()) + .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))? + } else { + let matching_functions = + abi.functions().filter(|func| func.name == self.sig).collect::>(); + match matching_functions.len() { + 0 => eyre::bail!("Function `{}` not found in the ABI", self.sig), + 1 => matching_functions[0], + 2.. => eyre::bail!( + "Multiple functions with the same name `{}` found in the ABI", + self.sig + ), + } + }; + let data = encode_function_args(func, &self.args)?; + + Ok((func.clone(), data.into())) + } + + /// Checks if the transaction is a deployment with either a size above the `CONTRACT_MAX_SIZE` + /// or specified `code_size_limit`. + /// + /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns + /// the user. + fn check_contract_sizes( + &self, + result: &ScriptResult, + known_contracts: &ContractsByArtifact, + create2_deployer: Address, + ) -> Result<()> { + // (name, &init, &deployed)[] + let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; + + // From artifacts + for (artifact, contract) in known_contracts.iter() { + let Some(bytecode) = contract.bytecode() else { continue }; + let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue }; + bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode)); + } + + // From traces + let create_nodes = result.traces.iter().flat_map(|(_, traces)| { + traces.nodes().iter().filter(|node| node.trace.kind.is_any_create()) + }); + let mut unknown_c = 0usize; + for node in create_nodes { + let init_code = &node.trace.data; + let deployed_code = &node.trace.output; + if !bytecodes.iter().any(|(_, b, _)| *b == init_code.as_ref()) { + bytecodes.push((format!("Unknown{unknown_c}"), init_code, deployed_code)); + unknown_c += 1; + } + continue; + } + + let mut prompt_user = false; + let max_size = match self.evm.env.code_size_limit { + Some(size) => size, + None => CONTRACT_MAX_SIZE, + }; + + for (data, to) in result.transactions.iter().flat_map(|txes| { + txes.iter().filter_map(|tx| { + tx.transaction + .input() + .filter(|data| data.len() > max_size) + .map(|data| (data, tx.transaction.to())) + }) + }) { + let mut offset = 0; + + // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. + if let Some(TxKind::Call(to)) = to { + if to == create2_deployer { + // Size of the salt prefix. + offset = 32; + } else { + continue; + } + } else if let Some(TxKind::Create) = to { + // Pass + } + + // Find artifact with a deployment code same as the data. + if let Some((name, _, deployed_code)) = + bytecodes.iter().find(|(_, init_code, _)| *init_code == &data[offset..]) + { + let deployment_size = deployed_code.len(); + + if deployment_size > max_size { + prompt_user = self.should_broadcast(); + sh_err!( + "`{name}` is above the contract size limit ({deployment_size} > {max_size})." + )?; + } + } + } + + // Only prompt if we're broadcasting and we've not disabled interactivity. + if prompt_user && + !self.non_interactive && + !Confirm::new().with_prompt("Do you wish to continue?".to_string()).interact()? + { + eyre::bail!("User canceled the script."); + } + + Ok(()) + } + + /// We only broadcast transactions if --broadcast or --resume was passed. + fn should_broadcast(&self) -> bool { + self.broadcast || self.resume + } +} + +impl Provider for ScriptArgs { + fn metadata(&self) -> Metadata { + Metadata::named("Script Args Provider") + } + + fn data(&self) -> Result, figment::Error> { + let mut dict = Dict::default(); + if let Some(ref etherscan_api_key) = + self.etherscan_api_key.as_ref().filter(|s| !s.trim().is_empty()) + { + dict.insert( + "etherscan_api_key".to_string(), + figment::value::Value::from(etherscan_api_key.to_string()), + ); + } + if let Some(timeout) = self.timeout { + dict.insert("transaction_timeout".to_string(), timeout.into()); + } + Ok(Map::from([(Config::selected_profile(), dict)])) + } +} + +#[derive(Default, Serialize)] +pub struct ScriptResult { + pub success: bool, + #[serde(rename = "raw_logs")] + pub logs: Vec, + pub traces: Traces, + pub gas_used: u64, + pub labeled_addresses: AddressHashMap, + #[serde(skip)] + pub transactions: Option, + pub returned: Bytes, + pub address: Option
, + #[serde(skip)] + pub breakpoints: Breakpoints, +} + +impl ScriptResult { + pub fn get_created_contracts(&self) -> Vec { + self.traces + .iter() + .flat_map(|(_, traces)| { + traces.nodes().iter().filter_map(|node| { + if node.trace.kind.is_any_create() { + return Some(AdditionalContract { + opcode: node.trace.kind, + address: node.trace.address, + init_code: node.trace.data.clone(), + }); + } + None + }) + }) + .collect() + } +} + +#[derive(Serialize)] +struct JsonResult<'a> { + logs: Vec, + returns: &'a HashMap, + #[serde(flatten)] + result: &'a ScriptResult, +} + +#[derive(Clone, Debug)] +pub struct ScriptConfig { + pub config: Config, + pub evm_opts: EvmOpts, + pub sender_nonce: u64, + /// Maps a rpc url to a backend + pub backends: HashMap, +} + +impl ScriptConfig { + pub async fn new(config: Config, evm_opts: EvmOpts) -> Result { + let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { + next_nonce(evm_opts.sender, fork_url, evm_opts.fork_block_number).await? + } else { + // dapptools compatibility + 1 + }; + + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::default() }) + } + + pub async fn update_sender(&mut self, sender: Address) -> Result<()> { + self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { + next_nonce(sender, fork_url, None).await? + } else { + // dapptools compatibility + 1 + }; + self.evm_opts.sender = sender; + Ok(()) + } + + async fn get_runner(&mut self) -> Result { + self._get_runner(None, false).await + } + + async fn get_runner_with_cheatcodes( + &mut self, + known_contracts: ContractsByArtifact, + script_wallets: Wallets, + debug: bool, + target: ArtifactId, + ) -> Result { + self._get_runner(Some((known_contracts, script_wallets, target)), debug).await + } + + async fn _get_runner( + &mut self, + cheats_data: Option<(ContractsByArtifact, Wallets, ArtifactId)>, + debug: bool, + ) -> Result { + trace!("preparing script runner"); + let env = self.evm_opts.evm_env().await?; + + let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { + match self.backends.get(fork_url) { + Some(db) => db.clone(), + None => { + let fork = self.evm_opts.get_fork(&self.config, env.clone()); + let backend = Backend::spawn(fork); + self.backends.insert(fork_url.clone(), backend.clone()); + backend + } + } + } else { + // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is + // no need to cache it, since there won't be any onchain simulation that we'd need + // to cache the backend for. + Backend::spawn(None) + }; + + // We need to enable tracing to decode contract names: local or external. + let mut builder = ExecutorBuilder::new() + .inspectors(|stack| { + stack + .trace_mode(if debug { TraceMode::Debug } else { TraceMode::Call }) + .odyssey(self.evm_opts.odyssey) + .create2_deployer(self.evm_opts.create2_deployer) + }) + .spec_id(self.config.evm_spec_id()) + .gas_limit(self.evm_opts.gas_limit()) + .legacy_assertions(self.config.legacy_assertions); + + if let Some((known_contracts, script_wallets, target)) = cheats_data { + builder = builder.inspectors(|stack| { + stack + .cheatcodes( + CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(known_contracts), + Some(target), + ) + .into(), + ) + .wallets(script_wallets) + .enable_isolation(self.evm_opts.isolate) + }); + } + + Ok(ScriptRunner::new(builder.build(env, db), self.evm_opts.clone())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use foundry_config::{NamedChain, UnresolvedEnvVarError}; + use std::fs; + use tempfile::tempdir; + + #[test] + fn can_parse_sig() { + let sig = "0x522bb704000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfFFb92266"; + let args = ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--sig", sig]); + assert_eq!(args.sig, sig); + } + + #[test] + fn can_parse_unlocked() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--sender", + "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "--unlocked", + ]); + assert!(args.unlocked); + + let key = U256::ZERO; + let args = ScriptArgs::try_parse_from([ + "foundry-cli", + "Contract.sol", + "--sender", + "0x4e59b44847b379578588920ca78fbf26c0b4956c", + "--unlocked", + "--private-key", + key.to_string().as_str(), + ]); + assert!(args.is_err()); + } + + #[test] + fn can_merge_script_config() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--etherscan-api-key", + "goerli", + ]); + let config = args.load_config().unwrap(); + assert_eq!(config.etherscan_api_key, Some("goerli".to_string())); + } + + #[test] + fn can_parse_verifier_url() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "script", + "script/Test.s.sol:TestScript", + "--fork-url", + "http://localhost:8545", + "--verifier-url", + "http://localhost:3000/api/verify", + "--etherscan-api-key", + "blacksmith", + "--broadcast", + "--verify", + "-vvvvv", + ]); + assert_eq!( + args.verifier.verifier_url, + Some("http://localhost:3000/api/verify".to_string()) + ); + } + + #[test] + fn can_extract_code_size_limit() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "script", + "script/Test.s.sol:TestScript", + "--fork-url", + "http://localhost:8545", + "--broadcast", + "--code-size-limit", + "50000", + ]); + assert_eq!(args.evm.env.code_size_limit, Some(50000)); + } + + #[test] + fn can_extract_script_etherscan_key() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + etherscan_api_key = "mumbai" + + [etherscan] + mumbai = { key = "https://etherscan-mumbai.com/" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "Contract.sol", + "--etherscan-api-key", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + + let config = args.load_config().unwrap(); + let mumbai = config.get_etherscan_api_key(Some(NamedChain::PolygonMumbai.into())); + assert_eq!(mumbai, Some("https://etherscan-mumbai.com/".to_string())); + } + + #[test] + fn can_extract_script_rpc_alias() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [rpc_endpoints] + polygonMumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_CAN_EXTRACT_RPC_ALIAS}" + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "DeployV1", + "--rpc-url", + "polygonMumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + + let err = args.load_config_and_evm_opts().unwrap_err(); + + assert!(err.downcast::().is_ok()); + + std::env::set_var("_CAN_EXTRACT_RPC_ALIAS", "123456"); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!(config.eth_rpc_url, Some("polygonMumbai".to_string())); + assert_eq!( + evm_opts.fork_url, + Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + ); + } + + #[test] + fn can_extract_script_rpc_and_etherscan_alias() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [rpc_endpoints] + mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_EXTRACT_RPC_ALIAS}" + + [etherscan] + mumbai = { key = "${_POLYSCAN_API_KEY}", chain = 80001, url = "https://api-testnet.polygonscan.com/" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "DeployV1", + "--rpc-url", + "mumbai", + "--etherscan-api-key", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + let err = args.load_config_and_evm_opts().unwrap_err(); + + assert!(err.downcast::().is_ok()); + + std::env::set_var("_EXTRACT_RPC_ALIAS", "123456"); + std::env::set_var("_POLYSCAN_API_KEY", "polygonkey"); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!(config.eth_rpc_url, Some("mumbai".to_string())); + assert_eq!( + evm_opts.fork_url, + Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + ); + let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); + assert_eq!(etherscan, Some("polygonkey".to_string())); + let etherscan = config.get_etherscan_api_key(None); + assert_eq!(etherscan, Some("polygonkey".to_string())); + } + + #[test] + fn can_extract_script_rpc_and_sole_etherscan_alias() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [rpc_endpoints] + mumbai = "https://polygon-mumbai.g.alchemy.com/v2/${_SOLE_EXTRACT_RPC_ALIAS}" + + [etherscan] + mumbai = { key = "${_SOLE_POLYSCAN_API_KEY}" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + let args = ScriptArgs::parse_from([ + "foundry-cli", + "DeployV1", + "--rpc-url", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + let err = args.load_config_and_evm_opts().unwrap_err(); + + assert!(err.downcast::().is_ok()); + + std::env::set_var("_SOLE_EXTRACT_RPC_ALIAS", "123456"); + std::env::set_var("_SOLE_POLYSCAN_API_KEY", "polygonkey"); + let (config, evm_opts) = args.load_config_and_evm_opts().unwrap(); + assert_eq!( + evm_opts.fork_url, + Some("https://polygon-mumbai.g.alchemy.com/v2/123456".to_string()) + ); + let etherscan = config.get_etherscan_api_key(Some(80001u64.into())); + assert_eq!(etherscan, Some("polygonkey".to_string())); + let etherscan = config.get_etherscan_api_key(None); + assert_eq!(etherscan, Some("polygonkey".to_string())); + } + + // + #[test] + fn test_5923() { + let args = + ScriptArgs::parse_from(["foundry-cli", "DeployV1", "--priority-gas-price", "100"]); + assert!(args.priority_gas_price.is_some()); + } + + // + #[test] + fn test_5910() { + let args = ScriptArgs::parse_from([ + "foundry-cli", + "--broadcast", + "--with-gas-price", + "0", + "SolveTutorial", + ]); + assert!(args.with_gas_price.unwrap().is_zero()); + } +} diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs new file mode 100644 index 0000000000000..e0fd4d1bc7e68 --- /dev/null +++ b/crates/script/src/multi_sequence.rs @@ -0,0 +1,166 @@ +use eyre::{ContextCompat, Result, WrapErr}; +use forge_script_sequence::{ + now, sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR, +}; +use foundry_common::{fs, shell}; +use foundry_compilers::ArtifactId; +use foundry_config::Config; +use serde::{Deserialize, Serialize}; +use std::{ + io::{BufWriter, Write}, + path::PathBuf, +}; + +/// Holds the sequences of multiple chain deployments. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct MultiChainSequence { + pub deployments: Vec, + #[serde(skip)] + pub path: PathBuf, + #[serde(skip)] + pub sensitive_path: PathBuf, + pub timestamp: u64, +} + +/// Sensitive values from script sequences. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveMultiChainSequence { + pub deployments: Vec, +} + +impl SensitiveMultiChainSequence { + fn from_multi_sequence(sequence: MultiChainSequence) -> Self { + Self { + deployments: sequence.deployments.into_iter().map(|sequence| sequence.into()).collect(), + } + } +} + +impl MultiChainSequence { + pub fn new( + deployments: Vec, + sig: &str, + target: &ArtifactId, + config: &Config, + dry_run: bool, + ) -> Result { + let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; + + Ok(Self { deployments, path, sensitive_path, timestamp: now().as_secs() }) + } + + /// Gets paths in the formats + /// ./broadcast/multi/contract_filename[-timestamp]/sig.json and + /// ./cache/multi/contract_filename[-timestamp]/sig.json + pub fn get_paths( + config: &Config, + sig: &str, + target: &ArtifactId, + dry_run: bool, + ) -> Result<(PathBuf, PathBuf)> { + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); + let mut common = PathBuf::new(); + + common.push("multi"); + + if dry_run { + common.push(DRY_RUN_DIR); + } + + let target_fname = target + .source + .file_name() + .wrap_err_with(|| format!("No filename for {:?}", target.source))? + .to_string_lossy(); + + common.push(format!("{target_fname}-latest")); + + broadcast.push(common.clone()); + cache.push(common); + + fs::create_dir_all(&broadcast)?; + fs::create_dir_all(&cache)?; + + let filename = format!("{}.json", sig_to_file_name(sig)); + + broadcast.push(filename.clone()); + cache.push(filename); + + Ok((broadcast, cache)) + } + + /// Loads the sequences for the multi chain deployment. + pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result { + let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; + let mut sequence: Self = foundry_compilers::utils::read_json_file(&path) + .wrap_err("Multi-chain deployment not found.")?; + let sensitive_sequence: SensitiveMultiChainSequence = + foundry_compilers::utils::read_json_file(&sensitive_path) + .wrap_err("Multi-chain deployment sensitive details not found.")?; + + sequence.deployments.iter_mut().enumerate().for_each(|(i, sequence)| { + sequence.fill_sensitive(&sensitive_sequence.deployments[i]); + }); + + sequence.path = path; + sequence.sensitive_path = sensitive_path; + + Ok(sequence) + } + + /// Saves the transactions as file if it's a standalone deployment. + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); + + self.timestamp = now().as_secs(); + + let sensitive_sequence = SensitiveMultiChainSequence::from_multi_sequence(self.clone()); + + // broadcast writes + //../Contract-latest/run.json + let mut writer = BufWriter::new(fs::create_file(&self.path)?); + serde_json::to_writer_pretty(&mut writer, &self)?; + writer.flush()?; + + if save_ts { + //../Contract-[timestamp]/run.json + let path = self.path.to_string_lossy(); + let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); + fs::create_dir_all(file.parent().unwrap())?; + fs::copy(&self.path, &file)?; + } + + // cache writes + //../Contract-latest/run.json + let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); + serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?; + writer.flush()?; + + if save_ts { + //../Contract-[timestamp]/run.json + let path = self.sensitive_path.to_string_lossy(); + let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); + fs::create_dir_all(file.parent().unwrap())?; + fs::copy(&self.sensitive_path, &file)?; + } + + if !silent { + if shell::is_json() { + sh_println!( + "{}", + serde_json::json!({ + "status": "success", + "transactions": self.path.display().to_string(), + "sensitive": self.sensitive_path.display().to_string(), + }) + )?; + } else { + sh_println!("\nTransactions saved to: {}\n", self.path.display())?; + sh_println!("Sensitive details saved to: {}\n", self.sensitive_path.display())?; + } + } + + Ok(()) + } +} diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs new file mode 100644 index 0000000000000..bc23fcf677052 --- /dev/null +++ b/crates/script/src/progress.rs @@ -0,0 +1,261 @@ +use crate::receipts::{check_tx_status, format_receipt, TxStatus}; +use alloy_chains::Chain; +use alloy_primitives::{ + map::{B256HashMap, HashMap}, + B256, +}; +use eyre::Result; +use forge_script_sequence::ScriptSequence; +use foundry_cli::utils::init_progress; +use foundry_common::{provider::RetryProvider, shell}; +use futures::StreamExt; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use parking_lot::RwLock; +use std::{fmt::Write, sync::Arc, time::Duration}; +use yansi::Paint; + +/// State of [ProgressBar]s displayed for the given [ScriptSequence]. +#[derive(Debug)] +pub struct SequenceProgressState { + /// The top spinner with content of the format "Sequence #{id} on {network} | {status}"" + top_spinner: ProgressBar, + /// Progress bar with the count of transactions. + txs: ProgressBar, + /// Progress var with the count of confirmed transactions. + receipts: ProgressBar, + /// Standalone spinners for pending transactions. + tx_spinners: B256HashMap, + /// Copy of the main [MultiProgress] instance. + multi: MultiProgress, +} + +impl SequenceProgressState { + pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + let mut state = if shell::is_quiet() || shell::is_json() { + let top_spinner = ProgressBar::hidden(); + let txs = ProgressBar::hidden(); + let receipts = ProgressBar::hidden(); + + Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi } + } else { + let mut template = "{spinner:.green}".to_string(); + write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain)) + .unwrap(); + template.push_str("{msg}"); + + let top_spinner = ProgressBar::new_spinner().with_style( + ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅"), + ); + let top_spinner = multi.add(top_spinner); + + let txs = multi.insert_after( + &top_spinner, + init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "), + ); + + let receipts = multi.insert_after( + &txs, + init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "), + ); + + top_spinner.enable_steady_tick(Duration::from_millis(100)); + txs.enable_steady_tick(Duration::from_millis(1000)); + receipts.enable_steady_tick(Duration::from_millis(1000)); + + txs.set_position(sequence.receipts.len() as u64); + receipts.set_position(sequence.receipts.len() as u64); + + Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi } + }; + + for tx_hash in sequence.pending.iter() { + state.tx_sent(*tx_hash); + } + + state + } + + /// Called when a new transaction is sent. Displays a spinner with a hash of the transaction and + /// advances the sent transactions progress bar. + pub fn tx_sent(&mut self, tx_hash: B256) { + // Avoid showing more than 10 spinners. + if self.tx_spinners.len() < 10 { + let spinner = if shell::is_quiet() || shell::is_json() { + ProgressBar::hidden() + } else { + let spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template(" {spinner:.green} {msg}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), + ) + .with_message(format!("{} {}", "[Pending]".yellow(), tx_hash)); + + let spinner = self.multi.insert_before(&self.txs, spinner); + spinner.enable_steady_tick(Duration::from_millis(100)); + spinner + }; + + self.tx_spinners.insert(tx_hash, spinner); + } + self.txs.inc(1); + } + + /// Removes the pending transaction spinner and advances confirmed transactions progress bar. + pub fn finish_tx_spinner(&mut self, tx_hash: B256) { + if let Some(spinner) = self.tx_spinners.remove(&tx_hash) { + spinner.finish_and_clear(); + } + self.receipts.inc(1); + } + + /// Same as finish_tx_spinner but also prints a message to stdout above all other progress bars. + pub fn finish_tx_spinner_with_msg(&mut self, tx_hash: B256, msg: &str) -> std::io::Result<()> { + self.finish_tx_spinner(tx_hash); + + if !(shell::is_quiet() || shell::is_json()) { + self.multi.println(msg)?; + } + + Ok(()) + } + + /// Sets status for the current sequence progress. + pub fn set_status(&mut self, status: &str) { + self.top_spinner.set_message(format!(" | {status}")); + } + + /// Hides transactions and receipts progress bar, leaving only top line with the latest set + /// status. + pub fn finish(&self) { + self.top_spinner.finish(); + self.txs.finish_and_clear(); + self.receipts.finish_and_clear(); + } +} + +/// Clonable wrapper around [SequenceProgressState]. +#[derive(Debug, Clone)] +pub struct SequenceProgress { + pub inner: Arc>, +} + +impl SequenceProgress { + pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + Self { + inner: Arc::new(RwLock::new(SequenceProgressState::new(sequence_idx, sequence, multi))), + } + } +} + +/// Container for multiple [SequenceProgress] instances keyed by sequence index. +#[derive(Debug, Clone, Default)] +pub struct ScriptProgress { + state: Arc>>, + multi: MultiProgress, +} + +impl ScriptProgress { + /// Returns a [SequenceProgress] instance for the given sequence index. If it doesn't exist, + /// creates one. + pub fn get_sequence_progress( + &self, + sequence_idx: usize, + sequence: &ScriptSequence, + ) -> SequenceProgress { + if let Some(progress) = self.state.read().get(&sequence_idx) { + return progress.clone(); + } + let progress = SequenceProgress::new(sequence_idx, sequence, self.multi.clone()); + self.state.write().insert(sequence_idx, progress.clone()); + progress + } + + /// Traverses a set of pendings and either finds receipts, or clears them from + /// the deployment sequence. + /// + /// For each `tx_hash`, we check if it has confirmed. If it has + /// confirmed, we push the receipt (if successful) or push an error (if + /// revert). If the transaction has not confirmed, but can be found in the + /// node's mempool, we wait for its receipt to be available. If the transaction + /// has not confirmed, and cannot be found in the mempool, we remove it from + /// the `deploy_sequence.pending` vector so that it will be rebroadcast in + /// later steps. + pub async fn wait_for_pending( + &self, + sequence_idx: usize, + deployment_sequence: &mut ScriptSequence, + provider: &RetryProvider, + timeout: u64, + ) -> Result<()> { + if deployment_sequence.pending.is_empty() { + return Ok(()); + } + + let count = deployment_sequence.pending.len(); + let seq_progress = self.get_sequence_progress(sequence_idx, deployment_sequence); + + seq_progress.inner.write().set_status("Waiting for pending transactions"); + + trace!("Checking status of {count} pending transactions"); + + let futs = deployment_sequence + .pending + .clone() + .into_iter() + .map(|tx| check_tx_status(provider, tx, timeout)); + let mut tasks = futures::stream::iter(futs).buffer_unordered(10); + + let mut errors: Vec = vec![]; + + while let Some((tx_hash, result)) = tasks.next().await { + match result { + Err(err) => { + errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}")); + + seq_progress.inner.write().finish_tx_spinner(tx_hash); + } + Ok(TxStatus::Dropped) => { + // We want to remove it from pending so it will be re-broadcast. + deployment_sequence.remove_pending(tx_hash); + errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}")); + + seq_progress.inner.write().finish_tx_spinner(tx_hash); + } + Ok(TxStatus::Success(receipt)) => { + trace!(tx_hash=?tx_hash, "received tx receipt"); + + let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; + + deployment_sequence.remove_pending(receipt.transaction_hash); + deployment_sequence.add_receipt(receipt); + } + Ok(TxStatus::Revert(receipt)) => { + // consider: + // if this is not removed from pending, then the script becomes + // un-resumable. Is this desirable on reverts? + warn!(tx_hash=?tx_hash, "Transaction Failure"); + deployment_sequence.remove_pending(receipt.transaction_hash); + + let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; + + errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); + } + } + } + + // print any errors + if !errors.is_empty() { + let mut error_msg = errors.join("\n"); + if !deployment_sequence.pending.is_empty() { + error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting + the transactions." + } + eyre::bail!(error_msg); + } + + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/script/providers.rs b/crates/script/src/providers.rs similarity index 70% rename from crates/forge/bin/cmd/script/providers.rs rename to crates/script/src/providers.rs index df05b852c1a39..eb6ea9319a77e 100644 --- a/crates/forge/bin/cmd/script/providers.rs +++ b/crates/script/src/providers.rs @@ -1,17 +1,14 @@ -use ethers::prelude::{Http, Middleware, Provider, RetryClient, U256}; +use alloy_primitives::map::{hash_map::Entry, HashMap}; +use alloy_provider::{utils::Eip1559Estimation, Provider}; use eyre::{Result, WrapErr}; -use foundry_common::{get_http_provider, RpcUrl}; +use foundry_common::provider::{get_http_provider, RetryProvider}; use foundry_config::Chain; -use std::{ - collections::{hash_map::Entry, HashMap}, - ops::Deref, - sync::Arc, -}; +use std::{ops::Deref, sync::Arc}; /// Contains a map of RPC urls to single instances of [`ProviderInfo`]. #[derive(Default)] pub struct ProvidersManager { - pub inner: HashMap, + pub inner: HashMap, } impl ProvidersManager { @@ -32,7 +29,7 @@ impl ProvidersManager { } impl Deref for ProvidersManager { - type Target = HashMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner @@ -42,25 +39,24 @@ impl Deref for ProvidersManager { /// Holds related metadata to each provider RPC. #[derive(Debug)] pub struct ProviderInfo { - pub provider: Arc>>, + pub provider: Arc, pub chain: u64, pub gas_price: GasPrice, - pub is_legacy: bool, } /// Represents the outcome of a gas price request #[derive(Debug)] pub enum GasPrice { - Legacy(Result), - EIP1559(Result<(U256, U256)>), + Legacy(Result), + EIP1559(Result), } impl ProviderInfo { - pub async fn new(rpc: &str, mut is_legacy: bool) -> Result { + pub async fn new(rpc: &str, mut is_legacy: bool) -> Result { let provider = Arc::new(get_http_provider(rpc)); - let chain = provider.get_chainid().await?.as_u64(); + let chain = provider.get_chain_id().await?; - if let Chain::Named(chain) = Chain::from(chain) { + if let Some(chain) = Chain::from(chain).named() { is_legacy |= chain.is_legacy(); }; @@ -74,14 +70,14 @@ impl ProviderInfo { ) }; - Ok(ProviderInfo { provider, chain, gas_price, is_legacy }) + Ok(Self { provider, chain, gas_price }) } /// Returns the gas price to use - pub fn gas_price(&self) -> Result { + pub fn gas_price(&self) -> Result { let res = match &self.gas_price { GasPrice::Legacy(res) => res.as_ref(), - GasPrice::EIP1559(res) => res.as_ref().map(|res| &res.0), + GasPrice::EIP1559(res) => res.as_ref().map(|res| &res.max_fee_per_gas), }; match res { Ok(val) => Ok(*val), diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs new file mode 100644 index 0000000000000..605cdf9ddb0de --- /dev/null +++ b/crates/script/src/receipts.rs @@ -0,0 +1,110 @@ +use alloy_chains::Chain; +use alloy_network::AnyTransactionReceipt; +use alloy_primitives::{utils::format_units, TxHash, U256}; +use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError}; +use eyre::{eyre, Result}; +use foundry_common::{provider::RetryProvider, retry, retry::RetryError, shell}; +use std::time::Duration; + +/// Convenience enum for internal signalling of transaction status +pub enum TxStatus { + Dropped, + Success(AnyTransactionReceipt), + Revert(AnyTransactionReceipt), +} + +impl From for TxStatus { + fn from(receipt: AnyTransactionReceipt) -> Self { + if !receipt.inner.inner.inner.receipt.status.coerce_status() { + Self::Revert(receipt) + } else { + Self::Success(receipt) + } + } +} + +/// Checks the status of a txhash by first polling for a receipt, then for +/// mempool inclusion. Returns the tx hash, and a status +pub async fn check_tx_status( + provider: &RetryProvider, + hash: TxHash, + timeout: u64, +) -> (TxHash, Result) { + let result = retry::Retry::new_no_delay(3) + .run_async_until_break(|| async { + match PendingTransactionBuilder::new(provider.clone(), hash) + .with_timeout(Some(Duration::from_secs(timeout))) + .get_receipt() + .await + { + Ok(receipt) => Ok(receipt.into()), + Err(e) => match provider.get_transaction_by_hash(hash).await { + Ok(_) => match e { + PendingTransactionError::TxWatcher(WatchTxError::Timeout) => { + Err(RetryError::Continue(eyre!( + "tx is still known to the node, waiting for receipt" + ))) + } + _ => Err(RetryError::Retry(e.into())), + }, + Err(_) => Ok(TxStatus::Dropped), + }, + } + }) + .await; + + (hash, result) +} + +/// Prints parts of the receipt to stdout +pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { + let gas_used = receipt.gas_used; + let gas_price = receipt.effective_gas_price; + let block_number = receipt.block_number.unwrap_or_default(); + let success = receipt.inner.inner.inner.receipt.status.coerce_status(); + + if shell::is_json() { + let _ = sh_println!( + "{}", + serde_json::json!({ + "chain": chain, + "status": if success { + "success" + } else { + "failed" + }, + "tx_hash": receipt.transaction_hash, + "contract_address": receipt.contract_address.map(|addr| addr.to_string()), + "block_number": block_number, + "gas_used": gas_used, + "gas_price": gas_price, + }) + ); + + String::new() + } else { + format!( + "\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n", + status = if success { "✅ [Success]" } else { "❌ [Failed]" }, + tx_hash = receipt.transaction_hash, + contract_address = if let Some(addr) = &receipt.contract_address { + format!("\nContract Address: {}", addr.to_checksum(None)) + } else { + String::new() + }, + gas = if gas_price == 0 { + format!("Gas Used: {gas_used}") + } else { + let paid = format_units((gas_used as u128).saturating_mul(gas_price), 18) + .unwrap_or_else(|_| "N/A".into()); + let gas_price = + format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); + format!( + "Paid: {} ETH ({gas_used} gas * {} gwei)", + paid.trim_end_matches('0'), + gas_price.trim_end_matches('0').trim_end_matches('.') + ) + }, + ) + } +} diff --git a/crates/script/src/runner.rs b/crates/script/src/runner.rs new file mode 100644 index 0000000000000..2d58e381f6356 --- /dev/null +++ b/crates/script/src/runner.rs @@ -0,0 +1,396 @@ +use super::ScriptResult; +use crate::build::ScriptPredeployLibraries; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_rpc_types::TransactionRequest; +use eyre::Result; +use foundry_cheatcodes::BroadcastableTransaction; +use foundry_config::Config; +use foundry_evm::{ + constants::CALLER, + executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, + opts::EvmOpts, + revm::interpreter::{return_ok, InstructionResult}, + traces::{TraceKind, Traces}, +}; +use std::collections::VecDeque; + +/// Drives script execution +#[derive(Debug)] +pub struct ScriptRunner { + pub executor: Executor, + pub evm_opts: EvmOpts, +} + +impl ScriptRunner { + pub fn new(executor: Executor, evm_opts: EvmOpts) -> Self { + Self { executor, evm_opts } + } + + /// Deploys the libraries and broadcast contract. Calls setUp method if requested. + pub fn setup( + &mut self, + libraries: &ScriptPredeployLibraries, + code: Bytes, + setup: bool, + sender_nonce: u64, + is_broadcast: bool, + need_create2_deployer: bool, + ) -> Result<(Address, ScriptResult)> { + trace!(target: "script", "executing setUP()"); + + if !is_broadcast { + if self.evm_opts.sender == Config::DEFAULT_SENDER { + // We max out their balance so that they can deploy and make calls. + self.executor.set_balance(self.evm_opts.sender, U256::MAX)?; + } + + if need_create2_deployer { + self.executor.deploy_create2_deployer()?; + } + } + + self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?; + + // We max out their balance so that they can deploy and make calls. + self.executor.set_balance(CALLER, U256::MAX)?; + + let mut library_transactions = VecDeque::new(); + let mut traces = Traces::default(); + + // Deploy libraries + match libraries { + ScriptPredeployLibraries::Default(libraries) => libraries.iter().for_each(|code| { + let result = self + .executor + .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None) + .expect("couldn't deploy library") + .raw; + + if let Some(deploy_traces) = result.traces { + traces.push((TraceKind::Deployment, deploy_traces)); + } + + library_transactions.push_back(BroadcastableTransaction { + rpc: self.evm_opts.fork_url.clone(), + transaction: TransactionRequest { + from: Some(self.evm_opts.sender), + input: code.clone().into(), + nonce: Some(sender_nonce + library_transactions.len() as u64), + ..Default::default() + } + .into(), + }) + }), + ScriptPredeployLibraries::Create2(libraries, salt) => { + let create2_deployer = self.executor.create2_deployer(); + for library in libraries { + let address = create2_deployer.create2_from_code(salt, library.as_ref()); + // Skip if already deployed + if !self.executor.is_empty_code(address)? { + continue; + } + let calldata = [salt.as_ref(), library.as_ref()].concat(); + let result = self + .executor + .transact_raw( + self.evm_opts.sender, + create2_deployer, + calldata.clone().into(), + U256::from(0), + ) + .expect("couldn't deploy library"); + + if let Some(deploy_traces) = result.traces { + traces.push((TraceKind::Deployment, deploy_traces)); + } + + library_transactions.push_back(BroadcastableTransaction { + rpc: self.evm_opts.fork_url.clone(), + transaction: TransactionRequest { + from: Some(self.evm_opts.sender), + input: calldata.into(), + nonce: Some(sender_nonce + library_transactions.len() as u64), + to: Some(TxKind::Call(create2_deployer)), + ..Default::default() + } + .into(), + }); + } + + // Sender nonce is not incremented when performing CALLs. We need to manually + // increase it. + self.executor.set_nonce( + self.evm_opts.sender, + sender_nonce + library_transactions.len() as u64, + )?; + } + }; + + let address = CALLER.create(self.executor.get_nonce(CALLER)?); + + // Set the contracts initial balance before deployment, so it is available during the + // construction + self.executor.set_balance(address, self.evm_opts.initial_balance)?; + + // HACK: if the current sender is the default script sender (which is a default value), we + // set its nonce to a very large value before deploying the script contract. This + // ensures that the nonce increase during this CREATE does not affect deployment + // addresses of contracts that are deployed in the script, Otherwise, we'd have a + // nonce mismatch during script execution and onchain simulation, potentially + // resulting in weird errors like . + let prev_sender_nonce = self.executor.get_nonce(self.evm_opts.sender)?; + if self.evm_opts.sender == CALLER { + self.executor.set_nonce(self.evm_opts.sender, u64::MAX / 2)?; + } + + // Deploy an instance of the contract + let DeployResult { + address, + raw: RawCallResult { mut logs, traces: constructor_traces, .. }, + } = self + .executor + .deploy(CALLER, code, U256::ZERO, None) + .map_err(|err| eyre::eyre!("Failed to deploy script:\n{}", err))?; + + if self.evm_opts.sender == CALLER { + self.executor.set_nonce(self.evm_opts.sender, prev_sender_nonce)?; + } + + traces.extend(constructor_traces.map(|traces| (TraceKind::Deployment, traces))); + + // Optionally call the `setUp` function + let (success, gas_used, labeled_addresses, transactions) = if !setup { + self.executor.backend_mut().set_test_contract(address); + (true, 0, Default::default(), Some(library_transactions)) + } else { + match self.executor.setup(Some(self.evm_opts.sender), address, None) { + Ok(RawCallResult { + reverted, + traces: setup_traces, + labels, + logs: setup_logs, + gas_used, + transactions: setup_transactions, + .. + }) => { + traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); + logs.extend_from_slice(&setup_logs); + + if let Some(txs) = setup_transactions { + library_transactions.extend(txs); + } + + (!reverted, gas_used, labels, Some(library_transactions)) + } + Err(EvmError::Execution(err)) => { + let RawCallResult { + reverted, + traces: setup_traces, + labels, + logs: setup_logs, + gas_used, + transactions, + .. + } = err.raw; + traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); + logs.extend_from_slice(&setup_logs); + + if let Some(txs) = transactions { + library_transactions.extend(txs); + } + + (!reverted, gas_used, labels, Some(library_transactions)) + } + Err(e) => return Err(e.into()), + } + }; + + Ok(( + address, + ScriptResult { + returned: Bytes::new(), + success, + gas_used, + labeled_addresses, + transactions, + logs, + traces, + address: None, + ..Default::default() + }, + )) + } + + /// Executes the method that will collect all broadcastable transactions. + pub fn script(&mut self, address: Address, calldata: Bytes) -> Result { + self.call(self.evm_opts.sender, address, calldata, U256::ZERO, None, false) + } + + /// Runs a broadcastable transaction locally and persists its state. + pub fn simulate( + &mut self, + from: Address, + to: Option
, + calldata: Option, + value: Option, + authorization_list: Option>, + ) -> Result { + if let Some(to) = to { + self.call( + from, + to, + calldata.unwrap_or_default(), + value.unwrap_or(U256::ZERO), + authorization_list, + true, + ) + } else if to.is_none() { + let res = self.executor.deploy( + from, + calldata.expect("No data for create transaction"), + value.unwrap_or(U256::ZERO), + None, + ); + let (address, RawCallResult { gas_used, logs, traces, .. }) = match res { + Ok(DeployResult { address, raw }) => (address, raw), + Err(EvmError::Execution(err)) => { + let ExecutionErr { raw, reason } = *err; + sh_err!("Failed with `{reason}`:\n")?; + (Address::ZERO, raw) + } + Err(e) => eyre::bail!("Failed deploying contract: {e:?}"), + }; + + Ok(ScriptResult { + returned: Bytes::new(), + success: address != Address::ZERO, + gas_used, + logs, + // Manually adjust gas for the trace to add back the stipend/real used gas + traces: traces + .map(|traces| vec![(TraceKind::Execution, traces)]) + .unwrap_or_default(), + address: Some(address), + ..Default::default() + }) + } else { + eyre::bail!("ENS not supported."); + } + } + + /// Executes the call + /// + /// This will commit the changes if `commit` is true. + /// + /// This will return _estimated_ gas instead of the precise gas the call would consume, so it + /// can be used as `gas_limit`. + fn call( + &mut self, + from: Address, + to: Address, + calldata: Bytes, + value: U256, + authorization_list: Option>, + commit: bool, + ) -> Result { + let mut res = if let Some(authorization_list) = authorization_list { + self.executor.call_raw_with_authorization( + from, + to, + calldata.clone(), + value, + authorization_list, + )? + } else { + self.executor.call_raw(from, to, calldata.clone(), value)? + }; + let mut gas_used = res.gas_used; + + // We should only need to calculate realistic gas costs when preparing to broadcast + // something. This happens during the onchain simulation stage, where we commit each + // collected transactions. + // + // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921 + if commit { + gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?; + res = self.executor.transact_raw(from, to, calldata, value)?; + } + + let RawCallResult { result, reverted, logs, traces, labels, transactions, .. } = res; + let breakpoints = res.cheatcodes.map(|cheats| cheats.breakpoints).unwrap_or_default(); + + Ok(ScriptResult { + returned: result, + success: !reverted, + gas_used, + logs, + traces: traces + .map(|traces| { + // Manually adjust gas for the trace to add back the stipend/real used gas + + vec![(TraceKind::Execution, traces)] + }) + .unwrap_or_default(), + labeled_addresses: labels, + transactions, + address: None, + breakpoints, + }) + } + + /// The executor will return the _exact_ gas value this transaction consumed, setting this value + /// as gas limit will result in `OutOfGas` so to come up with a better estimate we search over a + /// possible range we pick a higher gas limit 3x of a succeeded call should be safe. + /// + /// This might result in executing the same script multiple times. Depending on the user's goal, + /// it might be problematic when using `ffi`. + fn search_optimal_gas_usage( + &mut self, + res: &RawCallResult, + from: Address, + to: Address, + calldata: &Bytes, + value: U256, + ) -> Result { + let mut gas_used = res.gas_used; + if matches!(res.exit_reason, return_ok!()) { + // 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_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 | + InstructionResult::OutOfGas | + InstructionResult::OutOfFunds => { + lowest_gas_limit = mid_gas_limit; + } + _ => { + highest_gas_limit = mid_gas_limit; + // if last two successful estimations only vary by 10%, we consider this to + // sufficiently accurate + const ACCURACY: u64 = 10; + if (last_highest_gas_limit - highest_gas_limit) * ACCURACY / + last_highest_gas_limit < + 1 + { + // update the gas + gas_used = highest_gas_limit; + break; + } + last_highest_gas_limit = highest_gas_limit; + } + } + } + // Reset gas limit in the 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 new file mode 100644 index 0000000000000..adb205a87ed83 --- /dev/null +++ b/crates/script/src/sequence.rs @@ -0,0 +1,116 @@ +use crate::multi_sequence::MultiChainSequence; +use eyre::Result; +use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; +use foundry_cli::utils::Git; +use foundry_common::fmt::UIfmt; +use foundry_compilers::ArtifactId; +use foundry_config::Config; +use std::{ + fmt::{Error, Write}, + path::Path, +}; + +/// Format transaction details for display +fn format_transaction(index: usize, tx: &TransactionWithMetadata) -> Result { + let mut output = String::new(); + writeln!(output, "### Transaction {index} ###")?; + writeln!(output, "{}", tx.tx().pretty())?; + + // Show contract name and address if available + if !tx.opcode.is_any_create() { + if let (Some(name), Some(addr)) = (&tx.contract_name, &tx.contract_address) { + writeln!(output, "contract: {name}({addr})")?; + } + } + + // Show decoded function if available + if let (Some(func), Some(args)) = (&tx.function, &tx.arguments) { + if args.is_empty() { + writeln!(output, "data (decoded): {func}()")?; + } else { + writeln!(output, "data (decoded): {func}(")?; + for (i, arg) in args.iter().enumerate() { + writeln!(&mut output, " {}{}", arg, if i + 1 < args.len() { "," } else { "" })?; + } + writeln!(output, ")")?; + } + } + + writeln!(output)?; + Ok(output) +} + +/// Returns the commit hash of the project if it exists +pub fn get_commit_hash(root: &Path) -> Option { + Git::new(root).commit_hash(true, "HEAD").ok() +} + +pub enum ScriptSequenceKind { + Single(ScriptSequence), + Multi(MultiChainSequence), +} + +impl ScriptSequenceKind { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + match self { + Self::Single(sequence) => sequence.save(silent, save_ts), + Self::Multi(sequence) => sequence.save(silent, save_ts), + } + } + + pub fn sequences(&self) -> &[ScriptSequence] { + match self { + Self::Single(sequence) => std::slice::from_ref(sequence), + Self::Multi(sequence) => &sequence.deployments, + } + } + + pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { + match self { + Self::Single(sequence) => std::slice::from_mut(sequence), + Self::Multi(sequence) => &mut sequence.deployments, + } + } + /// Updates underlying sequence paths to not be under /dry-run directory. + pub fn update_paths_to_broadcasted( + &mut self, + config: &Config, + sig: &str, + target: &ArtifactId, + ) -> Result<()> { + match self { + Self::Single(sequence) => { + sequence.paths = + Some(ScriptSequence::get_paths(config, sig, target, sequence.chain, false)?); + } + Self::Multi(sequence) => { + (sequence.path, sequence.sensitive_path) = + MultiChainSequence::get_paths(config, sig, target, false)?; + } + }; + + Ok(()) + } + + pub fn show_transactions(&self) -> Result<()> { + for sequence in self.sequences() { + if !sequence.transactions.is_empty() { + sh_println!("\nChain {}\n", sequence.chain)?; + + for (i, tx) in sequence.transactions.iter().enumerate() { + sh_print!("{}", format_transaction(i + 1, tx)?)?; + } + } + } + + Ok(()) + } +} + +impl Drop for ScriptSequenceKind { + fn drop(&mut self) { + if let Err(err) = self.save(false, true) { + error!(?err, "could not save deployment sequence"); + } + } +} diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs new file mode 100644 index 0000000000000..cf6aeb349436b --- /dev/null +++ b/crates/script/src/simulate.rs @@ -0,0 +1,456 @@ +use super::{ + multi_sequence::MultiChainSequence, providers::ProvidersManager, runner::ScriptRunner, + sequence::ScriptSequenceKind, transaction::ScriptTransactionBuilder, +}; +use crate::{ + broadcast::{estimate_gas, BundledState}, + build::LinkedBuildData, + execute::{ExecutionArtifacts, ExecutionData}, + sequence::get_commit_hash, + ScriptArgs, ScriptConfig, ScriptResult, +}; +use alloy_network::TransactionBuilder; +use alloy_primitives::{map::HashMap, utils::format_units, Address, Bytes, TxKind, U256}; +use dialoguer::Confirm; +use eyre::{Context, Result}; +use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; +use foundry_cheatcodes::Wallets; +use foundry_cli::utils::{has_different_gas_calc, now}; +use foundry_common::{shell, ContractData}; +use foundry_evm::traces::{decode_trace_arena, render_trace_arena}; +use futures::future::{join_all, try_join_all}; +use parking_lot::RwLock; +use std::{ + collections::{BTreeMap, VecDeque}, + sync::Arc, +}; + +/// Same as [ExecutedState](crate::execute::ExecutedState), but also contains [ExecutionArtifacts] +/// which are obtained from [ScriptResult]. +/// +/// Can be either converted directly to [BundledState] or driven to it through +/// [FilledTransactionsState]. +pub struct PreSimulationState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, + pub execution_result: ScriptResult, + pub execution_artifacts: ExecutionArtifacts, +} + +impl PreSimulationState { + /// If simulation is enabled, simulates transactions against fork and fills gas estimation and + /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is + /// left empty. + /// + /// Both modes will panic if any of the transactions have None for the `rpc` field. + pub async fn fill_metadata(self) -> Result { + let address_to_abi = self.build_address_to_abi_map(); + + let mut transactions = self + .execution_result + .transactions + .clone() + .unwrap_or_default() + .into_iter() + .map(|tx| { + let rpc = tx.rpc.expect("missing broadcastable tx rpc url"); + let sender = tx.transaction.from().expect("all transactions should have a sender"); + let nonce = tx.transaction.nonce().expect("all transactions should have a sender"); + let to = tx.transaction.to(); + + let mut builder = ScriptTransactionBuilder::new(tx.transaction, rpc); + + if let Some(TxKind::Call(_)) = to { + builder.set_call( + &address_to_abi, + &self.execution_artifacts.decoder, + self.script_config.evm_opts.create2_deployer, + )?; + } else { + builder.set_create(false, sender.create(nonce), &address_to_abi)?; + } + + Ok(builder.build()) + }) + .collect::>>()?; + + if self.args.skip_simulation { + sh_println!("\nSKIPPING ON CHAIN SIMULATION.")?; + } else { + transactions = self.simulate_and_fill(transactions).await?; + } + + Ok(FilledTransactionsState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_artifacts: self.execution_artifacts, + transactions, + }) + } + + /// Builds separate runners and environments for each RPC used in script and executes all + /// transactions in those environments. + /// + /// Collects gas usage and metadata for each transaction. + pub async fn simulate_and_fill( + &self, + transactions: VecDeque, + ) -> Result> { + trace!(target: "script", "executing onchain simulation"); + + let runners = Arc::new( + self.build_runners() + .await? + .into_iter() + .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner)))) + .collect::>(), + ); + + let mut final_txs = VecDeque::new(); + + // Executes all transactions from the different forks concurrently. + let futs = transactions + .into_iter() + .map(|mut transaction| async { + let mut runner = runners.get(&transaction.rpc).expect("invalid rpc url").write(); + let tx = transaction.tx_mut(); + + let to = if let Some(TxKind::Call(to)) = tx.to() { Some(to) } else { None }; + let result = runner + .simulate( + tx.from() + .expect("transaction doesn't have a `from` address at execution time"), + to, + tx.input().map(Bytes::copy_from_slice), + tx.value(), + tx.authorization_list(), + ) + .wrap_err("Internal EVM error during simulation")?; + + if !result.success { + return Ok((None, false, result.traces)); + } + + // Simulate mining the transaction if the user passes `--slow`. + if self.args.slow { + runner.executor.env_mut().block.number += U256::from(1); + } + + let is_noop_tx = if let Some(to) = to { + runner.executor.is_empty_code(to)? && tx.value().unwrap_or_default().is_zero() + } else { + false + }; + + let transaction = ScriptTransactionBuilder::from(transaction) + .with_execution_result(&result, self.args.gas_estimate_multiplier) + .build(); + + eyre::Ok((Some(transaction), is_noop_tx, result.traces)) + }) + .collect::>(); + + if !shell::is_json() && self.script_config.evm_opts.verbosity > 3 { + sh_println!("==========================")?; + sh_println!("Simulated On-chain Traces:\n")?; + } + + let mut abort = false; + for res in join_all(futs).await { + let (tx, is_noop_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 &mut traces { + decode_trace_arena(trace, &self.execution_artifacts.decoder).await?; + sh_println!("{}", render_trace_arena(trace))?; + } + } + + if let Some(tx) = tx { + if is_noop_tx { + let to = tx.contract_address.unwrap(); + sh_warn!( + "Script contains a transaction to {to} which does not contain any code." + )?; + + // Only prompt if we're broadcasting and we've not disabled interactivity. + if self.args.should_broadcast() && + !self.args.non_interactive && + !Confirm::new() + .with_prompt("Do you wish to continue?".to_string()) + .interact()? + { + eyre::bail!("User canceled the script."); + } + } + + final_txs.push_back(tx); + } else { + abort = true; + } + } + + if abort { + eyre::bail!("Simulated execution failed.") + } + + Ok(final_txs) + } + + /// Build mapping from contract address to its ABI, code and contract name. + fn build_address_to_abi_map(&self) -> BTreeMap { + self.execution_artifacts + .decoder + .contracts + .iter() + .filter_map(move |(addr, contract_id)| { + if let Ok(Some((_, data))) = + self.build_data.known_contracts.find_by_name_or_identifier(contract_id) + { + return Some((*addr, data)); + } + None + }) + .collect() + } + + /// Build [ScriptRunner] forking given RPC for each RPC used in the script. + async fn build_runners(&self) -> Result> { + let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); + + if !shell::is_json() { + let n = rpcs.len(); + let s = if n != 1 { "s" } else { "" }; + sh_println!("\n## Setting up {n} EVM{s}.")?; + } + + let futs = rpcs.into_iter().map(|rpc| async move { + let mut script_config = self.script_config.clone(); + script_config.evm_opts.fork_url = Some(rpc.clone()); + let runner = script_config.get_runner().await?; + Ok((rpc.clone(), runner)) + }); + try_join_all(futs).await + } +} + +/// At this point we have converted transactions collected during script execution to +/// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and +/// verification. +pub struct FilledTransactionsState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: Wallets, + pub build_data: LinkedBuildData, + pub execution_artifacts: ExecutionArtifacts, + pub transactions: VecDeque, +} + +impl FilledTransactionsState { + /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of + /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi + /// chain deployment. + /// + /// Each transaction will be added with the correct transaction type and gas estimation. + pub async fn bundle(self) -> Result { + let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; + + if is_multi_deployment && !self.build_data.libraries.is_empty() { + eyre::bail!("Multi-chain deployment is not supported with libraries."); + } + + let mut total_gas_per_rpc: HashMap = HashMap::default(); + + // Batches sequence of transactions from different rpcs. + let mut new_sequence = VecDeque::new(); + let mut manager = ProvidersManager::default(); + let mut sequences = vec![]; + + // Peeking is used to check if the next rpc url is different. If so, it creates a + // [`ScriptSequence`] from all the collected transactions up to this point. + let mut txes_iter = self.transactions.clone().into_iter().peekable(); + + while let Some(mut tx) = txes_iter.next() { + let tx_rpc = tx.rpc.to_owned(); + let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; + + if let Some(tx) = tx.tx_mut().as_unsigned_mut() { + // Handles chain specific requirements for unsigned transactions. + tx.set_chain_id(provider_info.chain); + } + + if !self.args.skip_simulation { + let tx = tx.tx_mut(); + + if has_different_gas_calc(provider_info.chain) { + // only estimate gas for unsigned transactions + if let Some(tx) = tx.as_unsigned_mut() { + trace!("estimating with different gas calculation"); + let gas = tx.gas.expect("gas is set by simulation."); + + // We are trying to show the user an estimation of the total gas usage. + // + // However, some transactions might depend on previous ones. For + // example, tx1 might deploy a contract that tx2 uses. That + // will result in the following `estimate_gas` call to fail, + // since tx1 hasn't been broadcasted yet. + // + // Not exiting here will not be a problem when actually broadcasting, + // because for chains where `has_different_gas_calc` + // returns true, we await each transaction before + // broadcasting the next one. + if let Err(err) = estimate_gas( + tx, + &provider_info.provider, + self.args.gas_estimate_multiplier, + ) + .await + { + trace!("gas estimation failed: {err}"); + + // Restore gas value, since `estimate_gas` will remove it. + tx.set_gas_limit(gas); + } + } + } + + let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(0); + *total_gas += tx.gas().expect("gas is set"); + } + + new_sequence.push_back(tx); + // We only create a [`ScriptSequence`] object when we collect all the rpc related + // transactions. + if let Some(next_tx) = txes_iter.peek() { + if next_tx.rpc == tx_rpc { + continue; + } + } + + let sequence = + self.create_sequence(is_multi_deployment, provider_info.chain, new_sequence)?; + + sequences.push(sequence); + + new_sequence = VecDeque::new(); + } + + if !self.args.skip_simulation { + // Present gas information on a per RPC basis. + for (rpc, total_gas) in total_gas_per_rpc { + let provider_info = manager.get(&rpc).expect("provider is set."); + + // We don't store it in the transactions, since we want the most updated value. + // Right before broadcasting. + let per_gas = if let Some(gas_price) = self.args.with_gas_price { + gas_price.to() + } else { + provider_info.gas_price()? + }; + + let estimated_gas_price_raw = format_units(per_gas, 9) + .unwrap_or_else(|_| "[Could not calculate]".to_string()); + let estimated_gas_price = + estimated_gas_price_raw.trim_end_matches('0').trim_end_matches('.'); + + let estimated_amount_raw = format_units(total_gas.saturating_mul(per_gas), 18) + .unwrap_or_else(|_| "[Could not calculate]".to_string()); + let estimated_amount = estimated_amount_raw.trim_end_matches('0'); + + if !shell::is_json() { + sh_println!("\n==========================")?; + sh_println!("\nChain {}", provider_info.chain)?; + + sh_println!("\nEstimated gas price: {} gwei", estimated_gas_price)?; + sh_println!("\nEstimated total gas used for script: {total_gas}")?; + sh_println!("\nEstimated amount required: {estimated_amount} ETH",)?; + sh_println!("\n==========================")?; + } else { + sh_println!( + "{}", + serde_json::json!({ + "chain": provider_info.chain, + "estimated_gas_price": estimated_gas_price, + "estimated_total_gas_used": total_gas, + "estimated_amount_required": estimated_amount, + }) + )?; + } + } + } + + let sequence = if sequences.len() == 1 { + ScriptSequenceKind::Single(sequences.pop().expect("empty sequences")) + } else { + ScriptSequenceKind::Multi(MultiChainSequence::new( + sequences, + &self.args.sig, + &self.build_data.build_data.target, + &self.script_config.config, + !self.args.broadcast, + )?) + }; + + Ok(BundledState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + sequence, + }) + } + + /// Creates a [ScriptSequence] object from the given transactions. + fn create_sequence( + &self, + multi: bool, + chain: u64, + transactions: VecDeque, + ) -> Result { + // Paths are set to None for multi-chain sequences parts, because they don't need to be + // saved to a separate file. + let paths = if multi { + None + } else { + Some(ScriptSequence::get_paths( + &self.script_config.config, + &self.args.sig, + &self.build_data.build_data.target, + chain, + !self.args.broadcast, + )?) + }; + + let commit = get_commit_hash(&self.script_config.config.root); + + let libraries = self + .build_data + .libraries + .libs + .iter() + .flat_map(|(file, libs)| { + libs.iter() + .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy())) + }) + .collect(); + + let sequence = ScriptSequence { + transactions, + returns: self.execution_artifacts.returns.clone(), + receipts: vec![], + pending: vec![], + paths, + timestamp: now().as_secs(), + libraries, + chain, + commit, + }; + Ok(sequence) + } +} diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs new file mode 100644 index 0000000000000..2cf480b9bd0ad --- /dev/null +++ b/crates/script/src/transaction.rs @@ -0,0 +1,179 @@ +use super::ScriptResult; +use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::{hex, Address, TxKind, B256}; +use eyre::Result; +use forge_script_sequence::TransactionWithMetadata; +use foundry_common::{fmt::format_token_raw, ContractData, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_evm::traces::CallTraceDecoder; +use itertools::Itertools; +use revm_inspectors::tracing::types::CallKind; +use std::collections::BTreeMap; + +#[derive(Debug)] +pub struct ScriptTransactionBuilder { + transaction: TransactionWithMetadata, +} + +impl ScriptTransactionBuilder { + pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self { + let mut transaction = TransactionWithMetadata::from_tx_request(transaction); + transaction.rpc = rpc; + // If tx.gas is already set that means it was specified in script + transaction.is_fixed_gas_limit = transaction.tx().gas().is_some(); + + Self { transaction } + } + + /// Populate the transaction as CALL tx + pub fn set_call( + &mut self, + local_contracts: &BTreeMap, + decoder: &CallTraceDecoder, + create2_deployer: Address, + ) -> Result<()> { + if let Some(TxKind::Call(to)) = self.transaction.transaction.to() { + if to == create2_deployer { + if let Some(input) = self.transaction.transaction.input() { + let (salt, init_code) = input.split_at(32); + + self.set_create( + true, + create2_deployer.create2_from_code(B256::from_slice(salt), init_code), + local_contracts, + )?; + } + } else { + self.transaction.opcode = CallKind::Call; + self.transaction.contract_address = Some(to); + + let Some(data) = self.transaction.transaction.input() else { return Ok(()) }; + + if data.len() < SELECTOR_LEN { + return Ok(()); + } + + let (selector, data) = data.split_at(SELECTOR_LEN); + + let function = if let Some(info) = local_contracts.get(&to) { + // This CALL is made to a local contract. + self.transaction.contract_name = Some(info.name.clone()); + info.abi.functions().find(|function| function.selector() == selector) + } else { + // This CALL is made to an external contract; try to decode it from the given + // decoder. + decoder.functions.get(selector).and_then(|v| v.first()) + }; + + if let Some(function) = function { + self.transaction.function = Some(function.signature()); + + let values = function.abi_decode_input(data, false).inspect_err(|_| { + error!( + contract=?self.transaction.contract_name, + signature=?function, + data=hex::encode(data), + "Failed to decode function arguments", + ); + })?; + self.transaction.arguments = + Some(values.iter().map(format_token_raw).collect()); + } + } + } + + Ok(()) + } + + /// Populate the transaction as CREATE tx + /// + /// If this is a CREATE2 transaction this attempt to decode the arguments from the CREATE2 + /// deployer's function + pub fn set_create( + &mut self, + is_create2: bool, + address: Address, + contracts: &BTreeMap, + ) -> Result<()> { + if is_create2 { + self.transaction.opcode = CallKind::Create2; + } else { + self.transaction.opcode = CallKind::Create; + } + + let info = contracts.get(&address); + self.transaction.contract_name = info.map(|info| info.name.clone()); + self.transaction.contract_address = Some(address); + + let Some(data) = self.transaction.transaction.input() else { return Ok(()) }; + let Some(info) = info else { return Ok(()) }; + let Some(bytecode) = info.bytecode() else { return Ok(()) }; + + // `create2` transactions are prefixed by a 32 byte salt. + let creation_code = if is_create2 { + if data.len() < 32 { + return Ok(()) + } + &data[32..] + } else { + data + }; + + // The constructor args start after bytecode. + let contains_constructor_args = creation_code.len() > bytecode.len(); + if !contains_constructor_args { + return Ok(()); + } + 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).inspect_err(|_| { + error!( + contract=?self.transaction.contract_name, + signature=%format!("constructor({})", constructor.inputs.iter().map(|p| &p.ty).format(",")), + is_create2, + constructor_args=%hex::encode(constructor_args), + "Failed to decode constructor arguments", + ); + debug!(full_data=%hex::encode(data), bytecode=%hex::encode(creation_code)); + })?; + self.transaction.arguments = Some(values.iter().map(format_token_raw).collect()); + + Ok(()) + } + + /// Populates additional data from the transaction execution result. + pub fn with_execution_result( + mut self, + result: &ScriptResult, + gas_estimate_multiplier: u64, + ) -> Self { + let mut created_contracts = result.get_created_contracts(); + + // Add the additional contracts created in this transaction, so we can verify them later. + created_contracts.retain(|contract| { + // Filter out the contract that was created by the transaction itself. + self.transaction.contract_address != Some(contract.address) + }); + + self.transaction.additional_contracts = created_contracts; + + if !self.transaction.is_fixed_gas_limit { + if let Some(unsigned) = self.transaction.transaction.as_unsigned_mut() { + // We inflate the gas used by the user specified percentage + unsigned.gas = Some(result.gas_used * gas_estimate_multiplier / 100); + } + } + + self + } + + pub fn build(self) -> TransactionWithMetadata { + self.transaction + } +} + +impl From for ScriptTransactionBuilder { + fn from(transaction: TransactionWithMetadata) -> Self { + Self { transaction } + } +} diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs new file mode 100644 index 0000000000000..c2f9955434813 --- /dev/null +++ b/crates/script/src/verify.rs @@ -0,0 +1,273 @@ +use crate::{ + build::LinkedBuildData, + sequence::{get_commit_hash, ScriptSequenceKind}, + ScriptArgs, ScriptConfig, +}; +use alloy_primitives::{hex, Address}; +use eyre::{eyre, Result}; +use forge_script_sequence::{AdditionalContract, ScriptSequence}; +use forge_verify::{provider::VerificationProviderType, RetryArgs, VerifierArgs, VerifyArgs}; +use foundry_cli::opts::{EtherscanOpts, ProjectPathOpts}; +use foundry_common::ContractsByArtifact; +use foundry_compilers::{info::ContractInfo, Project}; +use foundry_config::{Chain, Config}; +use semver::Version; + +/// State after we have broadcasted the script. +/// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all +/// broadcasted transactions. +pub struct BroadcastedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub build_data: LinkedBuildData, + pub sequence: ScriptSequenceKind, +} + +impl BroadcastedState { + pub async fn verify(self) -> Result<()> { + let Self { args, script_config, build_data, mut sequence, .. } = self; + + let verify = VerifyBundle::new( + &script_config.config.project()?, + &script_config.config, + build_data.known_contracts, + args.retry, + args.verifier, + ); + + for sequence in sequence.sequences_mut() { + verify_contracts(sequence, &script_config.config, verify.clone()).await?; + } + + Ok(()) + } +} + +/// Data struct to help `ScriptSequence` verify contracts on `etherscan`. +#[derive(Clone)] +pub struct VerifyBundle { + pub num_of_optimizations: Option, + pub known_contracts: ContractsByArtifact, + pub project_paths: ProjectPathOpts, + pub etherscan: EtherscanOpts, + pub retry: RetryArgs, + pub verifier: VerifierArgs, + pub via_ir: bool, +} + +impl VerifyBundle { + pub fn new( + project: &Project, + config: &Config, + known_contracts: ContractsByArtifact, + retry: RetryArgs, + verifier: VerifierArgs, + ) -> Self { + let num_of_optimizations = + if config.optimizer == Some(true) { config.optimizer_runs } else { None }; + + let config_path = config.get_config_path(); + + let project_paths = ProjectPathOpts { + root: Some(project.paths.root.clone()), + contracts: Some(project.paths.sources.clone()), + remappings: project.paths.remappings.clone(), + remappings_env: None, + cache_path: Some(project.paths.cache.clone()), + lib_paths: project.paths.libraries.clone(), + hardhat: config.profile == Config::HARDHAT_PROFILE, + config_path: if config_path.exists() { Some(config_path) } else { None }, + }; + + let via_ir = config.via_ir; + + Self { + num_of_optimizations, + known_contracts, + etherscan: Default::default(), + project_paths, + retry, + verifier, + via_ir, + } + } + + /// Configures the chain and sets the etherscan key, if available + pub fn set_chain(&mut self, config: &Config, chain: Chain) { + // If dealing with multiple chains, we need to be able to change in between the config + // chain_id. + self.etherscan.key = config.get_etherscan_api_key(Some(chain)); + self.etherscan.chain = Some(chain); + } + + /// Given a `VerifyBundle` and contract details, it tries to generate a valid `VerifyArgs` to + /// use against the `contract_address`. + pub fn get_verify_args( + &self, + contract_address: Address, + create2_offset: usize, + data: &[u8], + libraries: &[String], + ) -> Option { + for (artifact, contract) in self.known_contracts.iter() { + let Some(bytecode) = contract.bytecode() else { continue }; + // If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning + // of the transaction + if data.split_at(create2_offset).1.starts_with(bytecode) { + let constructor_args = data.split_at(create2_offset + bytecode.len()).1.to_vec(); + + if artifact.source.extension().is_some_and(|e| e.to_str() == Some("vy")) { + warn!("Skipping verification of Vyper contract: {}", artifact.name); + } + + // Strip artifact profile from contract name when creating contract info. + let contract = ContractInfo { + path: Some(artifact.source.to_string_lossy().to_string()), + name: artifact + .name + .strip_suffix(&format!(".{}", &artifact.profile)) + .unwrap_or_else(|| &artifact.name) + .to_string(), + }; + + // We strip the build metadadata information, since it can lead to + // etherscan not identifying it correctly. eg: + // `v0.8.10+commit.fc410830.Linux.gcc` != `v0.8.10+commit.fc410830` + let version = Version::new( + artifact.version.major, + artifact.version.minor, + artifact.version.patch, + ); + + let verify = VerifyArgs { + address: contract_address, + contract: Some(contract), + compiler_version: Some(version.to_string()), + constructor_args: Some(hex::encode(constructor_args)), + constructor_args_path: None, + num_of_optimizations: self.num_of_optimizations, + etherscan: self.etherscan.clone(), + rpc: Default::default(), + flatten: false, + force: false, + skip_is_verified_check: true, + watch: true, + retry: self.retry, + libraries: libraries.to_vec(), + root: None, + verifier: self.verifier.clone(), + via_ir: self.via_ir, + evm_version: None, + show_standard_json_input: false, + guess_constructor_args: false, + compilation_profile: Some(artifact.profile.to_string()), + }; + + return Some(verify) + } + } + None + } +} + +/// Given the broadcast log, it matches transactions with receipts, and tries to verify any +/// created contract on etherscan. +async fn verify_contracts( + sequence: &mut ScriptSequence, + config: &Config, + mut verify: VerifyBundle, +) -> Result<()> { + trace!(target: "script", "verifying {} contracts [{}]", verify.known_contracts.len(), sequence.chain); + + verify.set_chain(config, sequence.chain.into()); + + if verify.etherscan.has_key() || verify.verifier.verifier != VerificationProviderType::Etherscan + { + trace!(target: "script", "prepare future verifications"); + + let mut future_verifications = Vec::with_capacity(sequence.receipts.len()); + let mut unverifiable_contracts = vec![]; + + // Make sure the receipts have the right order first. + sequence.sort_receipts(); + + for (receipt, tx) in sequence.receipts.iter_mut().zip(sequence.transactions.iter()) { + // create2 hash offset + let mut offset = 0; + + if tx.is_create2() { + receipt.contract_address = tx.contract_address; + offset = 32; + } + + // Verify contract created directly from the transaction + if let (Some(address), Some(data)) = (receipt.contract_address, tx.tx().input()) { + match verify.get_verify_args(address, offset, data, &sequence.libraries) { + Some(verify) => future_verifications.push(verify.run()), + None => unverifiable_contracts.push(address), + }; + } + + // Verify potential contracts created during the transaction execution + for AdditionalContract { address, init_code, .. } in &tx.additional_contracts { + match verify.get_verify_args(*address, 0, init_code.as_ref(), &sequence.libraries) { + Some(verify) => future_verifications.push(verify.run()), + None => unverifiable_contracts.push(*address), + }; + } + } + + trace!(target: "script", "collected {} verification jobs and {} unverifiable contracts", future_verifications.len(), unverifiable_contracts.len()); + + check_unverified(sequence, unverifiable_contracts, verify); + + let num_verifications = future_verifications.len(); + let mut num_of_successful_verifications = 0; + sh_println!("##\nStart verification for ({num_verifications}) contracts")?; + for verification in future_verifications { + match verification.await { + Ok(_) => { + num_of_successful_verifications += 1; + } + Err(err) => { + sh_err!("Failed to verify contract: {err:#}")?; + } + } + } + + if num_of_successful_verifications < num_verifications { + return Err(eyre!("Not all ({num_of_successful_verifications} / {num_verifications}) contracts were verified!")) + } + + sh_println!("All ({num_verifications}) contracts were verified!")?; + } + + Ok(()) +} + +fn check_unverified( + sequence: &ScriptSequence, + unverifiable_contracts: Vec
, + verify: VerifyBundle, +) { + if !unverifiable_contracts.is_empty() { + let _ = sh_warn!( + "We haven't found any matching bytecode for the following contracts: {:?}.\n\nThis may occur when resuming a verification, but the underlying source code or compiler version has changed.", + unverifiable_contracts + ); + + if let Some(commit) = &sequence.commit { + let current_commit = verify + .project_paths + .root + .map(|root| get_commit_hash(&root).unwrap_or_default()) + .unwrap_or_default(); + + if ¤t_commit != commit { + let _ = sh_warn!( + "Script was broadcasted on commit `{commit}`, but we are at `{current_commit}`." + ); + } + } + } +} diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml new file mode 100644 index 0000000000000..c83a10152dd02 --- /dev/null +++ b/crates/sol-macro-gen/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "forge-sol-macro-gen" +description = "Contains types and methods for generating rust bindings using sol!" +publish = false + +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-json-abi.workspace = true +alloy-sol-macro-input.workspace = true +alloy-sol-macro-expander = { workspace = true, features = ["json"] } +foundry-common.workspace = true + +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true +prettyplease.workspace = true + +serde_json.workspace = true +eyre.workspace = true diff --git a/crates/sol-macro-gen/src/lib.rs b/crates/sol-macro-gen/src/lib.rs new file mode 100644 index 0000000000000..9984c6cce2b21 --- /dev/null +++ b/crates/sol-macro-gen/src/lib.rs @@ -0,0 +1,5 @@ +//! This crate contains the logic for Rust bindings generating from Solidity contracts + +pub mod sol_macro_gen; + +pub use sol_macro_gen::*; diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs new file mode 100644 index 0000000000000..6ec534b965759 --- /dev/null +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -0,0 +1,353 @@ +//! SolMacroGen and MultiSolMacroGen +//! +//! This type encapsulates the logic for expansion of a Rust TokenStream from Solidity tokens. It +//! uses the `expand` method from `alloy_sol_macro_expander` underneath. +//! +//! It holds info such as `path` to the ABI file, `name` of the file and the rust binding being +//! generated, and lastly the `expansion` itself, i.e the Rust binding for the provided ABI. +//! +//! It contains methods to read the json abi, generate rust bindings from the abi and ultimately +//! write the bindings to a crate or modules. + +use alloy_sol_macro_expander::expand::expand; +use alloy_sol_macro_input::{SolInput, SolInputKind}; +use eyre::{Context, OptionExt, Result}; +use foundry_common::fs; +use proc_macro2::{Span, TokenStream}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + str::FromStr, +}; + +pub struct SolMacroGen { + pub path: PathBuf, + pub name: String, + pub expansion: Option, +} + +impl SolMacroGen { + pub fn new(path: PathBuf, name: String) -> Self { + Self { path, name, expansion: None } + } + + pub fn get_sol_input(&self) -> Result { + let path = self.path.to_string_lossy().into_owned(); + let name = proc_macro2::Ident::new(&self.name, Span::call_site()); + let tokens = quote::quote! { + #name, + #path + }; + + let sol_input: SolInput = syn::parse2(tokens).wrap_err("failed to parse input")?; + + Ok(sol_input) + } +} + +pub struct MultiSolMacroGen { + pub artifacts_path: PathBuf, + pub instances: Vec, +} + +impl MultiSolMacroGen { + pub fn new(artifacts_path: &Path, instances: Vec) -> Self { + Self { artifacts_path: artifacts_path.to_path_buf(), instances } + } + + pub fn populate_expansion(&mut self, bindings_path: &Path) -> Result<()> { + for instance in &mut self.instances { + let path = bindings_path.join(format!("{}.rs", instance.name.to_lowercase())); + let expansion = fs::read_to_string(path).wrap_err("Failed to read file")?; + + let tokens = TokenStream::from_str(&expansion) + .map_err(|e| eyre::eyre!("Failed to parse TokenStream: {e}"))?; + instance.expansion = Some(tokens); + } + Ok(()) + } + + pub fn generate_bindings(&mut self) -> Result<()> { + for instance in &mut self.instances { + Self::generate_binding(instance).wrap_err_with(|| { + format!( + "failed to generate bindings for {}:{}", + instance.path.display(), + instance.name + ) + })?; + } + + Ok(()) + } + + fn generate_binding(instance: &mut SolMacroGen) -> Result<()> { + let input = instance.get_sol_input()?.normalize_json()?; + + let SolInput { attrs: _, path: _, kind } = input; + + let tokens = match kind { + SolInputKind::Sol(mut file) => { + let sol_attr: syn::Attribute = syn::parse_quote! { + #[sol(rpc, alloy_sol_types = alloy::sol_types, alloy_contract = alloy::contract)] + }; + file.attrs.push(sol_attr); + expand(file).wrap_err("failed to expand")? + } + _ => unreachable!(), + }; + + instance.expansion = Some(tokens); + Ok(()) + } + + pub fn write_to_crate( + &mut self, + name: &str, + version: &str, + bindings_path: &Path, + single_file: bool, + alloy_version: Option, + alloy_rev: Option, + ) -> Result<()> { + self.generate_bindings()?; + + let src = bindings_path.join("src"); + + let _ = fs::create_dir_all(&src); + + // Write Cargo.toml + let cargo_toml_path = bindings_path.join("Cargo.toml"); + let mut toml_contents = format!( + r#"[package] +name = "{name}" +version = "{version}" +edition = "2021" + +[dependencies] +"# + ); + + let alloy_dep = Self::get_alloy_dep(alloy_version, alloy_rev); + write!(toml_contents, "{alloy_dep}")?; + + fs::write(cargo_toml_path, toml_contents).wrap_err("Failed to write Cargo.toml")?; + + let mut lib_contents = String::new(); + write!( + &mut lib_contents, + r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + //! This module contains the sol! generated bindings for solidity contracts. + //! This is autogenerated code. + //! Do not manually edit these files. + //! These files may be overwritten by the codegen system at any time. + "# + )?; + + // Write src + let parse_error = |name: &str| { + format!("failed to parse generated tokens as an AST for {name};\nthis is likely a bug") + }; + for instance in &self.instances { + let contents = instance.expansion.as_ref().unwrap(); + + let name = instance.name.to_lowercase(); + let path = src.join(format!("{name}.rs")); + let file = syn::parse2(contents.clone()) + .wrap_err_with(|| parse_error(&format!("{}:{}", path.display(), name)))?; + let contents = prettyplease::unparse(&file); + if single_file { + write!(&mut lib_contents, "{contents}")?; + } else { + fs::write(path, contents).wrap_err("failed to write to file")?; + write_mod_name(&mut lib_contents, &name)?; + } + } + + let lib_path = src.join("lib.rs"); + let lib_file = syn::parse_file(&lib_contents).wrap_err_with(|| parse_error("lib.rs"))?; + let lib_contents = prettyplease::unparse(&lib_file); + fs::write(lib_path, lib_contents).wrap_err("Failed to write lib.rs")?; + + Ok(()) + } + + pub fn write_to_module(&mut self, bindings_path: &Path, single_file: bool) -> Result<()> { + self.generate_bindings()?; + + let _ = fs::create_dir_all(bindings_path); + + let mut mod_contents = r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + //! This module contains the sol! generated bindings for solidity contracts. + //! This is autogenerated code. + //! Do not manually edit these files. + //! These files may be overwritten by the codegen system at any time. + "# + .to_string(); + + for instance in &self.instances { + let name = instance.name.to_lowercase(); + if !single_file { + // Module + write_mod_name(&mut mod_contents, &name)?; + let mut contents = String::new(); + + write!(contents, "{}", instance.expansion.as_ref().unwrap())?; + let file = syn::parse_file(&contents)?; + + let contents = prettyplease::unparse(&file); + fs::write(bindings_path.join(format!("{name}.rs")), contents) + .wrap_err("Failed to write file")?; + } else { + // Single File + let mut contents = String::new(); + write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; + write!(mod_contents, "{contents}")?; + } + } + + let mod_path = bindings_path.join("mod.rs"); + let mod_file = syn::parse_file(&mod_contents)?; + let mod_contents = prettyplease::unparse(&mod_file); + + fs::write(mod_path, mod_contents).wrap_err("Failed to write mod.rs")?; + + Ok(()) + } + + /// Checks that the generated bindings are up to date with the latest version of + /// `sol!`. + /// + /// Returns `Ok(())` if the generated bindings are up to date, otherwise it returns + /// `Err(_)`. + #[allow(clippy::too_many_arguments)] + pub fn check_consistency( + &self, + name: &str, + version: &str, + crate_path: &Path, + single_file: bool, + check_cargo_toml: bool, + is_mod: bool, + alloy_version: Option, + alloy_rev: Option, + ) -> Result<()> { + if check_cargo_toml { + self.check_cargo_toml(name, version, crate_path, alloy_version, alloy_rev)?; + } + + let mut super_contents = String::new(); + write!( + &mut super_contents, + r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + //! This module contains the sol! generated bindings for solidity contracts. + //! This is autogenerated code. + //! Do not manually edit these files. + //! These files may be overwritten by the codegen system at any time. + "# + )?; + if !single_file { + for instance in &self.instances { + let name = instance.name.to_lowercase(); + let path = if is_mod { + crate_path.join(format!("{name}.rs")) + } else { + crate_path.join(format!("src/{name}.rs")) + }; + let tokens = instance + .expansion + .as_ref() + .ok_or_eyre(format!("TokenStream for {path:?} does not exist"))? + .to_string(); + + self.check_file_contents(&path, &tokens)?; + write_mod_name(&mut super_contents, &name)?; + } + + let super_path = + if is_mod { crate_path.join("mod.rs") } else { crate_path.join("src/lib.rs") }; + self.check_file_contents(&super_path, &super_contents)?; + } + + Ok(()) + } + + fn check_file_contents(&self, file_path: &Path, expected_contents: &str) -> Result<()> { + eyre::ensure!( + file_path.is_file() && file_path.exists(), + "{} is not a file", + file_path.display() + ); + let file_contents = &fs::read_to_string(file_path).wrap_err("Failed to read file")?; + + // Format both + let file_contents = syn::parse_file(file_contents)?; + let formatted_file = prettyplease::unparse(&file_contents); + + let expected_contents = syn::parse_file(expected_contents)?; + let formatted_exp = prettyplease::unparse(&expected_contents); + + eyre::ensure!( + formatted_file == formatted_exp, + "File contents do not match expected contents for {file_path:?}" + ); + Ok(()) + } + + fn check_cargo_toml( + &self, + name: &str, + version: &str, + crate_path: &Path, + alloy_version: Option, + alloy_rev: Option, + ) -> Result<()> { + eyre::ensure!(crate_path.is_dir(), "Crate path must be a directory"); + + let cargo_toml_path = crate_path.join("Cargo.toml"); + + eyre::ensure!(cargo_toml_path.is_file(), "Cargo.toml must exist"); + let cargo_toml_contents = + fs::read_to_string(cargo_toml_path).wrap_err("Failed to read Cargo.toml")?; + + let name_check = format!("name = \"{name}\""); + let version_check = format!("version = \"{version}\""); + let alloy_dep_check = Self::get_alloy_dep(alloy_version, alloy_rev); + let toml_consistent = cargo_toml_contents.contains(&name_check) && + cargo_toml_contents.contains(&version_check) && + cargo_toml_contents.contains(&alloy_dep_check); + eyre::ensure!( + toml_consistent, + r#"The contents of Cargo.toml do not match the expected output of the latest `sol!` version. + This indicates that the existing bindings are outdated and need to be generated again."# + ); + + Ok(()) + } + + /// Returns the `alloy` dependency string for the Cargo.toml file. + /// If `alloy_version` is provided, it will use that version from crates.io. + /// If `alloy_rev` is provided, it will use that revision from the GitHub repository. + fn get_alloy_dep(alloy_version: Option, alloy_rev: Option) -> String { + if let Some(alloy_version) = alloy_version { + format!( + r#"alloy = {{ version = "{alloy_version}", features = ["sol-types", "contract"] }}"#, + ) + } else if let Some(alloy_rev) = alloy_rev { + format!( + r#"alloy = {{ git = "https://github.com/alloy-rs/alloy", rev = "{alloy_rev}", features = ["sol-types", "contract"] }}"#, + ) + } else { + r#"alloy = { git = "https://github.com/alloy-rs/alloy", features = ["sol-types", "contract"] }"#.to_string() + } + } +} + +fn write_mod_name(contents: &mut String, name: &str) -> Result<()> { + if syn::parse_str::(&format!("pub mod {name};")).is_ok() { + write!(contents, "pub mod {name};")?; + } else { + write!(contents, "pub mod r#{name};")?; + } + Ok(()) +} diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 849ba66fdf7ff..3e6efdc1fda1c 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -11,21 +11,31 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -foundry-config.workspace = true foundry-common.workspace = true +foundry-compilers = { workspace = true, features = ["project-util"] } +foundry-config.workspace = true + +alloy-primitives.workspace = true +alloy-provider.workspace = true -ethers.workspace = true -ethers-solc = { workspace = true, features = ["project-util"] } +eyre.workspace = true +fd-lock = "4.0" +parking_lot.workspace = true +regex.workspace = true +serde_json.workspace = true +tracing.workspace = true +tracing-subscriber = { workspace = true, features = ["env-filter"] } +rand.workspace = true +snapbox = { version = "0.6", features = ["json", "regex", "term-svg"] } +tempfile.workspace = true -eyre = "0.6" -once_cell = "1" -parking_lot = "0.12" -pretty_assertions = "1" -regex = "1" -serde_json = "1" -tempfile = "3" -walkdir = "2" +[dev-dependencies] +tokio.workspace = true +foundry-block-explorers.workspace = true [features] # feature for integration tests that test external projects diff --git a/crates/test-utils/src/fd_lock.rs b/crates/test-utils/src/fd_lock.rs new file mode 100644 index 0000000000000..1c5a479cd5efd --- /dev/null +++ b/crates/test-utils/src/fd_lock.rs @@ -0,0 +1,21 @@ +//! File locking utilities. + +use crate::util::pretty_err; +use std::{ + fs::{File, OpenOptions}, + path::Path, +}; + +pub use fd_lock::*; + +/// Creates a new lock file at the given path. +pub fn new_lock(lock_path: impl AsRef) -> RwLock { + fn new_lock(lock_path: &Path) -> RwLock { + let lock_file = pretty_err( + lock_path, + OpenOptions::new().read(true).write(true).create(true).truncate(false).open(lock_path), + ); + RwLock::new(lock_file) + } + new_lock(lock_path.as_ref()) +} diff --git a/crates/test-utils/src/filter.rs b/crates/test-utils/src/filter.rs new file mode 100644 index 0000000000000..1ba905d27d8c9 --- /dev/null +++ b/crates/test-utils/src/filter.rs @@ -0,0 +1,107 @@ +use foundry_common::TestFilter; +use regex::Regex; +use std::path::Path; + +#[derive(Clone, Debug)] +pub struct Filter { + test_regex: Regex, + contract_regex: Regex, + path_regex: Regex, + exclude_tests: Option, + exclude_contracts: Option, + exclude_paths: Option, +} + +impl Filter { + pub fn new(test_pattern: &str, contract_pattern: &str, path_pattern: &str) -> Self { + Self { + test_regex: Regex::new(test_pattern) + .unwrap_or_else(|_| panic!("Failed to parse test pattern: `{test_pattern}`")), + contract_regex: Regex::new(contract_pattern).unwrap_or_else(|_| { + panic!("Failed to parse contract pattern: `{contract_pattern}`") + }), + path_regex: Regex::new(path_pattern) + .unwrap_or_else(|_| panic!("Failed to parse path pattern: `{path_pattern}`")), + exclude_tests: None, + exclude_contracts: None, + exclude_paths: None, + } + } + + pub fn contract(contract_pattern: &str) -> Self { + Self::new(".*", contract_pattern, ".*") + } + + pub fn path(path_pattern: &str) -> Self { + Self::new(".*", ".*", path_pattern) + } + + /// All tests to also exclude + /// + /// This is a workaround since regex does not support negative look aheads + pub fn exclude_tests(mut self, pattern: &str) -> Self { + self.exclude_tests = Some(Regex::new(pattern).unwrap()); + self + } + + /// All contracts to also exclude + /// + /// This is a workaround since regex does not support negative look aheads + pub fn exclude_contracts(mut self, pattern: &str) -> Self { + self.exclude_contracts = Some(Regex::new(pattern).unwrap()); + self + } + + /// All paths to also exclude + /// + /// This is a workaround since regex does not support negative look aheads + pub fn exclude_paths(mut self, pattern: &str) -> Self { + self.exclude_paths = Some(Regex::new(pattern).unwrap()); + self + } + + pub fn matches_all() -> Self { + Self { + test_regex: Regex::new(".*").unwrap(), + contract_regex: Regex::new(".*").unwrap(), + path_regex: Regex::new(".*").unwrap(), + exclude_tests: None, + exclude_contracts: None, + exclude_paths: None, + } + } +} + +impl TestFilter for Filter { + fn matches_test(&self, test_name: &str) -> bool { + if let Some(exclude) = &self.exclude_tests { + if exclude.is_match(test_name) { + return false; + } + } + self.test_regex.is_match(test_name) + } + + fn matches_contract(&self, contract_name: &str) -> bool { + if let Some(exclude) = &self.exclude_contracts { + if exclude.is_match(contract_name) { + return false; + } + } + + self.contract_regex.is_match(contract_name) + } + + fn matches_path(&self, path: &Path) -> bool { + let Some(path) = path.to_str() else { + return false; + }; + + if let Some(exclude) = &self.exclude_paths { + if exclude.is_match(path) { + return false; + } + } + self.path_regex.is_match(path) + } +} diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 1cdbe23c96df6..2fda786c5deaf 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -1,15 +1,40 @@ -#![warn(unused_crate_dependencies)] +//! # foundry-test-utils +//! +//! Internal Foundry testing utilities. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// Shouldn't use sh_* macros here, as they don't get captured by the test runner. +#![allow(clippy::disallowed_macros)] + +#[macro_use] +extern crate tracing; // Macros useful for testing. mod macros; +pub mod rpc; + +pub mod fd_lock; + +mod filter; +pub use filter::Filter; + // Utilities for making it easier to handle tests. pub mod util; pub use util::{TestCommand, TestProject}; -pub mod script; +mod script; pub use script::{ScriptOutcome, ScriptTester}; // re-exports for convenience -pub use ethers_solc; -pub use tempfile; +pub use foundry_compilers; + +pub use snapbox::{self, assert_data_eq, file, str}; + +/// Initializes tracing for tests. +pub fn init_tracing() { + let _ = tracing_subscriber::FmtSubscriber::builder() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init(); +} diff --git a/crates/test-utils/src/macros.rs b/crates/test-utils/src/macros.rs index 88faf98bca0a9..08900ad149319 100644 --- a/crates/test-utils/src/macros.rs +++ b/crates/test-utils/src/macros.rs @@ -11,11 +11,11 @@ /// /// ```no_run /// use foundry_test_utils::*; -/// forgetest!(my_test, |prj: TestProject, mut cmd: TestCommand| { +/// forgetest!(my_test, |prj, cmd| { /// // adds `init` to forge's command arguments /// cmd.arg("init"); /// // executes forge and panics if the command failed or output is empty -/// cmd.assert_non_empty_stdout(); +/// cmd.assert_success().stdout_eq(str![[r#""#]]); /// }); /// ``` /// @@ -23,8 +23,8 @@ /// /// ```no_run /// use foundry_test_utils::*; -/// use foundry_test_utils::ethers_solc::PathStyle; -/// forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj: TestProject, mut cmd: TestCommand| { +/// use foundry_test_utils::foundry_compilers::PathStyle; +/// forgetest!(can_clean_hardhat, PathStyle::HardHat, |prj, cmd| { /// prj.assert_create_dirs_exists(); /// prj.assert_style_paths_exist(PathStyle::HardHat); /// cmd.arg("clean"); @@ -33,185 +33,95 @@ /// }); #[macro_export] macro_rules! forgetest { - ($(#[$meta:meta])* $test:ident, $fun:expr) => { - $crate::forgetest!($(#[$meta])* $test, $crate::ethers_solc::PathStyle::Dapptools, $fun); + ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { + $crate::forgetest!($(#[$attr])* $test, $crate::foundry_compilers::PathStyle::Dapptools, |$prj, $cmd| $e); }; - ($(#[$meta:meta])* $test:ident, $style:expr, $fun:expr) => { + ($(#[$attr:meta])* $test:ident, $style:expr, |$prj:ident, $cmd:ident| $e:expr) => { + #[allow(clippy::disallowed_macros)] #[test] - $(#[$meta])* + $(#[$attr])* fn $test() { - let (prj, cmd) = $crate::util::setup_forge(stringify!($test), $style); - let f = $fun; - f(prj, cmd); + let (mut $prj, mut $cmd) = $crate::util::setup_forge(stringify!($test), $style); + $e } }; } #[macro_export] macro_rules! forgetest_async { - ($(#[$meta:meta])* $test:ident, $fun:expr) => { - $crate::forgetest_async!($(#[$meta])* $test, $crate::ethers_solc::PathStyle::Dapptools, $fun); + ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { + $crate::forgetest_async!($(#[$attr])* $test, $crate::foundry_compilers::PathStyle::Dapptools, |$prj, $cmd| $e); }; - ($(#[$meta:meta])* $test:ident, $style:expr, $fun:expr) => { + ($(#[$attr:meta])* $test:ident, $style:expr, |$prj:ident, $cmd:ident| $e:expr) => { + #[allow(clippy::disallowed_macros)] #[tokio::test(flavor = "multi_thread")] - $(#[$meta])* + $(#[$attr])* async fn $test() { - let (prj, cmd) = $crate::util::setup_forge(stringify!($test), $style); - let f = $fun; - f(prj, cmd).await; + let (mut $prj, mut $cmd) = $crate::util::setup_forge(stringify!($test), $style); + $e } }; } #[macro_export] macro_rules! casttest { - ($(#[$meta:meta])* $test:ident, $fun:expr) => { - $crate::casttest!($(#[$meta])* $test, $crate::ethers_solc::PathStyle::Dapptools, $fun); + ($(#[$attr:meta])* $test:ident, $($async:ident)? |$prj:ident, $cmd:ident| $e:expr) => { + $crate::casttest!($(#[$attr])* $test, $crate::foundry_compilers::PathStyle::Dapptools, $($async)? |$prj, $cmd| $e); }; - ($(#[$meta:meta])* $test:ident, $style:expr, $fun:expr) => { + ($(#[$attr:meta])* $test:ident, $style:expr, |$prj:ident, $cmd:ident| $e:expr) => { + #[allow(clippy::disallowed_macros)] #[test] - $(#[$meta])* + $(#[$attr])* fn $test() { - let (prj, cmd) = $crate::util::setup_cast(stringify!($test), $style); - let f = $fun; - f(prj, cmd); + let (mut $prj, mut $cmd) = $crate::util::setup_cast(stringify!($test), $style); + $e + } + }; + ($(#[$attr:meta])* $test:ident, $style:expr, async |$prj:ident, $cmd:ident| $e:expr) => { + #[allow(clippy::disallowed_macros)] + #[tokio::test(flavor = "multi_thread")] + $(#[$attr])* + async fn $test() { + let (mut $prj, mut $cmd) = $crate::util::setup_cast(stringify!($test), $style); + $e } }; } /// Same as `forgetest` but returns an already initialized project workspace (`forge init`) #[macro_export] +#[allow(clippy::disallowed_macros)] macro_rules! forgetest_init { - ($(#[$meta:meta])* $test:ident, $fun:expr) => { - $crate::forgetest_init!($(#[$meta])* $test, $crate::ethers_solc::PathStyle::Dapptools, $fun); + ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { + $crate::forgetest_init!($(#[$attr])* $test, $crate::foundry_compilers::PathStyle::Dapptools, |$prj, $cmd| $e); }; - ($(#[$meta:meta])* $test:ident, $style:expr, $fun:expr) => { + ($(#[$attr:meta])* $test:ident, $style:expr, |$prj:ident, $cmd:ident| $e:expr) => { + #[allow(clippy::disallowed_macros)] #[test] - $(#[$meta])* + $(#[$attr])* fn $test() { - let (prj, cmd) = $crate::util::setup_forge(stringify!($test), $style); - $crate::util::initialize(prj.root()); - let f = $fun; - f(prj, cmd); + let (mut $prj, mut $cmd) = $crate::util::setup_forge(stringify!($test), $style); + $crate::util::initialize($prj.root()); + $e } }; } -/// Clones an external repository and makes sure the tests pass. -/// Can optionally enable fork mode as well if a fork block is passed. -/// The fork block needs to be greater than 0. +/// Setup forge soldeer #[macro_export] -macro_rules! forgetest_external { - // forgetest_external!(test_name, "owner/repo"); - ($(#[$meta:meta])* $test:ident, $repo:literal) => { - $crate::forgetest_external!($(#[$meta])* $test, $repo, 0, Vec::::new()); - }; - // forgetest_external!(test_name, "owner/repo", 1234); - ($(#[$meta:meta])* $test:ident, $repo:literal, $fork_block:literal) => { - $crate::forgetest_external!( - $(#[$meta])* - $test, - $repo, - $crate::ethers_solc::PathStyle::Dapptools, - $fork_block, - Vec::::new() - ); - }; - // forgetest_external!(test_name, "owner/repo", &["--extra-opt", "val"]); - ($(#[$meta:meta])* $test:ident, $repo:literal, $forge_opts:expr) => { - $crate::forgetest_external!($(#[$meta])* $test, $repo, 0, $forge_opts); +#[allow(clippy::disallowed_macros)] +macro_rules! forgesoldeer { + ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { + $crate::forgesoldeer!($(#[$attr])* $test, $crate::foundry_compilers::PathStyle::Dapptools, |$prj, $cmd| $e); }; - // forgetest_external!(test_name, "owner/repo", 1234, &["--extra-opt", "val"]); - ($(#[$meta:meta])* $test:ident, $repo:literal, $fork_block:literal, $forge_opts:expr) => { - $crate::forgetest_external!( - $(#[$meta])* - $test, - $repo, - $crate::ethers_solc::PathStyle::Dapptools, - $fork_block, - $forge_opts - ); - }; - // forgetest_external!(test_name, "owner/repo", PathStyle::Dapptools, 123); - ($(#[$meta:meta])* $test:ident, $repo:literal, $style:expr, $fork_block:literal, $forge_opts:expr) => { + ($(#[$attr:meta])* $test:ident, $style:expr, |$prj:ident, $cmd:ident| $e:expr) => { + #[allow(clippy::disallowed_macros)] #[test] - $(#[$meta])* + $(#[$attr])* fn $test() { - use std::process::{Command, Stdio}; - - // Skip fork tests if the RPC url is not set. - if $fork_block > 0 && std::env::var("ETH_RPC_URL").is_err() { - eprintln!("Skipping test {}. ETH_RPC_URL is not set.", $repo); - return - }; - - let (prj, mut cmd) = $crate::util::setup_forge(stringify!($test), $style); - - // Wipe the default structure - prj.wipe(); - - // Clone the external repository - let git_clone = - $crate::util::clone_remote(&format!("https://github.com/{}", $repo), prj.root()) - .expect("Could not clone repository. Is git installed?"); - assert!( - git_clone.status.success(), - "could not clone repository:\nstdout:\n{}\nstderr:\n{}", - String::from_utf8_lossy(&git_clone.stdout), - String::from_utf8_lossy(&git_clone.stderr) - ); - - // We just run make install, but we do not care if it worked or not, - // since some repositories do not have that target - let make_install = Command::new("make") - .arg("install") - .current_dir(prj.root()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); - - // Run the tests - cmd.arg("test").args($forge_opts).args([ - "--optimize", - "--optimizer-runs", - "20000", - "--ffi", - ]); - cmd.set_env("FOUNDRY_FUZZ_RUNS", "1"); - - let next_eth_rpc_url = foundry_utils::rpc::next_http_archive_rpc_endpoint(); - if $fork_block > 0 { - cmd.set_env("FOUNDRY_ETH_RPC_URL", next_eth_rpc_url); - cmd.set_env("FOUNDRY_FORK_BLOCK_NUMBER", stringify!($fork_block)); - } - cmd.assert_non_empty_stdout(); - } - }; -} - -/// A macro to compare outputs -#[macro_export] -macro_rules! pretty_eq { - ($expected:expr, $got:expr) => { - let expected = &*$expected; - let got = &*$got; - if expected != got { - panic!( - " -outputs differ! - -expected: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -{} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -got: -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -{} -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -", - expected, got - ); + let (mut $prj, mut $cmd) = $crate::util::setup_forge(stringify!($test), $style); + $crate::util::initialize($prj.root()); + $e } }; } diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs new file mode 100644 index 0000000000000..12762324dd2bb --- /dev/null +++ b/crates/test-utils/src/rpc.rs @@ -0,0 +1,325 @@ +//! RPC API keys utilities. + +use foundry_config::{NamedChain, NamedChain::Optimism}; +use rand::seq::SliceRandom; +use std::{ + env, + sync::{ + atomic::{AtomicUsize, Ordering}, + LazyLock, + }, +}; + +/// Env var key for ws archive endpoints. +const ENV_WS_ARCHIVE_ENDPOINTS: &str = "WS_ARCHIVE_URLS"; +/// Env var key for http archive endpoints. +const ENV_HTTP_ARCHIVE_ENDPOINTS: &str = "HTTP_ARCHIVE_URLS"; + +// List of general purpose infura keys to rotate through +static INFURA_KEYS: LazyLock> = LazyLock::new(|| { + let mut keys = vec![ + // "6cb19d07ca2d44f59befd61563b1037b", + // "6d46c0cca653407b861f3f93f7b0236a", + // "69a36846dec146e3a2898429be60be85", + // "16a8be88795540b9b3903d8de0f7baa5", + // "f4a0bdad42674adab5fc0ac077ffab2b", + // "5c812e02193c4ba793f8c214317582bd", + ]; + + keys.shuffle(&mut rand::thread_rng()); + + keys +}); + +// List of alchemy keys for mainnet +static ALCHEMY_KEYS: LazyLock> = LazyLock::new(|| { + let mut keys = vec![ + // "ib1f4u1ojm-9lJJypwkeZeG-75TJRB7O", + // "7mTtk6IW4DwroGnKmG_bOWri2hyaGYhX", + // "GL4M0hfzSYGU5e1_t804HoUDOObWP-FA", + // "WV407BEiBmjNJfKo9Uo_55u0z0ITyCOX", + // "Ge56dH9siMF4T0whP99sQXOcr2mFs8wZ", + // "QC55XC151AgkS3FNtWvz9VZGeu9Xd9lb", + // "pwc5rmJhrdoaSEfimoKEmsvOjKSmPDrP", + // "A5sZ85MIr4SzCMkT0zXh2eeamGIq3vGL", + // "9VWGraLx0tMiSWx05WH-ywgSVmMxs66W", + // "U4hsGWgl9lBM1j3jhSgJ4gbjHg2jRwKy", + "K-uNlqYoYCO9cdBHcifwCDAcEjDy1UHL", + "GWdgwabOE2XfBdLp_gIq-q6QHa7DSoag", + "Uz0cF5HCXFtpZlvd9NR7kHxfB_Wdpsx7", + "wWZMf1SOu9lT1GNIJHOX-5WL1MiYXycT", + // "HACxy4wNUoD-oLlCq_v5LG0bclLc_DRL", + // "_kCjfMjYo8x0rOm6YzmvSI0Qk-c8SO5I", + // "kD-M-g5TKb957S3bbOXxXPeMUxm1uTuU", + // "jQqqfTOQN_7A6gQEjzRYpVwXzxEBN9aj", + // "jGiK5vwDfC3F4r0bqukm-W2GqgdrxdSr", + // "Reoz-NZSjWczcAQOeVTz_Ejukb8mAton", + // "-DQx9U-heCeTgYsAXwaTurmGytc-0mbR", + // "sDNCLu_e99YZRkbWlVHiuM3BQ5uxYCZU", + // "M6lfpxTBrywHOvKXOS4yb7cTTpa25ZQ9", + // "UK8U_ogrbYB4lQFTGJHHDrbiS4UPnac6", + "Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", + // "UVatYU2Ax0rX6bDiqddeTRDdcCxzdpoE", + "bVjX9v-FpmUhf5R_oHIgwJx2kXvYPRbx", + ]; + keys.shuffle(&mut rand::thread_rng()); + keys +}); + +// List of etherscan keys for mainnet +static ETHERSCAN_MAINNET_KEYS: LazyLock> = LazyLock::new(|| { + let mut keys = vec![ + "MCAUM7WPE9XP5UQMZPCKIBUJHPM1C24FP6", + "JW6RWCG2C5QF8TANH4KC7AYIF1CX7RB5D1", + "ZSMDY6BI2H55MBE3G9CUUQT4XYUDBB6ZSK", + "4FYHTY429IXYMJNS4TITKDMUKW5QRYDX61", + "QYKNT5RHASZ7PGQE68FNQWH99IXVTVVD2I", + "VXMQ117UN58Y4RHWUB8K1UGCEA7UQEWK55", + "C7I2G4JTA5EPYS42Z8IZFEIMQNI5GXIJEV", + "A15KZUMZXXCK1P25Y1VP1WGIVBBHIZDS74", + "3IA6ASNQXN8WKN7PNFX7T72S9YG56X9FPG", + "ZUB97R31KSYX7NYVW6224Q6EYY6U56H591", + // Optimism + // "JQNGFHINKS1W7Y5FRXU4SPBYF43J3NYK46", + ]; + keys.shuffle(&mut rand::thread_rng()); + keys +}); + +// List of etherscan keys for Optimism. +static ETHERSCAN_OPTIMISM_KEYS: LazyLock> = + LazyLock::new(|| vec!["JQNGFHINKS1W7Y5FRXU4SPBYF43J3NYK46"]); + +/// Returns the next index to use. +fn next_idx() -> usize { + static NEXT_INDEX: AtomicUsize = AtomicUsize::new(0); + NEXT_INDEX.fetch_add(1, Ordering::SeqCst) +} + +/// Returns the next item in the list to use. +fn next(list: &[T]) -> &T { + &list[next_idx() % list.len()] +} + +/// Returns the next _mainnet_ rpc URL in inline +/// +/// This will rotate all available rpc endpoints +pub fn next_http_rpc_endpoint() -> String { + next_rpc_endpoint(NamedChain::Mainnet) +} + +/// Returns the next _mainnet_ rpc URL in inline +/// +/// This will rotate all available rpc endpoints +pub fn next_ws_rpc_endpoint() -> String { + next_ws_endpoint(NamedChain::Mainnet) +} + +/// Returns the next HTTP RPC URL. +pub fn next_rpc_endpoint(chain: NamedChain) -> String { + next_url(false, chain) +} + +/// Returns the next WS RPC URL. +pub fn next_ws_endpoint(chain: NamedChain) -> String { + next_url(true, chain) +} + +/// Returns a websocket URL that has access to archive state +pub fn next_http_archive_rpc_url() -> String { + next_archive_url(false) +} + +/// Returns an HTTP URL that has access to archive state +pub fn next_ws_archive_rpc_url() -> String { + next_archive_url(true) +} + +/// Returns a URL that has access to archive state. +/// +/// Uses either environment variables (comma separated urls) or default keys. +fn next_archive_url(is_ws: bool) -> String { + let urls = archive_urls(is_ws); + let url = if env_archive_urls(is_ws).is_empty() { + next(urls) + } else { + urls.choose_weighted(&mut rand::thread_rng(), |url| { + if url.contains("reth") { + 2usize + } else { + 1usize + } + }) + .unwrap() + }; + eprintln!("--- next_archive_url(is_ws={is_ws}) = {url} ---"); + url.clone() +} + +fn archive_urls(is_ws: bool) -> &'static [String] { + static WS: LazyLock> = LazyLock::new(|| get(true)); + static HTTP: LazyLock> = LazyLock::new(|| get(false)); + + fn get(is_ws: bool) -> Vec { + let env_urls = env_archive_urls(is_ws); + if !env_urls.is_empty() { + let mut urls = env_urls.to_vec(); + urls.shuffle(&mut rand::thread_rng()); + return urls; + } + + let mut urls = Vec::new(); + for &key in ALCHEMY_KEYS.iter() { + if is_ws { + urls.push(format!("wss://eth-mainnet.g.alchemy.com/v2/{key}")); + } else { + urls.push(format!("https://eth-mainnet.g.alchemy.com/v2/{key}")); + } + } + urls + } + + if is_ws { + &WS + } else { + &HTTP + } +} + +fn env_archive_urls(is_ws: bool) -> &'static [String] { + static WS: LazyLock> = LazyLock::new(|| get(true)); + static HTTP: LazyLock> = LazyLock::new(|| get(false)); + + fn get(is_ws: bool) -> Vec { + let env = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS }; + let env = env::var(env).unwrap_or_default(); + let env = env.trim(); + if env.is_empty() { + return vec![]; + } + env.split(',').map(str::trim).filter(|s| !s.is_empty()).map(ToString::to_string).collect() + } + + if is_ws { + &WS + } else { + &HTTP + } +} + +/// Returns the next etherscan api key +pub fn next_mainnet_etherscan_api_key() -> String { + next_etherscan_api_key(NamedChain::Mainnet) +} + +/// Returns the next etherscan api key for given chain. +pub fn next_etherscan_api_key(chain: NamedChain) -> String { + let keys = match chain { + Optimism => ÐERSCAN_OPTIMISM_KEYS, + _ => ÐERSCAN_MAINNET_KEYS, + }; + let key = next(keys).to_string(); + eprintln!("--- next_etherscan_api_key(chain={chain:?}) = {key} ---"); + key +} + +fn next_url(is_ws: bool, chain: NamedChain) -> String { + use NamedChain::*; + + if matches!(chain, NamedChain::Base) { + return "https://mainnet.base.org".to_string(); + } + + let idx = next_idx() % (INFURA_KEYS.len() + ALCHEMY_KEYS.len()); + let is_infura = idx < INFURA_KEYS.len(); + + let key = if is_infura { INFURA_KEYS[idx] } else { ALCHEMY_KEYS[idx - INFURA_KEYS.len()] }; + + // Nowhere near complete. + let prefix = if is_infura { + match chain { + Optimism => "optimism", + Arbitrum => "arbitrum", + Polygon => "polygon", + _ => "", + } + } else { + match chain { + Optimism => "opt", + Arbitrum => "arb", + Polygon => "polygon", + _ => "eth", + } + }; + let network = if is_infura { + match chain { + Mainnet | Optimism | Arbitrum | Polygon => "mainnet", + _ => chain.as_str(), + } + } else { + match chain { + Mainnet | Optimism | Arbitrum | Polygon => "mainnet", + _ => chain.as_str(), + } + }; + let full = if prefix.is_empty() { network.to_string() } else { format!("{prefix}-{network}") }; + + let url = match (is_ws, is_infura) { + (false, true) => format!("https://{full}.infura.io/v3/{key}"), + (true, true) => format!("wss://{full}.infura.io/ws/v3/{key}"), + (false, false) => format!("https://{full}.g.alchemy.com/v2/{key}"), + (true, false) => format!("wss://{full}.g.alchemy.com/v2/{key}"), + }; + eprintln!("--- next_url(is_ws={is_ws}, chain={chain:?}) = {url} ---"); + url +} + +#[cfg(test)] +#[allow(clippy::disallowed_macros)] +mod tests { + use super::*; + use alloy_primitives::address; + use foundry_config::Chain; + + #[tokio::test] + #[ignore = "run manually"] + async fn test_etherscan_keys() { + let address = address!("dAC17F958D2ee523a2206206994597C13D831ec7"); + let mut first_abi = None; + let mut failed = Vec::new(); + for (i, &key) in ETHERSCAN_MAINNET_KEYS.iter().enumerate() { + println!("trying key {i} ({key})"); + + let client = foundry_block_explorers::Client::builder() + .chain(Chain::mainnet()) + .unwrap() + .with_api_key(key) + .build() + .unwrap(); + + let mut fail = |e: &str| { + eprintln!("key {i} ({key}) failed: {e}"); + failed.push(key); + }; + + let abi = match client.contract_abi(address).await { + Ok(abi) => abi, + Err(e) => { + fail(&e.to_string()); + continue; + } + }; + + if let Some(first_abi) = &first_abi { + if abi != *first_abi { + fail("abi mismatch"); + } + } else { + first_abi = Some(abi); + } + } + if !failed.is_empty() { + panic!("failed keys: {failed:#?}"); + } + } +} diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index 4ff1b47aede3a..b82126d2db288 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -1,23 +1,54 @@ -use crate::TestCommand; -use ethers::{ - abi::Address, - prelude::{Middleware, NameOrAddress, U256}, - utils::hex, -}; +use crate::{init_tracing, util::lossy_string, TestCommand}; +use alloy_primitives::Address; +use alloy_provider::Provider; use eyre::Result; -use foundry_common::{get_http_provider, RetryProvider}; -use std::{collections::BTreeMap, path::Path, str::FromStr}; - -pub const BROADCAST_TEST_PATH: &str = "src/Broadcast.t.sol"; +use foundry_common::provider::{get_http_provider, RetryProvider}; +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, + str::FromStr, +}; +const BROADCAST_TEST_PATH: &str = "src/Broadcast.t.sol"; +const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); + +fn init_script_cmd( + cmd: &mut TestCommand, + project_root: &Path, + target_contract: &str, + endpoint: Option<&str>, +) { + cmd.forge_fuse(); + cmd.set_current_dir(project_root); + + cmd.args([ + "script", + "-R", + "ds-test/=lib/", + "-R", + "cheats/=cheats/", + target_contract, + "--root", + project_root.to_str().unwrap(), + "-vvvvv", + ]); + + if let Some(rpc_url) = endpoint { + cmd.args(["--fork-url", rpc_url]); + } +} /// A helper struct to test forge script scenarios pub struct ScriptTester { pub accounts_pub: Vec
, pub accounts_priv: Vec, pub provider: Option, - pub nonces: BTreeMap, - pub address_nonces: BTreeMap, + pub nonces: BTreeMap, + pub address_nonces: BTreeMap, pub cmd: TestCommand, + pub project_root: PathBuf, + pub target_contract: String, + pub endpoint: Option, } impl ScriptTester { @@ -28,26 +59,16 @@ impl ScriptTester { project_root: &Path, target_contract: &str, ) -> Self { - ScriptTester::copy_testdata(project_root).unwrap(); - cmd.set_current_dir(project_root); - - cmd.args([ - "script", - "-R", - "ds-test/=lib/", - target_contract, - "--root", - project_root.to_str().unwrap(), - "-vvvvv", - ]); + init_tracing(); + Self::copy_testdata(project_root).unwrap(); + init_script_cmd(&mut cmd, project_root, target_contract, endpoint); let mut provider = None; if let Some(endpoint) = endpoint { - cmd.args(["--fork-url", endpoint]); provider = Some(get_http_provider(endpoint)) } - ScriptTester { + Self { accounts_pub: vec![ Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap(), Address::from_str("0x70997970C51812dc3A010C7d01b50e0d17dc79C8").unwrap(), @@ -62,6 +83,9 @@ impl ScriptTester { nonces: BTreeMap::default(), address_nonces: BTreeMap::default(), cmd, + project_root: project_root.to_path_buf(), + target_contract: target_contract.to_string(), + endpoint: endpoint.map(|s| s.to_string()), } } @@ -71,9 +95,11 @@ impl ScriptTester { let target_contract = project_root.join(BROADCAST_TEST_PATH).to_string_lossy().to_string(); // copy the broadcast test - let testdata = Self::testdata_path(); - std::fs::copy(testdata + "/cheats/Broadcast.t.sol", project_root.join(BROADCAST_TEST_PATH)) - .expect("Failed to initialize broadcast contract"); + fs::copy( + Self::testdata_path().join("default/cheats/Broadcast.t.sol"), + project_root.join(BROADCAST_TEST_PATH), + ) + .expect("Failed to initialize broadcast contract"); Self::new(cmd, Some(endpoint), project_root, &target_contract) } @@ -85,36 +111,36 @@ impl ScriptTester { // copy the broadcast test let testdata = Self::testdata_path(); - std::fs::copy(testdata + "/cheats/Broadcast.t.sol", project_root.join(BROADCAST_TEST_PATH)) - .expect("Failed to initialize broadcast contract"); + fs::copy( + testdata.join("default/cheats/Broadcast.t.sol"), + project_root.join(BROADCAST_TEST_PATH), + ) + .expect("Failed to initialize broadcast contract"); Self::new(cmd, None, project_root, &target_contract) } /// Returns the path to the dir that contains testdata - fn testdata_path() -> String { - concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata").into() + fn testdata_path() -> &'static Path { + Path::new(TESTDATA) } /// Initialises the test contracts by copying them into the workspace fn copy_testdata(current_dir: &Path) -> Result<()> { let testdata = Self::testdata_path(); - std::fs::copy(testdata.clone() + "/cheats/Vm.sol", current_dir.join("src/Vm.sol"))?; - std::fs::copy(testdata + "/lib/ds-test/src/test.sol", current_dir.join("lib/test.sol"))?; - + fs::create_dir_all(current_dir.join("cheats"))?; + fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("cheats/Vm.sol"))?; + fs::copy(testdata.join("lib/ds-test/src/test.sol"), current_dir.join("lib/test.sol"))?; Ok(()) } - pub async fn load_private_keys(&mut self, keys_indexes: Vec) -> &mut Self { - for index in keys_indexes { + pub async fn load_private_keys(&mut self, keys_indexes: &[u32]) -> &mut Self { + for &index in keys_indexes { self.cmd.args(["--private-keys", &self.accounts_priv[index as usize]]); if let Some(provider) = &self.provider { let nonce = provider - .get_transaction_count( - NameOrAddress::Address(self.accounts_pub[index as usize]), - None, - ) + .get_transaction_count(self.accounts_pub[index as usize]) .await .unwrap(); self.nonces.insert(index, nonce); @@ -123,43 +149,35 @@ impl ScriptTester { self } - pub async fn load_addresses(&mut self, addresses: Vec
) -> &mut Self { - for address in addresses { - let nonce = self - .provider - .as_ref() - .unwrap() - .get_transaction_count(NameOrAddress::Address(address), None) - .await - .unwrap(); + pub async fn load_addresses(&mut self, addresses: &[Address]) -> &mut Self { + for &address in addresses { + let nonce = + self.provider.as_ref().unwrap().get_transaction_count(address).await.unwrap(); self.address_nonces.insert(address, nonce); } self } pub fn add_deployer(&mut self, index: u32) -> &mut Self { - self.cmd.args([ - "--sender", - &format!("0x{}", hex::encode(self.accounts_pub[index as usize].as_bytes())), - ]); - self + self.sender(self.accounts_pub[index as usize]) } /// Adds given address as sender pub fn sender(&mut self, addr: Address) -> &mut Self { - self.cmd.args(["--sender", format!("{addr:?}").as_str()]); - self + self.args(&["--sender", addr.to_string().as_str()]) } pub fn add_sig(&mut self, contract_name: &str, sig: &str) -> &mut Self { - self.cmd.args(["--tc", contract_name, "--sig", sig]); - self + self.args(&["--tc", contract_name, "--sig", sig]) + } + + pub fn add_create2_deployer(&mut self, create2_deployer: Address) -> &mut Self { + self.args(&["--create2-deployer", create2_deployer.to_string().as_str()]) } /// Adds the `--unlocked` flag pub fn unlocked(&mut self) -> &mut Self { - self.cmd.arg("--unlocked"); - self + self.arg("--unlocked") } pub fn simulate(&mut self, expected: ScriptOutcome) -> &mut Self { @@ -167,31 +185,26 @@ impl ScriptTester { } pub fn broadcast(&mut self, expected: ScriptOutcome) -> &mut Self { - self.cmd.arg("--broadcast"); - self.run(expected) + self.arg("--broadcast").run(expected) } pub fn resume(&mut self, expected: ScriptOutcome) -> &mut Self { - self.cmd.arg("--resume"); - self.run(expected) + self.arg("--resume").run(expected) } - /// In Vec<(private_key_slot, expected increment)> - pub async fn assert_nonce_increment(&mut self, keys_indexes: Vec<(u32, u32)>) -> &mut Self { - for (private_key_slot, expected_increment) in keys_indexes { - let nonce = self - .provider - .as_ref() - .unwrap() - .get_transaction_count( - NameOrAddress::Address(self.accounts_pub[private_key_slot as usize]), - None, - ) - .await - .unwrap(); + /// `[(private_key_slot, expected increment)]` + pub async fn assert_nonce_increment(&mut self, keys_indexes: &[(u32, u32)]) -> &mut Self { + for &(private_key_slot, expected_increment) in keys_indexes { + let addr = self.accounts_pub[private_key_slot as usize]; + let nonce = self.provider.as_ref().unwrap().get_transaction_count(addr).await.unwrap(); let prev_nonce = self.nonces.get(&private_key_slot).unwrap(); - assert_eq!(nonce, prev_nonce + U256::from(expected_increment)); + assert_eq!( + nonce, + (*prev_nonce + expected_increment as u64), + "nonce not incremented correctly for {addr}: \ + {prev_nonce} + {expected_increment} != {nonce}" + ); } self } @@ -199,42 +212,58 @@ impl ScriptTester { /// In Vec<(address, expected increment)> pub async fn assert_nonce_increment_addresses( &mut self, - address_indexes: Vec<(Address, u32)>, + address_indexes: &[(Address, u32)], ) -> &mut Self { for (address, expected_increment) in address_indexes { - let nonce = self - .provider - .as_ref() - .unwrap() - .get_transaction_count(NameOrAddress::Address(address), None) - .await - .unwrap(); - let prev_nonce = self.address_nonces.get(&address).unwrap(); - - assert_eq!(nonce, prev_nonce + U256::from(expected_increment)); + let nonce = + self.provider.as_ref().unwrap().get_transaction_count(*address).await.unwrap(); + let prev_nonce = self.address_nonces.get(address).unwrap(); + + assert_eq!(nonce, *prev_nonce + *expected_increment as u64); } self } pub fn run(&mut self, expected: ScriptOutcome) -> &mut Self { - let output = - if expected.is_err() { self.cmd.stderr_lossy() } else { self.cmd.stdout_lossy() }; + let out = self.cmd.execute(); + let (stdout, stderr) = (lossy_string(&out.stdout), lossy_string(&out.stderr)); + + trace!(target: "tests", "STDOUT\n{stdout}\n\nSTDERR\n{stderr}"); - if !output.contains(expected.as_str()) { - panic!("OUTPUT: {output}\n\nEXPECTED: {}", expected.as_str()); + if !stdout.contains(expected.as_str()) && !stderr.contains(expected.as_str()) { + panic!( + "--STDOUT--\n{stdout}\n\n--STDERR--\n{stderr}\n\n--EXPECTED--\n{:?} not found in stdout or stderr", + expected.as_str() + ); } + self } pub fn slow(&mut self) -> &mut Self { - self.cmd.arg("--slow"); + self.arg("--slow") + } + + pub fn arg(&mut self, arg: &str) -> &mut Self { + self.cmd.arg(arg); self } - pub fn args(&mut self, args: Vec) -> &mut Self { + pub fn args(&mut self, args: &[&str]) -> &mut Self { self.cmd.args(args); self } + + pub fn clear(&mut self) { + init_script_cmd( + &mut self.cmd, + &self.project_root, + &self.target_contract, + self.endpoint.as_deref(), + ); + self.nonces.clear(); + self.address_nonces.clear(); + } } /// Various `forge` script results @@ -247,39 +276,42 @@ pub enum ScriptOutcome { MissingSender, MissingWallet, StaticCallNotAllowed, - FailedScript, + ScriptFailed, UnsupportedLibraries, ErrorSelectForkOnBroadcast, + OkRun, } impl ScriptOutcome { pub fn as_str(&self) -> &'static str { match self { - ScriptOutcome::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.", - ScriptOutcome::OkSimulation => "SIMULATION COMPLETE. To broadcast these", - ScriptOutcome::OkBroadcast => "ONCHAIN EXECUTION COMPLETE & SUCCESSFUL", - ScriptOutcome::WarnSpecifyDeployer => "You have more than one deployer who could predeploy libraries. Using `--sender` instead.", - ScriptOutcome::MissingSender => "You seem to be using Foundry's default sender. Be sure to set your own --sender", - ScriptOutcome::MissingWallet => "No associated wallet", - ScriptOutcome::StaticCallNotAllowed => "Staticcalls are not allowed after vm.broadcast. Either remove it, or use vm.startBroadcast instead.", - ScriptOutcome::FailedScript => "Script failed.", - ScriptOutcome::UnsupportedLibraries => "Multi chain deployment does not support library linking at the moment.", - ScriptOutcome::ErrorSelectForkOnBroadcast => "You need to stop broadcasting before you can select forks." + Self::OkNoEndpoint => "If you wish to simulate on-chain transactions pass a RPC URL.", + Self::OkSimulation => "SIMULATION COMPLETE. To broadcast these", + Self::OkBroadcast => "ONCHAIN EXECUTION COMPLETE & SUCCESSFUL", + Self::WarnSpecifyDeployer => "Warning: You have more than one deployer who could predeploy libraries. Using `--sender` instead.", + Self::MissingSender => "You seem to be using Foundry's default sender. Be sure to set your own --sender", + Self::MissingWallet => "No associated wallet", + Self::StaticCallNotAllowed => "staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead", + Self::ScriptFailed => "script failed: ", + Self::UnsupportedLibraries => "Multi chain deployment does not support library linking at the moment.", + Self::ErrorSelectForkOnBroadcast => "cannot select forks during a broadcast", + Self::OkRun => "Script ran successfully", } } pub fn is_err(&self) -> bool { match self { - ScriptOutcome::OkNoEndpoint | - ScriptOutcome::OkSimulation | - ScriptOutcome::OkBroadcast | - ScriptOutcome::WarnSpecifyDeployer => false, - ScriptOutcome::MissingSender | - ScriptOutcome::MissingWallet | - ScriptOutcome::StaticCallNotAllowed | - ScriptOutcome::UnsupportedLibraries | - ScriptOutcome::ErrorSelectForkOnBroadcast | - ScriptOutcome::FailedScript => true, + Self::OkNoEndpoint | + Self::OkSimulation | + Self::OkBroadcast | + Self::WarnSpecifyDeployer | + Self::OkRun => false, + Self::MissingSender | + Self::MissingWallet | + Self::StaticCallNotAllowed | + Self::UnsupportedLibraries | + Self::ErrorSelectForkOnBroadcast | + Self::ScriptFailed => true, } } } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index 1bccadb2249c4..f2b71064f1f56 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,71 +1,313 @@ -use ethers_solc::{ - cache::SolFilesCache, +use crate::init_tracing; +use eyre::{Result, WrapErr}; +use foundry_compilers::{ + artifacts::Contract, + cache::CompilerCache, + compilers::multi::MultiCompiler, + error::Result as SolcResult, project_util::{copy_dir, TempProject}, - ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, Solc, + solc::SolcSettings, + ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, }; -use eyre::{Result, WrapErr}; use foundry_config::Config; -use once_cell::sync::Lazy; use parking_lot::Mutex; use regex::Regex; +use snapbox::{assert_data_eq, cmd::OutputAssert, Data, IntoData}; use std::{ env, ffi::OsStr, - fmt::Display, - fs, - fs::File, - io::{BufWriter, IsTerminal, Write}, + fs::{self, File}, + io::{BufWriter, IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, - process::{self, Command, Stdio}, + process::{ChildStdin, Command, Output, Stdio}, sync::{ atomic::{AtomicUsize, Ordering}, - Arc, + Arc, LazyLock, }, }; -static CURRENT_DIR_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); +static CURRENT_DIR_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + +/// The commit of forge-std to use. +const FORGE_STD_REVISION: &str = include_str!("../../../testdata/forge-std-rev"); -/// A lock used for pre-installing commonly used solc versions once. -/// Pre-installing is useful, because if two forge test require a missing solc at the same time, one -/// can encounter an OS error 26 textfile busy if it tries to write the freshly downloaded solc to -/// the right location while the other test already did that and is currently executing this solc -/// binary. -static PRE_INSTALL_SOLC_LOCK: Lazy> = Lazy::new(|| Mutex::new(false)); +/// Stores whether `stdout` is a tty / terminal. +pub static IS_TTY: LazyLock = LazyLock::new(|| std::io::stdout().is_terminal()); -// This stores `true` if the current terminal is a tty -pub static IS_TTY: Lazy = Lazy::new(|| std::io::stdout().is_terminal()); +/// Global default template path. Contains the global template project from which all other +/// temp projects are initialized. See [`initialize()`] for more info. +static TEMPLATE_PATH: LazyLock = + LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template")); -/// Contains a `forge init` initialized project -pub static FORGE_INITIALIZED: Lazy = Lazy::new(|| { - let (prj, mut cmd) = setup_forge("init-template", PathStyle::Dapptools); - cmd.args(["init", "--force"]); - cmd.assert_non_empty_stdout(); - prj -}); +/// Global default template lock. If its contents are not exactly `"1"`, the global template will +/// be re-initialized. See [`initialize()`] for more info. +static TEMPLATE_LOCK: LazyLock = + LazyLock::new(|| env::temp_dir().join("foundry-forge-test-template.lock")); -// identifier for tests +/// Global test identifier. static NEXT_ID: AtomicUsize = AtomicUsize::new(0); -/// Copies an initialized project to the given path -pub fn initialize(target: impl AsRef) { - FORGE_INITIALIZED.copy_to(target) +/// The default Solc version used when compiling tests. +pub const SOLC_VERSION: &str = "0.8.27"; + +/// Another Solc version used when compiling tests. +/// +/// Necessary to avoid downloading multiple versions. +pub const OTHER_SOLC_VERSION: &str = "0.8.26"; + +/// External test builder +#[derive(Clone, Debug)] +#[must_use = "ExtTester does nothing unless you `run` it"] +pub struct ExtTester { + pub org: &'static str, + pub name: &'static str, + pub rev: &'static str, + pub style: PathStyle, + pub fork_block: Option, + pub args: Vec, + pub envs: Vec<(String, String)>, + pub install_commands: Vec>, } -/// Clones a remote repository into the specified directory. -pub fn clone_remote( - repo_url: &str, - target_dir: impl AsRef, -) -> std::io::Result { - Command::new("git") - .args([ - "clone", - "--depth", - "1", - "--recursive", - repo_url, - target_dir.as_ref().to_str().expect("Target path for git clone does not exist"), - ]) - .output() +impl ExtTester { + /// Creates a new external test builder. + pub fn new(org: &'static str, name: &'static str, rev: &'static str) -> Self { + Self { + org, + name, + rev, + style: PathStyle::Dapptools, + fork_block: None, + args: vec![], + envs: vec![], + install_commands: vec![], + } + } + + /// Sets the path style. + pub fn style(mut self, style: PathStyle) -> Self { + self.style = style; + self + } + + /// Sets the fork block. + pub fn fork_block(mut self, fork_block: u64) -> Self { + self.fork_block = Some(fork_block); + self + } + + /// Adds an argument to the forge command. + pub fn arg(mut self, arg: impl Into) -> Self { + self.args.push(arg.into()); + self + } + + /// Adds multiple arguments to the forge command. + pub fn args(mut self, args: I) -> Self + where + I: IntoIterator, + A: Into, + { + self.args.extend(args.into_iter().map(Into::into)); + self + } + + /// Adds an environment variable to the forge command. + pub fn env(mut self, key: impl Into, value: impl Into) -> Self { + self.envs.push((key.into(), value.into())); + self + } + + /// Adds multiple environment variables to the forge command. + pub fn envs(mut self, envs: I) -> Self + where + I: IntoIterator, + K: Into, + V: Into, + { + self.envs.extend(envs.into_iter().map(|(k, v)| (k.into(), v.into()))); + self + } + + /// Adds a command to run after the project is cloned. + /// + /// Note that the command is run in the project's root directory, and it won't fail the test if + /// it fails. + pub fn install_command(mut self, command: &[&str]) -> Self { + self.install_commands.push(command.iter().map(|s| s.to_string()).collect()); + self + } + + /// Runs the test. + pub fn run(&self) { + // Skip fork tests if the RPC url is not set. + if self.fork_block.is_some() && std::env::var_os("ETH_RPC_URL").is_none() { + eprintln!("ETH_RPC_URL is not set; skipping"); + return; + } + + let (prj, mut test_cmd) = setup_forge(self.name, self.style.clone()); + + // Wipe the default structure. + prj.wipe(); + + // Clone the external repository. + let repo_url = format!("https://github.com/{}/{}.git", self.org, self.name); + let root = prj.root().to_str().unwrap(); + clone_remote(&repo_url, root); + + // Checkout the revision. + if self.rev.is_empty() { + let mut git = Command::new("git"); + git.current_dir(root).args(["log", "-n", "1"]); + println!("$ {git:?}"); + let output = git.output().unwrap(); + if !output.status.success() { + panic!("git log failed: {output:?}"); + } + let stdout = String::from_utf8(output.stdout).unwrap(); + let commit = stdout.lines().next().unwrap().split_whitespace().nth(1).unwrap(); + panic!("pin to latest commit: {commit}"); + } else { + let mut git = Command::new("git"); + git.current_dir(root).args(["checkout", self.rev]); + println!("$ {git:?}"); + let status = git.status().unwrap(); + if !status.success() { + panic!("git checkout failed: {status}"); + } + } + + // Run installation command. + for install_command in &self.install_commands { + let mut install_cmd = Command::new(&install_command[0]); + install_cmd.args(&install_command[1..]).current_dir(root); + println!("cd {root}; {install_cmd:?}"); + match install_cmd.status() { + Ok(s) => { + println!("\n\n{install_cmd:?}: {s}"); + if s.success() { + break; + } + } + Err(e) => { + eprintln!("\n\n{install_cmd:?}: {e}"); + } + } + } + + // Run the tests. + test_cmd.arg("test"); + test_cmd.args(&self.args); + test_cmd.args(["--fuzz-runs=32", "--ffi", "-vvv"]); + + test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); + if let Some(fork_block) = self.fork_block { + test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_url()); + test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string()); + } + test_cmd.env("FOUNDRY_INVARIANT_DEPTH", "15"); + test_cmd.env("FOUNDRY_ALLOW_INTERNAL_EXPECT_REVERT", "true"); + + test_cmd.assert_success(); + } +} + +/// Initializes a project with `forge init` at the given path. +/// +/// This should be called after an empty project is created like in +/// [some of this crate's macros](crate::forgetest_init). +/// +/// ## Note +/// +/// This doesn't always run `forge init`, instead opting to copy an already-initialized template +/// project from a global template path. This is done to speed up tests. +/// +/// This used to use a `static` `Lazy`, but this approach does not with `cargo-nextest` because it +/// runs each test in a separate process. Instead, we use a global lock file to ensure that only one +/// test can initialize the template at a time. +#[allow(clippy::disallowed_macros)] +pub fn initialize(target: &Path) { + println!("initializing {}", target.display()); + + let tpath = TEMPLATE_PATH.as_path(); + pretty_err(tpath, fs::create_dir_all(tpath)); + + // Initialize the global template if necessary. + let mut lock = crate::fd_lock::new_lock(TEMPLATE_LOCK.as_path()); + let mut _read = Some(lock.read().unwrap()); + if fs::read(&*TEMPLATE_LOCK).unwrap() != b"1" { + // We are the first to acquire the lock: + // - initialize a new empty temp project; + // - run `forge init`; + // - run `forge build`; + // - copy it over to the global template; + // Ideally we would be able to initialize a temp project directly in the global template, + // but `TempProject` does not currently allow this: https://github.com/foundry-rs/compilers/issues/22 + + // Release the read lock and acquire a write lock, initializing the lock file. + _read = None; + + let mut write = lock.write().unwrap(); + + let mut data = String::new(); + write.read_to_string(&mut data).unwrap(); + + if data != "1" { + // Initialize and build. + let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); + println!("- initializing template dir in {}", prj.root().display()); + + cmd.args(["init", "--force"]).assert_success(); + prj.write_config(Config { + solc: Some(foundry_config::SolcReq::Version(SOLC_VERSION.parse().unwrap())), + ..Default::default() + }); + + // Checkout forge-std. + let output = Command::new("git") + .current_dir(prj.root().join("lib/forge-std")) + .args(["checkout", FORGE_STD_REVISION]) + .output() + .expect("failed to checkout forge-std"); + assert!(output.status.success(), "{output:#?}"); + + // Build the project. + cmd.forge_fuse().arg("build").assert_success(); + + // Remove the existing template, if any. + let _ = fs::remove_dir_all(tpath); + + // Copy the template to the global template path. + pretty_err(tpath, copy_dir(prj.root(), tpath)); + + // Update lockfile to mark that template is initialized. + write.set_len(0).unwrap(); + write.seek(std::io::SeekFrom::Start(0)).unwrap(); + write.write_all(b"1").unwrap(); + } + + // Release the write lock and acquire a new read lock. + drop(write); + _read = Some(lock.read().unwrap()); + } + + println!("- copying template dir from {}", tpath.display()); + pretty_err(target, fs::create_dir_all(target)); + pretty_err(target, copy_dir(tpath, target)); +} + +/// Clones a remote repository into the specified directory. Panics if the command fails. +pub fn clone_remote(repo_url: &str, target_dir: &str) { + let mut cmd = Command::new("git"); + cmd.args(["clone", "--no-tags", "--recursive", "--shallow-submodules"]); + cmd.args([repo_url, target_dir]); + println!("{cmd:?}"); + let status = cmd.status().unwrap(); + if !status.success() { + panic!("git clone failed: {status}"); + } + println!(); } /// Setup an empty test project and return a command pointing to the forge @@ -79,16 +321,12 @@ pub fn setup_forge(name: &str, style: PathStyle) -> (TestProject, TestCommand) { } pub fn setup_forge_project(test: TestProject) -> (TestProject, TestCommand) { - // preinstall commonly used solc once, we execute this here because this is the shared - // entrypoint used by all `forgetest!` macros - install_commonly_used_solc(); - let cmd = test.forge_command(); (test, cmd) } /// How to initialize a remote git project -#[derive(Debug, Clone)] +#[derive(Clone, Debug)] pub struct RemoteProject { id: String, run_build: bool, @@ -145,7 +383,7 @@ pub fn setup_forge_remote(prj: impl Into) -> (TestProject, TestCo try_setup_forge_remote(prj).unwrap() } -/// Same as `setup_forge_remote` but not panicing +/// Same as `setup_forge_remote` but not panicking pub fn try_setup_forge_remote( config: impl Into, ) -> Result<(TestProject, TestCommand)> { @@ -156,8 +394,7 @@ pub fn try_setup_forge_remote( let prj = TestProject::with_project(tmp); if config.run_build { let mut cmd = prj.forge_command(); - cmd.arg("build"); - cmd.ensure_execute_success().wrap_err("`forge build` unsuccessful")?; + cmd.arg("build").assert_success(); } for addon in config.run_commands { debug_assert!(!addon.is_empty()); @@ -187,40 +424,17 @@ pub fn setup_cast_project(test: TestProject) -> (TestProject, TestCommand) { (test, cmd) } -/// pre-installs commonly used solc versions -fn install_commonly_used_solc() { - let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock(); - if !*is_preinstalled { - let v0_8_19 = std::thread::spawn(|| Solc::blocking_install(&"0.8.19".parse().unwrap())); - let v0_8_20 = std::thread::spawn(|| Solc::blocking_install(&"0.8.20".parse().unwrap())); - let v0_8_21 = std::thread::spawn(|| Solc::blocking_install(&"0.8.21".parse().unwrap())); - - let wait = |res: std::thread::JoinHandle<_>| -> Result<(), ()> { - if let Err(err) = res.join().unwrap() { - eprintln!("{err:?}"); - // there could be another process that's currently installing this version, so we - // sleep here for a bit and assume the other process will be finished then - std::thread::sleep(std::time::Duration::from_secs(15)); - Err(()) - } else { - Ok(()) - } - }; - - // only set to installed if succeeded - *is_preinstalled = wait(v0_8_19).and(wait(v0_8_20)).and(wait(v0_8_21)).is_ok(); - } -} - /// `TestProject` represents a temporary project to run tests against. /// /// Test projects are created from a global atomic counter to avoid duplicates. #[derive(Clone, Debug)] -pub struct TestProject { +pub struct TestProject< + T: ArtifactOutput + Default = ConfigurableArtifacts, +> { /// The directory in which this test executable is running. - root: PathBuf, + exe_root: PathBuf, /// The project in which the test should run. - inner: Arc>, + inner: Arc>, } impl TestProject { @@ -234,9 +448,10 @@ impl TestProject { } pub fn with_project(project: TempProject) -> Self { - let root = - env::current_exe().unwrap().parent().expect("executable's directory").to_path_buf(); - Self { root, inner: Arc::new(project) } + init_tracing(); + let this = env::current_exe().unwrap(); + let exe_root = this.parent().expect("executable's directory").to_path_buf(); + Self { exe_root, inner: Arc::new(project) } } /// Returns the root path of the project's workspace. @@ -244,59 +459,139 @@ impl TestProject { self.inner.root() } - pub fn inner(&self) -> &TempProject { - &self.inner - } - + /// Returns the paths config. pub fn paths(&self) -> &ProjectPathsConfig { - self.inner().paths() + self.inner.paths() } - /// Returns the path to the project's `foundry.toml` file - pub fn config_path(&self) -> PathBuf { + /// Returns the path to the project's `foundry.toml` file. + pub fn config(&self) -> PathBuf { self.root().join(Config::FILE_NAME) } - /// Returns the path to the project's cache file - pub fn cache_path(&self) -> &PathBuf { + /// Returns the path to the project's cache file. + pub fn cache(&self) -> &PathBuf { &self.paths().cache } - /// Writes the given config as toml to `foundry.toml` + /// Returns the path to the project's artifacts directory. + pub fn artifacts(&self) -> &PathBuf { + &self.paths().artifacts + } + + /// Removes the project's cache and artifacts directory. + pub fn clear(&self) { + self.clear_cache(); + self.clear_artifacts(); + } + + /// Removes this project's cache file. + pub fn clear_cache(&self) { + let _ = fs::remove_file(self.cache()); + } + + /// Removes this project's artifacts directory. + pub fn clear_artifacts(&self) { + let _ = fs::remove_dir_all(self.artifacts()); + } + + /// Updates the project's config with the given function. + pub fn update_config(&self, f: impl FnOnce(&mut Config)) { + self._update_config(Box::new(f)); + } + + fn _update_config(&self, f: Box) { + let mut config = self + .config() + .exists() + .then_some(()) + .and_then(|()| Config::load_with_root(self.root()).ok()) + .unwrap_or_default(); + config.remappings.clear(); + f(&mut config); + self.write_config(config); + } + + /// Writes the given config as toml to `foundry.toml`. + #[doc(hidden)] // Prefer `update_config`. pub fn write_config(&self, config: Config) { - let file = self.config_path(); + let file = self.config(); pretty_err(&file, fs::write(&file, config.to_string_pretty().unwrap())); } - /// Asserts that the `/foundry.toml` file exits + /// Adds a source file to the project. + pub fn add_source(&self, name: &str, contents: &str) -> SolcResult { + self.inner.add_source(name, Self::add_source_prelude(contents)) + } + + /// Adds a source file to the project. Prefer using `add_source` instead. + pub fn add_raw_source(&self, name: &str, contents: &str) -> SolcResult { + self.inner.add_source(name, contents) + } + + /// Adds a script file to the project. + pub fn add_script(&self, name: &str, contents: &str) -> SolcResult { + self.inner.add_script(name, Self::add_source_prelude(contents)) + } + + /// Adds a test file to the project. + pub fn add_test(&self, name: &str, contents: &str) -> SolcResult { + self.inner.add_test(name, Self::add_source_prelude(contents)) + } + + /// Adds a library file to the project. + pub fn add_lib(&self, name: &str, contents: &str) -> SolcResult { + self.inner.add_lib(name, Self::add_source_prelude(contents)) + } + + fn add_source_prelude(s: &str) -> String { + let mut s = s.to_string(); + if !s.contains("pragma solidity") { + s = format!("pragma solidity ={SOLC_VERSION};\n{s}"); + } + if !s.contains("// SPDX") { + s = format!("// SPDX-License-Identifier: MIT OR Apache-2.0\n{s}"); + } + s + } + + /// Asserts that the `/foundry.toml` file exists. + #[track_caller] pub fn assert_config_exists(&self) { - assert!(self.config_path().exists()); + assert!(self.config().exists()); } - /// Asserts that the `/cache/sol-files-cache.json` file exits + /// Asserts that the `/cache/sol-files-cache.json` file exists. + #[track_caller] pub fn assert_cache_exists(&self) { - assert!(self.cache_path().exists()); + assert!(self.cache().exists()); } - /// Asserts that the `/out` file exits + /// Asserts that the `/out` file exists. + #[track_caller] pub fn assert_artifacts_dir_exists(&self) { assert!(self.paths().artifacts.exists()); } /// Creates all project dirs and ensure they were created + #[track_caller] pub fn assert_create_dirs_exists(&self) { self.paths().create_all().unwrap_or_else(|_| panic!("Failed to create project paths")); - SolFilesCache::default().write(&self.paths().cache).expect("Failed to create cache"); + CompilerCache::::default() + .write(&self.paths().cache) + .expect("Failed to create cache"); self.assert_all_paths_exist(); } /// Ensures that the given layout exists + #[track_caller] pub fn assert_style_paths_exist(&self, style: PathStyle) { let paths = style.paths(&self.paths().root).unwrap(); - config_paths_exist(&paths, self.inner().project().cached); + config_paths_exist(&paths, self.inner.project().cached); } /// Copies the project's root directory to the given target + #[track_caller] pub fn copy_to(&self, target: impl AsRef) { let target = target.as_ref(); pretty_err(target, fs::create_dir_all(target)); @@ -323,24 +618,29 @@ impl TestProject { /// Adds DSTest as a source under "test.sol" pub fn insert_ds_test(&self) -> PathBuf { let s = include_str!("../../../testdata/lib/ds-test/src/test.sol"); - self.inner().add_source("test.sol", s).unwrap() + self.add_source("test.sol", s).unwrap() } /// Adds `console.sol` as a source under "console.sol" pub fn insert_console(&self) -> PathBuf { - let s = include_str!("../../../testdata/logs/console.sol"); - self.inner().add_source("console.sol", s).unwrap() + let s = include_str!("../../../testdata/default/logs/console.sol"); + self.add_source("console.sol", s).unwrap() } - /// Asserts all project paths exist - /// - /// - sources - /// - artifacts - /// - libs - /// - cache + /// Adds `Vm.sol` as a source under "Vm.sol" + pub fn insert_vm(&self) -> PathBuf { + let s = include_str!("../../../testdata/cheats/Vm.sol"); + self.add_source("Vm.sol", s).unwrap() + } + + /// Asserts all project paths exist. These are: + /// - sources + /// - artifacts + /// - libs + /// - cache pub fn assert_all_paths_exist(&self) { let paths = self.paths(); - config_paths_exist(paths, self.inner().project().cached); + config_paths_exist(paths, self.inner.project().cached); } /// Asserts that the artifacts dir and cache don't exist @@ -361,6 +661,7 @@ impl TestProject { current_dir_lock: None, saved_cwd: pretty_err("", std::env::current_dir()), stdin_fun: None, + redact_output: true, } } @@ -375,22 +676,27 @@ impl TestProject { current_dir_lock: None, saved_cwd: pretty_err("", std::env::current_dir()), stdin_fun: None, + redact_output: true, } } /// Returns the path to the forge executable. - pub fn forge_bin(&self) -> process::Command { - let forge = self.root.join(format!("../forge{}", env::consts::EXE_SUFFIX)); - let mut cmd = process::Command::new(forge); + pub fn forge_bin(&self) -> Command { + let forge = self.exe_root.join(format!("../forge{}", env::consts::EXE_SUFFIX)); + let forge = forge.canonicalize().unwrap_or_else(|_| forge.clone()); + let mut cmd = Command::new(forge); cmd.current_dir(self.inner.root()); + // Disable color output for comparisons; can be overridden with `--color always`. cmd.env("NO_COLOR", "1"); cmd } /// Returns the path to the cast executable. - pub fn cast_bin(&self) -> process::Command { - let cast = self.root.join(format!("../cast{}", env::consts::EXE_SUFFIX)); - let mut cmd = process::Command::new(cast); + pub fn cast_bin(&self) -> Command { + let cast = self.exe_root.join(format!("../cast{}", env::consts::EXE_SUFFIX)); + let cast = cast.canonicalize().unwrap_or_else(|_| cast.clone()); + let mut cmd = Command::new(cast); + // disable color output for comparisons cmd.env("NO_COLOR", "1"); cmd } @@ -404,7 +710,7 @@ impl TestProject { let mut cmd = self.forge_bin(); cmd.arg("config").arg("--root").arg(self.root()).args(args).arg("--json"); let output = cmd.output().unwrap(); - let c = String::from_utf8_lossy(&output.stdout); + let c = lossy_string(&output.stdout); let config: Config = serde_json::from_str(c.as_ref()).unwrap(); config.sanitized() } @@ -449,7 +755,7 @@ fn config_paths_exist(paths: &ProjectPathsConfig, cached: bool) { pub fn pretty_err(path: impl AsRef, res: Result) -> T { match res { Ok(t) => t, - Err(err) => panic!("{}: {err:?}", path.as_ref().display()), + Err(err) => panic!("{}: {err}", path.as_ref().display()), } } @@ -458,7 +764,7 @@ pub fn read_string(path: impl AsRef) -> String { pretty_err(path, std::fs::read_to_string(path)) } -/// A simple wrapper around a process::Command with some conveniences. +/// A simple wrapper around a Command with some conveniences. pub struct TestCommand { saved_cwd: PathBuf, /// The project used to launch this command. @@ -467,7 +773,9 @@ pub struct TestCommand { cmd: Command, // initial: Command, current_dir_lock: Option>, - stdin_fun: Option>, + stdin_fun: Option>, + /// If true, command output is redacted. + redact_output: bool, } impl TestCommand { @@ -476,22 +784,23 @@ impl TestCommand { &mut self.cmd } - /// replaces the command - pub fn set_cmd(&mut self, cmd: Command) -> &mut TestCommand { + /// Replaces the underlying command. + pub fn set_cmd(&mut self, cmd: Command) -> &mut Self { self.cmd = cmd; self } - /// Resets the command - pub fn forge_fuse(&mut self) -> &mut TestCommand { + /// Resets the command to the default `forge` command. + pub fn forge_fuse(&mut self) -> &mut Self { self.set_cmd(self.project.forge_bin()) } - pub fn cast_fuse(&mut self) -> &mut TestCommand { + /// Resets the command to the default `cast` command. + pub fn cast_fuse(&mut self) -> &mut Self { self.set_cmd(self.project.cast_bin()) } - /// Sets the current working directory + /// Sets the current working directory. pub fn set_current_dir(&mut self, p: impl AsRef) { drop(self.current_dir_lock.take()); let lock = CURRENT_DIR_LOCK.lock(); @@ -501,13 +810,13 @@ impl TestCommand { } /// Add an argument to pass to the command. - pub fn arg>(&mut self, arg: A) -> &mut TestCommand { + pub fn arg>(&mut self, arg: A) -> &mut Self { self.cmd.arg(arg); self } /// Add any number of arguments to the command. - pub fn args(&mut self, args: I) -> &mut TestCommand + pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, A: AsRef, @@ -516,20 +825,30 @@ impl TestCommand { self } - pub fn stdin(&mut self, fun: impl FnOnce(process::ChildStdin) + 'static) -> &mut TestCommand { + pub fn stdin(&mut self, fun: impl FnOnce(ChildStdin) + 'static) -> &mut Self { self.stdin_fun = Some(Box::new(fun)); self } /// Convenience function to add `--root project.root()` argument - pub fn root_arg(&mut self) -> &mut TestCommand { + pub fn root_arg(&mut self) -> &mut Self { let root = self.project.root().to_path_buf(); self.arg("--root").arg(root) } /// Set the environment variable `k` to value `v` for the command. - pub fn set_env(&mut self, k: impl AsRef, v: impl Display) { - self.cmd.env(k, v.to_string()); + pub fn env(&mut self, k: impl AsRef, v: impl AsRef) { + self.cmd.env(k, v); + } + + /// Set the environment variable `k` to value `v` for the command. + pub fn envs(&mut self, envs: I) + where + I: IntoIterator, + K: AsRef, + V: AsRef, + { + self.cmd.envs(envs); } /// Unsets the environment variable `k` for the command. @@ -542,322 +861,184 @@ impl TestCommand { /// Note that this does not need to be called normally, since the creation /// of this TestCommand causes its working directory to be set to the /// test's directory automatically. - pub fn current_dir>(&mut self, dir: P) -> &mut TestCommand { + pub fn current_dir>(&mut self, dir: P) -> &mut Self { self.cmd.current_dir(dir); self } /// Returns the `Config` as spit out by `forge config` + #[track_caller] pub fn config(&mut self) -> Config { self.cmd.args(["config", "--json"]); - let output = self.output(); - let c = String::from_utf8_lossy(&output.stdout); - let config = serde_json::from_str(c.as_ref()).unwrap(); + let output = self.assert().success().get_output().stdout_lossy(); self.forge_fuse(); - config + serde_json::from_str(output.as_ref()).unwrap() } /// Runs `git init` inside the project's dir - pub fn git_init(&self) -> process::Output { + #[track_caller] + pub fn git_init(&self) { let mut cmd = Command::new("git"); cmd.arg("init").current_dir(self.project.root()); - let output = cmd.output().unwrap(); - self.expect_success(output) - } - - /// Runs and captures the stdout of the given command. - pub fn stdout(&mut self) -> String { - let o = self.output(); - let stdout = String::from_utf8_lossy(&o.stdout); - match stdout.parse::() { - Ok(t) => t.replace("\r\n", "\n"), - Err(err) => { - panic!("could not convert from string: {err:?}\n\n{stdout}"); - } - } - } - - /// Returns the `stderr` of the output as `String`. - pub fn stderr_lossy(&mut self) -> String { - let output = self.execute(); - String::from_utf8_lossy(&output.stderr).to_string().replace("\r\n", "\n") + let output = OutputAssert::new(cmd.output().unwrap()); + output.success(); } - /// Returns the `stdout` of the output as `String`. - pub fn stdout_lossy(&mut self) -> String { - String::from_utf8_lossy(&self.output().stdout).to_string().replace("\r\n", "\n") - } - - /// Returns the output but does not expect that the command was successful - pub fn unchecked_output(&mut self) -> process::Output { - self.execute() - } - - /// Gets the output of a command. If the command failed, then this panics. - pub fn output(&mut self) -> process::Output { - let output = self.execute(); - self.expect_success(output) - } - - /// Runs the command and asserts that it resulted in success - pub fn assert_success(&mut self) { - self.output(); + /// Runs `git add .` inside the project's dir + #[track_caller] + pub fn git_add(&self) { + let mut cmd = Command::new("git"); + cmd.current_dir(self.project.root()); + cmd.arg("add").arg("."); + let output = OutputAssert::new(cmd.output().unwrap()); + output.success(); } - /// Executes command, applies stdin function and returns output - pub fn execute(&mut self) -> process::Output { - self.try_execute().unwrap() + /// Runs `git commit .` inside the project's dir + #[track_caller] + pub fn git_commit(&self, msg: &str) { + let mut cmd = Command::new("git"); + cmd.current_dir(self.project.root()); + cmd.arg("commit").arg("-m").arg(msg); + let output = OutputAssert::new(cmd.output().unwrap()); + output.success(); } - pub fn try_execute(&mut self) -> std::io::Result { - let mut child = - self.cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped()).spawn()?; - if let Some(fun) = self.stdin_fun.take() { - fun(child.stdin.take().unwrap()) + /// Runs the command, returning a [`snapbox`] object to assert the command output. + #[track_caller] + pub fn assert(&mut self) -> OutputAssert { + let assert = OutputAssert::new(self.execute()); + if self.redact_output { + return assert.with_assert(test_assert()); } - child.wait_with_output() + assert } - /// Executes command and expects an successful result + /// Runs the command and asserts that it resulted in success. #[track_caller] - pub fn ensure_execute_success(&mut self) -> Result { - let out = self.try_execute()?; - self.ensure_success(out) - } - - /// Runs the command and prints its output - /// You have to pass --nocapture to cargo test or the print won't be displayed. - /// The full command would be: cargo test -- --nocapture - pub fn print_output(&mut self) { - let output = self.execute(); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - } - - /// Writes the content of the output to new fixture files - pub fn write_fixtures(&mut self, name: impl AsRef) { - let name = name.as_ref(); - if let Some(parent) = name.parent() { - fs::create_dir_all(parent).unwrap(); - } - let output = self.execute(); - fs::write(format!("{}.stdout", name.display()), &output.stdout).unwrap(); - fs::write(format!("{}.stderr", name.display()), &output.stderr).unwrap(); + pub fn assert_success(&mut self) -> OutputAssert { + self.assert().success() } - /// Runs the command and asserts that it resulted in an error exit code. + /// Runs the command and asserts that it resulted in success, with expected JSON data. #[track_caller] - pub fn assert_err(&mut self) { - let o = self.execute(); - if o.status.success() { - panic!( - "\n\n===== {:?} =====\n\ - command succeeded but expected failure!\ - \n\ncwd: {}\ - \n\nstatus: {}\ - \n\nstdout: {}\n\nstderr: {}\ - \n\n=====\n", - self.cmd, - self.project.inner.paths(), - o.status, - String::from_utf8_lossy(&o.stdout), - String::from_utf8_lossy(&o.stderr) - ); - } + pub fn assert_json_stdout(&mut self, expected: impl IntoData) { + let expected = expected.is(snapbox::data::DataFormat::Json).unordered(); + let stdout = self.assert_success().get_output().stdout.clone(); + let actual = stdout.into_data().is(snapbox::data::DataFormat::Json).unordered(); + assert_data_eq!(actual, expected); } - /// Runs the command and asserts that something was printed to stderr. + /// Runs the command and asserts that it **succeeded** nothing was printed to stdout. #[track_caller] - pub fn assert_non_empty_stderr(&mut self) { - let o = self.execute(); - if o.status.success() || o.stderr.is_empty() { - panic!( - "\n\n===== {:?} =====\n\ - command succeeded but expected failure!\ - \n\ncwd: {}\ - \n\nstatus: {}\ - \n\nstdout: {}\n\nstderr: {}\ - \n\n=====\n", - self.cmd, - self.project.inner.paths(), - o.status, - String::from_utf8_lossy(&o.stdout), - String::from_utf8_lossy(&o.stderr) - ); - } + pub fn assert_empty_stdout(&mut self) { + self.assert_success().stdout_eq(Data::new()); } - /// Runs the command and asserts that something was printed to stdout. + /// Runs the command and asserts that it failed. #[track_caller] - pub fn assert_non_empty_stdout(&mut self) { - let o = self.execute(); - if !o.status.success() || o.stdout.is_empty() { - panic!( - "\n\n===== {:?} =====\n\ - command failed but expected success!\ - \n\ncwd: {}\ - \n\nstatus: {}\ - \n\nstdout: {}\n\nstderr: {}\ - \n\n=====\n", - self.cmd, - self.project.inner.paths(), - o.status, - String::from_utf8_lossy(&o.stdout), - String::from_utf8_lossy(&o.stderr) - ); - } + pub fn assert_failure(&mut self) -> OutputAssert { + self.assert().failure() } - /// Runs the command and asserts that nothing was printed to stdout. + /// Runs the command and asserts that the exit code is `expected`. #[track_caller] - pub fn assert_empty_stdout(&mut self) { - let o = self.execute(); - if !o.status.success() || !o.stderr.is_empty() { - panic!( - "\n\n===== {:?} =====\n\ - command succeeded but expected failure!\ - \n\ncwd: {}\ - \n\nstatus: {}\ - \n\nstdout: {}\n\nstderr: {}\ - \n\n=====\n", - self.cmd, - self.project.inner.paths(), - o.status, - String::from_utf8_lossy(&o.stdout), - String::from_utf8_lossy(&o.stderr) - ); - } + pub fn assert_code(&mut self, expected: i32) -> OutputAssert { + self.assert().code(expected) } + /// Runs the command and asserts that it **failed** nothing was printed to stderr. #[track_caller] - fn expect_success(&self, out: process::Output) -> process::Output { - self.ensure_success(out).unwrap() + pub fn assert_empty_stderr(&mut self) { + self.assert_failure().stderr_eq(Data::new()); } + /// Runs the command with a temporary file argument and asserts that the contents of the file + /// match the given data. #[track_caller] - pub fn ensure_success(&self, out: process::Output) -> Result { - if !out.status.success() { - let suggest = if out.stderr.is_empty() { - "\n\nDid your forge command end up with no output?".to_string() - } else { - "".to_string() - }; - eyre::bail!( - "\n\n==========\n\ - command failed but expected success!\ - {}\ - \n\ncommand: {:?}\ - \n\ncwd: {}\ - \n\nstatus: {}\ - \n\nstdout: {}\ - \n\nstderr: {}\ - \n\n==========\n", - suggest, - self.cmd, - self.project.inner.paths(), - out.status, - String::from_utf8_lossy(&out.stdout), - String::from_utf8_lossy(&out.stderr) - ); - } - Ok(out) + pub fn assert_file(&mut self, data: impl IntoData) { + self.assert_file_with(|this, path| _ = this.arg(path).assert_success(), data); } -} - -/// Extension trait for `std::process::Output` -/// -/// These function will read the path's content and assert that the process' output matches the -/// fixture. Since `forge` commands may emit colorized output depending on whether the current -/// terminal is tty, the path argument can be wrapped in [tty_fixture_path()] -pub trait OutputExt { - /// Ensure the command wrote the expected data to `stdout`. - fn stdout_matches_path(&self, expected_path: impl AsRef) -> &Self; - - /// Ensure the command wrote the expected data to `stderr`. - fn stderr_matches_path(&self, expected_path: impl AsRef) -> &Self; -} - -/// Patterns to remove from fixtures before comparing output -/// -/// This should strip everything that can vary from run to run, like elapsed time, file paths -static IGNORE_IN_FIXTURES: Lazy = Lazy::new(|| { - Regex::new(r"(\r|finished in (.*)?s|-->(.*).sol|Location(.|\n)*\.rs(.|\n)*Backtrace|Installing solc version(.*?)\n|Successfully installed solc(.*?)\n|runs: \d+, μ: \d+, ~: \d+)").unwrap() -}); -impl OutputExt for process::Output { + /// Creates a temporary file, passes it to `f`, then asserts that the contents of the file match + /// the given data. #[track_caller] - fn stdout_matches_path(&self, expected_path: impl AsRef) -> &Self { - let expected = fs::read_to_string(expected_path).unwrap(); - let expected = IGNORE_IN_FIXTURES.replace_all(&expected, "").replace('\\', "/"); - let stdout = String::from_utf8_lossy(&self.stdout); - let out = IGNORE_IN_FIXTURES.replace_all(&stdout, "").replace('\\', "/"); - - pretty_assertions::assert_eq!(expected, out); + pub fn assert_file_with(&mut self, f: impl FnOnce(&mut Self, &Path), data: impl IntoData) { + let file = tempfile::NamedTempFile::new().expect("couldn't create temporary file"); + f(self, file.path()); + assert_data_eq!(Data::read_from(file.path(), None), data); + } + /// Does not apply [`snapbox`] redactions to the command output. + pub fn with_no_redact(&mut self) -> &mut Self { + self.redact_output = false; self } + /// Executes command, applies stdin function and returns output #[track_caller] - fn stderr_matches_path(&self, expected_path: impl AsRef) -> &Self { - let expected = fs::read_to_string(expected_path).unwrap(); - let expected = IGNORE_IN_FIXTURES.replace_all(&expected, "").replace('\\', "/"); - let stderr = String::from_utf8_lossy(&self.stderr); - let out = IGNORE_IN_FIXTURES.replace_all(&stderr, "").replace('\\', "/"); - - pretty_assertions::assert_eq!(expected, out); - self + pub fn execute(&mut self) -> Output { + self.try_execute().unwrap() } -} -/// Returns the fixture path depending on whether the current terminal is tty -/// -/// This is useful in combination with [OutputExt] -pub fn tty_fixture_path(path: impl AsRef) -> PathBuf { - let path = path.as_ref(); - if *IS_TTY { - return if let Some(ext) = path.extension().and_then(|s| s.to_str()) { - path.with_extension(format!("tty.{ext}")) - } else { - path.with_extension("tty") + #[track_caller] + pub fn try_execute(&mut self) -> std::io::Result { + println!("executing {:?}", self.cmd); + let mut child = + self.cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::piped()).spawn()?; + if let Some(fun) = self.stdin_fun.take() { + fun(child.stdin.take().unwrap()); } + child.wait_with_output() } - path.to_path_buf() } -/// Return a recursive listing of all files and directories in the given -/// directory. This is useful for debugging transient and odd failures in -/// integration tests. -pub fn dir_list>(dir: P) -> Vec { - walkdir::WalkDir::new(dir) - .follow_links(true) - .into_iter() - .map(|result| result.unwrap().path().to_string_lossy().into_owned()) - .collect() +fn test_assert() -> snapbox::Assert { + snapbox::Assert::new() + .action_env(snapbox::assert::DEFAULT_ACTION_ENV) + .redact_with(test_redactions()) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tty_path_works() { - let path = "tests/fixture/test.stdout"; - if *IS_TTY { - assert_eq!(tty_fixture_path(path), PathBuf::from("tests/fixture/test.tty.stdout")); - } else { - assert_eq!(tty_fixture_path(path), PathBuf::from(path)); +fn test_redactions() -> snapbox::Redactions { + static REDACTIONS: LazyLock = LazyLock::new(|| { + let mut r = snapbox::Redactions::new(); + let redactions = [ + ("[SOLC_VERSION]", r"Solc( version)? \d+.\d+.\d+"), + ("[ELAPSED]", r"(finished )?in \d+(\.\d+)?\w?s( \(.*?s CPU time\))?"), + ("[GAS]", r"[Gg]as( used)?: \d+"), + ("[AVG_GAS]", r"μ: \d+, ~: \d+"), + ("[FILE]", r"-->.*\.sol"), + ("[FILE]", r"Location(.|\n)*\.rs(.|\n)*Backtrace"), + ("[COMPILING_FILES]", r"Compiling \d+ files?"), + ("[TX_HASH]", r"Transaction hash: 0x[0-9A-Fa-f]{64}"), + ("[ADDRESS]", r"Address: 0x[0-9A-Fa-f]{40}"), + ("[UPDATING_DEPENDENCIES]", r"Updating dependencies in .*"), + ("[SAVED_TRANSACTIONS]", r"Transactions saved to: .*\.json"), + ("[SAVED_SENSITIVE_VALUES]", r"Sensitive values saved to: .*\.json"), + ("[ESTIMATED_GAS_PRICE]", r"Estimated gas price:\s*(\d+(\.\d+)?)\s*gwei"), + ("[ESTIMATED_TOTAL_GAS_USED]", r"Estimated total gas used for script: \d+"), + ("[ESTIMATED_AMOUNT_REQUIRED]", r"Estimated amount required:\s*(\d+(\.\d+)?)\s*ETH"), + ]; + for (placeholder, re) in redactions { + r.insert(placeholder, Regex::new(re).expect(re)).expect(re); } - } + r + }); + REDACTIONS.clone() +} - #[test] - fn fixture_regex_matches() { - assert!(IGNORE_IN_FIXTURES.is_match( - r#" -Location: - cli/src/compile.rs:151 +/// Extension trait for [`Output`]. +pub trait OutputExt { + /// Returns the stdout as lossy string + fn stdout_lossy(&self) -> String; +} -Backtrace omitted. - "# - )); +impl OutputExt for Output { + fn stdout_lossy(&self) -> String { + lossy_string(&self.stdout) } } + +pub fn lossy_string(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).replace("\r\n", "\n") +} diff --git a/crates/ui/Cargo.toml b/crates/ui/Cargo.toml deleted file mode 100644 index 4a7f767d45e4f..0000000000000 --- a/crates/ui/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "ui" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -foundry-evm.workspace = true -foundry-common.workspace = true - -ethers.workspace = true - -crossterm = "0.26" -eyre = "0.6" -revm = { version = "3", features = ["std", "serde"] } -tui = { version = "0.19", default-features = false, features = ["crossterm"] } diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs deleted file mode 100644 index 0588fc2bd4086..0000000000000 --- a/crates/ui/src/lib.rs +++ /dev/null @@ -1,1346 +0,0 @@ -#![warn(unused_crate_dependencies)] - -use crossterm::{ - event::{ - self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, - MouseEvent, MouseEventKind, - }, - execute, - terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, -}; -use ethers::{solc::artifacts::ContractBytecodeSome, types::Address}; -use eyre::Result; -use foundry_common::evm::Breakpoints; -use foundry_evm::{ - debug::{DebugStep, Instruction}, - utils::{build_pc_ic_map, PCICMap}, - CallKind, -}; -use revm::{interpreter::opcode, primitives::SpecId}; -use std::{ - cmp::{max, min}, - collections::{BTreeMap, HashMap, VecDeque}, - io, - sync::mpsc, - thread, - time::{Duration, Instant}, -}; -use tui::{ - backend::{Backend, CrosstermBackend}, - layout::{Alignment, Constraint, Direction, Layout, Rect}, - style::{Color, Modifier, Style}, - terminal::Frame, - text::{Span, Spans, Text}, - widgets::{Block, Borders, Paragraph, Wrap}, - Terminal, -}; - -/// Trait for starting the UI -pub trait Ui { - /// Start the agent that will now take over - fn start(self) -> Result; -} - -/// Used to indicate why the UI stopped -pub enum TUIExitReason { - /// Exit using - CharExit, -} - -mod op_effects; -use op_effects::stack_indices_affected; - -pub struct Tui { - debug_arena: Vec<(Address, Vec, CallKind)>, - terminal: Terminal>, - /// Buffer for keys prior to execution, i.e. '10' + 'k' => move up 10 operations - key_buffer: String, - /// Current step in the debug steps - current_step: usize, - identified_contracts: HashMap, - known_contracts: HashMap, - known_contracts_sources: HashMap>, - /// A mapping of source -> (PC -> IC map for deploy code, PC -> IC map for runtime code) - pc_ic_maps: BTreeMap, - breakpoints: Breakpoints, -} - -impl Tui { - /// Create a tui - #[allow(unused_must_use)] - pub fn new( - debug_arena: Vec<(Address, Vec, CallKind)>, - current_step: usize, - identified_contracts: HashMap, - known_contracts: HashMap, - known_contracts_sources: HashMap>, - breakpoints: Breakpoints, - ) -> Result { - enable_raw_mode()?; - let mut stdout = io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; - let backend = CrosstermBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - terminal.hide_cursor(); - let pc_ic_maps = known_contracts - .iter() - .filter_map(|(contract_name, bytecode)| { - Some(( - contract_name.clone(), - ( - build_pc_ic_map( - SpecId::LATEST, - bytecode.bytecode.object.as_bytes()?.as_ref(), - ), - build_pc_ic_map( - SpecId::LATEST, - bytecode - .deployed_bytecode - .bytecode - .as_ref()? - .object - .as_bytes()? - .as_ref(), - ), - ), - )) - }) - .collect(); - Ok(Tui { - debug_arena, - terminal, - key_buffer: String::new(), - current_step, - identified_contracts, - known_contracts, - known_contracts_sources, - pc_ic_maps, - breakpoints, - }) - } - - /// Grab number from buffer. Used for something like '10k' to move up 10 operations - fn buffer_as_number(buffer: &str, default_value: usize) -> usize { - if let Ok(num) = buffer.parse() { - if num >= 1 { - num - } else { - default_value - } - } else { - default_value - } - } - - /// Create layout and subcomponents - #[allow(clippy::too_many_arguments)] - fn draw_layout( - f: &mut Frame, - address: Address, - identified_contracts: &HashMap, - known_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - call_kind: CallKind, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - show_shortcuts: bool, - ) { - let total_size = f.size(); - if total_size.width < 225 { - Tui::vertical_layout( - f, - address, - identified_contracts, - known_contracts, - pc_ic_maps, - known_contracts_sources, - debug_steps, - opcode_list, - current_step, - call_kind, - draw_memory, - stack_labels, - mem_utf, - show_shortcuts, - ); - } else { - Tui::square_layout( - f, - address, - identified_contracts, - known_contracts, - pc_ic_maps, - known_contracts_sources, - debug_steps, - opcode_list, - current_step, - call_kind, - draw_memory, - stack_labels, - mem_utf, - show_shortcuts, - ); - } - } - - #[allow(clippy::too_many_arguments)] - fn vertical_layout( - f: &mut Frame, - address: Address, - identified_contracts: &HashMap, - known_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - call_kind: CallKind, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - show_shortcuts: bool, - ) { - let total_size = f.size(); - let h_height = if show_shortcuts { 4 } else { 0 }; - - if let [app, footer] = Layout::default() - .constraints( - [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)].as_ref(), - ) - .direction(Direction::Vertical) - .split(total_size)[..] - { - if let [op_pane, stack_pane, memory_pane, src_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints( - [ - Constraint::Ratio(1, 6), - Constraint::Ratio(1, 6), - Constraint::Ratio(1, 6), - Constraint::Ratio(3, 6), - ] - .as_ref(), - ) - .split(app)[..] - { - if show_shortcuts { - Tui::draw_footer(f, footer); - } - Tui::draw_src( - f, - address, - identified_contracts, - known_contracts, - pc_ic_maps, - known_contracts_sources, - debug_steps[current_step].pc, - call_kind, - src_pane, - ); - Tui::draw_op_list( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - op_pane, - ); - Tui::draw_stack( - f, - debug_steps, - current_step, - stack_pane, - stack_labels, - draw_memory, - ); - Tui::draw_memory(f, debug_steps, current_step, memory_pane, mem_utf, draw_memory); - } else { - panic!("unable to create vertical panes") - } - } else { - panic!("unable to create footer / app") - }; - } - - #[allow(clippy::too_many_arguments)] - fn square_layout( - f: &mut Frame, - address: Address, - identified_contracts: &HashMap, - known_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - call_kind: CallKind, - draw_memory: &mut DrawMemory, - stack_labels: bool, - mem_utf: bool, - show_shortcuts: bool, - ) { - let total_size = f.size(); - let h_height = if show_shortcuts { 4 } else { 0 }; - - // split in 2 vertically - - if let [app, footer] = Layout::default() - .direction(Direction::Vertical) - .constraints( - [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)].as_ref(), - ) - .split(total_size)[..] - { - if let [left_pane, right_pane] = Layout::default() - .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)].as_ref()) - .split(app)[..] - { - // split right pane horizontally to construct stack and memory - if let [op_pane, src_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)].as_ref()) - .split(left_pane)[..] - { - if let [stack_pane, memory_pane] = Layout::default() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)].as_ref()) - .split(right_pane)[..] - { - if show_shortcuts { - Tui::draw_footer(f, footer) - }; - Tui::draw_src( - f, - address, - identified_contracts, - known_contracts, - pc_ic_maps, - known_contracts_sources, - debug_steps[current_step].pc, - call_kind, - src_pane, - ); - Tui::draw_op_list( - f, - address, - debug_steps, - opcode_list, - current_step, - draw_memory, - op_pane, - ); - Tui::draw_stack( - f, - debug_steps, - current_step, - stack_pane, - stack_labels, - draw_memory, - ); - Tui::draw_memory( - f, - debug_steps, - current_step, - memory_pane, - mem_utf, - draw_memory, - ); - } - } else { - panic!("Couldn't generate horizontal split layout 1:2."); - } - } else { - panic!("Couldn't generate vertical split layout 1:2."); - } - } else { - panic!("Couldn't generate application & footer") - } - } - - fn draw_footer(f: &mut Frame, area: Rect) { - let block_controls = Block::default(); - - let text_output = vec![Spans::from(Span::styled( - "[q]: quit | [k/j]: prev/next op | [a/s]: prev/next jump | [c/C]: prev/next call | [g/G]: start/end", Style::default().add_modifier(Modifier::DIM))), -Spans::from(Span::styled("[t]: stack labels | [m]: memory decoding | [shift + j/k]: scroll stack | [ctrl + j/k]: scroll memory | [']: goto breakpoint | [h] toggle help", Style::default().add_modifier(Modifier::DIM)))]; - - let paragraph = Paragraph::new(text_output) - .block(block_controls) - .alignment(Alignment::Center) - .wrap(Wrap { trim: false }); - - f.render_widget(paragraph, area); - } - - #[allow(clippy::too_many_arguments)] - fn draw_src( - f: &mut Frame, - address: Address, - identified_contracts: &HashMap, - known_contracts: &HashMap, - pc_ic_maps: &BTreeMap, - known_contracts_sources: &HashMap>, - pc: usize, - call_kind: CallKind, - area: Rect, - ) { - let block_source_code = Block::default() - .title(match call_kind { - CallKind::Create | CallKind::Create2 => "Contract creation", - CallKind::Call => "Contract call", - CallKind::StaticCall => "Contract staticcall", - CallKind::CallCode => "Contract callcode", - CallKind::DelegateCall => "Contract delegatecall", - }) - .borders(Borders::ALL); - - let mut text_output: Text = Text::from(""); - - if let Some(contract_name) = identified_contracts.get(&address) { - if let (Some(known), Some(source_code)) = - (known_contracts.get(contract_name), known_contracts_sources.get(contract_name)) - { - let pc_ic_map = pc_ic_maps.get(contract_name); - // grab either the creation source map or runtime sourcemap - if let Some((sourcemap, ic)) = - if matches!(call_kind, CallKind::Create | CallKind::Create2) { - known.bytecode.source_map().zip(pc_ic_map.and_then(|(c, _)| c.get(&pc))) - } else { - known - .deployed_bytecode - .bytecode - .as_ref() - .expect("no bytecode") - .source_map() - .zip(pc_ic_map.and_then(|(_, r)| r.get(&pc))) - } - { - match sourcemap { - Ok(sourcemap) => { - // we are handed a vector of SourceElements that give - // us a span of sourcecode that is currently being executed - // This includes an offset and length. This vector is in - // instruction pointer order, meaning the location of - // the instruction - sum(push_bytes[..pc]) - if let Some(source_idx) = sourcemap[*ic].index { - if let Some(source) = source_code.get(&source_idx) { - let offset = sourcemap[*ic].offset; - let len = sourcemap[*ic].length; - let max = source.len(); - - // split source into before, relevant, and after chunks - // split by line as well to do some formatting stuff - let mut before = source[..std::cmp::min(offset, max)] - .split_inclusive('\n') - .collect::>(); - let actual = source[std::cmp::min(offset, max).. - std::cmp::min(offset + len, max)] - .split_inclusive('\n') - .map(|s| s.to_string()) - .collect::>(); - let mut after = source[std::cmp::min(offset + len, max)..] - .split_inclusive('\n') - .collect::>(); - - let mut line_number = 0; - - let num_lines = before.len() + actual.len() + after.len(); - let height = area.height as usize; - let needed_highlight = actual.len(); - let mid_len = before.len() + actual.len(); - - // adjust what text we show of the source code - let (start_line, end_line) = if needed_highlight > height { - // highlighted section is more lines than we have avail - (before.len(), before.len() + needed_highlight) - } else if height > num_lines { - // we can fit entire source - (0, num_lines) - } else { - let remaining = height - needed_highlight; - let mut above = remaining / 2; - let mut below = remaining / 2; - if below > after.len() { - // unused space below the highlight - above += below - after.len(); - } else if above > before.len() { - // we have unused space above the highlight - below += above - before.len(); - } else { - // no unused space - } - - (before.len().saturating_sub(above), mid_len + below) - }; - - let max_line_num = num_lines.to_string().len(); - // We check if there is other text on the same line before the - // highlight starts - if let Some(last) = before.pop() { - if !last.ends_with('\n') { - before.iter().skip(start_line).for_each(|line| { - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Gray) - .bg(Color::DarkGray), - ), - Span::styled( - "\u{2800} ".to_string() + line, - Style::default() - .add_modifier(Modifier::DIM), - ), - ])); - line_number += 1; - }); - - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::raw(last), - Span::styled( - actual[0].to_string(), - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - - actual.iter().skip(1).for_each(|s| { - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::styled( - // this is a hack to add coloring - // because tui does weird trimming - if s.is_empty() || s == "\n" { - "\u{2800} \n".to_string() - } else { - s.to_string() - }, - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - }); - } else { - before.push(last); - before.iter().skip(start_line).for_each(|line| { - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Gray) - .bg(Color::DarkGray), - ), - Span::styled( - "\u{2800} ".to_string() + line, - Style::default() - .add_modifier(Modifier::DIM), - ), - ])); - - line_number += 1; - }); - actual.iter().for_each(|s| { - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::styled( - if s.is_empty() || s == "\n" { - "\u{2800} \n".to_string() - } else { - s.to_string() - }, - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - }); - } - } else { - actual.iter().for_each(|s| { - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Cyan) - .bg(Color::DarkGray) - .add_modifier(Modifier::BOLD), - ), - Span::raw("\u{2800} "), - Span::styled( - if s.is_empty() || s == "\n" { - "\u{2800} \n".to_string() - } else { - s.to_string() - }, - Style::default() - .fg(Color::Cyan) - .add_modifier(Modifier::BOLD), - ), - ])); - line_number += 1; - }); - } - - // fill in the rest of the line as unhighlighted - if let Some(last) = actual.last() { - if !last.ends_with('\n') { - if let Some(post) = after.pop_front() { - if let Some(last) = text_output.lines.last_mut() { - last.0.push(Span::raw(post)); - } - } - } - } - - // add after highlighted text - while mid_len + after.len() > end_line { - after.pop_back(); - } - after.iter().for_each(|line| { - text_output.lines.push(Spans::from(vec![ - Span::styled( - format!( - "{: >max_line_num$}", - line_number.to_string(), - max_line_num = max_line_num - ), - Style::default() - .fg(Color::Gray) - .bg(Color::DarkGray), - ), - Span::styled( - "\u{2800} ".to_string() + line, - Style::default().add_modifier(Modifier::DIM), - ), - ])); - line_number += 1; - }); - } else { - text_output.extend(Text::from("No source for srcmap index")); - } - } else { - text_output.extend(Text::from("No srcmap index")); - } - } - Err(e) => text_output.extend(Text::from(format!( - "Error in source map parsing: '{e}', please open an issue" - ))), - } - } else { - text_output.extend(Text::from("No sourcemap for contract")); - } - } else { - text_output.extend(Text::from(format!("Unknown contract at address {address:?}"))); - } - } else { - text_output.extend(Text::from(format!("Unknown contract at address {address:?}"))); - } - - let paragraph = - Paragraph::new(text_output).block(block_source_code).wrap(Wrap { trim: false }); - f.render_widget(paragraph, area); - } - - /// Draw opcode list into main component - fn draw_op_list( - f: &mut Frame, - address: Address, - debug_steps: &[DebugStep], - opcode_list: &[String], - current_step: usize, - draw_memory: &mut DrawMemory, - area: Rect, - ) { - let block_source_code = Block::default() - .title(format!( - "Address: {:?} | PC: {} | Gas used in call: {}", - address, - if let Some(step) = debug_steps.get(current_step) { - step.pc.to_string() - } else { - "END".to_string() - }, - debug_steps[current_step].total_gas_used, - )) - .borders(Borders::ALL); - let mut text_output: Vec = Vec::new(); - - // Scroll: - // Focused line is line that should always be at the center of the screen. - let display_start; - - let height = area.height as i32; - let extra_top_lines = height / 2; - let prev_start = draw_memory.current_startline; - // Absolute minimum start line - let abs_min_start = 0; - // Adjust for weird scrolling for max top line - let abs_max_start = (opcode_list.len() as i32 - 1) - (height / 2); - // actual minimum start line - let mut min_start = - max(current_step as i32 - height + extra_top_lines, abs_min_start) as usize; - - // actual max start line - let mut max_start = - max(min(current_step as i32 - extra_top_lines, abs_max_start), abs_min_start) as usize; - - // Sometimes, towards end of file, maximum and minim lines have swapped values. Swap if the - // case - if min_start > max_start { - std::mem::swap(&mut min_start, &mut max_start); - } - - if prev_start < min_start { - display_start = min_start; - } else if prev_start > max_start { - display_start = max_start; - } else { - display_start = prev_start; - } - draw_memory.current_startline = display_start; - - let max_pc_len = - debug_steps.iter().fold(0, |max_val, val| val.pc.max(max_val)).to_string().len(); - - // Define closure that prints one more line of source code - let mut add_new_line = |line_number| { - let bg_color = if line_number == current_step { Color::DarkGray } else { Color::Reset }; - - // Format line number - let line_number_format = if line_number == current_step { - let step: &DebugStep = &debug_steps[line_number]; - format!("{:0>max_pc_len$x}|▶", step.pc) - } else if line_number < debug_steps.len() { - let step: &DebugStep = &debug_steps[line_number]; - format!("{:0>max_pc_len$x}| ", step.pc) - } else { - "END CALL".to_string() - }; - - if let Some(op) = opcode_list.get(line_number) { - text_output.push(Spans::from(Span::styled( - format!("{line_number_format}{op}"), - Style::default().fg(Color::White).bg(bg_color), - ))); - } else { - text_output.push(Spans::from(Span::styled( - line_number_format, - Style::default().fg(Color::White).bg(bg_color), - ))); - } - }; - for number in display_start..opcode_list.len() { - add_new_line(number); - } - // Add one more "phantom" line so we see line where current segment execution ends - add_new_line(opcode_list.len()); - let paragraph = - Paragraph::new(text_output).block(block_source_code).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } - - /// Draw the stack into the stack pane - fn draw_stack( - f: &mut Frame, - debug_steps: &[DebugStep], - current_step: usize, - area: Rect, - stack_labels: bool, - draw_memory: &DrawMemory, - ) { - let stack = &debug_steps[current_step].stack; - let stack_space = - Block::default().title(format!("Stack: {}", stack.len())).borders(Borders::ALL); - let min_len = usize::max(format!("{}", stack.len()).len(), 2); - - let indices_affected = - if let Instruction::OpCode(op) = debug_steps[current_step].instruction { - stack_indices_affected(op) - } else { - vec![] - }; - - let text: Vec = stack - .iter() - .rev() - .enumerate() - .skip(draw_memory.current_stack_startline) - .map(|(i, stack_item)| { - let affected = - indices_affected.iter().find(|(affected_index, _name)| *affected_index == i); - - let mut words: Vec = (0..32) - .rev() - .map(|i| stack_item.byte(i)) - .map(|byte| { - Span::styled( - format!("{byte:02x} "), - if affected.is_some() { - Style::default().fg(Color::Cyan) - } else if byte == 0 { - // this improves compatibility across terminals by not combining - // color with DIM modifier - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - }, - ) - }) - .collect(); - - if stack_labels { - if let Some((_, name)) = affected { - words.push(Span::raw(format!("| {name}"))); - } else { - words.push(Span::raw("| ".to_string())); - } - } - - let mut spans = vec![Span::styled( - format!("{i:0min_len$}| "), - Style::default().fg(Color::White), - )]; - spans.extend(words); - spans.push(Span::raw("\n")); - - Spans::from(spans) - }) - .collect(); - - let paragraph = Paragraph::new(text).block(stack_space).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } - - /// Draw memory in memory pane - fn draw_memory( - f: &mut Frame, - debug_steps: &[DebugStep], - current_step: usize, - area: Rect, - mem_utf8: bool, - draw_mem: &DrawMemory, - ) { - let memory = &debug_steps[current_step].memory; - let stack_space = Block::default() - .title(format!("Memory (max expansion: {} bytes)", memory.effective_len())) - .borders(Borders::ALL); - let memory = memory.data(); - let max_i = memory.len() / 32; - let min_len = format!("{:x}", max_i * 32).len(); - - // color memory words based on write/read - let mut word = None; - let mut color = None; - if let Instruction::OpCode(op) = debug_steps[current_step].instruction { - let stack_len = debug_steps[current_step].stack.len(); - if stack_len > 0 { - let w = debug_steps[current_step].stack[stack_len - 1]; - match op { - opcode::MLOAD => { - word = Some(w.as_usize()); - color = Some(Color::Cyan); - } - opcode::MSTORE => { - word = Some(w.as_usize()); - color = Some(Color::Red); - } - _ => {} - } - } - } - - // color word on previous write op - if current_step > 0 { - let prev_step = current_step - 1; - let stack_len = debug_steps[prev_step].stack.len(); - if let Instruction::OpCode(op) = debug_steps[prev_step].instruction { - if op == opcode::MSTORE { - let prev_top = debug_steps[prev_step].stack[stack_len - 1]; - word = Some(prev_top.as_usize()); - color = Some(Color::Green); - } - } - } - - let height = area.height as usize; - let end_line = draw_mem.current_mem_startline + height; - - let text: Vec = memory - .chunks(32) - .enumerate() - .skip(draw_mem.current_mem_startline) - .take_while(|(i, _)| i < &end_line) - .map(|(i, mem_word)| { - let words: Vec = mem_word - .iter() - .enumerate() - .map(|(j, byte)| { - Span::styled( - format!("{byte:02x} "), - if let (Some(w), Some(color)) = (word, color) { - if (i == w / 32 && j >= w % 32) || (i == w / 32 + 1 && j < w % 32) { - Style::default().fg(color) - } else if *byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - } - } else if *byte == 0 { - Style::default().add_modifier(Modifier::DIM) - } else { - Style::default().fg(Color::White) - }, - ) - }) - .collect(); - - let mut spans = vec![Span::styled( - format!("{:0min_len$x}| ", i * 32), - Style::default().fg(Color::White), - )]; - spans.extend(words); - - if mem_utf8 { - let chars: Vec = mem_word - .chunks(4) - .map(|utf| { - if let Ok(utf_str) = std::str::from_utf8(utf) { - Span::raw(utf_str.replace(char::from(0), ".")) - } else { - Span::raw(".") - } - }) - .collect(); - spans.push(Span::raw("|")); - spans.extend(chars); - } - - spans.push(Span::raw("\n")); - - Spans::from(spans) - }) - .collect(); - let paragraph = Paragraph::new(text).block(stack_space).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); - } -} - -impl Ui for Tui { - fn start(mut self) -> Result { - // If something panics inside here, we should do everything we can to - // not corrupt the user's terminal. - std::panic::set_hook(Box::new(|e| { - disable_raw_mode().expect("Unable to disable raw mode"); - execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture) - .expect("unable to execute disable mouse capture"); - println!("{e}"); - })); - // This is the recommend tick rate from tui-rs, based on their examples - let tick_rate = Duration::from_millis(200); - - // Setup a channel to send interrupts - let (tx, rx) = mpsc::channel(); - thread::spawn(move || { - let mut last_tick = Instant::now(); - loop { - // Poll events since last tick - if last tick is greater than tick_rate, we demand - // immediate availability of the event. This may affect - // interactivity, but I'm not sure as it is hard to test. - if event::poll(tick_rate.saturating_sub(last_tick.elapsed())).unwrap() { - let event = event::read().unwrap(); - if let Event::Key(key) = event { - if tx.send(Interrupt::KeyPressed(key)).is_err() { - return - } - } else if let Event::Mouse(mouse) = event { - if tx.send(Interrupt::MouseEvent(mouse)).is_err() { - return - } - } - } - // Force update if time has passed - if last_tick.elapsed() > tick_rate { - if tx.send(Interrupt::IntervalElapsed).is_err() { - return - } - last_tick = Instant::now(); - } - } - }); - - self.terminal.clear()?; - let mut draw_memory: DrawMemory = DrawMemory::default(); - - let debug_call: Vec<(Address, Vec, CallKind)> = self.debug_arena.clone(); - let mut opcode_list: Vec = - debug_call[0].1.iter().map(|step| step.pretty_opcode()).collect(); - let mut last_index = 0; - - let mut stack_labels = false; - let mut mem_utf = false; - let mut show_shortcuts = true; - // UI thread that manages drawing - loop { - if last_index != draw_memory.inner_call_index { - opcode_list = debug_call[draw_memory.inner_call_index] - .1 - .iter() - .map(|step| step.pretty_opcode()) - .collect(); - last_index = draw_memory.inner_call_index; - } - // Grab interrupt - - let receiver = rx.recv()?; - - if let Some(c) = receiver.char_press() { - if self.key_buffer.ends_with('\'') { - // Find the location of the called breakpoint in the whole debug arena (at this - // address with this pc) - if let Some((caller, pc)) = self.breakpoints.get(&c) { - for (i, (_caller, debug_steps, _)) in debug_call.iter().enumerate() { - if _caller == caller { - if let Some(step) = - debug_steps.iter().position(|step| step.pc == *pc) - { - draw_memory.inner_call_index = i; - self.current_step = step; - break - } - } - } - } - self.key_buffer.clear(); - } else if let Interrupt::KeyPressed(event) = receiver { - match event.code { - // Exit - KeyCode::Char('q') => { - disable_raw_mode()?; - execute!( - self.terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - return Ok(TUIExitReason::CharExit) - } - // Move down - KeyCode::Char('j') | KeyCode::Down => { - // Grab number of times to do it - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - let max_mem = (debug_call[draw_memory.inner_call_index].1 - [self.current_step] - .memory - .len() / - 32) - .saturating_sub(1); - if draw_memory.current_mem_startline < max_mem { - draw_memory.current_mem_startline += 1; - } - } else if self.current_step < opcode_list.len() - 1 { - self.current_step += 1; - } else if draw_memory.inner_call_index < debug_call.len() - 1 { - draw_memory.inner_call_index += 1; - self.current_step = 0; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('J') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - let max_stack = debug_call[draw_memory.inner_call_index].1 - [self.current_step] - .stack - .len() - .saturating_sub(1); - if draw_memory.current_stack_startline < max_stack { - draw_memory.current_stack_startline += 1; - } - } - self.key_buffer.clear(); - } - // Move up - KeyCode::Char('k') | KeyCode::Up => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - draw_memory.current_mem_startline = - draw_memory.current_mem_startline.saturating_sub(1); - } else if self.current_step > 0 { - self.current_step -= 1; - } else if draw_memory.inner_call_index > 0 { - draw_memory.inner_call_index -= 1; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('K') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - draw_memory.current_stack_startline = - draw_memory.current_stack_startline.saturating_sub(1); - } - self.key_buffer.clear(); - } - // Go to top of file - KeyCode::Char('g') => { - draw_memory.inner_call_index = 0; - self.current_step = 0; - self.key_buffer.clear(); - } - // Go to bottom of file - KeyCode::Char('G') => { - draw_memory.inner_call_index = debug_call.len() - 1; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - self.key_buffer.clear(); - } - // Go to previous call - KeyCode::Char('c') => { - draw_memory.inner_call_index = - draw_memory.inner_call_index.saturating_sub(1); - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - self.key_buffer.clear(); - } - // Go to next call - KeyCode::Char('C') => { - if debug_call.len() > draw_memory.inner_call_index + 1 { - draw_memory.inner_call_index += 1; - self.current_step = 0; - } - self.key_buffer.clear(); - } - // Step forward - KeyCode::Char('s') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - let remaining_ops = - opcode_list[self.current_step..].to_vec().clone(); - self.current_step += remaining_ops - .iter() - .enumerate() - .find_map(|(i, op)| { - if i < remaining_ops.len() - 1 { - match ( - op.contains("JUMP") && op != "JUMPDEST", - &*remaining_ops[i + 1], - ) { - (true, "JUMPDEST") => Some(i + 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or(opcode_list.len() - 1); - if self.current_step > opcode_list.len() { - self.current_step = opcode_list.len() - 1 - }; - } - self.key_buffer.clear(); - } - // Step backwards - KeyCode::Char('a') => { - for _ in 0..Tui::buffer_as_number(&self.key_buffer, 1) { - let prev_ops = opcode_list[..self.current_step].to_vec().clone(); - self.current_step = prev_ops - .iter() - .enumerate() - .rev() - .find_map(|(i, op)| { - if i > 0 { - match ( - prev_ops[i - 1].contains("JUMP") && - prev_ops[i - 1] != "JUMPDEST", - &**op, - ) { - (true, "JUMPDEST") => Some(i - 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or_default(); - } - self.key_buffer.clear(); - } - // toggle stack labels - KeyCode::Char('t') => { - stack_labels = !stack_labels; - } - // toggle memory utf8 decoding - KeyCode::Char('m') => { - mem_utf = !mem_utf; - } - // toggle help notice - KeyCode::Char('h') => { - show_shortcuts = !show_shortcuts; - } - KeyCode::Char(other) => match other { - '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '\'' => { - self.key_buffer.push(other); - } - _ => { - // Invalid key, clear buffer - self.key_buffer.clear(); - } - }, - _ => { - self.key_buffer.clear(); - } - } - } - } else { - match receiver { - Interrupt::MouseEvent(event) => match event.kind { - MouseEventKind::ScrollUp => { - if self.current_step > 0 { - self.current_step -= 1; - } else if draw_memory.inner_call_index > 0 { - draw_memory.inner_call_index -= 1; - draw_memory.current_mem_startline = 0; - draw_memory.current_stack_startline = 0; - self.current_step = - debug_call[draw_memory.inner_call_index].1.len() - 1; - } - } - MouseEventKind::ScrollDown => { - if self.current_step < opcode_list.len() - 1 { - self.current_step += 1; - } else if draw_memory.inner_call_index < debug_call.len() - 1 { - draw_memory.inner_call_index += 1; - draw_memory.current_mem_startline = 0; - draw_memory.current_stack_startline = 0; - self.current_step = 0; - } - } - _ => {} - }, - Interrupt::IntervalElapsed => {} - _ => (), - } - } - - // Draw - let current_step = self.current_step; - self.terminal.draw(|f| { - Tui::draw_layout( - f, - debug_call[draw_memory.inner_call_index].0, - &self.identified_contracts, - &self.known_contracts, - &self.pc_ic_maps, - &self.known_contracts_sources, - &debug_call[draw_memory.inner_call_index].1[..], - &opcode_list, - current_step, - debug_call[draw_memory.inner_call_index].2, - &mut draw_memory, - stack_labels, - mem_utf, - show_shortcuts, - ) - })?; - } - } -} - -/// Why did we wake up drawing thread? -enum Interrupt { - KeyPressed(KeyEvent), - MouseEvent(MouseEvent), - IntervalElapsed, -} - -impl Interrupt { - fn char_press(&self) -> Option { - if let Self::KeyPressed(event) = &self { - if let KeyCode::Char(c) = event.code { - if c.is_alphanumeric() || c == '\'' { - return Some(c) - } - } - } - - None - } -} - -/// This is currently used to remember last scroll -/// position so screen doesn't wiggle as much. -struct DrawMemory { - pub current_startline: usize, - pub inner_call_index: usize, - pub current_mem_startline: usize, - pub current_stack_startline: usize, -} - -impl DrawMemory { - fn default() -> Self { - DrawMemory { - current_startline: 0, - inner_call_index: 0, - current_mem_startline: 0, - current_stack_startline: 0, - } - } -} diff --git a/crates/ui/src/op_effects.rs b/crates/ui/src/op_effects.rs deleted file mode 100644 index d34c6aa8a6464..0000000000000 --- a/crates/ui/src/op_effects.rs +++ /dev/null @@ -1,132 +0,0 @@ -/// Maps an opcode and returns a vector of named affected indices -pub fn stack_indices_affected(op: u8) -> Vec<(usize, &'static str)> { - match op { - 0x01 => vec![(0, "a"), (1, "b")], - 0x02 => vec![(0, "a"), (1, "b")], - 0x03 => vec![(0, "a"), (1, "b")], - 0x04 => vec![(0, "a"), (1, "b")], - 0x05 => vec![(0, "a"), (1, "b")], - 0x06 => vec![(0, "a"), (1, "b")], - 0x07 => vec![(0, "a"), (1, "b")], - 0x08 => vec![(0, "a"), (1, "b"), (2, "N")], - 0x09 => vec![(0, "a"), (1, "b"), (2, "N")], - 0x0a => vec![(0, "a"), (1, "exponent")], - 0x0b => vec![(0, "b"), (1, "x")], - 0x10 => vec![(0, "a"), (1, "b")], - 0x11 => vec![(0, "a"), (1, "b")], - 0x12 => vec![(0, "a"), (1, "b")], - 0x13 => vec![(0, "a"), (1, "b")], - 0x14 => vec![(0, "a"), (1, "b")], - 0x15 => vec![(0, "a")], - 0x16 => vec![(0, "a"), (1, "b")], - 0x17 => vec![(0, "a"), (1, "b")], - 0x18 => vec![(0, "a"), (1, "b")], - 0x19 => vec![(0, "a")], - 0x1a => vec![(0, "i"), (1, "x")], - 0x1b => vec![(0, "shift"), (1, "value")], - 0x1c => vec![(0, "shift"), (1, "value")], - 0x1d => vec![(0, "shift"), (1, "value")], - 0x20 => vec![(0, "offset"), (1, "size")], - 0x31 => vec![(0, "address")], - 0x35 => vec![(0, "offset")], - 0x37 => vec![(0, "destOffset"), (1, "offset"), (2, "size")], - 0x39 => vec![(0, "destOffset"), (1, "offset"), (2, "size")], - 0x3b => vec![(0, "address")], - 0x3c => vec![(0, "address"), (1, "destOffset"), (2, "offset"), (3, "size")], - 0x3e => vec![(0, "destOffset"), (1, "offset"), (2, "size")], - 0x3f => vec![(0, "address")], - 0x40 => vec![(0, "blockNumber")], - 0x50 => vec![(0, "y")], - 0x51 => vec![(0, "offset")], - 0x52 => vec![(0, "offset"), (1, "value")], - 0x53 => vec![(0, "offset"), (1, "value")], - 0x54 => vec![(0, "key")], - 0x55 => vec![(0, "key"), (1, "value")], - 0x56 => vec![(0, "jump_to")], - 0x57 => vec![(0, "jump_to"), (1, "if")], - 0x80 => vec![(0, "dup_value")], - 0x81 => vec![(1, "dup_value")], - 0x82 => vec![(2, "dup_value")], - 0x83 => vec![(3, "dup_value")], - 0x84 => vec![(4, "dup_value")], - 0x85 => vec![(5, "dup_value")], - 0x86 => vec![(6, "dup_value")], - 0x87 => vec![(7, "dup_value")], - 0x88 => vec![(8, "dup_value")], - 0x89 => vec![(9, "dup_value")], - 0x8a => vec![(10, "dup_value")], - 0x8b => vec![(11, "dup_value")], - 0x8c => vec![(12, "dup_value")], - 0x8d => vec![(13, "dup_value")], - 0x8e => vec![(14, "dup_value")], - 0x8f => vec![(15, "dup_value")], - 0x90 => vec![(0, "a"), (1, "swap_value")], - 0x91 => vec![(0, "a"), (2, "swap_value")], - 0x92 => vec![(0, "a"), (3, "swap_value")], - 0x93 => vec![(0, "a"), (4, "swap_value")], - 0x94 => vec![(0, "a"), (5, "swap_value")], - 0x95 => vec![(0, "a"), (6, "swap_value")], - 0x96 => vec![(0, "a"), (7, "swap_value")], - 0x97 => vec![(0, "a"), (8, "swap_value")], - 0x98 => vec![(0, "a"), (9, "swap_value")], - 0x99 => vec![(0, "a"), (10, "swap_value")], - 0x9a => vec![(0, "a"), (11, "swap_value")], - 0x9b => vec![(0, "a"), (12, "swap_value")], - 0x9c => vec![(0, "a"), (13, "swap_value")], - 0x9d => vec![(0, "a"), (14, "swap_value")], - 0x9e => vec![(0, "a"), (15, "swap_value")], - 0x9f => vec![(0, "a"), (16, "swap_value")], - 0xa0 => vec![(0, "offset"), (1, "size")], - 0xa1 => vec![(0, "offset"), (1, "size"), (2, "topic")], - 0xa2 => vec![(0, "offset"), (1, "size"), (2, "topic1"), (3, "topic2")], - 0xa3 => vec![(0, "offset"), (1, "size"), (2, "topic1"), (3, "topic2"), (4, "topic3")], - 0xa4 => vec![ - (0, "offset"), - (1, "size"), - (2, "topic1"), - (3, "topic2"), - (4, "topic3"), - (5, "topic4"), - ], - 0xf0 => vec![(0, "value"), (1, "offset"), (2, "size")], - 0xf1 => vec![ - (0, "gas"), - (1, "address"), - (2, "value"), - (3, "argsOffset"), - (4, "argsSize"), - (5, "retOffset"), - (6, "retSize"), - ], - 0xf2 => vec![ - (0, "gas"), - (1, "address"), - (2, "value"), - (3, "argsOffset"), - (4, "argsSize"), - (5, "retOffset"), - (6, "retSize"), - ], - 0xf3 => vec![(0, "offset"), (1, "size")], - 0xf4 => vec![ - (0, "gas"), - (1, "address"), - (2, "argsOffset"), - (3, "argsSize"), - (4, "retOffset"), - (5, "retSize"), - ], - 0xf5 => vec![(0, "value"), (1, "offset"), (2, "size"), (3, "salt")], - 0xfa => vec![ - (0, "gas"), - (1, "address"), - (2, "argsOffset"), - (3, "argsSize"), - (4, "retOffset"), - (5, "retSize"), - ], - 0xfd => vec![(0, "offset"), (1, "size")], - 0xff => vec![(0, "address")], - _ => vec![], - } -} diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml deleted file mode 100644 index 8d455e305fee3..0000000000000 --- a/crates/utils/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "foundry-utils" - -version.workspace = true -edition.workspace = true -rust-version.workspace = true -authors.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true - -[dependencies] -forge-fmt.workspace = true - -ethers-core.workspace = true -ethers-contract = { workspace = true, features = ["abigen"] } -ethers-addressbook.workspace = true -ethers-providers.workspace = true -ethers-solc.workspace = true - -eyre = { version = "0.6", default-features = false } -futures = "0.3" -glob = "0.3" -hex.workspace = true -once_cell = "1" -rand = "0.8" -serde_json = { version = "1", default-features = false } -tracing = "0.1" -dunce = "1" - -[dev-dependencies] -foundry-common.workspace = true -pretty_assertions = "1.3.0" diff --git a/crates/utils/README.md b/crates/utils/README.md deleted file mode 100644 index 45334f7718af8..0000000000000 --- a/crates/utils/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# foundry-utils - -Helper methods for interacting with EVM ABIs diff --git a/crates/utils/src/abi.rs b/crates/utils/src/abi.rs deleted file mode 100644 index 2901181ab1721..0000000000000 --- a/crates/utils/src/abi.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Convert a json abi into solidity inerface - -use ethers_contract::InternalStructs; -use ethers_core::{ - abi, - abi::{ - struct_def::{FieldType, StructFieldType}, - Contract as Abi, Event, EventParam, Function, Param, ParamType, RawAbi, SolStruct, - }, -}; -use std::collections::BTreeMap; - -/// This function takes a contract [`Abi`] and a name and proceeds to generate a Solidity -/// `interface` from that ABI. If the provided name is empty, then it defaults to `interface -/// Interface`. -/// -/// This is done by iterating over the functions and their ABI inputs/outputs, and generating -/// function signatures/inputs/outputs according to the ABI. -/// -/// Notes: -/// * ABI Encoder V2 is not supported yet -/// * Kudos to [maxme/abi2solidity](https://github.com/maxme/abi2solidity) for the algorithm -/// -/// Note: This takes a raw representation of the json abi (`RawAbi`) because the `ethers::abi::Abi` -/// does not deserialize the internal type of nested params which is required in order to generate -/// structs -pub fn abi_to_solidity(contract_abi: &RawAbi, mut contract_name: &str) -> eyre::Result { - if contract_name.trim().is_empty() { - contract_name = "Interface"; - }; - - let structs = InternalStructs::new(contract_abi.clone()); - - // this is a bit horrible but the easiest way to convert the types - let abi_str = serde_json::to_string(contract_abi)?; - let contract_abi: Abi = serde_json::from_str(&abi_str)?; - - let mut events = Vec::with_capacity(contract_abi.events.len()); - for event in contract_abi.events() { - let inputs = event - .inputs - .iter() - .enumerate() - .map(|(idx, param)| format_event_params(event, param, idx, &structs)) - .collect::>>()? - .join(", "); - - let event_string = format!("event {}({inputs});", event.name); - events.push(event_string); - } - - let mut functions = Vec::with_capacity(contract_abi.functions.len()); - for function in contract_abi.functions() { - let inputs = function - .inputs - .iter() - .map(|param| format_function_input_param(function, param, &structs)) - .collect::>>()? - .join(", "); - - let outputs = function - .outputs - .iter() - .map(|param| format_function_output_param(function, param, &structs)) - .collect::>>()? - .join(", "); - - let mutability = match function.state_mutability { - abi::StateMutability::Pure => "pure", - abi::StateMutability::View => "view", - abi::StateMutability::Payable => "payable", - _ => "", - }; - - let mut func = format!("function {}({inputs})", function.name); - if !mutability.is_empty() { - func = format!("{func} {mutability}"); - } - func = format!("{func} external"); - if !outputs.is_empty() { - func = format!("{func} returns ({outputs})"); - } - - functions.push(format!("{func};")); - } - - let functions = functions.join("\n"); - let events = events.join("\n"); - - let sol = if structs.structs_types().is_empty() { - if events.is_empty() { - format!( - r#"interface {contract_name} {{ - {functions} -}} -"# - ) - } else { - format!( - r#"interface {contract_name} {{ - {events} - - {functions} -}} -"# - ) - } - } else { - let structs = format_struct_types(&structs); - match events.is_empty() { - true => format!( - r#"interface {contract_name} {{ - {structs} - - {functions} -}} -"# - ), - false => format!( - r#"interface {contract_name} {{ - {events} - - {structs} - - {functions} -}} -"# - ), - } - }; - forge_fmt::fmt(&sol).map_err(|err| eyre::eyre!(err.to_string())) -} - -/// returns the Tokenstream for the corresponding rust type of the param -fn expand_input_param_type( - fun: &Function, - param: &str, - kind: &ParamType, - structs: &InternalStructs, -) -> eyre::Result { - match kind { - ParamType::Array(ty) => { - let ty = expand_input_param_type(fun, param, ty, structs)?; - Ok(format!("{ty}[]")) - } - ParamType::FixedArray(ty, size) => { - let ty = expand_input_param_type(fun, param, ty, structs)?; - Ok(format!("{ty}[{}]", *size)) - } - ParamType::Tuple(_) => { - let ty = if let Some(struct_name) = - structs.get_function_input_struct_solidity_id(&fun.name, param) - { - struct_name.rsplit('.').next().unwrap().to_string() - } else { - kind.to_string() - }; - Ok(ty) - } - _ => Ok(kind.to_string()), - } -} - -fn expand_output_param_type( - fun: &Function, - param: &Param, - kind: &ParamType, - structs: &InternalStructs, -) -> eyre::Result { - match kind { - ParamType::Array(ty) => { - let ty = expand_output_param_type(fun, param, ty, structs)?; - Ok(format!("{ty}[]")) - } - ParamType::FixedArray(ty, size) => { - let ty = expand_output_param_type(fun, param, ty, structs)?; - Ok(format!("{ty}[{}]", *size)) - } - ParamType::Tuple(_) => { - if param.internal_type.is_none() { - let result = - kind.to_string().trim_start_matches('(').trim_end_matches(')').to_string(); - Ok(result) - } else { - let ty = if let Some(struct_name) = structs.get_function_output_struct_solidity_id( - &fun.name, - param.internal_type.as_ref().unwrap(), - ) { - struct_name.rsplit('.').next().unwrap().to_string() - } else { - kind.to_string() - }; - Ok(ty) - } - } - _ => Ok(kind.to_string()), - } -} - -// Returns the function parameter formatted as a string, as well as inserts into the provided -// `structs` set in order to create type definitions for any Abi Encoder v2 structs. -fn format_function_input_param( - func: &Function, - param: &Param, - structs: &InternalStructs, -) -> eyre::Result { - let kind = expand_input_param_type(func, ¶m.name, ¶m.kind, structs)?; - Ok(format_param(param, kind)) -} - -// Returns the function parameter formatted as a string, as well as inserts into the provided -// `structs` set in order to create type definitions for any Abi Encoder v2 structs. -fn format_function_output_param( - func: &Function, - param: &Param, - structs: &InternalStructs, -) -> eyre::Result { - let kind = expand_output_param_type(func, param, ¶m.kind, structs)?; - Ok(format_param(param, kind)) -} - -fn format_param(param: &Param, kind: String) -> String { - // add `memory` if required (not needed for events, only for functions) - let is_memory = match param.kind { - ParamType::Array(_) | - ParamType::Bytes | - ParamType::String | - ParamType::FixedArray(_, _) => true, - ParamType::Tuple(_) => param.internal_type.is_some(), - _ => false, - }; - - let kind = if is_memory { format!("{kind} memory") } else { kind }; - - if param.name.is_empty() { - kind - } else { - format!("{kind} {}", param.name) - } -} - -/// returns the Tokenstream for the corresponding rust type of the event_param -fn expand_event_param_type( - event: &Event, - kind: &ParamType, - idx: usize, - structs: &InternalStructs, -) -> eyre::Result { - match kind { - ParamType::Array(ty) => { - let ty = expand_event_param_type(event, ty, idx, structs)?; - Ok(format!("{ty}[]")) - } - ParamType::FixedArray(ty, size) => { - let ty = expand_event_param_type(event, ty, idx, structs)?; - Ok(format!("{ty}[{}]", *size)) - } - ParamType::Tuple(_) => { - let ty = if let Some(struct_name) = - structs.get_event_input_struct_solidity_id(&event.name, idx) - { - struct_name.rsplit('.').next().unwrap().to_string() - } else { - kind.to_string() - }; - Ok(ty) - } - _ => Ok(kind.to_string()), - } -} - -fn format_event_params( - event: &Event, - param: &EventParam, - idx: usize, - structs: &InternalStructs, -) -> eyre::Result { - let kind = expand_event_param_type(event, ¶m.kind, idx, structs)?; - let ty = if param.name.is_empty() { - kind - } else if param.indexed { - format!("{kind} indexed {}", param.name) - } else { - format!("{kind} {}", param.name) - }; - Ok(ty) -} - -/// Returns all struct type defs -fn format_struct_types(structs: &InternalStructs) -> String { - structs - .structs_types() - .iter() - .collect::>() - .into_iter() - .map(|(name, ty)| format_struct_field(name, ty)) - .collect::>() - .join("\n") -} - -fn format_struct_field(name: &str, sol_struct: &SolStruct) -> String { - // strip member access if any - let name = name.split('.').last().unwrap(); - let mut def = format!("struct {name} {{\n"); - for field in sol_struct.fields.iter() { - let ty = match &field.ty { - FieldType::Elementary(ty) => ty.to_string(), - FieldType::Struct(ty) => struct_field_to_type(ty), - FieldType::Mapping(_) => { - unreachable!("illegal mapping type") - } - }; - - def.push_str(&format!("{ty} {};\n", field.name)); - } - - def.push('}'); - - def -} - -fn struct_field_to_type(ty: &StructFieldType) -> String { - match ty { - StructFieldType::Type(ty) => ty.name().to_string(), - StructFieldType::Array(ty) => { - format!("{}[]", struct_field_to_type(ty)) - } - StructFieldType::FixedArray(ty, size) => { - format!("{}[{}]", struct_field_to_type(ty), *size) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn abi2solidity() { - let contract_abi: RawAbi = serde_json::from_str(include_str!( - "../../../testdata/fixtures/SolidityGeneration/InterfaceABI.json" - )) - .unwrap(); - pretty_assertions::assert_eq!( - include_str!( - "../../../testdata/fixtures/SolidityGeneration/GeneratedNamedInterface.sol" - ), - abi_to_solidity(&contract_abi, "test").unwrap() - ); - pretty_assertions::assert_eq!( - include_str!( - "../../../testdata/fixtures/SolidityGeneration/GeneratedUnnamedInterface.sol" - ), - abi_to_solidity(&contract_abi, "").unwrap() - ); - } - #[test] - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn abi2solidity_gaugecontroller() { - let contract_abi: RawAbi = serde_json::from_str(include_str!( - "../../../testdata/fixtures/SolidityGeneration/GaugeController.json" - )) - .unwrap(); - pretty_assertions::assert_eq!( - include_str!( - "../../../testdata/fixtures/SolidityGeneration/GeneratedGaugeController.sol" - ), - abi_to_solidity(&contract_abi, "test").unwrap() - ); - } - #[test] - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn abi2dolidity_liquiditygauge() { - let contract_abi: RawAbi = serde_json::from_str(include_str!( - "../../../testdata/fixtures/SolidityGeneration/LiquidityGaugeV4.json" - )) - .unwrap(); - pretty_assertions::assert_eq!( - include_str!( - "../../../testdata/fixtures/SolidityGeneration/GeneratedLiquidityGaugeV4.sol" - ), - abi_to_solidity(&contract_abi, "test").unwrap() - ); - } - #[test] - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn abi2solidity_fastlane() { - let contract_abi: RawAbi = serde_json::from_str(include_str!( - "../../../testdata/fixtures/SolidityGeneration/Fastlane.json" - )) - .unwrap(); - pretty_assertions::assert_eq!( - include_str!("../../../testdata/fixtures/SolidityGeneration/GeneratedFastLane.sol"), - abi_to_solidity(&contract_abi, "test").unwrap() - ); - } - - #[test] - #[cfg(any(target_os = "linux", target_os = "macos"))] - fn abi2solidity_with_structs() { - let contract_abi: RawAbi = serde_json::from_str(include_str!( - "../../../testdata/fixtures/SolidityGeneration/WithStructs.json" - )) - .unwrap(); - pretty_assertions::assert_eq!( - include_str!("../../../testdata/fixtures/SolidityGeneration/WithStructs.sol").trim(), - abi_to_solidity(&contract_abi, "test").unwrap().trim() - ); - } -} diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs deleted file mode 100644 index e92dee054d157..0000000000000 --- a/crates/utils/src/error.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! error handling and support - -use ethers_core::{abi::AbiEncode, types::Bytes}; -use std::fmt::Display; - -/// Solidity revert prefix. -/// -/// `keccak256("Error(String)")[..4] == 0x08c379a0` -pub const REVERT_PREFIX: [u8; 4] = [8, 195, 121, 160]; - -/// Custom Cheatcode error prefix. -/// -/// `keccak256("CheatCodeError")[..4] == 0x0bc44503` -pub const ERROR_PREFIX: [u8; 4] = [11, 196, 69, 3]; - -/// An extension trait for `std::error::Error` that can abi-encode itself -pub trait SolError: std::error::Error { - /// Returns the abi-encoded custom error - /// - /// Same as `encode_string` but prefixed with `ERROR_PREFIX` - fn encode_error(&self) -> Bytes { - encode_error(self) - } - - /// Returns the error as abi-encoded String - /// - /// See also [`AbiEncode`](ethers::abi::AbiEncode) - fn encode_string(&self) -> Bytes { - self.to_string().encode().into() - } -} - -/// Encodes the given messages as solidity custom error -pub fn encode_error(reason: impl Display) -> Bytes { - [ERROR_PREFIX.as_slice(), reason.to_string().encode().as_slice()].concat().into() -} diff --git a/crates/utils/src/glob.rs b/crates/utils/src/glob.rs deleted file mode 100644 index e461895d0f288..0000000000000 --- a/crates/utils/src/glob.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::path::{Path, PathBuf}; - -/// Expand globs with a root path. -pub fn expand_globs( - root: &Path, - mut patterns: impl Iterator>, -) -> eyre::Result> { - patterns.try_fold(Vec::default(), |mut expanded, pattern| { - let paths = glob::glob(&root.join(pattern.as_ref()).display().to_string())?; - expanded.extend(paths.into_iter().collect::, _>>()?); - Ok(expanded) - }) -} diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs deleted file mode 100644 index df519a8d64590..0000000000000 --- a/crates/utils/src/lib.rs +++ /dev/null @@ -1,881 +0,0 @@ -#![doc = include_str!("../README.md")] -#![warn(unused_crate_dependencies)] - -use ethers_addressbook::contract; -use ethers_core::types::*; -use ethers_providers::{Middleware, Provider}; -use ethers_solc::{ - artifacts::{BytecodeObject, CompactBytecode, CompactContractBytecode, Libraries}, - contracts::ArtifactContracts, - ArtifactId, -}; -use eyre::{Result, WrapErr}; -use futures::future::BoxFuture; -use std::{ - collections::{BTreeMap, HashMap}, - env::VarError, - fmt::{Formatter, Write}, - path::{Path, PathBuf}, - str::FromStr, - time::Duration, -}; -use tracing::trace; - -pub mod abi; -pub mod error; -pub mod glob; -pub mod path; -pub mod rpc; - -/// Data passed to the post link handler of the linker for each linked artifact. -#[derive(Debug)] -pub struct PostLinkInput<'a, T, U> { - /// The fully linked bytecode of the artifact - pub contract: CompactContractBytecode, - /// All artifacts passed to the linker - pub known_contracts: &'a mut BTreeMap, - /// The ID of the artifact - pub id: ArtifactId, - /// Extra data passed to the handler, which can be used as a scratch space. - pub extra: &'a mut U, - /// Each dependency of the contract in their resolved form. - pub dependencies: Vec, -} - -/// Dependencies for an artifact. -#[derive(Debug)] -struct ArtifactDependencies { - /// All references to dependencies in the artifact's unlinked bytecode. - dependencies: Vec, - /// The ID of the artifact - artifact_id: ArtifactId, -} - -/// A dependency of an artifact. -#[derive(Debug)] -struct ArtifactDependency { - file: String, - key: String, - version: String, -} - -struct ArtifactCode { - code: CompactContractBytecode, - artifact_id: ArtifactId, -} - -impl std::fmt::Debug for ArtifactCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.artifact_id.fmt(f) - } -} - -#[derive(Debug)] -struct AllArtifactsBySlug { - /// all artifacts grouped by identifier - inner: BTreeMap, -} - -impl AllArtifactsBySlug { - /// Finds the code for the target of the artifact and the matching key. - fn find_code(&self, identifier: &String, version: &String) -> Option { - trace!(target : "forge::link", identifier, "fetching artifact by identifier"); - let code = self - .inner - .get(identifier) - .or(self.inner.get(&format!("{}.{}", identifier, version)))?; - - Some(code.code.clone()) - } -} - -#[derive(Debug)] -pub struct ResolvedDependency { - /// The address the linker resolved - pub address: Address, - /// The nonce used to resolve the dependency - pub nonce: U256, - pub id: String, - pub bytecode: Bytes, -} - -impl std::fmt::Display for ResolvedDependency { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} @ {} (resolved with nonce {})", self.id, self.address, self.nonce) - } -} - -/// Links the given artifacts with a link key constructor function, passing the result of each -/// linkage to the given callback. -/// -/// This function will recursively link all artifacts until none are unlinked. It does this by: -/// -/// 1. Using the specified predeployed library addresses (`deployed_library_addresses`) for known -/// libraries (specified by the user) 2. Otherwise, computing the address the library would live at -/// if deployed by `sender`, given a starting nonce of `nonce`. -/// -/// If the library was already deployed previously in step 2, the linker will re-use the previously -/// computed address instead of re-computing it. -/// -/// The linker will call `post_link` for each linked artifact, providing: -/// -/// 1. User-specified data (`extra`) -/// 2. The linked artifact's bytecode -/// 3. The ID of the artifact -/// 4. The dependencies necessary to deploy the contract -/// -/// # Note -/// -/// If you want to collect all dependencies of a set of contracts, you cannot just collect the -/// `dependencies` passed to the callback in a `Vec`, since the same library contract (with the -/// exact same address) might show up as a dependency for multiple contracts. -/// -/// Instead, you must deduplicate *and* preserve the deployment order by pushing the dependencies to -/// a `Vec` iff it has not been seen before. -/// -/// For an example of this, see [here](https://github.com/foundry-rs/foundry/blob/2308972dbc3a89c03488a05aceb3c428bb3e08c0/cli/src/cmd/forge/script/build.rs#L130-L151C9). -#[allow(clippy::too_many_arguments)] -pub fn link_with_nonce_or_address( - contracts: ArtifactContracts, - known_contracts: &mut BTreeMap, - deployed_library_addresses: Libraries, - sender: Address, - nonce: U256, - extra: &mut U, - post_link: impl Fn(PostLinkInput) -> eyre::Result<()>, - root: impl AsRef, -) -> Result<()> { - // create a mapping of fname => Vec<(fname, file, key)>, - let link_tree: BTreeMap = contracts - .iter() - .map(|(id, contract)| { - let key = id.identifier(); - let version = id.version.to_string(); - // Check if the version has metadata appended to it, which will be after the semver - // version with a `+` separator. If so, strip it off. - let version = match version.find('+') { - Some(idx) => (version[..idx]).to_string(), - None => version, - }; - let references = contract - .all_link_references() - .iter() - .flat_map(|(file, link)| link.keys().map(|key| (file.to_string(), key.to_string()))) - .map(|(file, key)| ArtifactDependency { - file, - key, - version: version.clone().to_owned(), - }) - .collect(); - - let references = - ArtifactDependencies { dependencies: references, artifact_id: id.clone() }; - (key, references) - }) - .collect(); - - let artifacts_by_slug = AllArtifactsBySlug { - inner: contracts - .iter() - .map(|(artifact_id, c)| { - ( - artifact_id.identifier(), - ArtifactCode { code: c.clone(), artifact_id: artifact_id.clone() }, - ) - }) - .collect(), - }; - - for (id, contract) in contracts.into_iter() { - let (abi, maybe_deployment_bytes, maybe_runtime) = ( - contract.abi.as_ref(), - contract.bytecode.as_ref(), - contract.deployed_bytecode.as_ref(), - ); - let mut internally_deployed_libraries = HashMap::new(); - - if let (Some(abi), Some(bytecode), Some(runtime)) = - (abi, maybe_deployment_bytes, maybe_runtime) - { - // we are going to mutate, but library contract addresses may change based on - // the test so we clone - let mut target_bytecode = bytecode.clone(); - let mut rt = runtime.clone(); - let mut target_bytecode_runtime = rt.bytecode.expect("No target runtime").clone(); - - // instantiate a vector that gets filled with library deployment bytecode - let mut dependencies = vec![]; - - match bytecode.object { - BytecodeObject::Unlinked(_) => { - trace!(target : "forge::link", target=id.identifier(), version=?id.version, "unlinked contract"); - - // link needed - recurse_link( - id.identifier(), - (&mut target_bytecode, &mut target_bytecode_runtime), - &artifacts_by_slug, - &link_tree, - &mut dependencies, - &mut internally_deployed_libraries, - &deployed_library_addresses, - &mut nonce.clone(), - sender, - root.as_ref(), - ); - } - BytecodeObject::Bytecode(ref bytes) => { - if bytes.as_ref().is_empty() { - // abstract, skip - continue - } - } - } - - rt.bytecode = Some(target_bytecode_runtime); - let tc = CompactContractBytecode { - abi: Some(abi.clone()), - bytecode: Some(target_bytecode), - deployed_bytecode: Some(rt), - }; - - let post_link_input = - PostLinkInput { contract: tc, known_contracts, id, extra, dependencies }; - - post_link(post_link_input)?; - } - } - Ok(()) -} - -/// Recursively links bytecode given a target contract artifact name, the bytecode(s) to be linked, -/// a mapping of contract artifact name to bytecode, a dependency mapping, a mutable list that -/// will be filled with the predeploy libraries, initial nonce, and the sender. -#[allow(clippy::too_many_arguments)] -fn recurse_link<'a>( - // target name - target: String, - // to-be-modified/linked bytecode - target_bytecode: (&'a mut CompactBytecode, &'a mut CompactBytecode), - // All contract artifacts - artifacts: &'a AllArtifactsBySlug, - // fname => Vec<(fname, file, key)> - dependency_tree: &'a BTreeMap, - // library deployment vector (file:contract:address, bytecode) - deployment: &'a mut Vec, - // libraries we have already deployed during the linking process. - // the key is `file:contract` and the value is the address we computed - internally_deployed_libraries: &'a mut HashMap, - // deployed library addresses fname => adddress - deployed_library_addresses: &'a Libraries, - // nonce to start at - nonce: &mut U256, - // sender - sender: Address, - // project root path - root: impl AsRef, -) { - // check if we have dependencies - if let Some(dependencies) = dependency_tree.get(&target) { - trace!(target : "forge::link", ?target, "linking contract"); - - // for each dependency, try to link - dependencies.dependencies.iter().for_each(|dep| { - let ArtifactDependency { file, key, version } = dep; - let next_target = format!("{file}:{key}"); - let root = PathBuf::from(root.as_ref().to_str().unwrap()); - // get the dependency - trace!(target : "forge::link", dependency = next_target, file, key, version=?dependencies.artifact_id.version, "get dependency"); - let artifact = match artifacts - .find_code(&next_target, version) { - Some(artifact) => artifact, - None => { - // In some project setups, like JS-style workspaces, you might not have node_modules available at the root of the foundry project. - // In this case, imported dependencies from outside the root might not have their paths tripped correctly. - // Therefore, we fall back to a manual path join to locate the file. - let fallback_path = dunce::canonicalize(root.join(file)).unwrap_or_else(|e| panic!("No artifact for contract \"{next_target}\". Attempted to compose fallback path but got got error {e}")); - let fallback_path = fallback_path.to_str().unwrap_or("No artifact for contract \"{next_target}\". Attempted to compose fallback path but could not create valid string"); - let fallback_target = format!("{fallback_path}:{key}"); - - trace!(target : "forge::link", fallback_dependency = fallback_target, file, key, version=?dependencies.artifact_id.version, "get dependency with fallback path"); - - match artifacts.find_code(&fallback_target, version) { - Some(artifact) => artifact, - None => panic!("No artifact for contract {next_target}"), - }}, - }; - let mut next_target_bytecode = artifact - .bytecode - .unwrap_or_else(|| panic!("No bytecode for contract {next_target}")); - let mut next_target_runtime_bytecode = artifact - .deployed_bytecode - .expect("No target runtime bytecode") - .bytecode - .expect("No target runtime"); - - // make sure dependency is fully linked - if let Some(deps) = dependency_tree.get(&format!("{file}:{key}")) { - if !deps.dependencies.is_empty() { - trace!(target : "forge::link", dependency = next_target, file, key, version=?dependencies.artifact_id.version, "dependency has dependencies"); - - // actually link the nested dependencies to this dependency - recurse_link( - format!("{file}:{key}"), - (&mut next_target_bytecode, &mut next_target_runtime_bytecode), - artifacts, - dependency_tree, - deployment, - internally_deployed_libraries, - deployed_library_addresses, - nonce, - sender, - root, - ); - } - } - - let mut deployed_address = None; - - if let Some(library_file) = deployed_library_addresses - .libs - .get(&PathBuf::from_str(file).expect("Invalid library path.")) - { - if let Some(address) = library_file.get(key) { - deployed_address = - Some(Address::from_str(address).expect("Invalid library address passed.")); - } - } - - let address = if let Some(deployed_address) = deployed_address { - trace!(target : "forge::link", dependency = next_target, file, key, "dependency has pre-defined address"); - - // the user specified the library address - deployed_address - } else if let Some((cached_nonce, deployed_address)) = internally_deployed_libraries.get(&format!("{file}:{key}")) { - trace!(target : "forge::link", dependency = next_target, file, key, "dependency was previously deployed"); - - // we previously deployed the library - let library = format!("{file}:{key}:0x{}", hex::encode(deployed_address)); - - // push the dependency into the library deployment vector - deployment.push(ResolvedDependency { - id: library, - address: *deployed_address, - nonce: *cached_nonce, - bytecode: next_target_bytecode.object.into_bytes().unwrap_or_else(|| panic!( "Bytecode should be linked for {next_target}")), - }); - *deployed_address - } else { - trace!(target : "forge::link", dependency = next_target, file, key, "dependency has to be deployed"); - - // we need to deploy the library - let used_nonce = *nonce; - let computed_address = ethers_core::utils::get_contract_address(sender, used_nonce); - *nonce += 1.into(); - let library = format!("{file}:{key}:0x{}", hex::encode(computed_address)); - - // push the dependency into the library deployment vector - deployment.push(ResolvedDependency { - id: library, - address: computed_address, - nonce: used_nonce, - bytecode: next_target_bytecode.object.into_bytes().unwrap_or_else(|| panic!( "Bytecode should be linked for {next_target}")), - }); - - // remember this library for later - internally_deployed_libraries.insert(format!("{file}:{key}"), (used_nonce, computed_address)); - - computed_address - }; - - // link the dependency to the target - target_bytecode.0.link(file.clone(), key.clone(), address); - target_bytecode.1.link(file.clone(), key.clone(), address); - trace!(target : "forge::link", ?target, dependency = next_target, file, key, "linking dependency done"); - }); - } -} - -/// Given a k/v serde object, it pretty prints its keys and values as a table. -pub fn to_table(value: serde_json::Value) -> String { - match value { - serde_json::Value::String(s) => s, - serde_json::Value::Object(map) => { - let mut s = String::new(); - for (k, v) in map.iter() { - writeln!(&mut s, "{k: <20} {v}\n").expect("could not write k/v to table"); - } - s - } - _ => "".to_owned(), - } -} - -/// Resolves an input to [`NameOrAddress`]. The input could also be a contract/token name supported -/// by -/// [`ethers-addressbook`](https://github.com/gakonst/ethers-rs/tree/master/ethers-addressbook). -pub fn resolve_addr>(to: T, chain: Option) -> Result { - Ok(match to.into() { - NameOrAddress::Address(addr) => NameOrAddress::Address(addr), - NameOrAddress::Name(contract_or_ens) => { - if let Some(contract) = contract(&contract_or_ens) { - let chain = chain - .ok_or_else(|| eyre::eyre!("resolving contract requires a known chain"))?; - NameOrAddress::Address(contract.address(chain).ok_or_else(|| { - eyre::eyre!( - "contract: {} not found in addressbook for network: {}", - contract_or_ens, - chain - ) - })?) - } else { - NameOrAddress::Name(contract_or_ens) - } - } - }) -} - -/// Reads the `ETHERSCAN_API_KEY` env variable -pub fn etherscan_api_key() -> eyre::Result { - std::env::var("ETHERSCAN_API_KEY").map_err(|err| match err { - VarError::NotPresent => { - eyre::eyre!( - r#" - You need an Etherscan Api Key to verify contracts. - Create one at https://etherscan.io/myapikey - Then export it with \`export ETHERSCAN_API_KEY=xxxxxxxx'"# - ) - } - VarError::NotUnicode(err) => { - eyre::eyre!("Invalid `ETHERSCAN_API_KEY`: {:?}", err) - } - }) -} - -/// A type that keeps track of attempts -#[derive(Debug, Clone)] -pub struct Retry { - retries: u32, - delay: Option, -} - -/// Sample retry logic implementation -impl Retry { - pub fn new(retries: u32, delay: Option) -> Self { - Self { retries, delay } - } - - fn handle_err(&mut self, err: eyre::Report) { - self.retries -= 1; - tracing::warn!( - "erroneous attempt ({} tries remaining): {}", - self.retries, - err.root_cause() - ); - if let Some(delay) = self.delay { - std::thread::sleep(Duration::from_secs(delay.into())); - } - } - - pub fn run(mut self, mut callback: F) -> eyre::Result - where - F: FnMut() -> eyre::Result, - { - loop { - match callback() { - Err(e) if self.retries > 0 => self.handle_err(e), - res => return res, - } - } - } - - pub async fn run_async<'a, T, F>(mut self, mut callback: F) -> eyre::Result - where - F: FnMut() -> BoxFuture<'a, eyre::Result>, - { - loop { - match callback().await { - Err(e) if self.retries > 0 => self.handle_err(e), - res => return res, - }; - } - } -} - -pub async fn next_nonce( - caller: Address, - provider_url: &str, - block: Option, -) -> Result { - let provider = Provider::try_from(provider_url) - .wrap_err_with(|| format!("Bad fork_url provider: {provider_url}"))?; - Ok(provider.get_transaction_count(caller, block).await?) -} - -#[cfg(test)] -mod tests { - use super::*; - use ethers_core::types::Address; - use ethers_solc::{Project, ProjectPathsConfig}; - use foundry_common::ContractsByArtifact; - - struct LinkerTest { - contracts: ArtifactContracts, - dependency_assertions: HashMap>, - project: Project, - } - - impl LinkerTest { - fn new(path: impl Into) -> Self { - let path = path.into(); - let paths = ProjectPathsConfig::builder() - .root("../../testdata/linking") - .lib("../../testdata/lib") - .sources(path.clone()) - .tests(path) - .build() - .unwrap(); - - let project = - Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap(); - let contracts = project - .compile() - .unwrap() - .with_stripped_file_prefixes(project.root()) - .into_artifacts() - .map(|(id, c)| (id, c.into_contract_bytecode())) - .collect::(); - - Self { contracts, dependency_assertions: HashMap::new(), project } - } - - fn assert_dependencies( - mut self, - artifact_id: String, - deps: Vec<(String, U256, Address)>, - ) -> Self { - self.dependency_assertions.insert(artifact_id, deps); - self - } - - fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: U256) { - let mut called_once = false; - link_with_nonce_or_address( - self.contracts, - &mut ContractsByArtifact::default(), - Default::default(), - sender, - initial_nonce, - &mut called_once, - |post_link_input| { - *post_link_input.extra = true; - let identifier = post_link_input.id.identifier(); - - // Skip ds-test as it always has no dependencies etc. (and the path is outside root so is not sanitized) - if identifier.contains("DSTest") { - return Ok(()) - } - - let assertions = self - .dependency_assertions - .get(&identifier) - .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); - - assert_eq!( - post_link_input.dependencies.len(), - assertions.len(), - "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}", - post_link_input.dependencies.len(), - assertions.len(), - post_link_input.dependencies - ); - - for (expected, actual) in assertions.iter().zip(post_link_input.dependencies.iter()) { - let expected_lib_id = format!("{}:{:?}", expected.0, expected.2); - assert_eq!(expected_lib_id, actual.id, "unexpected dependency, expected: {}, got: {}", expected_lib_id, actual.id); - assert_eq!(actual.nonce, expected.1, "nonce wrong for dependency, expected: {}, got: {}", expected.1, actual.nonce); - assert_eq!(actual.address, expected.2, "address wrong for dependency, expected: {}, got: {}", expected.2, actual.address); - } - - Ok(()) - }, - self.project.root(), - ) - .expect("Linking failed"); - - assert!(called_once, "linker did nothing"); - } - } - - #[test] - fn link_simple() { - LinkerTest::new("../../testdata/linking/simple") - .assert_dependencies("simple/Simple.t.sol:Lib".to_string(), vec![]) - .assert_dependencies( - "simple/Simple.t.sol:LibraryConsumer".to_string(), - vec![( - "simple/Simple.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), - vec![( - "simple/Simple.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .test_with_sender_and_nonce(Address::default(), U256::one()); - } - - #[test] - fn link_nested() { - LinkerTest::new("../../testdata/linking/nested") - .assert_dependencies("nested/Nested.t.sol:Lib".to_string(), vec![]) - .assert_dependencies( - "nested/Nested.t.sol:NestedLib".to_string(), - vec![( - "nested/Nested.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "nested/Nested.t.sol:LibraryConsumer".to_string(), - vec![ - // Lib shows up here twice, because the linker sees it twice, but it should - // have the same address and nonce. - ( - "nested/Nested.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:NestedLib".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ], - ) - .assert_dependencies( - "nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), - vec![ - ( - "nested/Nested.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:Lib".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "nested/Nested.t.sol:NestedLib".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ], - ) - .test_with_sender_and_nonce(Address::default(), U256::one()); - } - - /// This test ensures that complicated setups with many libraries, some of which are referenced - /// in more than one place, result in correct linking. - /// - /// Each `assert_dependencies` should be considered in isolation, i.e. read it as "if I wanted - /// to deploy this contract, I would have to deploy the dependencies in this order with this - /// nonce". - /// - /// A library may show up more than once, but it should *always* have the same nonce and address - /// with respect to the single `assert_dependencies` call. There should be no gaps in the nonce - /// otherwise, i.e. whenever a new dependency is encountered, the nonce should be a single - /// increment larger than the previous largest nonce. - #[test] - fn link_duplicate() { - LinkerTest::new("../../testdata/linking/duplicate") - .assert_dependencies("duplicate/Duplicate.t.sol:A".to_string(), vec![]) - .assert_dependencies("duplicate/Duplicate.t.sol:B".to_string(), vec![]) - .assert_dependencies( - "duplicate/Duplicate.t.sol:C".to_string(), - vec![( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:D".to_string(), - vec![( - "duplicate/Duplicate.t.sol:B".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - )], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:E".to_string(), - vec![ - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), - vec![ - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - U256::from(3), - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:D".to_string(), - U256::from(4), - Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - U256::from(3), - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:E".to_string(), - U256::from(5), - Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f").unwrap(), - ), - ], - ) - .assert_dependencies( - "duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest".to_string(), - vec![ - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - U256::from(3), - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:B".to_string(), - U256::from(2), - Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:D".to_string(), - U256::from(4), - Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:A".to_string(), - U256::one(), - Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:C".to_string(), - U256::from(3), - Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca").unwrap(), - ), - ( - "duplicate/Duplicate.t.sol:E".to_string(), - U256::from(5), - Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f").unwrap(), - ), - ], - ) - .test_with_sender_and_nonce(Address::default(), U256::one()); - } - - #[test] - fn test_resolve_addr() { - use std::str::FromStr; - - // DAI:mainnet exists in ethers-addressbook (0x6b175474e89094c44da98b954eedeac495271d0f) - assert_eq!( - resolve_addr(NameOrAddress::Name("dai".to_string()), Some(Chain::Mainnet)).ok(), - Some(NameOrAddress::Address( - Address::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap() - )) - ); - - // DAI:goerli exists in ethers-adddressbook (0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844) - assert_eq!( - resolve_addr(NameOrAddress::Name("dai".to_string()), Some(Chain::Goerli)).ok(), - Some(NameOrAddress::Address( - Address::from_str("0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844").unwrap() - )) - ); - - // DAI:moonbean does not exist in addressbook - assert!( - resolve_addr(NameOrAddress::Name("dai".to_string()), Some(Chain::MoonbeamDev)).is_err() - ); - - // If not present in addressbook, gets resolved to an ENS name. - assert_eq!( - resolve_addr( - NameOrAddress::Name("contractnotpresent".to_string()), - Some(Chain::Mainnet) - ) - .ok(), - Some(NameOrAddress::Name("contractnotpresent".to_string())), - ); - - // Nothing to resolve for an address. - assert_eq!( - resolve_addr(NameOrAddress::Address(Address::zero()), Some(Chain::Mainnet)).ok(), - Some(NameOrAddress::Address(Address::zero())), - ); - } -} diff --git a/crates/utils/src/path.rs b/crates/utils/src/path.rs deleted file mode 100644 index 45e4e07d9b826..0000000000000 --- a/crates/utils/src/path.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! General Foundry path utils - -use std::path::PathBuf; - -/// Canonicalize a path, returning an error if the path does not exist. -/// Mainly useful to apply canonicalization to paths obtained from project files -/// but still error properly instead of flattening the errors. -pub fn canonicalize_path(path: &PathBuf) -> eyre::Result { - Ok(dunce::canonicalize(path)?) -} diff --git a/crates/utils/src/rpc.rs b/crates/utils/src/rpc.rs deleted file mode 100644 index c32cbe199f12c..0000000000000 --- a/crates/utils/src/rpc.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Support rpc api keys - -use once_cell::sync::Lazy; -use rand::seq::SliceRandom; -use std::sync::atomic::{AtomicUsize, Ordering}; - -// List of general purpose infura keys to rotate through -static INFURA_KEYS: Lazy> = Lazy::new(|| { - let mut keys = vec![ - // "16a8be88795540b9b3903d8de0f7baa5", - // "f4a0bdad42674adab5fc0ac077ffab2b", - // "5c812e02193c4ba793f8c214317582bd", - ]; - - keys.shuffle(&mut rand::thread_rng()); - - keys -}); - -// List of alchemy keys for mainnet -static ALCHEMY_MAINNET_KEYS: Lazy> = Lazy::new(|| { - let mut keys = vec![ - "ib1f4u1ojm-9lJJypwkeZeG-75TJRB7O", - "7mTtk6IW4DwroGnKmG_bOWri2hyaGYhX", - "GL4M0hfzSYGU5e1_t804HoUDOObWP-FA", - "WV407BEiBmjNJfKo9Uo_55u0z0ITyCOX", - "Ge56dH9siMF4T0whP99sQXOcr2mFs8wZ", - "QC55XC151AgkS3FNtWvz9VZGeu9Xd9lb", - "pwc5rmJhrdoaSEfimoKEmsvOjKSmPDrP", - "A5sZ85MIr4SzCMkT0zXh2eeamGIq3vGL", - "9VWGraLx0tMiSWx05WH-ywgSVmMxs66W", - "U4hsGWgl9lBM1j3jhSgJ4gbjHg2jRwKy", - "K-uNlqYoYCO9cdBHcifwCDAcEjDy1UHL", - "GWdgwabOE2XfBdLp_gIq-q6QHa7DSoag", - "Uz0cF5HCXFtpZlvd9NR7kHxfB_Wdpsx7", - "wWZMf1SOu9lT1GNIJHOX-5WL1MiYXycT", - "HACxy4wNUoD-oLlCq_v5LG0bclLc_DRL", - "_kCjfMjYo8x0rOm6YzmvSI0Qk-c8SO5I", - "kD-M-g5TKb957S3bbOXxXPeMUxm1uTuU", - "jQqqfTOQN_7A6gQEjzRYpVwXzxEBN9aj", - "jGiK5vwDfC3F4r0bqukm-W2GqgdrxdSr", - "Reoz-NZSjWczcAQOeVTz_Ejukb8mAton", - "-DQx9U-heCeTgYsAXwaTurmGytc-0mbR", - "sDNCLu_e99YZRkbWlVHiuM3BQ5uxYCZU", - "M6lfpxTBrywHOvKXOS4yb7cTTpa25ZQ9", - "UK8U_ogrbYB4lQFTGJHHDrbiS4UPnac6", - ]; - - keys.shuffle(&mut rand::thread_rng()); - - keys -}); - -/// counts how many times a rpc endpoint was requested for _mainnet_ -static NEXT_RPC_ENDPOINT: AtomicUsize = AtomicUsize::new(0); - -// returns the current value of the atomic counter and increments it -fn next() -> usize { - NEXT_RPC_ENDPOINT.fetch_add(1, Ordering::SeqCst) -} - -fn num_keys() -> usize { - INFURA_KEYS.len() + ALCHEMY_MAINNET_KEYS.len() -} - -/// Returns the next _mainnet_ rpc endpoint in inline -/// -/// This will rotate all available rpc endpoints -pub fn next_http_rpc_endpoint() -> String { - next_rpc_endpoint("mainnet") -} - -pub fn next_rpc_endpoint(network: &str) -> String { - let idx = next() % num_keys(); - if idx < INFURA_KEYS.len() { - format!("https://{network}.infura.io/v3/{}", INFURA_KEYS[idx]) - } else { - let idx = idx - INFURA_KEYS.len(); - format!("https://eth-{network}.alchemyapi.io/v2/{}", ALCHEMY_MAINNET_KEYS[idx]) - } -} - -/// Returns endpoint that has access to archive state -pub fn next_http_archive_rpc_endpoint() -> String { - let idx = next() % ALCHEMY_MAINNET_KEYS.len(); - format!("https://eth-mainnet.alchemyapi.io/v2/{}", ALCHEMY_MAINNET_KEYS[idx]) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::HashSet; - - #[test] - #[ignore] - fn can_rotate_unique() { - let mut keys = HashSet::new(); - for _ in 0..100 { - keys.insert(next_http_rpc_endpoint()); - } - assert_eq!(keys.len(), num_keys()); - } -} diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml new file mode 100644 index 0000000000000..443d44cc110a4 --- /dev/null +++ b/crates/verify/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "forge-verify" +description = "Contract verification tools" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true +foundry-cli.workspace = true +foundry-common.workspace = true +foundry-evm.workspace = true +serde_json.workspace = true +alloy-json-abi.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types.workspace = true +alloy-dyn-abi.workspace = true +revm-primitives.workspace = true +serde.workspace = true +eyre.workspace = true +alloy-provider.workspace = true +tracing.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } +foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } + +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +reqwest = { workspace = true, features = ["json"] } +async-trait.workspace = true +futures.workspace = true +semver.workspace = true +regex = { workspace = true, default-features = false } +yansi.workspace = true +itertools.workspace = true + +ciborium = "0.2" + +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } +foundry-test-utils.workspace = true +tempfile.workspace = true diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs new file mode 100644 index 0000000000000..3a1664f16367b --- /dev/null +++ b/crates/verify/src/bytecode.rs @@ -0,0 +1,524 @@ +//! The `forge verify-bytecode` command. +use crate::{ + etherscan::EtherscanVerificationProvider, + utils::{ + check_and_encode_args, check_explorer_args, configure_env_block, maybe_predeploy_contract, + BytecodeType, JsonResult, + }, + verify::VerifierArgs, +}; +use alloy_primitives::{hex, Address, Bytes, U256}; +use alloy_provider::{ + network::{AnyTxEnvelope, TransactionBuilder}, + Provider, +}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionInput, TransactionRequest}; +use clap::{Parser, ValueHint}; +use eyre::{Context, OptionExt, Result}; +use foundry_cli::{ + opts::EtherscanOpts, + utils::{self, read_constructor_args_file, LoadConfig}, +}; +use foundry_common::shell; +use foundry_compilers::{artifacts::EvmVersion, info::ContractInfo}; +use foundry_config::{figment, impl_figment_convert, Config}; +use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, utils::configure_tx_req_env}; +use revm_primitives::{AccountInfo, TxKind}; +use std::path::PathBuf; + +impl_figment_convert!(VerifyBytecodeArgs); + +/// CLI arguments for `forge verify-bytecode`. +#[derive(Clone, Debug, Parser)] +pub struct VerifyBytecodeArgs { + /// The address of the contract to verify. + pub address: Address, + + /// The contract identifier in the form `:`. + pub contract: ContractInfo, + + /// The block at which the bytecode should be verified. + #[arg(long, value_name = "BLOCK")] + pub block: Option, + + /// The constructor args to generate the creation code. + #[arg( + long, + num_args(1..), + conflicts_with_all = &["constructor_args_path", "encoded_constructor_args"], + value_name = "ARGS", + )] + pub constructor_args: Option>, + + /// The ABI-encoded constructor arguments. + #[arg( + long, + conflicts_with_all = &["constructor_args_path", "constructor_args"], + value_name = "HEX", + )] + pub encoded_constructor_args: Option, + + /// The path to a file containing the constructor arguments. + #[arg( + long, + value_hint = ValueHint::FilePath, + value_name = "PATH", + conflicts_with_all = &["constructor_args", "encoded_constructor_args"] + )] + pub constructor_args_path: Option, + + /// The rpc url to use for verification. + #[arg(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")] + pub rpc_url: Option, + + /// Etherscan options. + #[command(flatten)] + pub etherscan: EtherscanOpts, + + /// Verifier options. + #[command(flatten)] + pub verifier: VerifierArgs, + + /// The project's root path. + /// + /// By default root of the Git repository, if in one, + /// or the current working directory. + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + pub root: Option, + + /// Ignore verification for creation or runtime bytecode. + #[arg(long, value_name = "BYTECODE_TYPE")] + pub ignore: Option, +} + +impl figment::Provider for VerifyBytecodeArgs { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("Verify Bytecode Provider") + } + + fn data( + &self, + ) -> Result, figment::Error> { + let mut dict = self.etherscan.dict(); + + if let Some(api_key) = &self.verifier.verifier_api_key { + dict.insert("etherscan_api_key".into(), api_key.as_str().into()); + } + + if let Some(block) = &self.block { + dict.insert("block".into(), figment::value::Value::serialize(block)?); + } + if let Some(rpc_url) = &self.rpc_url { + dict.insert("eth_rpc_url".into(), rpc_url.to_string().into()); + } + + Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) + } +} + +impl VerifyBytecodeArgs { + /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built + /// bytecode. + pub async fn run(mut self) -> Result<()> { + // Setup + let config = self.load_config()?; + let provider = utils::get_provider(&config)?; + + // If chain is not set, we try to get it from the RPC. + // If RPC is not set, the default chain is used. + let chain = match config.get_rpc_url() { + Some(_) => utils::get_chain(config.chain, &provider).await?, + None => config.chain.unwrap_or_default(), + }; + + // Set Etherscan options. + self.etherscan.chain = Some(chain); + self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); + + // Etherscan client + let etherscan = EtherscanVerificationProvider.client( + self.etherscan.chain.unwrap_or_default(), + self.verifier.verifier_url.as_deref(), + self.etherscan.key().as_deref(), + &config, + )?; + + // Get the bytecode at the address, bailing if it doesn't exist. + let code = provider.get_code_at(self.address).await?; + if code.is_empty() { + eyre::bail!("No bytecode found at address {}", self.address); + } + + if !shell::is_json() { + sh_println!( + "Verifying bytecode for contract {} at address {}", + self.contract.name, + self.address + )?; + } + + let mut json_results: Vec = vec![]; + + // Get creation tx hash. + let creation_data = etherscan.contract_creation_data(self.address).await; + + // Check if contract is a predeploy + let (creation_data, maybe_predeploy) = maybe_predeploy_contract(creation_data)?; + + trace!(maybe_predeploy = ?maybe_predeploy); + + // Get the constructor args using `source_code` endpoint. + let source_code = etherscan.contract_source_code(self.address).await?; + + // Check if the contract name matches. + let name = source_code.items.first().map(|item| item.contract_name.to_owned()); + if name.as_ref() != Some(&self.contract.name) { + eyre::bail!("Contract name mismatch"); + } + + // Obtain Etherscan compilation metadata. + let etherscan_metadata = source_code.items.first().unwrap(); + + // Obtain local artifact + let artifact = if let Ok(local_bytecode) = + crate::utils::build_using_cache(&self, etherscan_metadata, &config) + { + trace!("using cache"); + local_bytecode + } else { + crate::utils::build_project(&self, &config)? + }; + + // Get local bytecode (creation code) + let local_bytecode = artifact + .bytecode + .as_ref() + .and_then(|b| b.to_owned().into_bytes()) + .ok_or_eyre("Unlinked bytecode is not supported for verification")?; + + // Get and encode user provided constructor args + let provided_constructor_args = if let Some(path) = self.constructor_args_path.to_owned() { + // Read from file + Some(read_constructor_args_file(path)?) + } else { + self.constructor_args.to_owned() + } + .map(|args| check_and_encode_args(&artifact, args)) + .transpose()? + .or(self.encoded_constructor_args.to_owned().map(hex::decode).transpose()?); + + let mut constructor_args = if let Some(provided) = provided_constructor_args { + provided.into() + } else { + // If no constructor args were provided, try to retrieve them from the explorer. + check_explorer_args(source_code.clone())? + }; + + // This fails only when the contract expects constructor args but NONE were provided OR + // retrieved from explorer (in case of predeploys). + crate::utils::check_args_len(&artifact, &constructor_args)?; + + if maybe_predeploy { + if !shell::is_json() { + sh_warn!( + "Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.", + self.address + )?; + } + + // 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); + + // Deploy at genesis + let gen_blk_num = 0_u64; + let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; + let (mut env, mut executor) = crate::utils::get_tracing_executor( + &mut fork_config, + gen_blk_num, + etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), + evm_opts, + ) + .await?; + + env.block.number = U256::ZERO; // Genesis block + let genesis_block = provider.get_block(gen_blk_num.into(), true.into()).await?; + + // Setup genesis tx and env. + let deployer = Address::with_last_byte(0x1); + let mut gen_tx_req = TransactionRequest::default() + .with_from(deployer) + .with_input(Bytes::from(local_bytecode_vec)) + .into_create(); + + if let Some(ref block) = genesis_block { + configure_env_block(&mut env, block); + gen_tx_req.max_fee_per_gas = block.header.base_fee_per_gas.map(|g| g as u128); + gen_tx_req.gas = Some(block.header.gas_limit); + gen_tx_req.gas_price = block.header.base_fee_per_gas.map(|g| g as u128); + } + + // configure_tx_rq_env(&mut env, &gen_tx); + + configure_tx_req_env(&mut env, &gen_tx_req, None) + .wrap_err("Failed to configure tx request env")?; + + // Seed deployer account with funds + let account_info = AccountInfo { + balance: U256::from(100 * 10_u128.pow(18)), + nonce: 0, + ..Default::default() + }; + executor.backend_mut().insert_account_info(deployer, account_info); + + let fork_address = crate::utils::deploy_contract( + &mut executor, + &env, + config.evm_spec_id(), + gen_tx_req.to, + )?; + + // Compare runtime bytecode + let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes( + &mut executor, + &provider, + self.address, + fork_address, + None, + ) + .await?; + + let match_type = crate::utils::match_bytecodes( + &deployed_bytecode.original_bytes(), + &onchain_runtime_code, + &constructor_args, + true, + config.bytecode_hash, + ); + + crate::utils::print_result( + match_type, + BytecodeType::Runtime, + &mut json_results, + etherscan_metadata, + &config, + ); + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&json_results)?)?; + } + + return Ok(()); + } + + // We can unwrap directly as maybe_predeploy is false + let creation_data = creation_data.unwrap(); + // Get transaction and receipt. + trace!(creation_tx_hash = ?creation_data.transaction_hash); + let transaction = provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await + .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))? + .ok_or_else(|| { + eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) + })?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash) + .await + .or_else(|e| eyre::bail!("Couldn't fetch transaction receipt from RPC: {:?}", e))?; + let receipt = if let Some(receipt) = receipt { + receipt + } else { + eyre::bail!( + "Receipt not found for transaction hash {}", + creation_data.transaction_hash + ); + }; + + let mut transaction: TransactionRequest = match transaction.inner.inner { + AnyTxEnvelope::Ethereum(tx) => tx.into(), + AnyTxEnvelope::Unknown(_) => unreachable!("Unknown transaction type"), + }; + + // Extract creation code from creation tx input. + let maybe_creation_code = + if receipt.to.is_none() && receipt.contract_address == Some(self.address) { + match &transaction.input.input { + Some(input) => &input[..], + None => unreachable!("creation tx input is None"), + } + } else if receipt.to == Some(DEFAULT_CREATE2_DEPLOYER) { + match &transaction.input.input { + Some(input) => &input[32..], + None => unreachable!("creation tx input is None"), + } + } else { + eyre::bail!( + "Could not extract the creation code for contract at address {}", + self.address + ); + }; + + // In some cases, Etherscan will return incorrect constructor arguments. If this + // happens, try extracting arguments ourselves. + if !maybe_creation_code.ends_with(&constructor_args) { + trace!("mismatch of constructor args with etherscan"); + // If local bytecode is longer than on-chain one, this is probably not a match. + if maybe_creation_code.len() >= local_bytecode.len() { + constructor_args = + Bytes::copy_from_slice(&maybe_creation_code[local_bytecode.len()..]); + trace!( + target: "forge::verify", + "setting constructor args to latest {} bytes of bytecode", + constructor_args.len() + ); + } + } + + // 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); + + trace!(ignore = ?self.ignore); + // Check if `--ignore` is set to `creation`. + if !self.ignore.is_some_and(|b| b.is_creation()) { + // Compare creation code with locally built bytecode and `maybe_creation_code`. + let match_type = crate::utils::match_bytecodes( + local_bytecode_vec.as_slice(), + maybe_creation_code, + &constructor_args, + false, + config.bytecode_hash, + ); + + crate::utils::print_result( + match_type, + BytecodeType::Creation, + &mut json_results, + etherscan_metadata, + &config, + ); + + // If the creation code does not match, the runtime also won't match. Hence return. + if match_type.is_none() { + crate::utils::print_result( + None, + BytecodeType::Runtime, + &mut json_results, + etherscan_metadata, + &config, + ); + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&json_results)?)?; + } + return Ok(()); + } + } + + if !self.ignore.is_some_and(|b| b.is_runtime()) { + // Get contract creation block. + let simulation_block = match self.block { + Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block, + Some(_) => eyre::bail!("Invalid block number"), + None => { + let provider = utils::get_provider(&config)?; + provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| { + eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) + })? + .block_number.ok_or_else(|| { + eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag") + })? + } + }; + + // Fork the chain at `simulation_block`. + let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; + let (mut env, mut executor) = crate::utils::get_tracing_executor( + &mut fork_config, + simulation_block - 1, // env.fork_block_number + etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()), + evm_opts, + ) + .await?; + env.block.number = U256::from(simulation_block); + let block = provider.get_block(simulation_block.into(), true.into()).await?; + + // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same + // block. + let prev_block_id = BlockId::number(simulation_block - 1); + + // Use `transaction.from` instead of `creation_data.contract_creator` to resolve + // blockscout creation data discrepancy in case of CREATE2. + let prev_block_nonce = provider + .get_transaction_count(transaction.from.unwrap()) + .block_id(prev_block_id) + .await?; + transaction.set_nonce(prev_block_nonce); + + if let Some(ref block) = block { + configure_env_block(&mut env, block) + } + + // Replace the `input` with local creation code in the creation tx. + if let Some(TxKind::Call(to)) = transaction.kind() { + if to == DEFAULT_CREATE2_DEPLOYER { + let mut input = transaction.input.input.unwrap()[..32].to_vec(); // Salt + input.extend_from_slice(&local_bytecode_vec); + transaction.input = TransactionInput::both(Bytes::from(input)); + + // Deploy default CREATE2 deployer + executor.deploy_create2_deployer()?; + } + } else { + transaction.input = TransactionInput::both(Bytes::from(local_bytecode_vec)); + } + + // configure_req__env(&mut env, &transaction.inner); + configure_tx_req_env(&mut env, &transaction, None) + .wrap_err("Failed to configure tx request env")?; + + let fork_address = crate::utils::deploy_contract( + &mut executor, + &env, + config.evm_spec_id(), + transaction.to, + )?; + + // State committed using deploy_with_env, now get the runtime bytecode from the db. + let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes( + &mut executor, + &provider, + self.address, + fork_address, + Some(simulation_block), + ) + .await?; + + // Compare the onchain runtime bytecode with the runtime code from the fork. + let match_type = crate::utils::match_bytecodes( + &fork_runtime_code.original_bytes(), + &onchain_runtime_code, + &constructor_args, + true, + config.bytecode_hash, + ); + + crate::utils::print_result( + match_type, + BytecodeType::Runtime, + &mut json_results, + etherscan_metadata, + &config, + ); + } + + if shell::is_json() { + sh_println!("{}", serde_json::to_string(&json_results)?)?; + } + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/verify/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs similarity index 59% rename from crates/forge/bin/cmd/verify/etherscan/flatten.rs rename to crates/verify/src/etherscan/flatten.rs index fb32057924b0b..a0b3defd7f90e 100644 --- a/crates/forge/bin/cmd/verify/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -1,14 +1,19 @@ use super::{EtherscanSourceProvider, VerifyArgs}; -use ethers::{ - etherscan::verify::CodeFormat, - solc::{ - artifacts::{BytecodeHash, Source}, - AggregatedCompilerOutput, CompilerInput, Project, Solc, +use crate::provider::VerificationContext; +use eyre::{Context, Result}; +use foundry_block_explorers::verify::CodeFormat; +use foundry_compilers::{ + artifacts::{BytecodeHash, Source, Sources}, + buildinfo::RawBuildInfo, + compilers::{ + solc::{SolcCompiler, SolcLanguage, SolcVersionedInput}, + Compiler, CompilerInput, }, + solc::Solc, + AggregatedCompilerOutput, }; -use eyre::{Context, Result}; use semver::{BuildMetadata, Version}; -use std::{collections::BTreeMap, path::Path}; +use std::path::Path; #[derive(Debug)] pub struct EtherscanFlattenedSource; @@ -16,11 +21,9 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { fn source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)> { - let metadata = project.solc_config.settings.metadata.as_ref(); + let metadata = context.project.settings.solc.metadata.as_ref(); let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default(); eyre::ensure!( @@ -29,11 +32,18 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { bch, ); - let source = project.flatten(target).wrap_err("Failed to flatten contract")?; + let source = context + .project + .paths + .clone() + .with_language::() + .flatten(&context.target_path) + .wrap_err("Failed to flatten contract")?; if !args.force { // solc dry run of flattened code - self.check_flattened(source.clone(), version, target).map_err(|err| { + self.check_flattened(source.clone(), &context.compiler_version, &context.target_path) + .map_err(|err| { eyre::eyre!( "Failed to compile the flattened code locally: `{}`\ To skip this solc dry, have a look at the `--force` flag of this command.", @@ -42,15 +52,14 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { })?; } - let name = args.contract.name.clone(); - Ok((source, name, CodeFormat::SingleFile)) + Ok((source, context.target_name.clone(), CodeFormat::SingleFile)) } } impl EtherscanFlattenedSource { /// Attempts to compile the flattened content locally with the compiler version. /// - /// This expects the completely flattened `content´ and will try to compile it using the + /// This expects the completely flattened content and will try to compile it using the /// provided compiler. If the compiler is missing it will be installed. /// /// # Errors @@ -69,29 +78,29 @@ impl EtherscanFlattenedSource { contract_path: &Path, ) -> Result<()> { let version = strip_build_meta(version.clone()); - let solc = Solc::find_svm_installed_version(version.to_string())? - .unwrap_or(Solc::blocking_install(&version)?); + let solc = Solc::find_or_install(&version)?; - let input = CompilerInput { - language: "Solidity".to_string(), - sources: BTreeMap::from([("contract.sol".into(), Source::new(content))]), - settings: Default::default(), - }; + let input = SolcVersionedInput::build( + Sources::from([("contract.sol".into(), Source::new(content))]), + Default::default(), + SolcLanguage::Solidity, + version.clone(), + ); - let out = solc.compile(&input)?; - if out.has_error() { - let mut o = AggregatedCompilerOutput::default(); - o.extend(version, out); - eprintln!("{}", o.diagnostics(&[], Default::default())); + let out = SolcCompiler::Specific(solc).compile(&input)?; + if out.errors.iter().any(|e| e.is_error()) { + let mut o = AggregatedCompilerOutput::::default(); + o.extend(version, RawBuildInfo::new(&input, &out, false)?, "default", out); + let diags = o.diagnostics(&[], &[], Default::default()); - eprintln!( - r#"Failed to compile the flattened code locally. + eyre::bail!( + "\ +Failed to compile the flattened code locally. This could be a bug, please inspect the output of `forge flatten {}` and report an issue. To skip this solc dry, pass `--force`. -"#, +Diagnostics: {diags}", contract_path.display() ); - std::process::exit(1) } Ok(()) diff --git a/crates/verify/src/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs new file mode 100644 index 0000000000000..5a86c02a35e97 --- /dev/null +++ b/crates/verify/src/etherscan/mod.rs @@ -0,0 +1,588 @@ +use crate::{ + provider::{VerificationContext, VerificationProvider}, + retry::RETRY_CHECK_ON_VERIFY, + verify::{VerifyArgs, VerifyCheckArgs}, +}; +use alloy_json_abi::Function; +use alloy_primitives::hex; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionTrait; +use eyre::{eyre, Context, OptionExt, Result}; +use foundry_block_explorers::{ + errors::EtherscanError, + utils::lookup_compiler_version, + verify::{CodeFormat, VerifyContract}, + Client, +}; +use foundry_cli::utils::{get_provider, read_constructor_args_file, LoadConfig}; +use foundry_common::{abi::encode_function_args, retry::RetryError}; +use foundry_compilers::{artifacts::BytecodeObject, Artifact}; +use foundry_config::{Chain, Config}; +use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; +use regex::Regex; +use semver::{BuildMetadata, Version}; +use std::{fmt::Debug, sync::LazyLock}; + +mod flatten; + +mod standard_json; + +pub static RE_BUILD_COMMIT: LazyLock = + LazyLock::new(|| Regex::new(r"(?Pcommit\.[0-9,a-f]{8})").unwrap()); + +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct EtherscanVerificationProvider; + +/// The contract source provider for [EtherscanVerificationProvider] +/// +/// Returns source, contract_name and the source [CodeFormat] +trait EtherscanSourceProvider: Send + Sync + Debug { + fn source( + &self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(String, String, CodeFormat)>; +} + +#[async_trait::async_trait] +impl VerificationProvider for EtherscanVerificationProvider { + async fn preflight_verify_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()> { + let _ = self.prepare_verify_request(&args, &context).await?; + Ok(()) + } + + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { + let (etherscan, verify_args) = self.prepare_verify_request(&args, &context).await?; + + if !args.skip_is_verified_check && + self.is_contract_verified(ðerscan, &verify_args).await? + { + sh_println!( + "\nContract [{}] {:?} is already verified. Skipping verification.", + verify_args.contract_name, + verify_args.address.to_checksum(None) + )?; + + return Ok(()) + } + + trace!(?verify_args, "submitting verification request"); + + let resp = args + .retry + .into_retry() + .run_async(|| async { + sh_println!( + "\nSubmitting verification for [{}] {}.", + verify_args.contract_name, + verify_args.address + )?; + let resp = etherscan + .submit_contract_verification(&verify_args) + .await + .wrap_err_with(|| { + // valid json + let args = serde_json::to_string(&verify_args).unwrap(); + error!(?args, "Failed to submit verification"); + format!("Failed to submit contract verification, payload:\n{args}") + })?; + + trace!(?resp, "Received verification response"); + + if resp.status == "0" { + if resp.result == "Contract source code already verified" + // specific for blockscout response + || resp.result == "Smart-contract already verified." + { + return Ok(None) + } + + if resp.result.starts_with("Unable to locate ContractCode at") { + warn!("{}", resp.result); + return Err(eyre!("Etherscan could not detect the deployment.")) + } + + warn!("Failed verify submission: {:?}", resp); + sh_err!( + "Encountered an error verifying this contract:\nResponse: `{}`\nDetails: + `{}`", + resp.message, + resp.result + )?; + std::process::exit(1); + } + + Ok(Some(resp)) + }) + .await?; + + if let Some(resp) = resp { + sh_println!( + "Submitted contract for verification:\n\tResponse: `{}`\n\tGUID: `{}`\n\tURL: {}", + resp.message, + resp.result, + etherscan.address_url(args.address) + )?; + + if args.watch { + let check_args = VerifyCheckArgs { + id: resp.result, + etherscan: args.etherscan, + retry: RETRY_CHECK_ON_VERIFY, + verifier: args.verifier, + }; + return self.check(check_args).await + } + } else { + sh_println!("Contract source code already verified")?; + } + + Ok(()) + } + + /// Executes the command to check verification status on Etherscan + async fn check(&self, args: VerifyCheckArgs) -> Result<()> { + let config = args.load_config()?; + let etherscan = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key().as_deref(), + &config, + )?; + args.retry + .into_retry() + .run_async_until_break(|| async { + let resp = etherscan + .check_contract_verification_status(args.id.clone()) + .await + .wrap_err("Failed to request verification status") + .map_err(RetryError::Retry)?; + + trace!(?resp, "Received verification response"); + + let _ = sh_println!( + "Contract verification status:\nResponse: `{}`\nDetails: `{}`", + resp.message, + resp.result + ); + + if resp.result == "Pending in queue" { + return Err(RetryError::Retry(eyre!("Verification is still pending..."))) + } + + if resp.result == "Unable to verify" { + return Err(RetryError::Retry(eyre!("Unable to verify."))) + } + + if resp.result == "Already Verified" { + let _ = sh_println!("Contract source code already verified"); + return Ok(()) + } + + if resp.status == "0" { + return Err(RetryError::Break(eyre!("Contract failed to verify."))) + } + + if resp.result == "Pass - Verified" { + let _ = sh_println!("Contract successfully verified"); + } + + Ok(()) + }) + .await + .wrap_err("Checking verification result failed") + } +} + +impl EtherscanVerificationProvider { + /// Create a source provider + fn source_provider(&self, args: &VerifyArgs) -> Box { + if args.flatten { + Box::new(flatten::EtherscanFlattenedSource) + } else { + Box::new(standard_json::EtherscanStandardJsonSource) + } + } + + /// Configures the API request to the Etherscan API using the given [`VerifyArgs`]. + async fn prepare_verify_request( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(Client, VerifyContract)> { + let config = args.load_config()?; + let etherscan = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key().as_deref(), + &config, + )?; + let verify_args = self.create_verify_request(args, context).await?; + + Ok((etherscan, verify_args)) + } + + /// Queries the Etherscan API to verify if the contract is already verified. + async fn is_contract_verified( + &self, + etherscan: &Client, + verify_contract: &VerifyContract, + ) -> Result { + let check = etherscan.contract_abi(verify_contract.address).await; + + if let Err(err) = check { + match err { + EtherscanError::ContractCodeNotVerified(_) => return Ok(false), + error => return Err(error.into()), + } + } + + Ok(true) + } + + /// Create an Etherscan client. + pub(crate) fn client( + &self, + chain: Chain, + verifier_url: Option<&str>, + etherscan_key: Option<&str>, + config: &Config, + ) -> Result { + let etherscan_config = config.get_etherscan_config_with_chain(Some(chain))?; + + let etherscan_api_url = verifier_url + .or_else(|| etherscan_config.as_ref().map(|c| c.api_url.as_str())) + .map(str::to_owned); + + let api_url = etherscan_api_url.as_deref(); + let base_url = etherscan_config + .as_ref() + .and_then(|c| c.browser_url.as_deref()) + .or_else(|| chain.etherscan_urls().map(|(_, url)| url)); + + let etherscan_key = + etherscan_key.or_else(|| etherscan_config.as_ref().map(|c| c.key.as_str())); + + let mut builder = Client::builder(); + + builder = if let Some(api_url) = api_url { + // we don't want any trailing slashes because this can cause cloudflare issues: + let api_url = api_url.trim_end_matches('/'); + builder + .with_chain_id(chain) + .with_api_url(api_url)? + .with_url(base_url.unwrap_or(api_url))? + } else { + builder.chain(chain)? + }; + + builder + .with_api_key(etherscan_key.unwrap_or_default()) + .build() + .wrap_err("Failed to create Etherscan client") + } + + /// Creates the `VerifyContract` Etherscan request in order to verify the contract + /// + /// If `--flatten` is set to `true` then this will send with [`CodeFormat::SingleFile`] + /// otherwise this will use the [`CodeFormat::StandardJsonInput`] + pub async fn create_verify_request( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result { + let (source, contract_name, code_format) = + self.source_provider(args).source(args, context)?; + + let mut compiler_version = context.compiler_version.clone(); + compiler_version.build = match RE_BUILD_COMMIT.captures(compiler_version.build.as_str()) { + Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, + _ => BuildMetadata::EMPTY, + }; + + let compiler_version = + format!("v{}", ensure_solc_build_metadata(context.compiler_version.clone()).await?); + let constructor_args = self.constructor_args(args, context).await?; + let mut verify_args = + VerifyContract::new(args.address, contract_name, source, compiler_version) + .constructor_arguments(constructor_args) + .code_format(code_format); + + if args.via_ir { + // we explicitly set this __undocumented__ argument to true if provided by the user, + // though this info is also available in the compiler settings of the standard json + // object if standard json is used + // unclear how Etherscan interprets this field in standard-json mode + verify_args = verify_args.via_ir(true); + } + + if code_format == CodeFormat::SingleFile { + verify_args = if let Some(optimizations) = args.num_of_optimizations { + verify_args.optimized().runs(optimizations as u32) + } else if context.config.optimizer == Some(true) { + verify_args + .optimized() + .runs(context.config.optimizer_runs.unwrap_or(200).try_into()?) + } else { + verify_args.not_optimized() + }; + } + + Ok(verify_args) + } + + /// Return the optional encoded constructor arguments. If the path to + /// constructor arguments was provided, read them and encode. Otherwise, + /// return whatever was set in the [VerifyArgs] args. + async fn constructor_args( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result> { + if let Some(ref constructor_args_path) = args.constructor_args_path { + let abi = context.get_target_abi()?; + let constructor = abi + .constructor() + .ok_or_else(|| eyre!("Can't retrieve constructor info from artifact ABI."))?; + #[allow(deprecated)] + let func = Function { + name: "constructor".to_string(), + inputs: constructor.inputs.clone(), + outputs: vec![], + state_mutability: alloy_json_abi::StateMutability::NonPayable, + }; + let encoded_args = encode_function_args( + &func, + read_constructor_args_file(constructor_args_path.to_path_buf())?, + )?; + let encoded_args = hex::encode(encoded_args); + return Ok(Some(encoded_args[8..].into())) + } + if args.guess_constructor_args { + return Ok(Some(self.guess_constructor_args(args, context).await?)) + } + + Ok(args.constructor_args.clone()) + } + + /// Uses Etherscan API to fetch contract creation transaction. + /// If transaction is a create transaction or a invocation of default CREATE2 deployer, tries to + /// match provided creation code with local bytecode of the target contract. + /// If bytecode match, returns latest bytes of on-chain creation code as constructor arguments. + async fn guess_constructor_args( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result { + let provider = get_provider(&context.config)?; + let client = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key.as_deref(), + &context.config, + )?; + + let creation_data = client.contract_creation_data(args.address).await?; + let transaction = provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await? + .ok_or_eyre("Transaction not found")?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash) + .await? + .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; + + let maybe_creation_code = if receipt.contract_address == Some(args.address) { + transaction.inner.inner.input() + } else if transaction.to() == Some(DEFAULT_CREATE2_DEPLOYER) { + &transaction.inner.inner.input()[32..] + } else { + eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") + }; + + let output = context.project.compile_file(&context.target_path)?; + let artifact = output + .find(&context.target_path, &context.target_name) + .ok_or_eyre("Contract artifact wasn't found locally")?; + let bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not contain bytecode")?; + + let bytecode = match bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => Ok(bytes), + BytecodeObject::Unlinked(_) => { + Err(eyre!("You have to provide correct libraries to use --guess-constructor-args")) + } + }?; + + if maybe_creation_code.starts_with(bytecode) { + let constructor_args = &maybe_creation_code[bytecode.len()..]; + let constructor_args = hex::encode(constructor_args); + sh_println!("Identified constructor arguments: {constructor_args}")?; + Ok(constructor_args) + } else { + eyre::bail!("Local bytecode doesn't match on-chain bytecode") + } + } +} + +/// Given any solc [Version] return a [Version] with build metadata +/// +/// # Example +/// +/// ```ignore +/// use semver::{BuildMetadata, Version}; +/// let version = Version::new(1, 2, 3); +/// let version = ensure_solc_build_metadata(version).await?; +/// assert_ne!(version.build, BuildMetadata::EMPTY); +/// ``` +async fn ensure_solc_build_metadata(version: Version) -> Result { + if version.build != BuildMetadata::EMPTY { + Ok(version) + } else { + Ok(lookup_compiler_version(&version).await?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + use foundry_common::fs; + use foundry_test_utils::{forgetest_async, str}; + use tempfile::tempdir; + + #[test] + fn can_extract_etherscan_verify_config() { + let temp = tempdir().unwrap(); + let root = temp.path(); + + let config = r#" + [profile.default] + + [etherscan] + mumbai = { key = "dummykey", chain = 80001, url = "https://api-testnet.polygonscan.com/" } + "#; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + + let args: VerifyArgs = VerifyArgs::parse_from([ + "foundry-cli", + "0xd8509bee9c9bf012282ad33aba0d87241baf5064", + "src/Counter.sol:Counter", + "--chain", + "mumbai", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + + let config = args.load_config().unwrap(); + + let etherscan = EtherscanVerificationProvider::default(); + let client = etherscan + .client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key().as_deref(), + &config, + ) + .unwrap(); + assert_eq!(client.etherscan_api_url().as_str(), "https://api-testnet.polygonscan.com/"); + + assert!(format!("{client:?}").contains("dummykey")); + + let args: VerifyArgs = VerifyArgs::parse_from([ + "foundry-cli", + "0xd8509bee9c9bf012282ad33aba0d87241baf5064", + "src/Counter.sol:Counter", + "--chain", + "mumbai", + "--verifier-url", + "https://verifier-url.com/", + "--root", + root.as_os_str().to_str().unwrap(), + ]); + + let config = args.load_config().unwrap(); + + let etherscan = EtherscanVerificationProvider::default(); + let client = etherscan + .client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key().as_deref(), + &config, + ) + .unwrap(); + assert_eq!(client.etherscan_api_url().as_str(), "https://verifier-url.com/"); + assert!(format!("{client:?}").contains("dummykey")); + } + + #[tokio::test(flavor = "multi_thread")] + async fn fails_on_disabled_cache_and_missing_info() { + let temp = tempdir().unwrap(); + let root = temp.path(); + let root_path = root.as_os_str().to_str().unwrap(); + + let config = r" + [profile.default] + cache = false + "; + + let toml_file = root.join(Config::FILE_NAME); + fs::write(toml_file, config).unwrap(); + + let address = "0xd8509bee9c9bf012282ad33aba0d87241baf5064"; + let contract_name = "Counter"; + let src_dir = "src"; + fs::create_dir_all(root.join(src_dir)).unwrap(); + let contract_path = format!("{src_dir}/Counter.sol"); + fs::write(root.join(&contract_path), "").unwrap(); + + // No compiler argument + let args = VerifyArgs::parse_from([ + "foundry-cli", + address, + &format!("{contract_path}:{contract_name}"), + "--root", + root_path, + ]); + let result = args.resolve_context().await; + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" + ); + } + + forgetest_async!(respects_path_for_duplicate, |prj, cmd| { + prj.add_source("Counter1", "contract Counter {}").unwrap(); + prj.add_source("Counter2", "contract Counter {}").unwrap(); + + cmd.args(["build", "--force"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +... +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +"#]]); + + let args = VerifyArgs::parse_from([ + "foundry-cli", + "0x0000000000000000000000000000000000000000", + "src/Counter1.sol:Counter", + "--root", + &prj.root().to_string_lossy(), + ]); + let context = args.resolve_context().await.unwrap(); + + let mut etherscan = EtherscanVerificationProvider::default(); + etherscan.preflight_verify_check(args, context).await.unwrap(); + }); +} diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs new file mode 100644 index 0000000000000..e2fb5d2a47c03 --- /dev/null +++ b/crates/verify/src/etherscan/standard_json.rs @@ -0,0 +1,55 @@ +use super::{EtherscanSourceProvider, VerifyArgs}; +use crate::provider::VerificationContext; +use eyre::{Context, Result}; +use foundry_block_explorers::verify::CodeFormat; +use foundry_compilers::{artifacts::StandardJsonCompilerInput, solc::SolcLanguage}; + +#[derive(Debug)] +pub struct EtherscanStandardJsonSource; +impl EtherscanSourceProvider for EtherscanStandardJsonSource { + fn source( + &self, + _args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(String, String, CodeFormat)> { + let mut input: StandardJsonCompilerInput = context + .project + .standard_json_input(&context.target_path) + .wrap_err("Failed to get standard json input")? + .normalize_evm_version(&context.compiler_version); + + let mut settings = context.compiler_settings.solc.settings.clone(); + settings.libraries.libs = input + .settings + .libraries + .libs + .into_iter() + .map(|(f, libs)| { + (f.strip_prefix(context.project.root()).unwrap_or(&f).to_path_buf(), libs) + }) + .collect(); + + settings.remappings = input.settings.remappings; + + // remove all incompatible settings + settings.sanitize(&context.compiler_version, SolcLanguage::Solidity); + + input.settings = settings; + + let source = + serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; + + trace!(target: "forge::verify", standard_json=source, "determined standard json input"); + + let name = format!( + "{}:{}", + context + .target_path + .strip_prefix(context.project.root()) + .unwrap_or(context.target_path.as_path()) + .display(), + context.target_name.clone() + ); + Ok((source, name, CodeFormat::StandardJsonInput)) + } +} diff --git a/crates/verify/src/lib.rs b/crates/verify/src/lib.rs new file mode 100644 index 0000000000000..a46fdba901550 --- /dev/null +++ b/crates/verify/src/lib.rs @@ -0,0 +1,29 @@ +//! Smart contract verification. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate foundry_common; + +#[macro_use] +extern crate tracing; + +mod etherscan; + +pub mod provider; + +pub mod bytecode; +pub use bytecode::VerifyBytecodeArgs; + +pub mod retry; +pub use retry::RetryArgs; + +mod sourcify; + +pub mod verify; +pub use verify::{VerifierArgs, VerifyArgs, VerifyCheckArgs}; + +mod types; + +mod utils; diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs new file mode 100644 index 0000000000000..8b3c510b64a9d --- /dev/null +++ b/crates/verify/src/provider.rs @@ -0,0 +1,194 @@ +use crate::{ + etherscan::EtherscanVerificationProvider, + sourcify::SourcifyVerificationProvider, + verify::{VerifyArgs, VerifyCheckArgs}, +}; +use alloy_json_abi::JsonAbi; +use async_trait::async_trait; +use eyre::{OptionExt, Result}; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::{ + artifacts::{output_selection::OutputSelection, Metadata, Source}, + compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler}, + multi::MultiCompilerSettings, + solc::Solc, + Graph, Project, +}; +use foundry_config::Config; +use semver::Version; +use std::{fmt, path::PathBuf, str::FromStr}; + +/// Container with data required for contract verification. +#[derive(Debug, Clone)] +pub struct VerificationContext { + pub config: Config, + pub project: Project, + pub target_path: PathBuf, + pub target_name: String, + pub compiler_version: Version, + pub compiler_settings: MultiCompilerSettings, +} + +impl VerificationContext { + pub fn new( + target_path: PathBuf, + target_name: String, + compiler_version: Version, + config: Config, + compiler_settings: MultiCompilerSettings, + ) -> Result { + let mut project = config.project()?; + project.no_artifacts = true; + + let solc = Solc::find_or_install(&compiler_version)?; + project.compiler.solc = Some(SolcCompiler::Specific(solc)); + + Ok(Self { config, project, target_name, target_path, compiler_version, compiler_settings }) + } + + /// Compiles target contract requesting only ABI and returns it. + pub fn get_target_abi(&self) -> Result { + let mut project = self.project.clone(); + project.update_output_selection(|selection| { + *selection = OutputSelection::common_output_selection(["abi".to_string()]) + }); + + let output = ProjectCompiler::new() + .quiet(true) + .files([self.target_path.clone()]) + .compile(&project)?; + + let artifact = output + .find(&self.target_path, &self.target_name) + .ok_or_eyre("failed to find target artifact when compiling for abi")?; + + artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") + } + + /// Compiles target file requesting only metadata and returns it. + pub fn get_target_metadata(&self) -> Result { + let mut project = self.project.clone(); + project.update_output_selection(|selection| { + *selection = OutputSelection::common_output_selection(["metadata".to_string()]); + }); + + let output = ProjectCompiler::new() + .quiet(true) + .files([self.target_path.clone()]) + .compile(&project)?; + + let artifact = output + .find(&self.target_path, &self.target_name) + .ok_or_eyre("failed to find target artifact when compiling for metadata")?; + + artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI") + } + + /// Returns [Vec] containing imports of the target file. + pub fn get_target_imports(&self) -> Result> { + let mut sources = self.project.paths.read_input_files()?; + sources.insert(self.target_path.clone(), Source::read(&self.target_path)?); + let graph = + Graph::::resolve_sources(&self.project.paths, sources)?; + + Ok(graph.imports(&self.target_path).into_iter().cloned().collect()) + } +} + +/// An abstraction for various verification providers such as etherscan, sourcify, blockscout +#[async_trait] +pub trait VerificationProvider { + /// This should ensure the verify request can be prepared successfully. + /// + /// Caution: Implementers must ensure that this _never_ sends the actual verify request + /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given + /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a + /// contract deployment that's executed before the verify request and the subsequent verify task + /// fails due to misconfiguration. + async fn preflight_verify_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()>; + + /// Sends the actual verify request for the targeted contract. + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>; + + /// Checks whether the contract is verified. + async fn check(&self, args: VerifyCheckArgs) -> Result<()>; +} + +impl FromStr for VerificationProviderType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "e" | "etherscan" => Ok(Self::Etherscan), + "s" | "sourcify" => Ok(Self::Sourcify), + "b" | "blockscout" => Ok(Self::Blockscout), + "o" | "oklink" => Ok(Self::Oklink), + "c" | "custom" => Ok(Self::Custom), + _ => Err(format!("Unknown provider: {s}")), + } + } +} + +impl fmt::Display for VerificationProviderType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Etherscan => { + write!(f, "etherscan")?; + } + Self::Sourcify => { + write!(f, "sourcify")?; + } + Self::Blockscout => { + write!(f, "blockscout")?; + } + Self::Oklink => { + write!(f, "oklink")?; + } + Self::Custom => { + write!(f, "custom")?; + } + }; + Ok(()) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)] +pub enum VerificationProviderType { + Etherscan, + #[default] + Sourcify, + Blockscout, + Oklink, + /// Custom verification provider, requires compatibility with the Etherscan API. + Custom, +} + +impl VerificationProviderType { + /// Returns the corresponding `VerificationProvider` for the key + pub fn client(&self, key: &Option) -> Result> { + if key.as_ref().is_some_and(|k| !k.is_empty()) && matches!(self, Self::Sourcify) { + return Ok(Box::::default()); + } + match self { + Self::Etherscan => { + if key.as_ref().is_none_or(|key| key.is_empty()) { + eyre::bail!("ETHERSCAN_API_KEY must be set") + } + Ok(Box::::default()) + } + Self::Sourcify => { + sh_println!( + "Attempting to verify on Sourcify, pass the --etherscan-api-key to verify on Etherscan OR use the --verifier flag to verify on any other provider" + )?; + Ok(Box::::default()) + } + Self::Blockscout => Ok(Box::::default()), + Self::Oklink => Ok(Box::::default()), + Self::Custom => Ok(Box::::default()), + } + } +} diff --git a/crates/forge/bin/cmd/retry.rs b/crates/verify/src/retry.rs similarity index 73% rename from crates/forge/bin/cmd/retry.rs rename to crates/verify/src/retry.rs index 0a8db8f1fd523..a01b1c94522aa 100644 --- a/crates/forge/bin/cmd/retry.rs +++ b/crates/verify/src/retry.rs @@ -1,5 +1,6 @@ use clap::{builder::RangedU64ValueParser, Parser}; -use foundry_utils::Retry; +use foundry_common::retry::Retry; +use std::time::Duration; /// Retry config used when waiting for verification pub const RETRY_CHECK_ON_VERIFY: RetryArgs = RetryArgs { retries: 8, delay: 15 }; @@ -8,21 +9,21 @@ pub const RETRY_CHECK_ON_VERIFY: RetryArgs = RetryArgs { retries: 8, delay: 15 } pub const RETRY_VERIFY_ON_CREATE: RetryArgs = RetryArgs { retries: 15, delay: 5 }; /// Retry arguments for contract verification. -#[derive(Debug, Clone, Copy, Parser)] -#[clap(about = "Allows to use retry arguments for contract verification")] // override doc +#[derive(Clone, Copy, Debug, Parser)] +#[command(about = "Allows to use retry arguments for contract verification")] // override doc pub struct RetryArgs { /// Number of attempts for retrying verification. - #[clap( + #[arg( long, - value_parser = RangedU64ValueParser::::new().range(1..=10), + value_parser = RangedU64ValueParser::::new().range(1..), default_value = "5", )] pub retries: u32, - /// Optional delay to apply inbetween verification attempts, in seconds. - #[clap( + /// Optional delay to apply in between verification attempts, in seconds. + #[arg( long, - value_parser = RangedU64ValueParser::::new().range(0..=30), + value_parser = RangedU64ValueParser::::new().range(0..=180), default_value = "5", )] pub delay: u32, @@ -34,9 +35,10 @@ impl Default for RetryArgs { } } -impl From for Retry { - fn from(r: RetryArgs) -> Self { - Retry::new(r.retries, Some(r.delay)) +impl RetryArgs { + /// Converts the arguments into a `Retry` instance. + pub fn into_retry(self) -> Retry { + Retry::new(self.retries, Duration::from_secs(self.delay as u64)) } } diff --git a/crates/verify/src/sourcify.rs b/crates/verify/src/sourcify.rs new file mode 100644 index 0000000000000..a57335d39398b --- /dev/null +++ b/crates/verify/src/sourcify.rs @@ -0,0 +1,201 @@ +use crate::{ + provider::{VerificationContext, VerificationProvider}, + verify::{VerifyArgs, VerifyCheckArgs}, +}; +use alloy_primitives::map::HashMap; +use async_trait::async_trait; +use eyre::Result; +use foundry_common::fs; +use futures::FutureExt; +use reqwest::Url; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + +pub static SOURCIFY_URL: &str = "https://sourcify.dev/server/"; + +/// The type that can verify a contract on `sourcify` +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct SourcifyVerificationProvider; + +#[async_trait] +impl VerificationProvider for SourcifyVerificationProvider { + async fn preflight_verify_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()> { + let _ = self.prepare_request(&args, &context)?; + Ok(()) + } + + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { + let body = self.prepare_request(&args, &context)?; + + trace!("submitting verification request {:?}", body); + + let client = reqwest::Client::new(); + + let resp = args + .retry + .into_retry() + .run_async(|| { + async { + sh_println!( + "\nSubmitting verification for [{}] {:?}.", + context.target_name, + args.address.to_string() + )?; + let response = client + .post(args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL)) + .header("Content-Type", "application/json") + .body(serde_json::to_string(&body)?) + .send() + .await?; + + let status = response.status(); + if !status.is_success() { + let error: serde_json::Value = response.json().await?; + eyre::bail!( + "Sourcify verification request for address ({}) \ + failed with status code {status}\n\ + Details: {error:#}", + args.address, + ); + } + + let text = response.text().await?; + Ok(Some(serde_json::from_str::(&text)?)) + } + .boxed() + }) + .await?; + + self.process_sourcify_response(resp.map(|r| r.result)) + } + + async fn check(&self, args: VerifyCheckArgs) -> Result<()> { + let resp = args + .retry + .into_retry() + .run_async(|| { + async { + let url = Url::from_str( + args.verifier.verifier_url.as_deref().unwrap_or(SOURCIFY_URL), + )?; + let query = format!( + "check-by-addresses?addresses={}&chainIds={}", + args.id, + args.etherscan.chain.unwrap_or_default().id(), + ); + let url = url.join(&query)?; + let response = reqwest::get(url).await?; + if !response.status().is_success() { + eyre::bail!( + "Failed to request verification status with status code {}", + response.status() + ); + }; + + Ok(Some(response.json::>().await?)) + } + .boxed() + }) + .await?; + + self.process_sourcify_response(resp) + } +} + +impl SourcifyVerificationProvider { + /// Configures the API request to the sourcify API using the given [`VerifyArgs`]. + fn prepare_request( + &self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result { + let metadata = context.get_target_metadata()?; + let imports = context.get_target_imports()?; + + let mut files = HashMap::with_capacity_and_hasher(2 + imports.len(), Default::default()); + + let metadata = serde_json::to_string_pretty(&metadata)?; + files.insert("metadata.json".to_string(), metadata); + + let contract_path = context.target_path.clone(); + let filename = contract_path.file_name().unwrap().to_string_lossy().to_string(); + files.insert(filename, fs::read_to_string(&contract_path)?); + + for import in imports { + let import_entry = format!("{}", import.display()); + files.insert(import_entry, fs::read_to_string(&import)?); + } + + let req = SourcifyVerifyRequest { + address: args.address.to_string(), + chain: args.etherscan.chain.unwrap_or_default().id().to_string(), + files, + chosen_contract: None, + }; + + Ok(req) + } + + fn process_sourcify_response( + &self, + response: Option>, + ) -> Result<()> { + let Some([response, ..]) = response.as_deref() else { return Ok(()) }; + match response.status.as_str() { + "perfect" => { + if let Some(ts) = &response.storage_timestamp { + sh_println!("Contract source code already verified. Storage Timestamp: {ts}")?; + } else { + sh_println!("Contract successfully verified")?; + } + } + "partial" => { + sh_println!("The recompiled contract partially matches the deployed version")?; + } + "false" => sh_println!("Contract source code is not verified")?, + s => eyre::bail!("Unknown status from sourcify. Status: {s:?}"), + } + Ok(()) + } +} + +#[derive(Debug, Serialize)] +pub struct SourcifyVerifyRequest { + address: String, + chain: String, + files: HashMap, + #[serde(rename = "chosenContract", skip_serializing_if = "Option::is_none")] + chosen_contract: Option, +} + +#[derive(Debug, Deserialize)] +pub struct SourcifyVerificationResponse { + result: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct SourcifyResponseElement { + status: String, + #[serde(rename = "storageTimestamp")] + storage_timestamp: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_check_addresses_url() { + let url = Url::from_str("https://server-verify.hashscan.io").unwrap(); + let url = url.join("check-by-addresses?addresses=0x1234&chainIds=1").unwrap(); + assert_eq!( + url.as_str(), + "https://server-verify.hashscan.io/check-by-addresses?addresses=0x1234&chainIds=1" + ); + } +} diff --git a/crates/verify/src/types.rs b/crates/verify/src/types.rs new file mode 100644 index 0000000000000..20d86499642d7 --- /dev/null +++ b/crates/verify/src/types.rs @@ -0,0 +1,44 @@ +use eyre::Result; +use serde::{Deserialize, Serialize}; +use std::{fmt, str::FromStr}; + +/// Enum to represent the type of verification: `full` or `partial`. +/// Ref: +#[derive(Debug, Clone, clap::ValueEnum, Default, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub enum VerificationType { + #[default] + #[serde(rename = "full")] + Full, + #[serde(rename = "partial")] + Partial, +} + +impl FromStr for VerificationType { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + match s { + "full" => Ok(Self::Full), + "partial" => Ok(Self::Partial), + _ => eyre::bail!("Invalid verification type"), + } + } +} + +impl From for String { + fn from(v: VerificationType) -> Self { + match v { + VerificationType::Full => "full".to_string(), + VerificationType::Partial => "partial".to_string(), + } + } +} + +impl fmt::Display for VerificationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Full => write!(f, "full"), + Self::Partial => write!(f, "partial"), + } + } +} diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs new file mode 100644 index 0000000000000..132f0218f8d79 --- /dev/null +++ b/crates/verify/src/utils.rs @@ -0,0 +1,441 @@ +use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; +use alloy_dyn_abi::DynSolValue; +use alloy_primitives::{Address, Bytes, U256}; +use alloy_provider::{network::AnyRpcBlock, Provider}; +use alloy_rpc_types::BlockId; +use clap::ValueEnum; +use eyre::{OptionExt, Result}; +use foundry_block_explorers::{ + contract::{ContractCreationData, ContractMetadata, Metadata}, + errors::EtherscanError, +}; +use foundry_common::{abi::encode_args, compile::ProjectCompiler, provider::RetryProvider, shell}; +use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion}; +use foundry_config::Config; +use foundry_evm::{ + constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, opts::EvmOpts, + traces::TraceMode, +}; +use reqwest::Url; +use revm_primitives::{ + db::Database, + env::{EnvWithHandlerCfg, HandlerCfg}, + Bytecode, Env, SpecId, TxKind, +}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use yansi::Paint; + +/// Enum to represent the type of bytecode being verified +#[derive(Debug, Serialize, Deserialize, Clone, Copy, ValueEnum)] +pub enum BytecodeType { + #[serde(rename = "creation")] + Creation, + #[serde(rename = "runtime")] + Runtime, +} + +impl BytecodeType { + /// Check if the bytecode type is creation + pub fn is_creation(&self) -> bool { + matches!(self, Self::Creation) + } + + /// Check if the bytecode type is runtime + pub fn is_runtime(&self) -> bool { + matches!(self, Self::Runtime) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JsonResult { + pub bytecode_type: BytecodeType, + pub match_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +pub fn match_bytecodes( + local_bytecode: &[u8], + bytecode: &[u8], + constructor_args: &[u8], + is_runtime: bool, + bytecode_hash: BytecodeHash, +) -> Option { + // 1. Try full match + if local_bytecode == bytecode { + // If the bytecode_hash = 'none' in Config. Then it's always a partial match according to + // sourcify definitions. Ref: https://docs.sourcify.dev/docs/full-vs-partial-match/. + if bytecode_hash == BytecodeHash::None { + return Some(VerificationType::Partial); + } + + Some(VerificationType::Full) + } else { + is_partial_match(local_bytecode, bytecode, constructor_args, is_runtime) + .then_some(VerificationType::Partial) + } +} + +pub fn build_project( + args: &VerifyBytecodeArgs, + config: &Config, +) -> Result { + let project = config.project()?; + let compiler = ProjectCompiler::new(); + + let mut output = compiler.compile(&project)?; + + let artifact = output + .remove_contract(&args.contract) + .ok_or_eyre("Build Error: Contract artifact not found locally")?; + + Ok(artifact.into_contract_bytecode()) +} + +pub fn build_using_cache( + args: &VerifyBytecodeArgs, + etherscan_settings: &Metadata, + config: &Config, +) -> Result { + let project = config.project()?; + let cache = project.read_cache_file()?; + let cached_artifacts = cache.read_artifacts::()?; + + for (key, value) in cached_artifacts { + let name = args.contract.name.to_owned() + ".sol"; + let version = etherscan_settings.compiler_version.to_owned(); + // Ignores vyper + if version.starts_with("vyper:") { + eyre::bail!("Vyper contracts are not supported") + } + // Parse etherscan version string + let version = version.split('+').next().unwrap_or("").trim_start_matches('v').to_string(); + + // Check if `out/directory` name matches the contract name + if key.ends_with(name.as_str()) { + let name = name.replace(".sol", ".json"); + for artifact in value.into_values().flatten() { + // Check if ABI file matches the name + if !artifact.file.ends_with(&name) { + continue; + } + + // Check if Solidity version matches + if let Ok(version) = Version::parse(&version) { + if !(artifact.version.major == version.major && + artifact.version.minor == version.minor && + artifact.version.patch == version.patch) + { + continue; + } + } + + return Ok(artifact.artifact) + } + } + } + + eyre::bail!("couldn't find cached artifact for contract {}", args.contract.name) +} + +pub fn print_result( + res: Option, + bytecode_type: BytecodeType, + json_results: &mut Vec, + etherscan_config: &Metadata, + config: &Config, +) { + if let Some(res) = res { + if !shell::is_json() { + let _ = sh_println!( + "{} with status {}", + format!("{bytecode_type:?} code matched").green().bold(), + res.green().bold() + ); + } else { + let json_res = JsonResult { bytecode_type, match_type: Some(res), message: None }; + json_results.push(json_res); + } + } else if !shell::is_json() { + let _ = sh_err!( + "{bytecode_type:?} code did not match - this may be due to varying compiler settings" + ); + let mismatches = find_mismatch_in_settings(etherscan_config, config); + for mismatch in mismatches { + let _ = sh_eprintln!("{}", mismatch.red().bold()); + } + } else { + let json_res = JsonResult { + bytecode_type, + match_type: res, + message: Some(format!( + "{bytecode_type:?} code did not match - this may be due to varying compiler settings" + )), + }; + json_results.push(json_res); + } +} + +fn is_partial_match( + mut local_bytecode: &[u8], + mut bytecode: &[u8], + constructor_args: &[u8], + is_runtime: bool, +) -> bool { + // 1. Check length of constructor args + if constructor_args.is_empty() || is_runtime { + // Assume metadata is at the end of the bytecode + return try_extract_and_compare_bytecode(local_bytecode, bytecode) + } + + // 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) +} + +fn try_extract_and_compare_bytecode(mut local_bytecode: &[u8], mut bytecode: &[u8]) -> bool { + local_bytecode = extract_metadata_hash(local_bytecode); + bytecode = extract_metadata_hash(bytecode); + + // Now compare the local code and bytecode + local_bytecode == bytecode +} + +/// @dev This assumes that the metadata is at the end of the bytecode +fn extract_metadata_hash(bytecode: &[u8]) -> &[u8] { + // Get the last two bytes of the bytecode to find the length of CBOR metadata + let metadata_len = &bytecode[bytecode.len() - 2..]; + let metadata_len = u16::from_be_bytes([metadata_len[0], metadata_len[1]]); + + if metadata_len as usize <= bytecode.len() { + if ciborium::from_reader::( + &bytecode[bytecode.len() - 2 - metadata_len as usize..bytecode.len() - 2], + ) + .is_ok() + { + &bytecode[..bytecode.len() - 2 - metadata_len as usize] + } else { + bytecode + } + } else { + bytecode + } +} + +fn find_mismatch_in_settings( + etherscan_settings: &Metadata, + local_settings: &Config, +) -> Vec { + let mut mismatches: Vec = vec![]; + if etherscan_settings.evm_version != local_settings.evm_version.to_string().to_lowercase() { + let str = format!( + "EVM version mismatch: local={}, onchain={}", + local_settings.evm_version, etherscan_settings.evm_version + ); + mismatches.push(str); + } + let local_optimizer: u64 = if local_settings.optimizer == Some(true) { 1 } else { 0 }; + if etherscan_settings.optimization_used != local_optimizer { + let str = format!( + "Optimizer mismatch: local={}, onchain={}", + local_settings.optimizer.unwrap_or(false), + etherscan_settings.optimization_used + ); + mismatches.push(str); + } + if local_settings.optimizer_runs.is_some_and(|runs| etherscan_settings.runs != runs as u64) || + (local_settings.optimizer_runs.is_none() && etherscan_settings.runs > 0) + { + let str = format!( + "Optimizer runs mismatch: local={}, onchain={}", + local_settings.optimizer_runs.unwrap(), + etherscan_settings.runs + ); + mismatches.push(str); + } + + mismatches +} + +pub fn maybe_predeploy_contract( + creation_data: Result, +) -> Result<(Option, bool), eyre::ErrReport> { + let mut maybe_predeploy = false; + match creation_data { + Ok(creation_data) => Ok((Some(creation_data), maybe_predeploy)), + // Ref: https://explorer.mode.network/api?module=contract&action=getcontractcreation&contractaddresses=0xC0d3c0d3c0D3c0d3C0D3c0D3C0d3C0D3C0D30010 + Err(EtherscanError::EmptyResult { status, message }) + if status == "1" && message == "OK" => + { + maybe_predeploy = true; + Ok((None, maybe_predeploy)) + } + // Ref: https://api.basescan.org/api?module=contract&action=getcontractcreation&contractaddresses=0xC0d3c0d3c0D3c0d3C0D3c0D3C0d3C0D3C0D30010&apiKey=YourAPIKey + Err(EtherscanError::Serde { error: _, content }) if content.contains("GENESIS") => { + maybe_predeploy = true; + Ok((None, maybe_predeploy)) + } + Err(e) => eyre::bail!("Error fetching creation data from verifier-url: {:?}", e), + } +} + +pub fn check_and_encode_args( + artifact: &CompactContractBytecode, + args: Vec, +) -> Result, eyre::ErrReport> { + if let Some(constructor) = artifact.abi.as_ref().and_then(|abi| abi.constructor()) { + if constructor.inputs.len() != args.len() { + eyre::bail!( + "Mismatch of constructor arguments length. Expected {}, got {}", + constructor.inputs.len(), + args.len() + ); + } + encode_args(&constructor.inputs, &args).map(|args| DynSolValue::Tuple(args).abi_encode()) + } else { + Ok(Vec::new()) + } +} + +pub fn check_explorer_args(source_code: ContractMetadata) -> Result { + if let Some(args) = source_code.items.first() { + Ok(args.constructor_arguments.clone()) + } else { + eyre::bail!("No constructor arguments found from block explorer"); + } +} + +pub fn check_args_len( + artifact: &CompactContractBytecode, + args: &Bytes, +) -> Result<(), eyre::ErrReport> { + if let Some(constructor) = artifact.abi.as_ref().and_then(|abi| abi.constructor()) { + if !constructor.inputs.is_empty() && args.is_empty() { + eyre::bail!( + "Contract expects {} constructor argument(s), but none were provided", + constructor.inputs.len() + ); + } + } + Ok(()) +} + +pub async fn get_tracing_executor( + fork_config: &mut Config, + fork_blk_num: u64, + evm_version: EvmVersion, + evm_opts: EvmOpts, +) -> Result<(Env, TracingExecutor)> { + fork_config.fork_block_number = Some(fork_blk_num); + fork_config.evm_version = evm_version; + + let create2_deployer = evm_opts.create2_deployer; + let (env, fork, _chain, is_odyssey) = + TracingExecutor::get_fork_material(fork_config, evm_opts).await?; + + let executor = TracingExecutor::new( + env.clone(), + fork, + Some(fork_config.evm_version), + TraceMode::Call, + is_odyssey, + create2_deployer, + ); + + Ok((env, executor)) +} + +pub fn configure_env_block(env: &mut Env, block: &AnyRpcBlock) { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.beneficiary; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); +} + +pub fn deploy_contract( + executor: &mut TracingExecutor, + env: &Env, + spec_id: SpecId, + to: Option, +) -> Result { + let env_with_handler = EnvWithHandlerCfg::new(Box::new(env.clone()), HandlerCfg::new(spec_id)); + + if to.is_some_and(|to| to.is_call()) { + let TxKind::Call(to) = to.unwrap() else { unreachable!() }; + if to != DEFAULT_CREATE2_DEPLOYER { + eyre::bail!("Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx."); + } + let result = executor.transact_with_env(env_with_handler)?; + + trace!(transact_result = ?result.exit_reason); + if result.result.len() != 20 { + eyre::bail!( + "Failed to deploy contract on fork at block: call result is not exactly 20 bytes" + ); + } + + Ok(Address::from_slice(&result.result)) + } else { + let deploy_result = executor.deploy_with_env(env_with_handler, None)?; + trace!(deploy_result = ?deploy_result.raw.exit_reason); + Ok(deploy_result.address) + } +} + +pub async fn get_runtime_codes( + executor: &mut TracingExecutor, + provider: &RetryProvider, + address: Address, + fork_address: Address, + block: Option, +) -> Result<(Bytecode, Bytes)> { + let fork_runtime_code = executor + .backend_mut() + .basic(fork_address)? + .ok_or_else(|| { + eyre::eyre!( + "Failed to get runtime code for contract deployed on fork at address {}", + fork_address + ) + })? + .code + .ok_or_else(|| { + eyre::eyre!( + "Bytecode does not exist for contract deployed on fork at address {}", + fork_address + ) + })?; + + let onchain_runtime_code = if let Some(block) = block { + provider.get_code_at(address).block_id(BlockId::number(block)).await? + } else { + provider.get_code_at(address).await? + }; + + Ok((fork_runtime_code, onchain_runtime_code)) +} + +/// Returns `true` if the URL only consists of host. +/// +/// This is used to check user input url for missing /api path +#[inline] +pub fn is_host_only(url: &Url) -> bool { + matches!(url.path(), "/" | "") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_host_only() { + assert!(!is_host_only(&Url::parse("https://blockscout.net/api").unwrap())); + assert!(is_host_only(&Url::parse("https://blockscout.net/").unwrap())); + assert!(is_host_only(&Url::parse("https://blockscout.net").unwrap())); + } +} diff --git a/crates/verify/src/verify.rs b/crates/verify/src/verify.rs new file mode 100644 index 0000000000000..e5b650c0e3630 --- /dev/null +++ b/crates/verify/src/verify.rs @@ -0,0 +1,462 @@ +//! The `forge verify-bytecode` command. + +use crate::{ + etherscan::EtherscanVerificationProvider, + provider::{VerificationProvider, VerificationProviderType}, + utils::is_host_only, + RetryArgs, +}; +use alloy_primitives::Address; +use alloy_provider::Provider; +use clap::{Parser, ValueHint}; +use eyre::Result; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils::{self, LoadConfig}, +}; +use foundry_common::{compile::ProjectCompiler, ContractsByArtifact}; +use foundry_compilers::{artifacts::EvmVersion, compilers::solc::Solc, info::ContractInfo}; +use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config, SolcReq}; +use itertools::Itertools; +use reqwest::Url; +use revm_primitives::HashSet; +use semver::BuildMetadata; +use std::path::PathBuf; + +use crate::provider::VerificationContext; + +/// Verification provider arguments +#[derive(Clone, Debug, Parser)] +pub struct VerifierArgs { + /// The contract verification provider to use. + #[arg(long, help_heading = "Verifier options", default_value = "sourcify", value_enum)] + pub verifier: VerificationProviderType, + + /// The verifier API KEY, if using a custom provider. + #[arg(long, help_heading = "Verifier options", env = "VERIFIER_API_KEY")] + pub verifier_api_key: Option, + + /// The verifier URL, if using a custom provider. + #[arg(long, help_heading = "Verifier options", env = "VERIFIER_URL")] + pub verifier_url: Option, +} + +impl Default for VerifierArgs { + fn default() -> Self { + Self { + verifier: VerificationProviderType::Sourcify, + verifier_api_key: None, + verifier_url: None, + } + } +} + +/// CLI arguments for `forge verify-contract`. +#[derive(Clone, Debug, Parser)] +pub struct VerifyArgs { + /// The address of the contract to verify. + pub address: Address, + + /// The contract identifier in the form `:`. + pub contract: Option, + + /// The ABI-encoded constructor arguments. + #[arg( + long, + conflicts_with = "constructor_args_path", + value_name = "ARGS", + visible_alias = "encoded-constructor-args" + )] + pub constructor_args: Option, + + /// The path to a file containing the constructor arguments. + #[arg(long, value_hint = ValueHint::FilePath, value_name = "PATH")] + pub constructor_args_path: Option, + + /// Try to extract constructor arguments from on-chain creation code. + #[arg(long)] + pub guess_constructor_args: bool, + + /// The `solc` version to use to build the smart contract. + #[arg(long, value_name = "VERSION")] + pub compiler_version: Option, + + /// The compilation profile to use to build the smart contract. + #[arg(long, value_name = "PROFILE_NAME")] + pub compilation_profile: Option, + + /// The number of optimization runs used to build the smart contract. + #[arg(long, visible_alias = "optimizer-runs", value_name = "NUM")] + pub num_of_optimizations: Option, + + /// Flatten the source code before verifying. + #[arg(long)] + pub flatten: bool, + + /// Do not compile the flattened smart contract before verifying (if --flatten is passed). + #[arg(short, long)] + pub force: bool, + + /// Do not check if the contract is already verified before verifying. + #[arg(long)] + pub skip_is_verified_check: bool, + + /// Wait for verification result after submission. + #[arg(long)] + pub watch: bool, + + /// Set pre-linked libraries. + #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] + pub libraries: Vec, + + /// The project's root path. + /// + /// By default root of the Git repository, if in one, + /// or the current working directory. + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + pub root: Option, + + /// Prints the standard json compiler input. + /// + /// The standard json compiler input can be used to manually submit contract verification in + /// the browser. + #[arg(long, conflicts_with = "flatten")] + pub show_standard_json_input: bool, + + /// Use the Yul intermediate representation compilation pipeline. + #[arg(long)] + pub via_ir: bool, + + /// The EVM version to use. + /// + /// Overrides the version specified in the config. + #[arg(long)] + pub evm_version: Option, + + #[command(flatten)] + pub etherscan: EtherscanOpts, + + #[command(flatten)] + pub rpc: RpcOpts, + + #[command(flatten)] + pub retry: RetryArgs, + + #[command(flatten)] + pub verifier: VerifierArgs, +} + +impl_figment_convert!(VerifyArgs); + +impl figment::Provider for VerifyArgs { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("Verify Provider") + } + + fn data( + &self, + ) -> Result, figment::Error> { + let mut dict = self.etherscan.dict(); + dict.extend(self.rpc.dict()); + + if let Some(root) = self.root.as_ref() { + dict.insert("root".to_string(), figment::value::Value::serialize(root)?); + } + if let Some(optimizer_runs) = self.num_of_optimizations { + dict.insert("optimizer".to_string(), figment::value::Value::serialize(true)?); + dict.insert( + "optimizer_runs".to_string(), + figment::value::Value::serialize(optimizer_runs)?, + ); + } + if let Some(evm_version) = self.evm_version { + dict.insert("evm_version".to_string(), figment::value::Value::serialize(evm_version)?); + } + if self.via_ir { + dict.insert("via_ir".to_string(), figment::value::Value::serialize(self.via_ir)?); + } + + if let Some(api_key) = &self.verifier.verifier_api_key { + dict.insert("etherscan_api_key".into(), api_key.as_str().into()); + } + + Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) + } +} + +impl VerifyArgs { + /// Run the verify command to submit the contract's source code for verification on etherscan + pub async fn run(mut self) -> Result<()> { + let config = self.load_config()?; + + if self.guess_constructor_args && config.get_rpc_url().is_none() { + eyre::bail!( + "You have to provide a valid RPC URL to use --guess-constructor-args feature" + ) + } + + // If chain is not set, we try to get it from the RPC. + // If RPC is not set, the default chain is used. + let chain = match config.get_rpc_url() { + Some(_) => { + let provider = utils::get_provider(&config)?; + utils::get_chain(config.chain, provider).await? + } + None => config.chain.unwrap_or_default(), + }; + + let context = self.resolve_context().await?; + + // Set Etherscan options. + self.etherscan.chain = Some(chain); + self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); + + if self.show_standard_json_input { + let args = EtherscanVerificationProvider::default() + .create_verify_request(&self, &context) + .await?; + sh_println!("{}", args.source)?; + return Ok(()) + } + + let verifier_url = self.verifier.verifier_url.clone(); + sh_println!("Start verifying contract `{}` deployed on {chain}", self.address)?; + if let Some(version) = &self.compiler_version { + sh_println!("Compiler version: {version}")?; + } + if let Some(optimizations) = &self.num_of_optimizations { + sh_println!("Optimizations: {optimizations}")? + } + if let Some(args) = &self.constructor_args { + if !args.is_empty() { + sh_println!("Constructor args: {args}")? + } + } + self.verifier.verifier.client(&self.etherscan.key())?.verify(self, context).await.map_err(|err| { + if let Some(verifier_url) = verifier_url { + match Url::parse(&verifier_url) { + Ok(url) => { + if is_host_only(&url) { + return err.wrap_err(format!( + "Provided URL `{verifier_url}` is host only.\n Did you mean to use the API endpoint`{verifier_url}/api` ?" + )) + } + } + Err(url_err) => { + return err.wrap_err(format!( + "Invalid URL {verifier_url} provided: {url_err}" + )) + } + } + } + + err + }) + } + + /// Returns the configured verification provider + pub fn verification_provider(&self) -> Result> { + self.verifier.verifier.client(&self.etherscan.key()) + } + + /// Resolves [VerificationContext] object either from entered contract name or by trying to + /// match bytecode located at given address. + pub async fn resolve_context(&self) -> Result { + let mut config = self.load_config()?; + config.libraries.extend(self.libraries.clone()); + + let project = config.project()?; + + if let Some(ref contract) = self.contract { + let contract_path = if let Some(ref path) = contract.path { + project.root().join(PathBuf::from(path)) + } else { + project.find_contract_path(&contract.name)? + }; + + let cache = project.read_cache_file().ok(); + + let mut version = if let Some(ref version) = self.compiler_version { + version.trim_start_matches('v').parse()? + } else if let Some(ref solc) = config.solc { + match solc { + SolcReq::Version(version) => version.to_owned(), + SolcReq::Local(solc) => Solc::new(solc)?.version, + } + } else if let Some(entry) = + cache.as_ref().and_then(|cache| cache.files.get(&contract_path).cloned()) + { + let unique_versions = entry + .artifacts + .get(&contract.name) + .map(|artifacts| artifacts.keys().collect::>()) + .unwrap_or_default(); + + if unique_versions.is_empty() { + eyre::bail!("No matching artifact found for {}", contract.name); + } else if unique_versions.len() > 1 { + warn!( + "Ambiguous compiler versions found in cache: {}", + unique_versions.iter().join(", ") + ); + eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") + } + + unique_versions.into_iter().next().unwrap().to_owned() + } else { + eyre::bail!("If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml") + }; + + let settings = if let Some(profile) = &self.compilation_profile { + if profile == "default" { + &project.settings + } else if let Some(settings) = project.additional_settings.get(profile.as_str()) { + settings + } else { + eyre::bail!("Unknown compilation profile: {}", profile) + } + } else if let Some((cache, entry)) = cache + .as_ref() + .and_then(|cache| Some((cache, cache.files.get(&contract_path)?.clone()))) + { + let profiles = entry + .artifacts + .get(&contract.name) + .and_then(|artifacts| { + let mut cached_artifacts = artifacts.get(&version); + // If we try to verify with specific build version and no cached artifacts + // found, then check if we have artifacts cached for same version but + // without any build metadata. + // This could happen when artifacts are built / cached + // with a version like `0.8.20` but verify is using a compiler-version arg + // as `0.8.20+commit.a1b79de6`. + // See . + if cached_artifacts.is_none() && version.build != BuildMetadata::EMPTY { + version.build = BuildMetadata::EMPTY; + cached_artifacts = artifacts.get(&version); + } + cached_artifacts + }) + .map(|artifacts| artifacts.keys().collect::>()) + .unwrap_or_default(); + + if profiles.is_empty() { + eyre::bail!("No matching artifact found for {}", contract.name); + } else if profiles.len() > 1 { + eyre::bail!("Ambiguous compilation profiles found in cache: {}, please specify the profile through `--compilation-profile` flag", profiles.iter().join(", ")) + } + + let profile = profiles.into_iter().next().unwrap().to_owned(); + let settings = cache.profiles.get(&profile).expect("must be present"); + + settings + } else if project.additional_settings.is_empty() { + &project.settings + } else { + eyre::bail!("If cache is disabled, compilation profile must be provided with `--compiler-version` option or set in foundry.toml") + }; + + VerificationContext::new( + contract_path, + contract.name.clone(), + version, + config, + settings.clone(), + ) + } else { + if config.get_rpc_url().is_none() { + eyre::bail!("You have to provide a contract name or a valid RPC URL") + } + let provider = utils::get_provider(&config)?; + let code = provider.get_code_at(self.address).await?; + + let output = ProjectCompiler::new().compile(&project)?; + let contracts = ContractsByArtifact::new( + output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), + ); + + let Some((artifact_id, _)) = contracts.find_by_deployed_code_exact(&code) else { + eyre::bail!(format!( + "Bytecode at {} does not match any local contracts", + self.address + )) + }; + + let settings = project + .settings_profiles() + .find_map(|(name, settings)| { + (name == artifact_id.profile.as_str()).then_some(settings) + }) + .expect("must be present"); + + VerificationContext::new( + artifact_id.source.clone(), + artifact_id.name.split('.').next().unwrap().to_owned(), + artifact_id.version.clone(), + config, + settings.clone(), + ) + } + } +} + +/// Check verification status arguments +#[derive(Clone, Debug, Parser)] +pub struct VerifyCheckArgs { + /// The verification ID. + /// + /// For Etherscan - Submission GUID. + /// + /// For Sourcify - Contract Address. + pub id: String, + + #[command(flatten)] + pub retry: RetryArgs, + + #[command(flatten)] + pub etherscan: EtherscanOpts, + + #[command(flatten)] + pub verifier: VerifierArgs, +} + +impl_figment_convert_cast!(VerifyCheckArgs); + +impl VerifyCheckArgs { + /// Run the verify command to submit the contract's source code for verification on etherscan + pub async fn run(self) -> Result<()> { + sh_println!( + "Checking verification status on {}", + self.etherscan.chain.unwrap_or_default() + )?; + self.verifier.verifier.client(&self.etherscan.key())?.check(self).await + } +} + +impl figment::Provider for VerifyCheckArgs { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("Verify Check Provider") + } + + fn data( + &self, + ) -> Result, figment::Error> { + self.etherscan.data() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_parse_verify_contract() { + let args: VerifyArgs = VerifyArgs::parse_from([ + "foundry-cli", + "0x0000000000000000000000000000000000000000", + "src/Domains.sol:Domains", + "--via-ir", + ]); + assert!(args.via_ir); + } +} diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml new file mode 100644 index 0000000000000..b1310b98e86a8 --- /dev/null +++ b/crates/wallets/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "foundry-wallets" + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +foundry-config.workspace = true + +alloy-primitives.workspace = true +alloy-signer = { workspace = true, features = ["eip712"] } +alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } +alloy-signer-ledger = { workspace = true, features = ["eip712"] } +alloy-signer-trezor.workspace = true +alloy-network.workspace = true +alloy-consensus.workspace = true +alloy-sol-types.workspace = true +alloy-dyn-abi.workspace = true + +# aws-kms +alloy-signer-aws = { workspace = true, features = ["eip712"], optional = true } +aws-config = { version = "1", default-features = true, optional = true } +aws-sdk-kms = { version = "1", default-features = false, optional = true } + +# gcp-kms +alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true } +gcloud-sdk = { version = "0.26", features = [ + "google-cloud-kms-v1", + "google-longrunning", +], optional = true } + +async-trait.workspace = true +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +derive_builder = "0.20" +eyre.workspace = true +rpassword = "7" +serde.workspace = true +thiserror.workspace = true +tracing.workspace = true +eth-keystore = "0.5.0" + +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } + +[features] +aws-kms = ["dep:alloy-signer-aws", "dep:aws-config", "dep:aws-sdk-kms"] +gcp-kms = ["dep:alloy-signer-gcp", "dep:gcloud-sdk"] diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs new file mode 100644 index 0000000000000..9deb037b71fb8 --- /dev/null +++ b/crates/wallets/src/error.rs @@ -0,0 +1,55 @@ +use alloy_primitives::hex::FromHexError; +use alloy_signer::k256::ecdsa; +use alloy_signer_ledger::LedgerError; +use alloy_signer_local::LocalSignerError; +use alloy_signer_trezor::TrezorError; + +#[cfg(feature = "aws-kms")] +use alloy_signer_aws::AwsSignerError; + +#[cfg(feature = "gcp-kms")] +use alloy_signer_gcp::GcpSignerError; + +#[derive(Debug, thiserror::Error)] +pub enum PrivateKeyError { + #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] + InvalidHex(#[from] FromHexError), + #[error("Failed to create wallet from private key. Invalid private key. But env var {0} exists. Is the `$` anchor missing?")] + ExistsAsEnvVar(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum WalletSignerError { + #[error(transparent)] + Local(#[from] LocalSignerError), + #[error("Failed to decrypt keystore: incorrect password")] + IncorrectKeystorePassword, + #[error(transparent)] + Ledger(#[from] LedgerError), + #[error(transparent)] + Trezor(#[from] TrezorError), + #[error(transparent)] + #[cfg(feature = "aws-kms")] + Aws(#[from] AwsSignerError), + #[error(transparent)] + #[cfg(feature = "gcp-kms")] + Gcp(#[from] GcpSignerError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + InvalidHex(#[from] FromHexError), + #[error(transparent)] + Ecdsa(#[from] ecdsa::Error), + #[error("foundry was not built with support for {0} signer")] + UnsupportedSigner(&'static str), +} + +impl WalletSignerError { + pub fn aws_unsupported() -> Self { + Self::UnsupportedSigner("AWS KMS") + } + + pub fn gcp_unsupported() -> Self { + Self::UnsupportedSigner("Google Cloud KMS") + } +} diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs new file mode 100644 index 0000000000000..e3be0971e47a9 --- /dev/null +++ b/crates/wallets/src/lib.rs @@ -0,0 +1,21 @@ +//! # foundry-wallets +//! +//! Utilities for working with multiple signers. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + +pub mod error; +pub mod multi_wallet; +pub mod raw_wallet; +pub mod utils; +pub mod wallet; +pub mod wallet_signer; + +pub use multi_wallet::MultiWalletOpts; +pub use raw_wallet::RawWalletOpts; +pub use wallet::WalletOpts; +pub use wallet_signer::{PendingSigner, WalletSigner}; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs new file mode 100644 index 0000000000000..19dbd978691ea --- /dev/null +++ b/crates/wallets/src/multi_wallet.rs @@ -0,0 +1,479 @@ +use crate::{ + utils, + wallet_signer::{PendingSigner, WalletSigner}, +}; +use alloy_primitives::{map::AddressHashMap, Address}; +use alloy_signer::Signer; +use clap::Parser; +use derive_builder::Builder; +use eyre::Result; +use foundry_config::Config; +use serde::Serialize; +use std::path::PathBuf; + +/// Container for multiple wallets. +#[derive(Debug, Default)] +pub struct MultiWallet { + /// Vector of wallets that require an action to be unlocked. + /// Those are lazily unlocked on the first access of the signers. + pending_signers: Vec, + /// Contains unlocked signers. + signers: AddressHashMap, +} + +impl MultiWallet { + pub fn new(pending_signers: Vec, signers: Vec) -> Self { + let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); + Self { pending_signers, signers } + } + + fn maybe_unlock_pending(&mut self) -> Result<()> { + for pending in self.pending_signers.drain(..) { + let signer = pending.unlock()?; + self.signers.insert(signer.address(), signer); + } + Ok(()) + } + + pub fn signers(&mut self) -> Result<&AddressHashMap> { + self.maybe_unlock_pending()?; + Ok(&self.signers) + } + + pub fn into_signers(mut self) -> Result> { + self.maybe_unlock_pending()?; + Ok(self.signers) + } + + pub fn add_signer(&mut self, signer: WalletSigner) { + self.signers.insert(signer.address(), signer); + } +} + +/// A macro that initializes multiple wallets +/// +/// Should be used with a [`MultiWallet`] instance +macro_rules! create_hw_wallets { + ($self:ident, $create_signer:expr, $signers:ident) => { + let mut $signers = vec![]; + + if let Some(hd_paths) = &$self.hd_paths { + for path in hd_paths { + let hw = $create_signer(Some(path), 0).await?; + $signers.push(hw); + } + } + + if let Some(mnemonic_indexes) = &$self.mnemonic_indexes { + for index in mnemonic_indexes { + let hw = $create_signer(None, *index).await?; + $signers.push(hw); + } + } + + if $signers.is_empty() { + let hw = $create_signer(None, 0).await?; + $signers.push(hw); + } + }; +} + +/// The wallet options can either be: +/// 1. Ledger +/// 2. Trezor +/// 3. Mnemonics (via file path) +/// 4. Keystores (via file path) +/// 5. Private Keys (cleartext in CLI) +/// 6. Private Keys (interactively via secure prompt) +/// 7. AWS KMS +#[derive(Builder, Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Wallet options", about = None, long_about = None)] +pub struct MultiWalletOpts { + /// The sender accounts. + #[arg( + long, + short = 'a', + help_heading = "Wallet options - raw", + value_name = "ADDRESSES", + env = "ETH_FROM", + num_args(0..), + )] + #[builder(default = "None")] + pub froms: Option>, + + /// Open an interactive prompt to enter your private key. + /// + /// Takes a value for the number of keys to enter. + #[arg( + long, + short, + help_heading = "Wallet options - raw", + default_value = "0", + value_name = "NUM" + )] + pub interactives: u32, + + /// Use the provided private keys. + #[arg(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] + #[builder(default = "None")] + pub private_keys: Option>, + + /// Use the provided private key. + #[arg( + long, + help_heading = "Wallet options - raw", + conflicts_with = "private_keys", + value_name = "RAW_PRIVATE_KEY" + )] + #[builder(default = "None")] + pub private_key: Option, + + /// Use the mnemonic phrases of mnemonic files at the specified paths. + #[arg(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] + #[builder(default = "None")] + pub mnemonics: Option>, + + /// Use a BIP39 passphrases for the mnemonic. + #[arg(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] + #[builder(default = "None")] + pub mnemonic_passphrases: Option>, + + /// The wallet derivation path. + /// + /// Works with both --mnemonic-path and hardware wallets. + #[arg( + long = "mnemonic-derivation-paths", + alias = "hd-paths", + help_heading = "Wallet options - raw", + value_name = "PATH" + )] + #[builder(default = "None")] + pub hd_paths: Option>, + + /// Use the private key from the given mnemonic index. + /// + /// Can be used with --mnemonics, --ledger, --aws and --trezor. + #[arg( + long, + conflicts_with = "hd_paths", + help_heading = "Wallet options - raw", + default_value = "0", + value_name = "INDEXES" + )] + pub mnemonic_indexes: Option>, + + /// Use the keystore by its filename in the given folder. + #[arg( + long = "keystore", + visible_alias = "keystores", + help_heading = "Wallet options - keystore", + value_name = "PATHS", + env = "ETH_KEYSTORE" + )] + #[builder(default = "None")] + pub keystore_paths: Option>, + + /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename. + #[arg( + long = "account", + visible_alias = "accounts", + help_heading = "Wallet options - keystore", + value_name = "ACCOUNT_NAMES", + env = "ETH_KEYSTORE_ACCOUNT", + conflicts_with = "keystore_paths" + )] + #[builder(default = "None")] + pub keystore_account_names: Option>, + + /// The keystore password. + /// + /// Used with --keystore. + #[arg( + long = "password", + help_heading = "Wallet options - keystore", + requires = "keystore_paths", + value_name = "PASSWORDS" + )] + #[builder(default = "None")] + pub keystore_passwords: Option>, + + /// The keystore password file path. + /// + /// Used with --keystore. + #[arg( + long = "password-file", + help_heading = "Wallet options - keystore", + requires = "keystore_paths", + value_name = "PATHS", + env = "ETH_PASSWORD" + )] + #[builder(default = "None")] + pub keystore_password_files: Option>, + + /// Use a Ledger hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub ledger: bool, + + /// Use a Trezor hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub trezor: bool, + + /// Use AWS Key Management Service. + #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] + pub aws: bool, +} + +impl MultiWalletOpts { + /// Returns [MultiWallet] container configured with provided options. + pub async fn get_multi_wallet(&self) -> Result { + let mut pending = Vec::new(); + let mut signers: Vec = Vec::new(); + + if let Some(ledgers) = self.ledgers().await? { + signers.extend(ledgers); + } + if let Some(trezors) = self.trezors().await? { + signers.extend(trezors); + } + if let Some(aws_signers) = self.aws_signers().await? { + signers.extend(aws_signers); + } + if let Some((pending_keystores, unlocked)) = self.keystores()? { + pending.extend(pending_keystores); + signers.extend(unlocked); + } + if let Some(pks) = self.private_keys()? { + signers.extend(pks); + } + if let Some(mnemonics) = self.mnemonics()? { + signers.extend(mnemonics); + } + if self.interactives > 0 { + pending.extend(std::iter::repeat_n( + PendingSigner::Interactive, + self.interactives as usize, + )); + } + + Ok(MultiWallet::new(pending, signers)) + } + + pub fn private_keys(&self) -> Result>> { + let mut pks = vec![]; + if let Some(private_key) = &self.private_key { + pks.push(private_key); + } + if let Some(private_keys) = &self.private_keys { + for pk in private_keys { + pks.push(pk); + } + } + if !pks.is_empty() { + let wallets = pks + .into_iter() + .map(|pk| utils::create_private_key_signer(pk)) + .collect::>>()?; + Ok(Some(wallets)) + } else { + Ok(None) + } + } + + fn keystore_paths(&self) -> Result>> { + if let Some(keystore_paths) = &self.keystore_paths { + return Ok(Some(keystore_paths.iter().map(PathBuf::from).collect())); + } + if let Some(keystore_account_names) = &self.keystore_account_names { + let default_keystore_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + return Ok(Some( + keystore_account_names + .iter() + .map(|keystore_name| default_keystore_dir.join(keystore_name)) + .collect(), + )); + } + Ok(None) + } + + /// Returns all wallets read from the provided keystores arguments + /// + /// Returns `Ok(None)` if no keystore provided. + pub fn keystores(&self) -> Result, Vec)>> { + if let Some(keystore_paths) = self.keystore_paths()? { + let mut pending = Vec::new(); + let mut signers = Vec::new(); + + let mut passwords_iter = + self.keystore_passwords.clone().unwrap_or_default().into_iter(); + + let mut password_files_iter = + self.keystore_password_files.clone().unwrap_or_default().into_iter(); + + for path in &keystore_paths { + let (maybe_signer, maybe_pending) = utils::create_keystore_signer( + path, + passwords_iter.next().as_deref(), + password_files_iter.next().as_deref(), + )?; + if let Some(pending_signer) = maybe_pending { + pending.push(pending_signer); + } else if let Some(signer) = maybe_signer { + signers.push(signer); + } + } + return Ok(Some((pending, signers))); + } + Ok(None) + } + + pub fn mnemonics(&self) -> Result>> { + if let Some(ref mnemonics) = self.mnemonics { + let mut wallets = vec![]; + + let mut hd_paths_iter = self.hd_paths.clone().unwrap_or_default().into_iter(); + + let mut passphrases_iter = + self.mnemonic_passphrases.clone().unwrap_or_default().into_iter(); + + let mut indexes_iter = self.mnemonic_indexes.clone().unwrap_or_default().into_iter(); + + for mnemonic in mnemonics { + let wallet = utils::create_mnemonic_signer( + mnemonic, + passphrases_iter.next().as_deref(), + hd_paths_iter.next().as_deref(), + indexes_iter.next().unwrap_or(0), + )?; + wallets.push(wallet); + } + return Ok(Some(wallets)); + } + Ok(None) + } + + pub async fn ledgers(&self) -> Result>> { + if self.ledger { + let mut args = self.clone(); + + if let Some(paths) = &args.hd_paths { + if paths.len() > 1 { + eyre::bail!("Ledger only supports one signer."); + } + args.mnemonic_indexes = None; + } + + create_hw_wallets!(args, utils::create_ledger_signer, wallets); + return Ok(Some(wallets)); + } + Ok(None) + } + + pub async fn trezors(&self) -> Result>> { + if self.trezor { + create_hw_wallets!(self, utils::create_trezor_signer, wallets); + return Ok(Some(wallets)); + } + Ok(None) + } + + pub async fn aws_signers(&self) -> Result>> { + #[cfg(feature = "aws-kms")] + if self.aws { + let mut wallets = vec![]; + let aws_keys = std::env::var("AWS_KMS_KEY_IDS") + .or(std::env::var("AWS_KMS_KEY_ID"))? + .split(',') + .map(|k| k.to_string()) + .collect::>(); + + for key in aws_keys { + let aws_signer = WalletSigner::from_aws(key).await?; + wallets.push(aws_signer) + } + + return Ok(Some(wallets)); + } + + Ok(None) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{path::Path, str::FromStr}; + + #[test] + fn parse_keystore_args() { + let args: MultiWalletOpts = + MultiWalletOpts::parse_from(["foundry-cli", "--keystores", "my/keystore/path"]); + assert_eq!(args.keystore_paths, Some(vec!["my/keystore/path".to_string()])); + + std::env::set_var("ETH_KEYSTORE", "MY_KEYSTORE"); + let args: MultiWalletOpts = MultiWalletOpts::parse_from(["foundry-cli"]); + assert_eq!(args.keystore_paths, Some(vec!["MY_KEYSTORE".to_string()])); + + std::env::remove_var("ETH_KEYSTORE"); + } + + #[test] + fn parse_keystore_password_file() { + let keystore = + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); + let keystore_file = keystore + .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); + + let keystore_password_file = keystore.join("password-ec554").into_os_string(); + + let args: MultiWalletOpts = MultiWalletOpts::parse_from([ + "foundry-cli", + "--keystores", + keystore_file.to_str().unwrap(), + "--password-file", + keystore_password_file.to_str().unwrap(), + ]); + assert_eq!( + args.keystore_password_files, + Some(vec![keystore_password_file.to_str().unwrap().to_string()]) + ); + + let (_, unlocked) = args.keystores().unwrap().unwrap(); + assert_eq!(unlocked.len(), 1); + assert_eq!( + unlocked[0].address(), + Address::from_str("0xec554aeafe75601aaab43bd4621a22284db566c2").unwrap() + ); + } + + // https://github.com/foundry-rs/foundry/issues/5179 + #[test] + fn should_not_require_the_mnemonics_flag_with_mnemonic_indexes() { + let wallet_options = vec![ + ("ledger", "--mnemonic-indexes", 1), + ("trezor", "--mnemonic-indexes", 2), + ("aws", "--mnemonic-indexes", 10), + ]; + + for test_case in wallet_options { + let args: MultiWalletOpts = MultiWalletOpts::parse_from([ + "foundry-cli", + &format!("--{}", test_case.0), + test_case.1, + &test_case.2.to_string(), + ]); + + match test_case.0 { + "ledger" => assert!(args.ledger), + "trezor" => assert!(args.trezor), + "aws" => assert!(args.aws), + _ => panic!("Should have matched one of the previous wallet options"), + } + + assert_eq!( + args.mnemonic_indexes.expect("--mnemonic-indexes should have been set")[0], + test_case.2 + ) + } + } +} diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs new file mode 100644 index 0000000000000..3a5169cad9774 --- /dev/null +++ b/crates/wallets/src/raw_wallet.rs @@ -0,0 +1,62 @@ +use crate::{utils, PendingSigner, WalletSigner}; +use clap::Parser; +use eyre::Result; +use serde::Serialize; + +/// A wrapper for the raw data options for `Wallet`, extracted to also be used standalone. +/// The raw wallet options can either be: +/// 1. Private Key (cleartext in CLI) +/// 2. Private Key (interactively via secure prompt) +/// 3. Mnemonic (via file path) +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Wallet options - raw", about = None, long_about = None)] +pub struct RawWalletOpts { + /// Open an interactive prompt to enter your private key. + #[arg(long, short)] + pub interactive: bool, + + /// Use the provided private key. + #[arg(long, value_name = "RAW_PRIVATE_KEY")] + pub private_key: Option, + + /// Use the mnemonic phrase of mnemonic file at the specified path. + #[arg(long, alias = "mnemonic-path")] + pub mnemonic: Option, + + /// Use a BIP39 passphrase for the mnemonic. + #[arg(long, value_name = "PASSPHRASE")] + pub mnemonic_passphrase: Option, + + /// The wallet derivation path. + /// + /// Works with both --mnemonic-path and hardware wallets. + #[arg(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] + pub hd_path: Option, + + /// Use the private key from the given mnemonic index. + /// + /// Used with --mnemonic-path. + #[arg(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] + pub mnemonic_index: u32, +} + +impl RawWalletOpts { + /// Returns signer configured by provided parameters. + pub fn signer(&self) -> Result> { + if self.interactive { + return Ok(Some(PendingSigner::Interactive.unlock()?)); + } + if let Some(private_key) = &self.private_key { + return Ok(Some(utils::create_private_key_signer(private_key)?)); + } + if let Some(mnemonic) = &self.mnemonic { + return Ok(Some(utils::create_mnemonic_signer( + mnemonic, + self.mnemonic_passphrase.as_deref(), + self.hd_path.as_deref(), + self.mnemonic_index, + )?)); + } + Ok(None) + } +} diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs new file mode 100644 index 0000000000000..ab1871d6b90b0 --- /dev/null +++ b/crates/wallets/src/utils.rs @@ -0,0 +1,163 @@ +use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; +use alloy_primitives::{hex::FromHex, B256}; +use alloy_signer_ledger::HDPath as LedgerHDPath; +use alloy_signer_local::PrivateKeySigner; +use alloy_signer_trezor::HDPath as TrezorHDPath; +use eyre::{Context, Result}; +use foundry_config::Config; +use std::{ + fs, + path::{Path, PathBuf}, +}; + +fn ensure_pk_not_env(pk: &str) -> Result<()> { + if !pk.starts_with("0x") && std::env::var(pk).is_ok() { + return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string()).into()); + } + Ok(()) +} + +/// Validates and sanitizes user inputs, returning configured [WalletSigner]. +pub fn create_private_key_signer(private_key_str: &str) -> Result { + let Ok(private_key) = B256::from_hex(private_key_str) else { + ensure_pk_not_env(private_key_str)?; + eyre::bail!("Failed to decode private key") + }; + match PrivateKeySigner::from_bytes(&private_key) { + Ok(pk) => Ok(WalletSigner::Local(pk)), + Err(err) => { + ensure_pk_not_env(private_key_str)?; + eyre::bail!("Failed to create wallet from private key: {err}") + } + } +} + +/// Creates [WalletSigner] instance from given mnemonic parameters. +/// +/// Mnemonic can be either a file path or a mnemonic phrase. +pub fn create_mnemonic_signer( + mnemonic: &str, + passphrase: Option<&str>, + hd_path: Option<&str>, + index: u32, +) -> Result { + let mnemonic = if Path::new(mnemonic).is_file() { + fs::read_to_string(mnemonic)?.replace('\n', "") + } else { + mnemonic.to_owned() + }; + + Ok(WalletSigner::from_mnemonic(&mnemonic, passphrase, hd_path, index)?) +} + +/// Creates [WalletSigner] instance from given Ledger parameters. +pub async fn create_ledger_signer( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + LedgerHDPath::Other(hd_path.to_owned()) + } else { + LedgerHDPath::LedgerLive(mnemonic_index as usize) + }; + + WalletSigner::from_ledger_path(derivation).await.wrap_err_with(|| { + "\ +Could not connect to Ledger device. +Make sure it's connected and unlocked, with no other desktop wallet apps open." + }) +} + +/// Creates [WalletSigner] instance from given Trezor parameters. +pub async fn create_trezor_signer( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + TrezorHDPath::Other(hd_path.to_owned()) + } else { + TrezorHDPath::TrezorLive(mnemonic_index as usize) + }; + + WalletSigner::from_trezor_path(derivation).await.wrap_err_with(|| { + "\ +Could not connect to Trezor device. +Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." + }) +} + +pub fn maybe_get_keystore_path( + maybe_path: Option<&str>, + maybe_name: Option<&str>, +) -> Result> { + let default_keystore_dir = Config::foundry_keystores_dir() + .ok_or_else(|| eyre::eyre!("Could not find the default keystore directory."))?; + Ok(maybe_path + .map(PathBuf::from) + .or_else(|| maybe_name.map(|name| default_keystore_dir.join(name)))) +} + +/// Creates keystore signer from given parameters. +/// +/// If correct password or password file is provided, the keystore is decrypted and a [WalletSigner] +/// is returned. +/// +/// Otherwise, a [PendingSigner] is returned, which can be used to unlock the keystore later, +/// prompting user for password. +pub fn create_keystore_signer( + path: &PathBuf, + maybe_password: Option<&str>, + maybe_password_file: Option<&str>, +) -> Result<(Option, Option)> { + if !path.exists() { + eyre::bail!("Keystore file `{path:?}` does not exist") + } + + if path.is_dir() { + eyre::bail!( + "Keystore path `{path:?}` is a directory. Please specify the keystore file directly." + ) + } + + let password = match (maybe_password, maybe_password_file) { + (Some(password), _) => Ok(Some(password.to_string())), + (_, Some(password_file)) => { + let password_file = Path::new(password_file); + if !password_file.is_file() { + Err(eyre::eyre!("Keystore password file `{password_file:?}` does not exist")) + } else { + Ok(Some( + fs::read_to_string(password_file) + .wrap_err_with(|| { + format!("Failed to read keystore password file at {password_file:?}") + })? + .trim_end() + .to_string(), + )) + } + } + (None, None) => Ok(None), + }?; + + if let Some(password) = password { + let wallet = PrivateKeySigner::decrypt_keystore(path, password) + .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?; + Ok((Some(WalletSigner::Local(wallet)), None)) + } else { + Ok((None, Some(PendingSigner::Keystore(path.clone())))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_private_key_signer() { + let pk = B256::random(); + let pk_str = pk.to_string(); + assert!(create_private_key_signer(&pk_str).is_ok()); + // skip 0x + assert!(create_private_key_signer(&pk_str[2..]).is_ok()); + } +} diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs new file mode 100644 index 0000000000000..29ce54438586d --- /dev/null +++ b/crates/wallets/src/wallet.rs @@ -0,0 +1,212 @@ +use crate::{raw_wallet::RawWalletOpts, utils, wallet_signer::WalletSigner}; +use alloy_primitives::Address; +use clap::Parser; +use eyre::Result; +use serde::Serialize; + +/// The wallet options can either be: +/// 1. Raw (via private key / mnemonic file, see `RawWallet`) +/// 2. Ledger +/// 3. Trezor +/// 4. Keystore (via file path) +/// 5. AWS KMS +/// 6. Google Cloud KMS +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[command(next_help_heading = "Wallet options", about = None, long_about = None)] +pub struct WalletOpts { + /// The sender account. + #[arg( + long, + short, + value_name = "ADDRESS", + help_heading = "Wallet options - raw", + env = "ETH_FROM" + )] + pub from: Option
, + + #[command(flatten)] + pub raw: RawWalletOpts, + + /// Use the keystore in the given folder or file. + #[arg( + long = "keystore", + help_heading = "Wallet options - keystore", + value_name = "PATH", + env = "ETH_KEYSTORE" + )] + pub keystore_path: Option, + + /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename + #[arg( + long = "account", + help_heading = "Wallet options - keystore", + value_name = "ACCOUNT_NAME", + env = "ETH_KEYSTORE_ACCOUNT", + conflicts_with = "keystore_path" + )] + pub keystore_account_name: Option, + + /// The keystore password. + /// + /// Used with --keystore. + #[arg( + long = "password", + help_heading = "Wallet options - keystore", + requires = "keystore_path", + value_name = "PASSWORD" + )] + pub keystore_password: Option, + + /// The keystore password file path. + /// + /// Used with --keystore. + #[arg( + long = "password-file", + help_heading = "Wallet options - keystore", + requires = "keystore_path", + value_name = "PASSWORD_FILE", + env = "ETH_PASSWORD" + )] + pub keystore_password_file: Option, + + /// Use a Ledger hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub ledger: bool, + + /// Use a Trezor hardware wallet. + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] + pub trezor: bool, + + /// Use AWS Key Management Service. + #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] + pub aws: bool, + + /// Use Google Cloud Key Management Service. + #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))] + pub gcp: bool, +} + +impl WalletOpts { + pub async fn signer(&self) -> Result { + trace!("start finding signer"); + + let signer = if self.ledger { + utils::create_ledger_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) + .await? + } else if self.trezor { + utils::create_trezor_signer(self.raw.hd_path.as_deref(), self.raw.mnemonic_index) + .await? + } else if self.aws { + let key_id = std::env::var("AWS_KMS_KEY_ID")?; + WalletSigner::from_aws(key_id).await? + } else if self.gcp { + let project_id = std::env::var("GCP_PROJECT_ID")?; + let location = std::env::var("GCP_LOCATION")?; + let keyring = std::env::var("GCP_KEYRING")?; + let key_name = std::env::var("GCP_KEY_NAME")?; + let key_version = std::env::var("GCP_KEY_VERSION")?.parse()?; + WalletSigner::from_gcp(project_id, location, keyring, key_name, key_version).await? + } else if let Some(raw_wallet) = self.raw.signer()? { + raw_wallet + } else if let Some(path) = utils::maybe_get_keystore_path( + self.keystore_path.as_deref(), + self.keystore_account_name.as_deref(), + )? { + let (maybe_signer, maybe_pending) = utils::create_keystore_signer( + &path, + self.keystore_password.as_deref(), + self.keystore_password_file.as_deref(), + )?; + if let Some(pending) = maybe_pending { + pending.unlock()? + } else if let Some(signer) = maybe_signer { + signer + } else { + unreachable!() + } + } else { + eyre::bail!( + "\ +Error accessing local wallet. Did you set a private key, mnemonic or keystore? +Run `cast send --help` or `forge create --help` and use the corresponding CLI +flag to set your key via: +--private-key, --mnemonic-path, --aws, --gcp, --interactive, --trezor or --ledger. +Alternatively, if you're using a local node with unlocked accounts, +use the --unlocked flag and either set the `ETH_FROM` environment variable to the address +of the unlocked account you want to use, or provide the --from flag with the address directly." + ) + }; + + Ok(signer) + } +} + +impl From for WalletOpts { + fn from(options: RawWalletOpts) -> Self { + Self { raw: options, ..Default::default() } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_signer::Signer; + use std::{path::Path, str::FromStr}; + + #[tokio::test] + async fn find_keystore() { + let keystore = + Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../cast/tests/fixtures/keystore")); + let keystore_file = keystore + .join("UTC--2022-12-20T10-30-43.591916000Z--ec554aeafe75601aaab43bd4621a22284db566c2"); + let password_file = keystore.join("password-ec554"); + let wallet: WalletOpts = WalletOpts::parse_from([ + "foundry-cli", + "--from", + "560d246fcddc9ea98a8b032c9a2f474efb493c28", + "--keystore", + keystore_file.to_str().unwrap(), + "--password-file", + password_file.to_str().unwrap(), + ]); + let signer = wallet.signer().await.unwrap(); + assert_eq!( + signer.address(), + Address::from_str("ec554aeafe75601aaab43bd4621a22284db566c2").unwrap() + ); + } + + #[tokio::test] + async fn illformed_private_key_generates_user_friendly_error() { + let wallet = WalletOpts { + raw: RawWalletOpts { + interactive: false, + private_key: Some("123".to_string()), + mnemonic: None, + mnemonic_passphrase: None, + hd_path: None, + mnemonic_index: 0, + }, + from: None, + keystore_path: None, + keystore_account_name: None, + keystore_password: None, + keystore_password_file: None, + ledger: false, + trezor: false, + aws: false, + gcp: false, + }; + match wallet.signer().await { + Ok(_) => { + panic!("illformed private key shouldn't decode") + } + Err(x) => { + assert!( + x.to_string().contains("Failed to decode private key"), + "Error message is not user-friendly" + ); + } + } + } +} diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs new file mode 100644 index 0000000000000..812df986300be --- /dev/null +++ b/crates/wallets/src/wallet_signer.rs @@ -0,0 +1,284 @@ +use crate::error::WalletSignerError; +use alloy_consensus::SignableTransaction; +use alloy_dyn_abi::TypedData; +use alloy_network::TxSigner; +use alloy_primitives::{hex, Address, ChainId, PrimitiveSignature, B256}; +use alloy_signer::Signer; +use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; +use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}; +use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner}; +use alloy_sol_types::{Eip712Domain, SolStruct}; +use async_trait::async_trait; +use std::path::PathBuf; + +#[cfg(feature = "aws-kms")] +use {alloy_signer_aws::AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient}; + +#[cfg(feature = "gcp-kms")] +use { + alloy_signer_gcp::{GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier}, + gcloud_sdk::{ + google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient, + GoogleApi, + }, +}; + +pub type Result = std::result::Result; + +/// Wrapper enum around different signers. +#[derive(Debug)] +pub enum WalletSigner { + /// Wrapper around local wallet. e.g. private key, mnemonic + Local(PrivateKeySigner), + /// Wrapper around Ledger signer. + Ledger(LedgerSigner), + /// Wrapper around Trezor signer. + Trezor(TrezorSigner), + /// Wrapper around AWS KMS signer. + #[cfg(feature = "aws-kms")] + Aws(AwsSigner), + /// Wrapper around Google Cloud KMS signer. + #[cfg(feature = "gcp-kms")] + Gcp(GcpSigner), +} + +impl WalletSigner { + pub async fn from_ledger_path(path: LedgerHDPath) -> Result { + let ledger = LedgerSigner::new(path, None).await?; + Ok(Self::Ledger(ledger)) + } + + pub async fn from_trezor_path(path: TrezorHDPath) -> Result { + let trezor = TrezorSigner::new(path, None).await?; + Ok(Self::Trezor(trezor)) + } + + pub async fn from_aws(key_id: String) -> Result { + #[cfg(feature = "aws-kms")] + { + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = AwsClient::new(&config); + + Ok(Self::Aws(AwsSigner::new(client, key_id, None).await?)) + } + + #[cfg(not(feature = "aws-kms"))] + { + let _ = key_id; + Err(WalletSignerError::aws_unsupported()) + } + } + + pub async fn from_gcp( + project_id: String, + location: String, + keyring: String, + key_name: String, + key_version: u64, + ) -> Result { + #[cfg(feature = "gcp-kms")] + { + let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring); + let client = match GoogleApi::from_function( + KeyManagementServiceClient::new, + "https://cloudkms.googleapis.com", + None, + ) + .await + { + Ok(c) => c, + Err(e) => return Err(WalletSignerError::from(GcpSignerError::GoogleKmsError(e))), + }; + + let specifier = KeySpecifier::new(keyring, &key_name, key_version); + + Ok(Self::Gcp(GcpSigner::new(client, specifier, None).await?)) + } + + #[cfg(not(feature = "gcp-kms"))] + { + let _ = project_id; + let _ = location; + let _ = keyring; + let _ = key_name; + let _ = key_version; + Err(WalletSignerError::gcp_unsupported()) + } + } + + pub fn from_private_key(private_key: &B256) -> Result { + Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?)) + } + + /// Returns a list of addresses available to use with current signer + /// + /// - for Ledger and Trezor signers the number of addresses to retrieve is specified as argument + /// - the result for Ledger signers includes addresses available for both LedgerLive and Legacy + /// derivation paths + /// - for Local and AWS signers the result contains a single address + pub async fn available_senders(&self, max: usize) -> Result> { + let mut senders = Vec::new(); + match self { + Self::Local(local) => { + senders.push(local.address()); + } + Self::Ledger(ledger) => { + for i in 0..max { + if let Ok(address) = + ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await + { + senders.push(address); + } + } + for i in 0..max { + if let Ok(address) = + ledger.get_address_with_path(&LedgerHDPath::Legacy(i)).await + { + senders.push(address); + } + } + } + Self::Trezor(trezor) => { + for i in 0..max { + if let Ok(address) = + trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await + { + senders.push(address); + } + } + } + #[cfg(feature = "aws-kms")] + Self::Aws(aws) => { + senders.push(alloy_signer::Signer::address(aws)); + } + #[cfg(feature = "gcp-kms")] + Self::Gcp(gcp) => { + senders.push(alloy_signer::Signer::address(gcp)); + } + } + Ok(senders) + } + + pub fn from_mnemonic( + mnemonic: &str, + passphrase: Option<&str>, + derivation_path: Option<&str>, + index: u32, + ) -> Result { + let mut builder = MnemonicBuilder::::default().phrase(mnemonic); + + if let Some(passphrase) = passphrase { + builder = builder.password(passphrase) + } + + builder = if let Some(hd_path) = derivation_path { + builder.derivation_path(hd_path)? + } else { + builder.index(index)? + }; + + Ok(Self::Local(builder.build()?)) + } +} + +macro_rules! delegate { + ($s:ident, $inner:ident => $e:expr) => { + match $s { + Self::Local($inner) => $e, + Self::Ledger($inner) => $e, + Self::Trezor($inner) => $e, + #[cfg(feature = "aws-kms")] + Self::Aws($inner) => $e, + #[cfg(feature = "gcp-kms")] + Self::Gcp($inner) => $e, + } + }; +} + +#[async_trait] +impl Signer for WalletSigner { + /// Signs the given hash. + async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_hash(hash)).await + } + + async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_message(message)).await + } + + fn address(&self) -> Address { + delegate!(self, inner => alloy_signer::Signer::address(inner)) + } + + fn chain_id(&self) -> Option { + delegate!(self, inner => inner.chain_id()) + } + + fn set_chain_id(&mut self, chain_id: Option) { + delegate!(self, inner => inner.set_chain_id(chain_id)) + } + + async fn sign_typed_data( + &self, + payload: &T, + domain: &Eip712Domain, + ) -> alloy_signer::Result + where + Self: Sized, + { + delegate!(self, inner => inner.sign_typed_data(payload, domain)).await + } + + async fn sign_dynamic_typed_data( + &self, + payload: &TypedData, + ) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await + } +} + +#[async_trait] +impl TxSigner for WalletSigner { + fn address(&self) -> Address { + delegate!(self, inner => alloy_signer::Signer::address(inner)) + } + + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_transaction(tx)).await + } +} + +/// Signers that require user action to be obtained. +#[derive(Debug, Clone)] +pub enum PendingSigner { + Keystore(PathBuf), + Interactive, +} + +impl PendingSigner { + pub fn unlock(self) -> Result { + match self { + Self::Keystore(path) => { + let password = rpassword::prompt_password("Enter keystore password:")?; + match PrivateKeySigner::decrypt_keystore(path, password) { + Ok(signer) => Ok(WalletSigner::Local(signer)), + Err(e) => match e { + // Catch the `MacMismatch` error, which indicates an incorrect password and + // return a more user-friendly `IncorrectKeystorePassword`. + alloy_signer_local::LocalSignerError::EthKeystoreError( + eth_keystore::KeystoreError::MacMismatch, + ) => Err(WalletSignerError::IncorrectKeystorePassword), + _ => Err(WalletSignerError::Local(e)), + }, + } + } + Self::Interactive => { + let private_key = rpassword::prompt_password("Enter private key:")?; + Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?) + } + } + } +} diff --git a/deny.toml b/deny.toml index 37d7afc5b3ed9..b107bf5282c41 100644 --- a/deny.toml +++ b/deny.toml @@ -2,12 +2,11 @@ # 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 = [ - "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 + # proc-macro-error is unmaintained + "RUSTSEC-2024-0370", ] # This section is considered when running `cargo deny check bans`. @@ -15,14 +14,12 @@ 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" # List of crates to deny -deny = [ - { name = "openssl" }, -] +deny = [{ name = "openssl" }] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -32,7 +29,9 @@ skip = [] skip-tree = [] [licenses] -unlicensed = "deny" +version = 2 +confidence-threshold = 0.8 + # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. @@ -43,12 +42,15 @@ allow = [ "BSD-2-Clause", "BSD-3-Clause", "ISC", - "Unicode-DFS-2016", + "Unicode-3.0", "OpenSSL", "Unlicense", "WTFPL", "BSL-1.0", "0BSD", + "MPL-2.0", + "CDDL-1.0", + "Zlib", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses @@ -57,17 +59,11 @@ exceptions = [ # CC0 is a permissive license but somewhat unclear status for source code # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal - { allow = ["CC0-1.0"], name = "secp256k1" }, - { allow = ["CC0-1.0"], name = "secp256k1-sys" }, { allow = ["CC0-1.0"], name = "tiny-keccak" }, - { allow = ["CC0-1.0"], name = "more-asserts" }, { allow = ["CC0-1.0"], name = "trezor-client" }, { allow = ["CC0-1.0"], name = "notify" }, - { allow = ["CC0-1.0"], name = "constant_time_eq" }, { allow = ["CC0-1.0"], name = "dunce" }, - - { allow = ["GPL-3.0"], name = "fastrlp" }, - { allow = ["GPL-3.0"], name = "fastrlp-derive" }, + { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, ] #copyleft = "deny" @@ -76,16 +72,13 @@ exceptions = [ name = "unicode-ident" version = "*" expression = "(MIT OR Apache-2.0) AND Unicode-DFS-2016" -license-files = [ - { path = "LICENSE-UNICODE", hash = 0x3fb01745 } -] +license-files = [{ path = "LICENSE-UNICODE", hash = 0x3fb01745 }] + [[licenses.clarify]] name = "ring" version = "*" expression = "OpenSSL" -license-files = [ - { path = "LICENSE", hash = 0xbd0eed23 } -] +license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] # This section is considered when running `cargo deny check sources`. # More documentation about the 'sources' section can be found here: @@ -96,4 +89,9 @@ license-files = [ unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered -unknown-git = "allow" \ No newline at end of file +unknown-git = "deny" +allow-git = [ + "https://github.com/alloy-rs/alloy", + "https://github.com/paradigmxyz/revm-inspectors", + "https://github.com/bluealloy/revm", +] diff --git a/docs/dev/README.md b/docs/dev/README.md index db7cc3f537c73..cde2a01c31d49 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -1,42 +1,82 @@ -# Contributing Quick Start +# Developer Docs -The foundry Rust project is organized as a regular [Cargo workspace][cargo-workspace]. +The Foundry project is organized as a regular [Cargo workspace][cargo-workspace]. -Simply running +## Installation requirements +- [Rust](https://rustup.rs/) +- Make + +We use `cargo-nextest` as test runner (both locally and in the [CI](#ci)): + +- [Nextest](https://nexte.st/docs/installation/pre-built-binaries/#with-cargo-binstall) + +## Recommended + +If you are working in VSCode, we recommend you install the [rust-analyzer](https://rust-analyzer.github.io/) extension, and use the following VSCode user settings: + +```json +"editor.formatOnSave": true, +"rust-analyzer.rustfmt.extraArgs": ["+nightly"], +"[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" +} +``` + +Note that we use Rust's latest `nightly` for formatting. If you see `;` being inserted by your code editor it is a good indication you are on `stable`. + +## Getting started + +Build the project. + +```sh +$ make build +``` + +Run all tests. + +```sh +$ make test ``` -$ cargo test + +Run all tests and linters in preparation for a PR. + +```sh +$ make pr ``` -should be enough to get you started! +## Contents + +- [Architecture](./architecture.md) +- [Cheatcodes](./cheatcodes.md) +- [Debugging](./debugging.md) +- [Scripting](./scripting.md) -To learn more about how foundry's tools works, see [./architecture.md](./architecture.md). -It also explains the high-level layout of some aspects of the source code. -To read more about how to use it, see [📖 Foundry Book][foundry-book] -Note though, that the internal documentation is very incomplete. +_Note: This is incomplete and possibly outdated_ -# Getting in Touch +## Getting in Touch See also [Getting Help](../../README.md#getting-help) -# Issue Labels +## Issue Labels + +Whenever a ticket is initially opened a [`T-needs-triage`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-needs-triage) label is assigned. This means that a member has yet to correctly label it. + +If this is your first time contributing have a look at our [`first-issue`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3A%22first+issue%22) tickets. These are tickets we think are a good way to get familiar with the codebase. + +We classify the tickets in two major categories: [`T-feature`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-feature) and [`T-bug`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-bug). Additional labels are usually applied to help categorize the ticket for future reference. + +We also make use of [`T-meta`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-meta) aggregation tickets. These tickets are tickets to collect related features and bugs. + +We also have [`T-discuss`](https://github.com/foundry-rs/foundry/issues?q=is%3Aissue+is%3Aopen+label%3AT-to-discuss) tickets that require further discussion before proceeding on an implementation. Feel free to jump into the conversation! + +## CI -- [good-first-issue](https://github.com/foundry-rs/foundry/labels/good%20first%20issue) - are good issues to get into the project. -- [D-easy](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-easy), - [D-average](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-medium), - [D-hard](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-hard), - [D-chore](https://github.com/foundry-rs/foundry/issues?q=is%3Aopen+is%3Aissue+label%3AD-chore), - labels indicate how hard it would be to write a fix or add a feature. +We use GitHub Actions for continuous integration (CI). -# CI +We use [cargo-nextest][nextest] as the test runner. -We use GitHub Actions for CI. -We use [cargo-nextest][nextest] as the test runner -If `cargo test` passes locally, that's a good sign that CI will be green as well. -We also have tests that make use of forking mode which can be long running if the required state is not already cached locally. -Forking-related tests are executed exclusively in a separate CI job, they are identified by `fork` in their name. -So all of them can be easily skipped by `cargo t -- --skip fork` +If `make test` passes locally, that's a good sign that CI will be green as well. [foundry-book]: https://book.getfoundry.sh [cargo-workspace]: https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 4e1f3bb5e5431..d0b9640605550 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md @@ -1,18 +1,17 @@ # Architecture -This document describes the high-level architecture of foundry. +This document describes the high-level architecture of Foundry. ### `evm/` -foundry's evm tooling. This is built around [`revm`](https://github.com/bluealloy/revm) and has additional -implementation of +Foundry's EVM tooling. This is built around [`revm`](https://github.com/bluealloy/revm) and has additional +implementation of: -- [cheatcodes](./cheatcodes.md) a set of solidity calls dedicated to testing which can manipulate the environment in - which the execution is run +- [cheatcodes](./cheatcodes.md) a set of solidity calls dedicated to testing which can manipulate the environment in which the execution is run ### `config/` -Includes all of foundry's settings and how to get them +Includes all of Foundry's settings and how to get them ### `cli/` diff --git a/docs/dev/cheatcodes.md b/docs/dev/cheatcodes.md index cc9c757ca0be2..0815ca66bef50 100644 --- a/docs/dev/cheatcodes.md +++ b/docs/dev/cheatcodes.md @@ -1,92 +1,164 @@ -# Cheat codes +# Cheatcodes -foundry's EVM support is mainly dedicated to testing and exploration, it features a set of cheat codes which can +Foundry's EVM support is mainly dedicated to testing and exploration, it features a set of cheatcodes which can manipulate the environment in which the execution is run. Most of the time, simply testing your smart contracts outputs isn't enough. To manipulate the state of the EVM, as well -as test for specific reverts and events, Foundry is shipped with a set of cheat codes. +as test for specific reverts and events, Foundry is shipped with a set of cheatcodes. -## `revm` `Inspector` +## [`revm::Inspector`](https://docs.rs/revm/latest/revm/trait.Inspector.html) -To understand how cheat codes are implemented, we first need to look -at [`revm::Inspector`](https://docs.rs/revm/latest/revm/trait.Inspector.html), a trait that provides a set of event -hooks to be notified at certain stages of EVM execution. +To understand how cheatcodes are implemented, we first need to look at [`revm::Inspector`](https://docs.rs/revm/latest/revm/trait.Inspector.html), +a trait that provides a set of callbacks to be notified at certain stages of EVM execution. -For example [`Inspector::call`](https://docs.rs/revm/latest/revm/trait.Inspector.html#method.call) is called wen the EVM is about to execute a call: +For example, [`Inspector::call`](https://docs.rs/revm/latest/revm/trait.Inspector.html#method.call) +is called when the EVM is about to execute a call and is provided with the call's inputs and the +current state of the EVM. -```rust - fn call( - &mut self, - _data: &mut EVMData<'_, DB>, - _inputs: &mut CallInputs, - _is_static: bool -) -> (InstructionResult, Gas, Bytes) { ... } -``` - -## [Foundry Inspectors](../../evm/src/executor/inspector) +## [Foundry inspectors](../../crates/evm/evm/src/inspectors/) -the `evm` crate has a variety of inspectors for different use cases, such as +The [`evm`](../../crates/evm/evm/) crate has a variety of inspectors for different use cases, such as -- coverage -- tracing -- debugger -- cheat codes + logging +- coverage +- tracing +- debugger +- logging -## [Cheat code Inspector](../../evm/src/executor/inspector/cheatcodes) +## [Cheatcode inspector](../../crates/cheatcodes/src/inspector.rs) -The concept of cheat codes and cheat code inspector is very simple. +The concept of cheatcodes and cheatcode inspector is very simple. -In solidity cheat codes are calls to a specific address, the cheat code handler address: +Cheatcodes are calls to a specific address, the cheatcode handler address, defined as +`address(uint160(uint256(keccak256("hevm cheat code"))))` (`0x7109709ECfa91a80626fF3989D68f67F5b1DD12D`). -`address(uint160(uint256(keccak256('hevm cheat code'))))`: 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D +In Solidity, this can be initialized as `Vm constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);`, +but generally this is inherited from `forge-std/Test.sol`. -which can be initialized like `Vm constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);`, when -inheriting from `forge-std/Test.sol` it can be accessed via `vm.` directly. - -Since cheat codes are bound to a constant address, the cheat code inspector listens for that address: +Since cheatcodes are bound to a constant address, the cheatcode inspector listens for that address: ```rust impl Inspector for Cheatcodes { - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - is_static: bool, - ) -> (Return, Gas, Bytes) { + fn call(&mut self, ...) -> ... { if call.contract == CHEATCODE_ADDRESS { - // intercepted cheat code call + // intercepted cheatcode call // --snip-- } } } ``` -When a call to a cheat code is intercepted we try to decode the calldata into a known cheat code. +When a call to a cheatcode is intercepted we try to decode the calldata into a known cheatcode. + +Rust bindings for the cheatcode interface are generated via the [Alloy](https://github.com/alloy-rs) [`sol!`](https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html) macro. -Rust bindings for the cheat code interface are generated -via [ethers-rs](https://github.com/gakonst/ethers-rs/) `abigen!` macro: +If a call was successfully decoded into the `VmCalls` enum that the `sol!` macro generates, the +last step is a large `match` over the decoded function call structs, which serves as the +implementation handler for the cheatcode. This is also automatically generated, in part, by the +`sol!` macro, through the use of a custom internal derive procedural macro. + +## Cheatcodes implementation + +All the cheatcodes are defined in a large [`sol!`] macro call in [`cheatcodes/spec/src/vm.rs`]: ```rust -// Bindings for cheatcodes -abigen!( - HEVM, - r#"[ - roll(uint256) - warp(uint256) - fee(uint256) - // --snip-- - ]"#); +sol! { +#[derive(Cheatcode)] +interface Vm { + // ======== Types ======== + + /// Error thrown by a cheatcode. + error CheatcodeError(string message); + + // ... + + // ======== EVM ======== + + /// Gets the address for a given private key. + #[cheatcode(group = Evm, safety = Safe)] + function addr(uint256 privateKey) external pure returns (address keyAddr); + + /// Gets the nonce of an account. + #[cheatcode(group = Evm, safety = Safe)] + function getNonce(address account) external view returns (uint64 nonce); + + // ... +} +} ``` -If a call was successfully decoded into the `HEVMCalls` enum that the `abigen!` macro generates, the remaining step is -essentially a large `match` over the decoded `HEVMCalls` which serves as the implementation handler for the cheat code. +This, combined with the use of an internal [`Cheatcode` derive macro](#cheatcode-derive-macro), +allows us to generate both the Rust definitions and the JSON specification of the cheatcodes. + +Cheatcodes are manually implemented through the [`Cheatcode` trait](#cheatcode-trait), which is +called in the [`Cheatcodes` inspector](#cheatcode-inspector) implementation. + +### [`sol!`] + +Generates the raw Rust bindings for the cheatcodes, as well as lets us specify custom attributes +individually for each item, such as functions and structs, or for entire interfaces. + +The way bindings are generated and extra information can be found in the [`sol!`] documentation. + +We leverage this macro to apply the [`Cheatcode` derive macro](#cheatcode-derive-macro) on the `Vm` interface. + +### [`Cheatcode`](../../crates/macros/src/cheatcodes.rs) derive macro + +This is derived once on the `Vm` interface declaration, which recursively applies it to all of the +interface's items, as well as the `sol!`-generated items, such as the `VmCalls` enum. + +This macro performs extra checks on functions and structs at compile time to make sure they are +documented and have named parameters, and generates a macro which is later used to implement the +`match { ... }` function that is to be used to dispatch the cheatcode implementations after a call is +decoded. + +The latter is what fails compilation when adding a new cheatcode, and is fixed by implementing the +[`Cheatcode` trait](#cheatcode-trait) to the newly-generated function call struct(s). + +The `Cheatcode` derive macro also parses the `#[cheatcode(...)]` attributes on functions, which are +used to specify additional properties of the JSON interface. + +These are all the attributes that can be specified on cheatcode functions: + +- `#[cheatcode(group = )]`: The group that the cheatcode belongs to. Required. +- `#[cheatcode(status = )]`: The current status of the cheatcode. E.g. whether it is stable or experimental, etc. Defaults to `Stable`. +- `#[cheatcode(safety = )]`: Whether the cheatcode is safe to use inside of scripts. E.g. it does not change state in an unexpected way. Defaults to the group's safety if unspecified. If the group is ambiguous, then it must be specified manually. + +Multiple attributes can be specified by separating them with commas, e.g. `#[cheatcode(group = "evm", status = "unstable")]`. + +### `Cheatcode` trait + +This trait defines the interface that all cheatcode implementations must implement. +There are two methods that can be implemented: + +- `apply`: implemented when the cheatcode is pure and does not need to access EVM data +- `apply_stateful`: implemented when the cheatcode needs to access EVM data +- `apply_full`: implemented when the cheatcode needs to access EVM data and the EVM executor itself, for example to recursively call back into the EVM to execute an arbitrary transaction + +Only one of these methods can be implemented. + +This trait is implemented manually for each cheatcode in the [`foundry-cheatcodes`](../../crates/cheatcodes/) +crate on the `sol!`-generated function call structs. + +### [JSON interface](../../crates/cheatcodes/assets/cheatcodes.json) + +The [JSON interface](../../crates/cheatcodes/assets/cheatcodes.json) and [schema](../../crates/cheatcodes/assets/cheatcodes.schema.json) +are automatically generated from the [`sol!` macro call](#sol) by running `cargo cheats`. + +The initial execution of this command, following the addition of a new cheat code, will result in an +update to the JSON files, which is expected to fail. This failure is necessary for the CI system to +detect that changes have occurred. Subsequent executions should pass, confirming the successful +update of the files. -## Adding a new cheat code +### Adding a new cheatcode -This process consists of 4 steps: +1. Add its Solidity definition(s) in [`cheatcodes/spec/src/vm.rs`]. Ensure that all structs and functions are documented, and that all function parameters are named. This will initially fail to compile because of the automatically generated `match { ... }` expression. This is expected, and will be fixed in the next step +2. Implement the cheatcode in [`cheatcodes`] in its category's respective module. Follow the existing implementations as a guide. +3. If a struct, enum, error, or event was added to `Vm`, update [`spec::Cheatcodes::new`] +4. Update the JSON interface by running `cargo cheats` twice. This is expected to fail the first time that this is run after adding a new cheatcode; see [JSON interface](#json-interface) +5. Write an integration test for the cheatcode in [`testdata/cheats/`] -1. add the function signature to the `abigen!` macro so a new `HEVMCalls` variant is generated -2. implement the cheat code handler -3. add a Solidity test for the cheatcode under [`testdata/cheats`](https://github.com/foundry-rs/foundry/tree/master/testdata/cheats) -4. add the function signature - to [forge-std Vm interface](https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol) +[`sol!`]: https://docs.rs/alloy-sol-macro/latest/alloy_sol_macro/macro.sol.html +[`cheatcodes/spec/src/vm.rs`]: ../../crates/cheatcodes/spec/src/vm.rs +[`cheatcodes`]: ../../crates/cheatcodes/ +[`spec::Cheatcodes::new`]: ../../crates/cheatcodes/spec/src/lib.rs#L74 +[`testdata/cheats/`]: ../../testdata/default/cheats/ diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md index 15281efecd9c5..f72b8f4bfbae3 100644 --- a/docs/dev/debugging.md +++ b/docs/dev/debugging.md @@ -1,23 +1,21 @@ -## Debugging Foundry tools +# Debugging This is a working document intended to outline some commands contributors can use to debug various parts of Foundry. -### Logs +## Logs -All crates use [tracing](https://docs.rs/tracing/latest/tracing/) for logging. An console formatter is installed in each binary (`cast`, `forge`, `anvil`). +All crates use [tracing](https://docs.rs/tracing/latest/tracing/) for logging. A console formatter is installed in each binary (`cast`, `forge`, `anvil`). By setting `RUST_LOG=` you can get a lot more info out of Forge and Cast. For example, running Forge with `RUST_LOG=forge` will emit all logs of the `cli` crate, same for Cast. The most basic valid filter is a log level, of which these are valid: -- `error` -- `warn` -- `info` -- `debug` -- `trace` +- `error` +- `warn` +- `info` +- `debug` +- `trace` Filters are explained in detail in the [`env_logger` crate docs](https://docs.rs/env_logger). -### Compiler input and output - -You can get the compiler input JSON and output JSON from `ethers-solc` by passing the `--build-info` flag. This will create two files: one for the input and one for the output. +You can also use the `dbg!` macro from Rust's standard library. diff --git a/docs/dev/scripting.md b/docs/dev/scripting.md index 8e5fc03ca1b2a..cdface73a28e5 100644 --- a/docs/dev/scripting.md +++ b/docs/dev/scripting.md @@ -1,10 +1,10 @@ +# Scripting -# Scripting - Flow Diagrams - -1. [High level overview](#high-level-overview) - 1. [Notes](#notes) -2. [Script Execution](#script-execution) -3. [Nonce Management](#nonce-management) +- [Scripting](#scripting) + - [High level overview](#high-level-overview) + - [Notes](#notes) + - [Script Execution](#script-execution) + - [Nonce Management](#nonce-management) ## High level overview @@ -48,19 +48,20 @@ graph TD; ``` ### Notes -1) `[..]` - concurrently executed -2) The bit below does not actually influence the state initially defined by `--broadcast`. It only happens because there might be private keys declared inside the script that need to be collected again. `--resume` only resumes **publishing** the transactions, nothing more! +1. `[..]` - concurrently executed + +2. The bit below does not actually influence the state initially defined by `--broadcast`. It only happens because there might be private keys declared inside the script that need to be collected again. `--resume` only resumes **publishing** the transactions, nothing more! ```mermaid graph TD; ScriptArgs::execute-- "(resume || verify) && !broadcast" -->ScriptArgs::resume_deployment; ``` -3) `ScriptArgs::execute` executes the script, while `ScriptArgs::onchain_simulation` only executes the broadcastable transactions collected by `ScriptArgs::execute`. - +3. `ScriptArgs::execute` executes the script, while `ScriptArgs::onchain_simulation` only executes the broadcastable transactions collected by `ScriptArgs::execute`. ## Script Execution + ```mermaid graph TD; subgraph ScriptArgs::execute @@ -85,7 +86,6 @@ Executor::call-. BroadcastableTransactions .->ScriptArgs::handle_broadcastable_t ``` - ## Nonce Management During the first execution stage on `forge script`, foundry has to adjust the nonce from the sender to make sure the execution and state are as close as possible to its on-chain representation. @@ -94,7 +94,6 @@ Making sure that `msg.sender` is our signer when calling `setUp()` and `run()` a We skip this, if the user hasn't set a sender and they're using the `Config::DEFAULT_SENDER`. - ```mermaid graph TD @@ -135,4 +134,4 @@ graph TD L-->M[cheatcode.corrected_nonce=true]; M-->continue_run; continue_run-->end_run; -``` \ No newline at end of file +``` diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000000..45ed6bb432ffb --- /dev/null +++ b/flake.lock @@ -0,0 +1,119 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1719468428, + "narHash": "sha256-vN5xJAZ4UGREEglh3lfbbkIj+MPEYMuqewMn4atZFaQ=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "1e3deb3d8a86a870d925760db1a5adecc64d329d", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", + "solc": "solc" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1719714047, + "narHash": "sha256-MeNPopLLv63EZj5L43j4TZkmW4wj1ouoc/h/E20sl/U=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "cb216719ce89a43dfb3d1b86a9575e89f4b727a4", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, + "solc": { + "inputs": { + "flake-utils": [ + "flake-utils" + ], + "nixpkgs": [ + "nixpkgs" + ], + "solc-macos-amd64-list-json": "solc-macos-amd64-list-json" + }, + "locked": { + "lastModified": 1717442267, + "narHash": "sha256-6TnQvA6Q/xC3r1M+wGC5gnDc/5XfOPjC8X6LlGDWDNc=", + "owner": "hellwolf", + "repo": "solc.nix", + "rev": "2ac2862f224aa0d67cbc6b3246392489f8a50596", + "type": "github" + }, + "original": { + "owner": "hellwolf", + "repo": "solc.nix", + "type": "github" + } + }, + "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, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000000..ff783b4956e4e --- /dev/null +++ b/flake.nix @@ -0,0 +1,52 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs = { + nixpkgs.follows = "nixpkgs"; + }; + }; + solc = { + url = "github:hellwolf/solc.nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + }; + + outputs = { self, nixpkgs, rust-overlay, flake-utils, solc }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ rust-overlay.overlays.default solc.overlay ]; + }; + lib = pkgs.lib; + toolchain = pkgs.rust-bin.stable.latest.default.override { + extensions = [ "rustfmt" "clippy" "rust-src" ]; + }; + in + { + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + pkg-config + solc_0_8_23 + (solc.mkDefault pkgs solc_0_8_23) + toolchain + ]; + buildInputs = lib.optionals pkgs.stdenv.isDarwin [ + pkgs.darwin.apple_sdk.frameworks.AppKit + ]; + packages = with pkgs; [ + rust-analyzer-unwrapped + ]; + + # Environment variables + RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library"; + LD_LIBRARY_PATH = lib.makeLibraryPath [ pkgs.libusb1 ]; + }; + }); +} diff --git a/foundryup/README.md b/foundryup/README.md index 39a53288c001e..5504063abfe37 100644 --- a/foundryup/README.md +++ b/foundryup/README.md @@ -2,6 +2,8 @@ Update or revert to a specific Foundry branch with ease. +`foundryup` supports installing and managing multiple versions. + ## Installing ```sh @@ -16,10 +18,22 @@ To install the **nightly** version: foundryup ``` -To install a specific **version** (in this case the `nightly` version): +To **install** a specific **version** (in this case the `nightly` version): + +```sh +foundryup --install nightly +``` + +To **list** all **versions** installed: + +```sh +foundryup --list +``` + +To switch between different versions and **use**: ```sh -foundryup --version nightly +foundryup --use nightly-00efa0d5965269149f374ba142fb1c3c7edd6c94 ``` To install a specific **branch** (in this case the `release/0.1.0` branch's latest commit): @@ -62,6 +76,6 @@ foundryup --path ./git/foundry --- -**Tip**: All flags have a single character shorthand equivalent! You can use `-v` instead of `--version`, etc. +**Tip**: All flags have a single character shorthand equivalent! You can use `-i` instead of `--install`, etc. --- diff --git a/foundryup/foundryup b/foundryup/foundryup index 4ea420ecc39b6..a3a6d1bb58823 100755 --- a/foundryup/foundryup +++ b/foundryup/foundryup @@ -1,29 +1,46 @@ #!/usr/bin/env bash -set -e +set -eo pipefail + +# NOTE: if you make modifications to this script, please increment the version number. +# Major / minor: incremented for each stable release of Foundry. +# Patch: incremented for each change between stable releases. +FOUNDRYUP_INSTALLER_VERSION="1.0.1" BASE_DIR=${XDG_CONFIG_HOME:-$HOME} FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} +FOUNDRY_VERSIONS_DIR="$FOUNDRY_DIR/versions" FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin" FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1" +FOUNDRY_BIN_URL="https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup" +FOUNDRY_BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" + +FOUNDRYUP_JOBS="" BINS=(forge cast anvil chisel) -export RUSTFLAGS="-C target-cpu=native" +export RUSTFLAGS="${RUSTFLAGS:--C target-cpu=native}" main() { need_cmd git need_cmd curl - while [[ $1 ]]; do + while [[ -n $1 ]]; do case $1 in --) shift; break;; + -v|--version) shift; version;; + -U|--update) shift; update;; -r|--repo) shift; FOUNDRYUP_REPO=$1;; -b|--branch) shift; FOUNDRYUP_BRANCH=$1;; - -v|--version) shift; FOUNDRYUP_VERSION=$1;; + -i|--install) shift; FOUNDRYUP_VERSION=$1;; + -l|--list) shift; list;; + -u|--use) shift; FOUNDRYUP_VERSION=$1; use;; -p|--path) shift; FOUNDRYUP_LOCAL_REPO=$1;; -P|--pr) shift; FOUNDRYUP_PR=$1;; -C|--commit) shift; FOUNDRYUP_COMMIT=$1;; + -j|--jobs) shift; FOUNDRYUP_JOBS=$1;; + --arch) shift; FOUNDRYUP_ARCH=$1;; + --platform) shift; FOUNDRYUP_PLATFORM=$1;; -h|--help) usage exit 0 @@ -35,6 +52,12 @@ main() { esac; shift done + CARGO_BUILD_ARGS=(--release) + + if [ -n "$FOUNDRYUP_JOBS" ]; then + CARGO_BUILD_ARGS+=(--jobs "$FOUNDRYUP_JOBS") + fi + # Print the banner after successfully parsing args banner @@ -46,19 +69,21 @@ main() { fi fi + check_bins_in_use + # Installs foundry from a local repository if --path parameter is provided if [[ -n "$FOUNDRYUP_LOCAL_REPO" ]]; then need_cmd cargo # Ignore branches/versions as we do not want to modify local git state if [ -n "$FOUNDRYUP_REPO" ] || [ -n "$FOUNDRYUP_BRANCH" ] || [ -n "$FOUNDRYUP_VERSION" ]; then - warn "--branch, --version, and --repo arguments are ignored during local install" + warn "--branch, --install, --use, and --repo arguments are ignored during local install" fi # Enter local repo and build say "installing from $FOUNDRYUP_LOCAL_REPO" cd "$FOUNDRYUP_LOCAL_REPO" - ensure cargo build --bins --release # need 4 speed + ensure cargo build --bins "${CARGO_BUILD_ARGS[@]}" for bin in "${BINS[@]}"; do # Remove prior installations if they exist @@ -75,20 +100,11 @@ main() { # Install by downloading binaries if [[ "$FOUNDRYUP_REPO" == "foundry-rs/foundry" && -z "$FOUNDRYUP_BRANCH" && -z "$FOUNDRYUP_COMMIT" ]]; then - FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-nightly} + FOUNDRYUP_VERSION=${FOUNDRYUP_VERSION:-stable} FOUNDRYUP_TAG=$FOUNDRYUP_VERSION # Normalize versions (handle channels, versions without v prefix - if [[ "$FOUNDRYUP_VERSION" == "nightly" ]]; then - # Locate real nightly tag - SHA=$(ensure curl -sSf "https://api.github.com/repos/$FOUNDRYUP_REPO/git/refs/tags/nightly" \ - | grep -Eo '"sha"[^,]*' \ - | grep -Eo '[^:]*$' \ - | tr -d '"' \ - | tr -d ' ' \ - | cut -d ':' -f2 ) - FOUNDRYUP_TAG="nightly-${SHA}" - elif [[ "$FOUNDRYUP_VERSION" == nightly* ]]; then + if [[ "$FOUNDRYUP_VERSION" =~ ^nightly ]]; then FOUNDRYUP_VERSION="nightly" elif [[ "$FOUNDRYUP_VERSION" == [[:digit:]]* ]]; then # Add v prefix @@ -98,16 +114,15 @@ main() { say "installing foundry (version ${FOUNDRYUP_VERSION}, tag ${FOUNDRYUP_TAG})" - PLATFORM="$(uname -s)" + uname_s=$(uname -s) + PLATFORM=$(tolower "${FOUNDRYUP_PLATFORM:-$uname_s}") EXT="tar.gz" case $PLATFORM in - Linux) - PLATFORM="linux" - ;; - Darwin) + linux) ;; + darwin|mac*) PLATFORM="darwin" ;; - MINGW*) + mingw*|win*) EXT="zip" PLATFORM="win32" ;; @@ -116,7 +131,8 @@ main() { ;; esac - ARCHITECTURE="$(uname -m)" + uname_m=$(uname -m) + ARCHITECTURE=$(tolower "${FOUNDRYUP_ARCH:-$uname_m}") if [ "${ARCHITECTURE}" = "x86_64" ]; then # Redirect stderr to /dev/null to avoid printing errors if non Rosetta. if [ "$(sysctl -n sysctl.proc_translated 2>/dev/null)" = "1" ]; then @@ -135,15 +151,22 @@ main() { BIN_ARCHIVE_URL="${RELEASE_URL}foundry_${FOUNDRYUP_VERSION}_${PLATFORM}_${ARCHITECTURE}.$EXT" MAN_TARBALL_URL="${RELEASE_URL}foundry_man_${FOUNDRYUP_VERSION}.tar.gz" + ensure mkdir -p "$FOUNDRY_VERSIONS_DIR" # Download and extract the binaries archive - say "downloading latest forge, cast, anvil, and chisel" + say "downloading forge, cast, anvil, and chisel for $FOUNDRYUP_TAG version" if [ "$PLATFORM" = "win32" ]; then tmp="$(mktemp -d 2>/dev/null || echo ".")/foundry.zip" ensure download "$BIN_ARCHIVE_URL" "$tmp" - ensure unzip "$tmp" -d "$FOUNDRY_BIN_DIR" + ensure unzip "$tmp" -d "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" rm -f "$tmp" else - ensure download "$BIN_ARCHIVE_URL" | ensure tar -xzC "$FOUNDRY_BIN_DIR" + tmp="$(mktemp -d 2>/dev/null || echo ".")/foundry.tar.gz" + ensure download "$BIN_ARCHIVE_URL" "$tmp" + # Make sure it's a valid tar archive. + ensure tar tf $tmp 1> /dev/null + ensure mkdir -p "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" + ensure tar -C "$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_TAG" -xvf $tmp + rm -f "$tmp" fi # Optionally download the manuals @@ -155,26 +178,9 @@ main() { say 'skipping manpage download: missing "tar"' fi - for bin in "${BINS[@]}"; do - bin_path="$FOUNDRY_BIN_DIR/$bin" - - # Print installed msg - say "installed - $(ensure "$bin_path" --version)" - - # Check if the default path of the binary is not in FOUNDRY_BIN_DIR - which_path="$(which "$bin")" - if [ "$which_path" != "$bin_path" ]; then - warn "" - cat 1>&2 < OPTIONS: -h, --help Print help information - -v, --version Install a specific version - -b, --branch Install a specific branch - -P, --pr Install a specific Pull Request - -C, --commit Install a specific commit - -r, --repo Install from a remote GitHub repo (uses default branch if no other options are set) - -p, --path Install a local repository + -v, --version Print the version of foundryup + -U, --update Update foundryup to the latest version + -i, --install Install a specific version from built binaries + -l, --list List versions installed from built binaries + -u, --use Use a specific installed version from built binaries + -b, --branch Build and install a specific branch + -P, --pr Build and install a specific Pull Request + -C, --commit Build and install a specific commit + -r, --repo Build and install from a remote GitHub repo (uses default branch if no other options are set) + -p, --path Build and install a local repository + -j, --jobs Number of CPUs to use for building Foundry (default: all CPUs) + --arch Install a specific architecture (supports amd64 and arm64) + --platform Install a specific platform (supports win32, linux, and darwin) EOF } +version() { + say "$FOUNDRYUP_INSTALLER_VERSION" + exit 0 +} + +update() { + say "updating foundryup..." + + # Download to a temporary file first + tmp_file="$(mktemp)" + ensure download "$FOUNDRY_BIN_URL" "$tmp_file" + + # Replace the current foundryup with the downloaded file + ensure mv "$tmp_file" "$FOUNDRY_BIN_PATH" + ensure chmod +x "$FOUNDRY_BIN_PATH" + + say "successfully updated foundryup" + exit 0 +} + +list() { + if [ -d "$FOUNDRY_VERSIONS_DIR" ]; then + for VERSION in $FOUNDRY_VERSIONS_DIR/*; do + say "${VERSION##*/}" + for bin in "${BINS[@]}"; do + bin_path="$VERSION/$bin" + say "- $(ensure "$bin_path" -V)" + done + printf "\n" + done + else + for bin in "${BINS[@]}"; do + bin_path="$FOUNDRY_BIN_DIR/$bin" + say "- $(ensure "$bin_path" -V)" + done + fi + exit 0 +} + +use() { + [ -z "$FOUNDRYUP_VERSION" ] && err "no version provided" + FOUNDRY_VERSION_DIR="$FOUNDRY_VERSIONS_DIR/$FOUNDRYUP_VERSION" + if [ -d "$FOUNDRY_VERSION_DIR" ]; then + + check_bins_in_use + + for bin in "${BINS[@]}"; do + bin_path="$FOUNDRY_BIN_DIR/$bin" + cp "$FOUNDRY_VERSION_DIR/$bin" "$bin_path" + # Print usage msg + say "use - $(ensure "$bin_path" -V)" + + # Check if the default path of the binary is not in FOUNDRY_BIN_DIR + which_path="$(command -v "$bin" || true)" + if [ -n "$which_path" ] && [ "$which_path" != "$bin_path" ]; then + warn "" + cat 1>&2 </dev/null } +check_bins_in_use() { + if check_cmd pgrep; then + for bin in "${BINS[@]}"; do + if pgrep -x "$bin" >/dev/null; then + err "Error: '$bin' is currently running. Please stop the process and try again." + fi + done + else + warn "Make sure no foundry process is running during the install process!" + fi +} + # Run a command that should never fail. If the command fails execution -# will immediately terminate with an error showing the failing -# command. +# will immediately terminate with an error showing the failing command. ensure() { if ! "$@"; then err "command failed: $*"; fi } # Downloads $1 into $2 or stdout download() { - if [ "$2" ]; then + if [ -n "$2" ]; then # output into $2 if check_cmd curl; then curl -#o "$2" -L "$1" @@ -289,23 +412,23 @@ download() { fi } -# Banner Function for Foundry +# Banner Function for Foundry banner() { printf ' .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx - + ╔═╗ ╔═╗ ╦ ╦ ╔╗╔ ╔╦╗ ╦═╗ ╦ ╦ Portable and modular toolkit - ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development + ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development ╚ ╚═╝ ╚═╝ ╝╚╝ ═╩╝ ╩╚═ ╩ written in Rust. .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx -Repo : https://github.com/foundry-rs/ -Book : https://book.getfoundry.sh/ -Chat : https://t.me/foundry_rs/ +Repo : https://github.com/foundry-rs/foundry +Book : https://book.getfoundry.sh/ +Chat : https://t.me/foundry_rs/ Support : https://t.me/foundry_support/ -Contribute : https://github.com/orgs/foundry-rs/projects/2/ +Contribute : https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx @@ -313,4 +436,4 @@ Contribute : https://github.com/orgs/foundry-rs/projects/2/ } -main "$@" || exit 1 +main "$@" diff --git a/foundryup/install b/foundryup/install index 0c6f7b1f15bd8..22870f939a930 100755 --- a/foundryup/install +++ b/foundryup/install @@ -1,29 +1,28 @@ #!/usr/bin/env bash -set -e +set -eo pipefail -echo Installing foundryup... +echo "Installing foundryup..." -BASE_DIR=${XDG_CONFIG_HOME:-$HOME} -FOUNDRY_DIR=${FOUNDRY_DIR-"$BASE_DIR/.foundry"} +BASE_DIR="${XDG_CONFIG_HOME:-$HOME}" +FOUNDRY_DIR="${FOUNDRY_DIR:-"$BASE_DIR/.foundry"}" FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin" FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1" BIN_URL="https://raw.githubusercontent.com/foundry-rs/foundry/master/foundryup/foundryup" BIN_PATH="$FOUNDRY_BIN_DIR/foundryup" - # Create the .foundry bin directory and foundryup binary if it doesn't exist. -mkdir -p $FOUNDRY_BIN_DIR -curl -# -L $BIN_URL -o $BIN_PATH -chmod +x $BIN_PATH +mkdir -p "$FOUNDRY_BIN_DIR" +curl -sSf -L "$BIN_URL" -o "$BIN_PATH" +chmod +x "$BIN_PATH" # Create the man directory for future man files if it doesn't exist. -mkdir -p $FOUNDRY_MAN_DIR +mkdir -p "$FOUNDRY_MAN_DIR" # Store the correct profile file (i.e. .profile for bash or .zshenv for ZSH). case $SHELL in */zsh) - PROFILE=${ZDOTDIR-"$HOME"}/.zshenv + PROFILE="${ZDOTDIR-"$HOME"}/.zshenv" PREF_SHELL=zsh ;; */bash) @@ -46,13 +45,20 @@ esac # Only add foundryup if it isn't already in PATH. if [[ ":$PATH:" != *":${FOUNDRY_BIN_DIR}:"* ]]; then # Add the foundryup directory to the path and ensure the old PATH variables remain. - echo >> $PROFILE && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> $PROFILE + # If the shell is fish, echo fish_add_path instead of export. + if [[ "$PREF_SHELL" == "fish" ]]; then + echo >> "$PROFILE" && echo "fish_add_path -a $FOUNDRY_BIN_DIR" >> "$PROFILE" + else + echo >> "$PROFILE" && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> "$PROFILE" + fi fi # Warn MacOS users that they may need to manually install libusb via Homebrew: -if [[ "$OSTYPE" =~ ^darwin ]] && [[ ! -f /usr/local/opt/libusb/lib/libusb-1.0.0.dylib && ! -f /opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib ]]; then +if [[ "$OSTYPE" =~ ^darwin ]] && [[ ! -f /usr/local/opt/libusb/lib/libusb-1.0.0.dylib ]] && [[ ! -f /opt/homebrew/opt/libusb/lib/libusb-1.0.0.dylib ]]; then echo && echo "warning: libusb not found. You may need to install it manually on MacOS via Homebrew (brew install libusb)." fi -echo && echo "Detected your preferred shell is ${PREF_SHELL} and added foundryup to PATH. Run 'source ${PROFILE}' or start a new terminal session to use foundryup." +echo +echo "Detected your preferred shell is $PREF_SHELL and added foundryup to PATH." +echo "Run 'source $PROFILE' or start a new terminal session to use foundryup." echo "Then, simply run 'foundryup' to install Foundry." diff --git a/rustfmt.toml b/rustfmt.toml index 3b4a88a3cd6f2..68c3c93033d4f 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -7,6 +7,5 @@ binop_separator = "Back" trailing_comma = "Vertical" trailing_semicolon = false use_field_init_shorthand = true - -# Ignore automatically-generated code. -ignore = ["crates/abi/src/bindings"] +format_code_in_doc_comments = true +doc_comment_code_block_width = 100 diff --git a/testdata/.gitignore b/testdata/.gitignore index 1da64773e66dc..b84c736371bab 100644 --- a/testdata/.gitignore +++ b/testdata/.gitignore @@ -1,3 +1,4 @@ # Compiler files cache/ -out/ \ No newline at end of file +out/ +.lock diff --git a/testdata/README.md b/testdata/README.md index 566da52d136f1..5af60d647e17a 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -4,9 +4,9 @@ A test suite that tests different aspects of Foundry. ### Structure -- [`core`](core): Tests for fundamental aspects of Foundry -- [`logs`](logs): Tests for Foundry logging capabilities -- [`cheats`](cheats): Tests for Foundry cheatcodes -- [`fuzz`](fuzz): Tests for the Foundry fuzzer -- [`trace`](trace): Tests for the Foundry tracer -- [`fork`](fork): Tests for Foundry forking capabilities +- [`core`](default/core): Tests for fundamental aspects of Foundry +- [`logs`](default/logs): Tests for Foundry logging capabilities +- [`cheats`](default/cheats): Tests for Foundry cheatcodes +- [`fuzz`](default/fuzz): Tests for the Foundry fuzzer +- [`trace`](default/trace): Tests for the Foundry tracer +- [`fork`](default/fork): Tests for Foundry forking capabilities diff --git a/testdata/cheats/Derive.t.sol b/testdata/cheats/Derive.t.sol deleted file mode 100644 index 868ed790bc722..0000000000000 --- a/testdata/cheats/Derive.t.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "./Vm.sol"; - -contract DeriveTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testDerive() public { - string memory mnemonic = "test test test test test test test test test test test junk"; - - uint256 privateKey = vm.deriveKey(mnemonic, 0); - assertEq(privateKey, 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); - - uint256 privateKeyDerivationPathChanged = vm.deriveKey(mnemonic, "m/44'/60'/0'/1/", 0); - assertEq(privateKeyDerivationPathChanged, 0x6abb89895f93b02c1b9470db0fa675297f6cca832a5fc66d5dfd7661a42b37be); - - uint256 privateKeyFile = vm.deriveKey("fixtures/Derive/mnemonic_english.txt", 2); - assertEq(privateKeyFile, 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a); - } - - uint256 constant numLanguages = 10; - - function testDeriveLang() public { - string[numLanguages] memory mnemonics = [ - unicode"谐 谐 谐 谐 谐 谐 谐 谐 谐 谐 谐 宗", - unicode"諧 諧 諧 諧 諧 諧 諧 諧 諧 諧 諧 宗", - "uzenina uzenina uzenina uzenina uzenina uzenina uzenina uzenina uzenina uzenina uzenina nevina", - "test test test test test test test test test test test junk", - unicode"sonde sonde sonde sonde sonde sonde sonde sonde sonde sonde sonde hématome", - "surgelato surgelato surgelato surgelato surgelato surgelato surgelato surgelato surgelato surgelato surgelato mansarda", - unicode"ほんけ ほんけ ほんけ ほんけ ほんけ ほんけ ほんけ ほんけ ほんけ ほんけ ほんけ ぜんご", - unicode"큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 큰어머니 시스템", - "sobra sobra sobra sobra sobra sobra sobra sobra sobra sobra sobra guarani", - "tacto tacto tacto tacto tacto tacto tacto tacto tacto tacto tacto lacra" - ]; - string[numLanguages] memory languages = [ - "chinese_simplified", - "chinese_traditional", - "czech", - "english", - "french", - "italian", - "japanese", - "korean", - "portuguese", - "spanish" - ]; - uint256[numLanguages] memory privateKeys = [ - 0x533bbfc4a21d5cc6ca8ac3a4b6b1dc76e15804e078b0d53d72ba698ca0733a5d, - 0x3ed7268b64e326a75fd4e894a979eed93cc1480f1badebc869542d8508168fe8, - 0x56ab29e6a8d77caeb67976faf95980ee5bbd672a6ae98cac507e8a0cb252b47c, - 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80, - 0xcdb159305d67bfba6096f47090c34895a75b8f9cc96b6d7e01b99f271e039586, - 0x9976dba80160dd3b89735ea2af8e2b474011972fc92883f4100dd3955f8d921d, - 0xff0bda7ec337713c62b948f307d8197f1a7f95db93b739b0c354654395005b7f, - 0x0ca388477381e73413bbc6188fdac45c583d0215cc43eebec49dc90e4903591a, - 0x857c78c7e0866fcd734077d92892ba215a7b5a9afb6ff437be271686fb0ef9bd, - 0x7eb53bee6299530662f3c91a9b1754cf80aa2d9d89b254ae241825f9414a4a0a - ]; - uint256[numLanguages] memory privateKeysDerivationPathChanged = [ - 0xce09fd1ec0fa74f801f85faa6eeb20019c7378180fed673676ddfb48f1360fc8, - 0x504425bc503d1a6842acbda23e76e852d568a947a7f3ee6cae3bebb677baf1ee, - 0x2e5ff4571add07ecb1f3aac0d394a5564582f9c57c01c12229121e5dff2582f3, - 0x6abb89895f93b02c1b9470db0fa675297f6cca832a5fc66d5dfd7661a42b37be, - 0xe8b159aa146238eaab2b44614aaec7e5f1e0cffa2c3526c198cf101a833e222f, - 0xe2099cc4ccacb8cd902213c5056e54460dfde550a0cf036bd3070e5b176c2f42, - 0xef82b00bb18b2efb9ac1af3530afcba8c1b4c2b16041993d898cfa5d04b81e09, - 0xa851aca713f11e2b971c1a34c448fb112974321d13f4ecf1db19554d72a9c6c7, - 0xcaec3e6839b0eeebcbea3861951721846d4d38bac91b78f1a5c8d2e1269f61fe, - 0x41211f5e0f7373cbd8728cbf2431d4ea0732136475e885328f36e5fd3cee2a43 - ]; - uint256[numLanguages] memory privateKeysFile = [ - 0xa540f6a3a6df6d39dc8b5b2290c9d08cc1e2a2a240023933b10940ec9320a7d9, - 0xacfa4014ea48cb4849422952ac083f49e95409d4d7ac6131ec1481c6e91ffbb0, - 0x3f498bf39f2c211208edac088674526d2edd9acf02464fb0e559bb9352b90ccd, - 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a, - 0x18031c4ccc75784e1b9f772a9d158efe3ca83a525ca2b4bf29f09e09d19ce195, - 0x4da226c5aacbba261bc160f9e43e7147c0b9bfa581f7160d5f2bb9d2e34358f9, - 0x59d7d5fb59d74775cae83c162c49da9af6ded75664200f675168c9322403b291, - 0xd515ca4969e31a59a4a8f23a2fcdad0c7702137ae7cb59fdfbe863c617ca4794, - 0x81b81ee315311874aab9a6560e775e74af5e4003832746df4bf044a9c3987c2f, - 0x2ba37b948b89117cde7ebb7e22bb3954249fa0575f05bfbf47cd3ec20c6f7ebd - ]; - - for (uint256 i = 0; i < numLanguages; ++i) { - string memory language = languages[i]; - string memory mnemonic = mnemonics[i]; - - uint256 privateKey = vm.deriveKey(mnemonic, 0, language); - assertEq(privateKey, privateKeys[i]); - - uint256 privateKeyDerivationPathChanged = vm.deriveKey(mnemonic, "m/44'/60'/0'/1/", 0, language); - assertEq(privateKeyDerivationPathChanged, privateKeysDerivationPathChanged[i]); - - string memory prefix = "fixtures/Derive/mnemonic_"; - string memory postfix = ".txt"; - string memory mnemonicPath = string(abi.encodePacked(prefix, language, postfix)); - uint256 privateKeyFile = vm.deriveKey(mnemonicPath, 2, language); - assertEq(privateKeyFile, privateKeysFile[i]); - } - } -} diff --git a/testdata/cheats/ExpectRevert.t.sol b/testdata/cheats/ExpectRevert.t.sol deleted file mode 100644 index dd68a5b38f484..0000000000000 --- a/testdata/cheats/ExpectRevert.t.sol +++ /dev/null @@ -1,188 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "./Vm.sol"; - -contract Reverter { - error CustomError(); - - function revertWithMessage(string memory message) public pure { - require(false, message); - } - - function doNotRevert() public pure {} - - function panic() public pure returns (uint256) { - return uint256(100) - uint256(101); - } - - function revertWithCustomError() public pure { - revert CustomError(); - } - - function nestedRevert(Reverter inner, string memory message) public pure { - inner.revertWithMessage(message); - } - - function callThenRevert(Dummy dummy, string memory message) public pure { - dummy.callMe(); - require(false, message); - } - - function revertWithoutReason() public pure { - revert(); - } -} - -contract ConstructorReverter { - constructor(string memory message) { - require(false, message); - } -} - -/// Used to ensure that the dummy data from `vm.expectRevert` -/// is large enough to decode big structs. -/// -/// The struct is based on issue #2454 -struct LargeDummyStruct { - address a; - uint256 b; - bool c; - address d; - address e; - string f; - address[8] g; - address h; - uint256 i; -} - -contract Dummy { - function callMe() public pure returns (string memory) { - return "thanks for calling"; - } - - function largeReturnType() public pure returns (LargeDummyStruct memory) { - require(false, "reverted with large return type"); - } -} - -contract ExpectRevertTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function shouldRevert() internal { - revert(); - } - - function testExpectRevertString() public { - Reverter reverter = new Reverter(); - vm.expectRevert("revert"); - reverter.revertWithMessage("revert"); - } - - function testFailRevertNotOnImmediateNextCall() public { - Reverter reverter = new Reverter(); - // expectRevert should only work for the next call. However, - // we do not inmediately revert, so, - // we fail. - vm.expectRevert("revert"); - reverter.doNotRevert(); - reverter.revertWithMessage("revert"); - } - - function testFailDanglingOnInternalCall() public { - vm.expectRevert(); - shouldRevert(); - } - - function testExpectRevertConstructor() public { - vm.expectRevert("constructor revert"); - new ConstructorReverter("constructor revert"); - } - - function testExpectRevertBuiltin() public { - Reverter reverter = new Reverter(); - vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x11)); - reverter.panic(); - } - - function testExpectRevertCustomError() public { - Reverter reverter = new Reverter(); - vm.expectRevert(abi.encodePacked(Reverter.CustomError.selector)); - reverter.revertWithCustomError(); - } - - function testExpectRevertNested() public { - Reverter reverter = new Reverter(); - Reverter inner = new Reverter(); - vm.expectRevert("nested revert"); - reverter.nestedRevert(inner, "nested revert"); - } - - function testExpectRevertCallsThenReverts() public { - Reverter reverter = new Reverter(); - Dummy dummy = new Dummy(); - vm.expectRevert("called a function and then reverted"); - reverter.callThenRevert(dummy, "called a function and then reverted"); - } - - function testDummyReturnDataForBigType() public { - Dummy dummy = new Dummy(); - vm.expectRevert("reverted with large return type"); - dummy.largeReturnType(); - } - - function testFailExpectRevertErrorDoesNotMatch() public { - Reverter reverter = new Reverter(); - vm.expectRevert("should revert with this message"); - reverter.revertWithMessage("but reverts with this message"); - } - - function testFailExpectRevertDidNotRevert() public { - Reverter reverter = new Reverter(); - vm.expectRevert("does not revert, but we think it should"); - reverter.doNotRevert(); - } - - function testExpectRevertNoReason() public { - Reverter reverter = new Reverter(); - vm.expectRevert(bytes("")); - reverter.revertWithoutReason(); - } - - function testExpectRevertAnyRevert() public { - vm.expectRevert(); - new ConstructorReverter("hello this is a revert message"); - - Reverter reverter = new Reverter(); - vm.expectRevert(); - reverter.revertWithMessage("this is also a revert message"); - - vm.expectRevert(); - reverter.panic(); - - vm.expectRevert(); - reverter.revertWithCustomError(); - - Reverter reverter2 = new Reverter(); - vm.expectRevert(); - reverter.nestedRevert(reverter2, "this too is a revert message"); - - Dummy dummy = new Dummy(); - vm.expectRevert(); - reverter.callThenRevert(dummy, "revert message 4 i ran out of synonims for also"); - - vm.expectRevert(); - reverter.revertWithoutReason(); - } - - function testFailExpectRevertAnyRevertDidNotRevert() public { - Reverter reverter = new Reverter(); - vm.expectRevert(); - reverter.doNotRevert(); - } - - function testFailExpectRevertDangling() public { - vm.expectRevert("dangling"); - } -} diff --git a/testdata/cheats/Fs.t.sol b/testdata/cheats/Fs.t.sol deleted file mode 100644 index 81aec047f7080..0000000000000 --- a/testdata/cheats/Fs.t.sol +++ /dev/null @@ -1,310 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "./Vm.sol"; - -contract FsProxy is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function readFile(string calldata path) external returns (string memory) { - return vm.readFile(path); - } - - function readDir(string calldata path) external returns (Vm.DirEntry[] memory) { - return vm.readDir(path); - } - - function readFileBinary(string calldata path) external returns (bytes memory) { - return vm.readFileBinary(path); - } - - function readLine(string calldata path) external returns (string memory) { - return vm.readLine(path); - } - - function writeLine(string calldata path, string calldata data) external { - return vm.writeLine(path, data); - } - - function writeFile(string calldata path, string calldata data) external { - return vm.writeLine(path, data); - } - - function writeFileBinary(string calldata path, bytes calldata data) external { - return vm.writeFileBinary(path, data); - } - - function removeFile(string calldata path) external { - return vm.removeFile(path); - } - - function fsMetadata(string calldata path) external returns (Vm.FsMetadata memory) { - return vm.fsMetadata(path); - } - - function createDir(string calldata path) external { - return vm.createDir(path, false); - } - - function createDir(string calldata path, bool recursive) external { - return vm.createDir(path, recursive); - } -} - -contract FsTest is DSTest { - FsProxy public fsProxy; - Vm constant vm = Vm(HEVM_ADDRESS); - bytes constant FOUNDRY_TOML_ACCESS_ERR = "Access to foundry.toml is not allowed."; - bytes constant FOUNDRY_READ_ERR = "The path \"/etc/hosts\" is not allowed to be accessed for read operations."; - bytes constant FOUNDRY_READ_DIR_ERR = "The path \"/etc\" is not allowed to be accessed for read operations."; - bytes constant FOUNDRY_WRITE_ERR = "The path \"/etc/hosts\" is not allowed to be accessed for write operations."; - - function assertEntry(Vm.DirEntry memory entry, uint64 depth, bool dir) private { - assertEq(entry.errorMessage, ""); - assertEq(entry.depth, depth); - assertEq(entry.isDir, dir); - assertEq(entry.isSymlink, false); - } - - function testReadFile() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/read.txt"; - - assertEq(vm.readFile(path), "hello readable world\nthis is the second line!"); - - vm.expectRevert(FOUNDRY_READ_ERR); - fsProxy.readFile("/etc/hosts"); - - vm.expectRevert(FOUNDRY_READ_ERR); - fsProxy.readFileBinary("/etc/hosts"); - } - - function testReadLine() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/read.txt"; - - assertEq(vm.readLine(path), "hello readable world"); - assertEq(vm.readLine(path), "this is the second line!"); - assertEq(vm.readLine(path), ""); - - vm.expectRevert(FOUNDRY_READ_ERR); - fsProxy.readLine("/etc/hosts"); - } - - function testWriteFile() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/write_file.txt"; - string memory data = "hello writable world"; - vm.writeFile(path, data); - - assertEq(vm.readFile(path), data); - - vm.removeFile(path); - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeFile("/etc/hosts", "malicious stuff"); - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeFileBinary("/etc/hosts", "malicious stuff"); - } - - function testCopyFile() public { - string memory from = "fixtures/File/read.txt"; - string memory to = "fixtures/File/copy.txt"; - uint64 copied = vm.copyFile(from, to); - assertEq(vm.fsMetadata(to).length, uint256(copied)); - assertEq(vm.readFile(from), vm.readFile(to)); - vm.removeFile(to); - } - - function testWriteLine() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/write_line.txt"; - - string memory line1 = "first line"; - vm.writeLine(path, line1); - - string memory line2 = "second line"; - vm.writeLine(path, line2); - - assertEq(vm.readFile(path), string.concat(line1, "\n", line2, "\n")); - - vm.removeFile(path); - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeLine("/etc/hosts", "malicious stuff"); - } - - function testCloseFile() public { - string memory path = "fixtures/File/read.txt"; - - assertEq(vm.readLine(path), "hello readable world"); - vm.closeFile(path); - assertEq(vm.readLine(path), "hello readable world"); - } - - function testRemoveFile() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/remove_file.txt"; - string memory data = "hello writable world"; - - vm.writeFile(path, data); - assertEq(vm.readLine(path), data); - - vm.removeFile(path); - vm.writeLine(path, data); - assertEq(vm.readLine(path), data); - - vm.removeFile(path); - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.removeFile("/etc/hosts"); - } - - function testWriteLineFoundrytoml() public { - fsProxy = new FsProxy(); - - string memory root = vm.projectRoot(); - string memory foundryToml = string.concat(root, "/", "foundry.toml"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeLine(foundryToml, "\nffi = true\n"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeLine("foundry.toml", "\nffi = true\n"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeLine("./foundry.toml", "\nffi = true\n"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeLine("./Foundry.toml", "\nffi = true\n"); - } - - function testWriteFoundrytoml() public { - fsProxy = new FsProxy(); - - string memory root = vm.projectRoot(); - string memory foundryToml = string.concat(root, "/", "foundry.toml"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeFile(foundryToml, "\nffi = true\n"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeFile("foundry.toml", "\nffi = true\n"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeFile("./foundry.toml", "\nffi = true\n"); - - vm.expectRevert(FOUNDRY_TOML_ACCESS_ERR); - fsProxy.writeFile("./Foundry.toml", "\nffi = true\n"); - } - - function testReadDir() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/Dir"; - - { - Vm.DirEntry[] memory entries = vm.readDir(path); - assertEq(entries.length, 2); - assertEntry(entries[0], 1, false); - assertEntry(entries[1], 1, true); - - Vm.DirEntry[] memory entries2 = vm.readDir(path, 1); - assertEq(entries2.length, 2); - assertEq(entries[0].path, entries2[0].path); - assertEq(entries[1].path, entries2[1].path); - - string memory contents = vm.readFile(entries[0].path); - assertEq(contents, unicode"Wow! 😀\n"); - } - - { - Vm.DirEntry[] memory entries = vm.readDir(path, 2); - assertEq(entries.length, 4); - assertEntry(entries[2], 2, false); - assertEntry(entries[3], 2, true); - } - - { - Vm.DirEntry[] memory entries = vm.readDir(path, 3); - assertEq(entries.length, 5); - assertEntry(entries[4], 3, false); - } - - vm.expectRevert(FOUNDRY_READ_DIR_ERR); - fsProxy.readDir("/etc"); - } - - function testCreateRemoveDir() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/Dir/remove_dir"; - string memory child = string.concat(path, "/child"); - - vm.createDir(path, false); - assertEq(vm.fsMetadata(path).isDir, true); - - vm.removeDir(path, false); - vm.expectRevert(); - fsProxy.fsMetadata(path); - - // reverts because not recursive - vm.expectRevert(); - fsProxy.createDir(child, false); - - vm.createDir(child, true); - assertEq(vm.fsMetadata(child).isDir, true); - - // deleted both, recursively - vm.removeDir(path, true); - vm.expectRevert(); - fsProxy.fsMetadata(path); - vm.expectRevert(); - fsProxy.fsMetadata(child); - } - - function testFsMetadata() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File"; - Vm.FsMetadata memory metadata = vm.fsMetadata(path); - assertEq(metadata.isDir, true); - assertEq(metadata.isSymlink, false); - assertEq(metadata.readOnly, false); - assertGt(metadata.length, 0); - // These fields aren't available on all platforms, default to zero - // assertGt(metadata.modified, 0); - // assertGt(metadata.accessed, 0); - // assertGt(metadata.created, 0); - - path = "fixtures/File/read.txt"; - metadata = vm.fsMetadata(path); - assertEq(metadata.isDir, false); - - path = "fixtures/File/symlink"; - metadata = vm.fsMetadata(path); - assertEq(metadata.isSymlink, false); - - vm.expectRevert(); - fsProxy.fsMetadata("../not-found"); - - vm.expectRevert(FOUNDRY_READ_ERR); - fsProxy.fsMetadata("/etc/hosts"); - } - - // not testing file cheatcodes per se - function testCheatCodeErrorPrefix() public { - try vm.readFile("/etc/hosts") { - emit log("Error: reading /etc/hosts should revert"); - fail(); - } catch (bytes memory err) { - assertEq(err, abi.encodeWithSignature("CheatCodeError", FOUNDRY_READ_ERR)); - } - } -} diff --git a/testdata/cheats/RpcUrls.t.sol b/testdata/cheats/RpcUrls.t.sol deleted file mode 100644 index 497092384eef1..0000000000000 --- a/testdata/cheats/RpcUrls.t.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "./Vm.sol"; - -contract RpcUrlTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - // returns the correct url - function testCanGetRpcUrl() public { - string memory url = vm.rpcUrl("rpcAlias"); // note: this alias is pre-configured in the test runner - assertEq(url, "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf"); - } - - // returns an error if env alias does not exist - function testRevertsOnMissingEnv() public { - vm.expectRevert("invalid rpc url rpcUrlEnv"); - string memory url = this.rpcUrl("rpcUrlEnv"); - } - - // can set env and return correct url - function testCanSetAndGetURLAndAllUrls() public { - // this will fail because alias is not set - vm.expectRevert( - "Failed to resolve env var `RPC_ENV_ALIAS` in `${RPC_ENV_ALIAS}`: environment variable not found" - ); - string[2][] memory _urls = this.rpcUrls(); - - string memory url = vm.rpcUrl("rpcAlias"); - vm.setEnv("RPC_ENV_ALIAS", url); - string memory envUrl = vm.rpcUrl("rpcEnvAlias"); - assertEq(url, envUrl); - - string[2][] memory allUrls = vm.rpcUrls(); - assertEq(allUrls.length, 2); - - string[2] memory val = allUrls[0]; - assertEq(val[0], "rpcAlias"); - - string[2] memory env = allUrls[1]; - assertEq(env[0], "rpcEnvAlias"); - } - - function rpcUrl(string memory _alias) public returns (string memory) { - return vm.rpcUrl(_alias); - } - - function rpcUrls() public returns (string[2][] memory) { - return vm.rpcUrls(); - } -} diff --git a/testdata/cheats/Sign.t.sol b/testdata/cheats/Sign.t.sol deleted file mode 100644 index 986d9243f3688..0000000000000 --- a/testdata/cheats/Sign.t.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "./Vm.sol"; - -contract SignTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function testSignDigest(uint248 pk, bytes32 digest) public { - vm.assume(pk != 0); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); - address expected = vm.addr(pk); - address actual = ecrecover(digest, v, r, s); - - assertEq(actual, expected, "digest signer did not match"); - } - - function testSignMessage(uint248 pk, bytes memory message) public { - testSignDigest(pk, keccak256(message)); - } -} diff --git a/testdata/cheats/Snapshots.t.sol b/testdata/cheats/Snapshots.t.sol deleted file mode 100644 index 3ce7246104f56..0000000000000 --- a/testdata/cheats/Snapshots.t.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "./Vm.sol"; - -struct Storage { - uint256 slot0; - uint256 slot1; -} - -contract SnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - Storage store; - - function setUp() public { - store.slot0 = 10; - store.slot1 = 20; - } - - function testSnapshot() public { - uint256 snapshot = vm.snapshot(); - store.slot0 = 300; - store.slot1 = 400; - - assertEq(store.slot0, 300); - assertEq(store.slot1, 400); - - vm.revertTo(snapshot); - assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); - assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); - } - - // tests that snapshots can also revert changes to `block` - function testBlockValues() public { - uint256 num = block.number; - uint256 time = block.timestamp; - uint256 prevrandao = block.prevrandao; - - uint256 snapshot = vm.snapshot(); - - vm.warp(1337); - assertEq(block.timestamp, 1337); - - vm.roll(99); - assertEq(block.number, 99); - - vm.prevrandao(bytes32(uint256(123))); - assertEq(block.prevrandao, 123); - - assert(vm.revertTo(snapshot)); - - assertEq(block.number, num, "snapshot revert for block.number unsuccessful"); - assertEq(block.timestamp, time, "snapshot revert for block.timestamp unsuccessful"); - assertEq(block.prevrandao, prevrandao, "snapshot revert for block.prevrandao unsuccessful"); - } -} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 2b35df6db0c8c..cc873d3e1be21 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -1,642 +1,525 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; +// Automatically generated from `foundry-cheatcodes` Vm definitions. Do not modify manually. +// This interface is just for internal testing purposes. Use `forge-std` instead. -interface Vm { - // Possible caller modes for readCallers() - enum CallerMode { - None, - Broadcast, - RecurrentBroadcast, - Prank, - RecurrentPrank - } - - // This allows us to getRecordedLogs() - struct Log { - bytes32[] topics; - bytes data; - address emitter; - } - - // Used in getRpcStructs - struct Rpc { - string name; - string url; - } - - // Used in readDir - struct DirEntry { - string errorMessage; - string path; - uint64 depth; - bool isDir; - bool isSymlink; - } - - // Used in fsMetadata - struct FsMetadata { - bool isDir; - bool isSymlink; - uint256 length; - bool readOnly; - uint256 modified; - uint256 accessed; - uint256 created; - } - - // Returned by 'createWallet'. Used with 'sign' and 'getNonce' - struct Wallet { - address addr; - uint256 publicKeyX; - uint256 publicKeyY; - uint256 privateKey; - } - - // Set block.timestamp (newTimestamp) - function warp(uint256) external; - - // Set block.difficulty (newDifficulty) - // No longer works from Paris onwards. - function difficulty(uint256) external; - - // Set block.prevrandao (newPrevrandao) - function prevrandao(bytes32) external; - - // Set block.height (newHeight) - function roll(uint256) external; - - // Set block.basefee (newBasefee) - function fee(uint256) external; - - // Set block.coinbase (who) - function coinbase(address) external; - - // Loads a storage slot from an address (who, slot) - function load(address, bytes32) external returns (bytes32); - - // Stores a value to an address' storage slot, (who, slot, value) - function store(address, bytes32, bytes32) external; - - // Signs data, (privateKey, digest) => (v, r, s) - function sign(uint256, bytes32) external returns (uint8, bytes32, bytes32); - - // Gets address for a given private key, (privateKey) => (address) - function addr(uint256) external returns (address); - - // Derive a private key from a provided English mnemonic string (or mnemonic file path) at the derivation path m/44'/60'/0'/0/{index} - function deriveKey(string calldata, uint32) external returns (uint256); - - // Derive a private key from a provided English mnemonic string (or mnemonic file path) at the derivation path {path}{index} - function deriveKey(string calldata, string calldata, uint32) external returns (uint256); - - // Derive a private key from a provided mnemonic string (or mnemonic file path) of specified language at the derivation path m/44'/60'/0'/0/{index} - function deriveKey(string calldata, uint32, string calldata) external returns (uint256); - - // Derive a private key from a provided mnemonic string (or mnemonic file path) of specified language at the derivation path {path}{index} - function deriveKey(string calldata, string calldata, uint32, string calldata) external returns (uint256); - - // Adds a private key to the local forge wallet and returns the address - function rememberKey(uint256) external returns (address); - - // Derives a private key from the name, labels the account with that name, and returns the wallet - function createWallet(string calldata) external returns (Wallet memory); - - // Generates a wallet from the private key and returns the wallet - function createWallet(uint256) external returns (Wallet memory); - - // Generates a wallet from the private key, labels the account with that name, and returns the wallet - function createWallet(uint256, string calldata) external returns (Wallet memory); - - // Signs data, (Wallet, digest) => (v, r, s) - function sign(Wallet calldata, bytes32) external returns (uint8, bytes32, bytes32); - - // Get nonce for a Wallet - function getNonce(Wallet calldata) external returns (uint64); - - // Performs a foreign function call via terminal, (stringInputs) => (result) - function ffi(string[] calldata) external returns (bytes memory); - - // Set environment variables, (name, value) - function setEnv(string calldata, string calldata) external; - - // Read environment variables, (name) => (value) - function envBool(string calldata) external returns (bool); - - function envUint(string calldata) external returns (uint256); - - function envInt(string calldata) external returns (int256); - - function envAddress(string calldata) external returns (address); - - function envBytes32(string calldata) external returns (bytes32); - - function envString(string calldata) external returns (string memory); - - function envBytes(string calldata) external returns (bytes memory); - - // Read environment variables as arrays, (name, delim) => (value[]) - function envBool(string calldata, string calldata) external returns (bool[] memory); - - function envUint(string calldata, string calldata) external returns (uint256[] memory); - - function envInt(string calldata, string calldata) external returns (int256[] memory); - - function envAddress(string calldata, string calldata) external returns (address[] memory); - - function envBytes32(string calldata, string calldata) external returns (bytes32[] memory); - - function envString(string calldata, string calldata) external returns (string[] memory); - - function envBytes(string calldata, string calldata) external returns (bytes[] memory); - - // Read environment variables with default value, (name, value) => (value) - function envOr(string calldata, bool) external returns (bool); - - function envOr(string calldata, uint256) external returns (uint256); - - function envOr(string calldata, int256) external returns (int256); - - function envOr(string calldata, address) external returns (address); - - function envOr(string calldata, bytes32) external returns (bytes32); - - function envOr(string calldata, string calldata) external returns (string memory); - - function envOr(string calldata, bytes calldata) external returns (bytes memory); - - // Read environment variables as arrays with default value, (name, value[]) => (value[]) - function envOr(string calldata, string calldata, bool[] calldata) external returns (bool[] memory); - - function envOr(string calldata, string calldata, uint256[] calldata) external returns (uint256[] memory); - - function envOr(string calldata, string calldata, int256[] calldata) external returns (int256[] memory); - - function envOr(string calldata, string calldata, address[] calldata) external returns (address[] memory); - - function envOr(string calldata, string calldata, bytes32[] calldata) external returns (bytes32[] memory); - - function envOr(string calldata, string calldata, string[] calldata) external returns (string[] memory); - - function envOr(string calldata, string calldata, bytes[] calldata) external returns (bytes[] memory); +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.6.2 <0.9.0; +pragma experimental ABIEncoderV2; - // Sets the *next* call's msg.sender to be the input address - function prank(address) external; - - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called - function startPrank(address) external; - - // Sets the *next* call's msg.sender to be the input address, and the tx.origin to be the second input - function prank(address, address) external; - - // Sets all subsequent calls' msg.sender to be the input address until `stopPrank` is called, and the tx.origin to be the second input - function startPrank(address, address) external; - - // Resets subsequent calls' msg.sender to be `address(this)` - function stopPrank() external; - - // Reads the current msg.sender and tx.origin from state - function readCallers() external returns (CallerMode, address, address); - - // Sets an address' balance, (who, newBalance) - function deal(address, uint256) external; - - // Sets an address' code, (who, newCode) - function etch(address, bytes calldata) external; - - // Skips a test. - function skip(bool) external; - - // Sleeps for a given number of milliseconds. - function sleep(uint256) external; - - // Expects an error on next call +interface Vm { + enum CallerMode { None, Broadcast, RecurrentBroadcast, Prank, RecurrentPrank } + enum AccountAccessKind { Call, DelegateCall, CallCode, StaticCall, Create, SelfDestruct, Resume, Balance, Extcodesize, Extcodehash, Extcodecopy } + enum ForgeContext { TestGroup, Test, Coverage, Snapshot, ScriptGroup, ScriptDryRun, ScriptBroadcast, ScriptResume, Unknown } + enum BroadcastTxType { Call, Create, Create2 } + struct Log { bytes32[] topics; bytes data; address emitter; } + struct Rpc { string key; string url; } + struct EthGetLogs { address emitter; bytes32[] topics; bytes data; bytes32 blockHash; uint64 blockNumber; bytes32 transactionHash; uint64 transactionIndex; uint256 logIndex; bool removed; } + struct DirEntry { string errorMessage; string path; uint64 depth; bool isDir; bool isSymlink; } + struct FsMetadata { bool isDir; bool isSymlink; uint256 length; bool readOnly; uint256 modified; uint256 accessed; uint256 created; } + struct Wallet { address addr; uint256 publicKeyX; uint256 publicKeyY; uint256 privateKey; } + struct FfiResult { int32 exitCode; bytes stdout; bytes stderr; } + struct ChainInfo { uint256 forkId; uint256 chainId; } + struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } + struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } + struct Gas { uint64 gasLimit; uint64 gasTotalUsed; uint64 gasMemoryUsed; int64 gasRefunded; uint64 gasRemaining; } + struct DebugStep { uint256[] stack; bytes memoryInput; uint8 opcode; uint64 depth; bool isOutOfGas; address contractAddr; } + struct BroadcastTxSummary { bytes32 txHash; BroadcastTxType txType; address contractAddress; uint64 blockNumber; bool success; } + struct SignedDelegation { uint8 v; bytes32 r; bytes32 s; uint64 nonce; address implementation; } + struct PotentialRevert { address reverter; bool partialMatch; bytes revertData; } + function _expectCheatcodeRevert() external; + function _expectCheatcodeRevert(bytes4 revertData) external; + function _expectCheatcodeRevert(bytes calldata revertData) external; + function accesses(address target) external returns (bytes32[] memory readSlots, bytes32[] memory writeSlots); + function activeFork() external view returns (uint256 forkId); + function addr(uint256 privateKey) external pure returns (address keyAddr); + function allowCheatcodes(address account) external; + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals) external pure; + function assertApproxEqAbsDecimal(uint256 left, uint256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals) external pure; + function assertApproxEqAbsDecimal(int256 left, int256 right, uint256 maxDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta) external pure; + function assertApproxEqAbs(uint256 left, uint256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta) external pure; + function assertApproxEqAbs(int256 left, int256 right, uint256 maxDelta, string calldata error) external pure; + function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals) external pure; + function assertApproxEqRelDecimal(uint256 left, uint256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals) external pure; + function assertApproxEqRelDecimal(int256 left, int256 right, uint256 maxPercentDelta, uint256 decimals, string calldata error) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta) external pure; + function assertApproxEqRel(uint256 left, uint256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta) external pure; + function assertApproxEqRel(int256 left, int256 right, uint256 maxPercentDelta, string calldata error) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertEq(bool left, bool right) external pure; + function assertEq(bool left, bool right, string calldata error) external pure; + function assertEq(string calldata left, string calldata right) external pure; + function assertEq(string calldata left, string calldata right, string calldata error) external pure; + function assertEq(bytes calldata left, bytes calldata right) external pure; + function assertEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertEq(bool[] calldata left, bool[] calldata right) external pure; + function assertEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right) external pure; + function assertEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertEq(int256[] calldata left, int256[] calldata right) external pure; + function assertEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertEq(uint256 left, uint256 right) external pure; + function assertEq(address[] calldata left, address[] calldata right) external pure; + function assertEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + function assertEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertEq(string[] calldata left, string[] calldata right) external pure; + function assertEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right) external pure; + function assertEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertEq(uint256 left, uint256 right, string calldata error) external pure; + function assertEq(int256 left, int256 right) external pure; + function assertEq(int256 left, int256 right, string calldata error) external pure; + function assertEq(address left, address right) external pure; + function assertEq(address left, address right, string calldata error) external pure; + function assertEq(bytes32 left, bytes32 right) external pure; + function assertEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertFalse(bool condition) external pure; + function assertFalse(bool condition, string calldata error) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertGeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertGeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGe(uint256 left, uint256 right) external pure; + function assertGe(uint256 left, uint256 right, string calldata error) external pure; + function assertGe(int256 left, int256 right) external pure; + function assertGe(int256 left, int256 right, string calldata error) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertGtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertGtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertGt(uint256 left, uint256 right) external pure; + function assertGt(uint256 left, uint256 right, string calldata error) external pure; + function assertGt(int256 left, int256 right) external pure; + function assertGt(int256 left, int256 right, string calldata error) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertLeDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertLeDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLe(uint256 left, uint256 right) external pure; + function assertLe(uint256 left, uint256 right, string calldata error) external pure; + function assertLe(int256 left, int256 right) external pure; + function assertLe(int256 left, int256 right, string calldata error) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertLtDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertLtDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertLt(uint256 left, uint256 right) external pure; + function assertLt(uint256 left, uint256 right, string calldata error) external pure; + function assertLt(int256 left, int256 right) external pure; + function assertLt(int256 left, int256 right, string calldata error) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals) external pure; + function assertNotEqDecimal(uint256 left, uint256 right, uint256 decimals, string calldata error) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals) external pure; + function assertNotEqDecimal(int256 left, int256 right, uint256 decimals, string calldata error) external pure; + function assertNotEq(bool left, bool right) external pure; + function assertNotEq(bool left, bool right, string calldata error) external pure; + function assertNotEq(string calldata left, string calldata right) external pure; + function assertNotEq(string calldata left, string calldata right, string calldata error) external pure; + function assertNotEq(bytes calldata left, bytes calldata right) external pure; + function assertNotEq(bytes calldata left, bytes calldata right, string calldata error) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right) external pure; + function assertNotEq(bool[] calldata left, bool[] calldata right, string calldata error) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right) external pure; + function assertNotEq(uint256[] calldata left, uint256[] calldata right, string calldata error) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right) external pure; + function assertNotEq(int256[] calldata left, int256[] calldata right, string calldata error) external pure; + function assertNotEq(uint256 left, uint256 right) external pure; + function assertNotEq(address[] calldata left, address[] calldata right) external pure; + function assertNotEq(address[] calldata left, address[] calldata right, string calldata error) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right) external pure; + function assertNotEq(bytes32[] calldata left, bytes32[] calldata right, string calldata error) external pure; + function assertNotEq(string[] calldata left, string[] calldata right) external pure; + function assertNotEq(string[] calldata left, string[] calldata right, string calldata error) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right) external pure; + function assertNotEq(bytes[] calldata left, bytes[] calldata right, string calldata error) external pure; + function assertNotEq(uint256 left, uint256 right, string calldata error) external pure; + function assertNotEq(int256 left, int256 right) external pure; + function assertNotEq(int256 left, int256 right, string calldata error) external pure; + function assertNotEq(address left, address right) external pure; + function assertNotEq(address left, address right, string calldata error) external pure; + function assertNotEq(bytes32 left, bytes32 right) external pure; + function assertNotEq(bytes32 left, bytes32 right, string calldata error) external pure; + function assertTrue(bool condition) external pure; + function assertTrue(bool condition, string calldata error) external pure; + function assume(bool condition) external pure; + function assumeNoRevert() external pure; + function assumeNoRevert(PotentialRevert calldata potentialRevert) external pure; + function assumeNoRevert(PotentialRevert[] calldata potentialReverts) external pure; + function attachDelegation(SignedDelegation calldata signedDelegation) external; + function blobBaseFee(uint256 newBlobBaseFee) external; + function blobhashes(bytes32[] calldata hashes) external; + function breakpoint(string calldata char) external pure; + function breakpoint(string calldata char, bool value) external pure; + function broadcastRawTransaction(bytes calldata data) external; + function broadcast() external; + function broadcast(address signer) external; + function broadcast(uint256 privateKey) external; + function chainId(uint256 newChainId) external; + function clearMockedCalls() external; + function cloneAccount(address source, address target) external; + function closeFile(string calldata path) external; + function coinbase(address newCoinbase) external; + function computeCreate2Address(bytes32 salt, bytes32 initCodeHash, address deployer) external pure returns (address); + function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external pure returns (address); + function computeCreateAddress(address deployer, uint256 nonce) external pure returns (address); + function contains(string calldata subject, string calldata search) external returns (bool result); + function cool(address target) external; + function copyFile(string calldata from, string calldata to) external returns (uint64 copied); + function copyStorage(address from, address to) external; + function createDir(string calldata path, bool recursive) external; + function createFork(string calldata urlOrAlias) external returns (uint256 forkId); + function createFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); + function createFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); + function createSelectFork(string calldata urlOrAlias) external returns (uint256 forkId); + function createSelectFork(string calldata urlOrAlias, uint256 blockNumber) external returns (uint256 forkId); + function createSelectFork(string calldata urlOrAlias, bytes32 txHash) external returns (uint256 forkId); + function createWallet(string calldata walletLabel) external returns (Wallet memory wallet); + function createWallet(uint256 privateKey) external returns (Wallet memory wallet); + function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); + function deal(address account, uint256 newBalance) external; + function deleteSnapshot(uint256 snapshotId) external returns (bool success); + function deleteSnapshots() external; + function deleteStateSnapshot(uint256 snapshotId) external returns (bool success); + function deleteStateSnapshots() 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 pure returns (uint256 privateKey); + function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey); + function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index, string calldata language) external pure returns (uint256 privateKey); + function difficulty(uint256 newDifficulty) external; + function dumpState(string calldata pathToStateJson) external; + function ensNamehash(string calldata name) external pure returns (bytes32); + function envAddress(string calldata name) external view returns (address value); + function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); + function envBool(string calldata name) external view returns (bool value); + function envBool(string calldata name, string calldata delim) external view returns (bool[] memory value); + function envBytes32(string calldata name) external view returns (bytes32 value); + function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); + function envBytes(string calldata name) external view returns (bytes memory value); + function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); + function envExists(string calldata name) external view returns (bool result); + function envInt(string calldata name) external view returns (int256 value); + function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); + function envOr(string calldata name, bool defaultValue) external view returns (bool value); + function envOr(string calldata name, uint256 defaultValue) external view returns (uint256 value); + function envOr(string calldata name, string calldata delim, address[] calldata defaultValue) external view returns (address[] memory value); + function envOr(string calldata name, string calldata delim, bytes32[] calldata defaultValue) external view returns (bytes32[] memory value); + function envOr(string calldata name, string calldata delim, string[] calldata defaultValue) external view returns (string[] memory value); + function envOr(string calldata name, string calldata delim, bytes[] calldata defaultValue) external view returns (bytes[] memory value); + function envOr(string calldata name, int256 defaultValue) external view returns (int256 value); + function envOr(string calldata name, address defaultValue) external view returns (address value); + function envOr(string calldata name, bytes32 defaultValue) external view returns (bytes32 value); + function envOr(string calldata name, string calldata defaultValue) external view returns (string memory value); + function envOr(string calldata name, bytes calldata defaultValue) external view returns (bytes memory value); + function envOr(string calldata name, string calldata delim, bool[] calldata defaultValue) external view returns (bool[] memory value); + function envOr(string calldata name, string calldata delim, uint256[] calldata defaultValue) external view returns (uint256[] memory value); + function envOr(string calldata name, string calldata delim, int256[] calldata defaultValue) external view returns (int256[] memory value); + function envString(string calldata name) external view returns (string memory value); + function envString(string calldata name, string calldata delim) external view returns (string[] memory value); + function envUint(string calldata name) external view returns (uint256 value); + function envUint(string calldata name, string calldata delim) external view returns (uint256[] memory value); + function etch(address target, bytes calldata newRuntimeBytecode) external; + function eth_getLogs(uint256 fromBlock, uint256 toBlock, address target, bytes32[] calldata topics) external returns (EthGetLogs[] memory logs); + function exists(string calldata path) external view returns (bool result); + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) external; + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external; + function expectCall(address callee, bytes calldata data) external; + function expectCall(address callee, bytes calldata data, uint64 count) external; + function expectCall(address callee, uint256 msgValue, bytes calldata data) external; + 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; + function expectEmit() external; + function expectEmit(address emitter) external; + function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, uint64 count) external; + function expectEmit(bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData, address emitter, uint64 count) external; + function expectEmit(uint64 count) external; + function expectEmit(address emitter, uint64 count) external; + function expectPartialRevert(bytes4 revertData) external; + function expectPartialRevert(bytes4 revertData, address reverter) external; function expectRevert() external; - - function expectRevert(bytes calldata) external; - - function expectRevert(bytes4) external; - - // Record all storage reads and writes + function expectRevert(bytes4 revertData) external; + function expectRevert(bytes4 revertData, address reverter, uint64 count) external; + function expectRevert(bytes calldata revertData, address reverter, uint64 count) external; + function expectRevert(bytes calldata revertData) external; + function expectRevert(address reverter) external; + function expectRevert(bytes4 revertData, address reverter) external; + function expectRevert(bytes calldata revertData, address reverter) external; + function expectRevert(uint64 count) external; + function expectRevert(bytes4 revertData, uint64 count) external; + function expectRevert(bytes calldata revertData, uint64 count) external; + function expectRevert(address reverter, uint64 count) external; + function expectSafeMemory(uint64 min, uint64 max) external; + function expectSafeMemoryCall(uint64 min, uint64 max) external; + function fee(uint256 newBasefee) external; + function ffi(string[] calldata commandInput) external returns (bytes memory result); + function foundryVersionAtLeast(string calldata version) external view returns (bool); + function foundryVersionCmp(string calldata version) external view returns (int256); + function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + function getArtifactPathByCode(bytes calldata code) external view returns (string memory path); + function getArtifactPathByDeployedCode(bytes calldata deployedCode) external view returns (string memory path); + function getBlobBaseFee() external view returns (uint256 blobBaseFee); + function getBlobhashes() external view returns (bytes32[] memory hashes); + function getBlockNumber() external view returns (uint256 height); + function getBlockTimestamp() external view returns (uint256 timestamp); + function getBroadcast(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary memory); + function getBroadcasts(string calldata contractName, uint64 chainId, BroadcastTxType txType) external view returns (BroadcastTxSummary[] memory); + function getBroadcasts(string calldata contractName, uint64 chainId) external view returns (BroadcastTxSummary[] memory); + function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); + function getDeployedCode(string calldata artifactPath) external view returns (bytes memory runtimeBytecode); + function getDeployment(string calldata contractName) external view returns (address deployedAddress); + function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); + function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + function getFoundryVersion() external view returns (string memory version); + function getLabel(address account) external view returns (string memory currentLabel); + function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external returns (bool found, bytes32 key, bytes32 parent); + function getMappingLength(address target, bytes32 mappingSlot) external returns (uint256 length); + function getMappingSlotAt(address target, bytes32 mappingSlot, uint256 idx) external returns (bytes32 value); + function getNonce(address account) external view returns (uint64 nonce); + function getNonce(Wallet calldata wallet) external returns (uint64 nonce); + function getRecordedLogs() external returns (Log[] memory logs); + function getStateDiff() external view returns (string memory diff); + function getStateDiffJson() external view returns (string memory diff); + function getWallets() external returns (address[] memory wallets); + function indexOf(string calldata input, string calldata key) external pure returns (uint256); + function isContext(ForgeContext context) external view returns (bool result); + function isDir(string calldata path) external view returns (bool result); + function isFile(string calldata path) external view returns (bool result); + function isPersistent(address account) external view returns (bool persistent); + function keyExists(string calldata json, string calldata key) external view returns (bool); + function keyExistsJson(string calldata json, string calldata key) external view returns (bool); + function keyExistsToml(string calldata toml, string calldata key) external view returns (bool); + function label(address account, string calldata newLabel) external; + function lastCallGas() external view returns (Gas memory gas); + function load(address target, bytes32 slot) external view returns (bytes32 data); + function loadAllocs(string calldata pathToAllocsJson) external; + function makePersistent(address account) external; + function makePersistent(address account0, address account1) external; + function makePersistent(address account0, address account1, address account2) external; + function makePersistent(address[] calldata accounts) external; + function mockCallRevert(address callee, bytes calldata data, bytes calldata revertData) external; + function mockCallRevert(address callee, uint256 msgValue, bytes calldata data, bytes calldata revertData) external; + function mockCallRevert(address callee, bytes4 data, bytes calldata revertData) external; + function mockCallRevert(address callee, uint256 msgValue, bytes4 data, bytes calldata revertData) external; + function mockCall(address callee, bytes calldata data, bytes calldata returnData) external; + function mockCall(address callee, uint256 msgValue, bytes calldata data, bytes calldata returnData) external; + function mockCall(address callee, bytes4 data, bytes calldata returnData) external; + function mockCall(address callee, uint256 msgValue, bytes4 data, bytes calldata returnData) external; + function mockCalls(address callee, bytes calldata data, bytes[] calldata returnData) external; + function mockCalls(address callee, uint256 msgValue, bytes calldata data, bytes[] calldata returnData) external; + function mockFunction(address callee, address target, bytes calldata data) external; + function parseAddress(string calldata stringifiedValue) external pure returns (address parsedValue); + function parseBool(string calldata stringifiedValue) external pure returns (bool parsedValue); + function parseBytes(string calldata stringifiedValue) external pure returns (bytes memory parsedValue); + function parseBytes32(string calldata stringifiedValue) external pure returns (bytes32 parsedValue); + function parseInt(string calldata stringifiedValue) external pure returns (int256 parsedValue); + function parseJsonAddress(string calldata json, string calldata key) external pure returns (address); + function parseJsonAddressArray(string calldata json, string calldata key) external pure returns (address[] memory); + function parseJsonBool(string calldata json, string calldata key) external pure returns (bool); + function parseJsonBoolArray(string calldata json, string calldata key) external pure returns (bool[] memory); + function parseJsonBytes(string calldata json, string calldata key) external pure returns (bytes memory); + function parseJsonBytes32(string calldata json, string calldata key) external pure returns (bytes32); + function parseJsonBytes32Array(string calldata json, string calldata key) external pure returns (bytes32[] memory); + function parseJsonBytesArray(string calldata json, string calldata key) external pure returns (bytes[] memory); + function parseJsonInt(string calldata json, string calldata key) external pure returns (int256); + function parseJsonIntArray(string calldata json, string calldata key) external pure returns (int256[] memory); + 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); + function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); + function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address); + function parseTomlAddressArray(string calldata toml, string calldata key) external pure returns (address[] memory); + function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool); + function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory); + function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory); + function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32); + function parseTomlBytes32Array(string calldata toml, string calldata key) external pure returns (bytes32[] memory); + function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory); + function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256); + function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory); + function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); + function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); + function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); + function parseTomlTypeArray(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + function parseTomlType(string calldata toml, string calldata typeDescription) external pure returns (bytes memory); + function parseTomlType(string calldata toml, string calldata key, string calldata typeDescription) external pure returns (bytes memory); + function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); + function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); + function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); + function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); + function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); + function pauseGasMetering() external; + function pauseTracing() external view; + function prank(address msgSender) external; + function prank(address msgSender, address txOrigin) external; + function prank(address msgSender, bool delegateCall) external; + function prank(address msgSender, address txOrigin, bool delegateCall) external; + function prevrandao(bytes32 newPrevrandao) external; + function prevrandao(uint256 newPrevrandao) external; + function projectRoot() external view returns (string memory path); + function prompt(string calldata promptText) external returns (string memory input); + function promptAddress(string calldata promptText) external returns (address); + function promptSecret(string calldata promptText) external returns (string memory input); + function promptSecretUint(string calldata promptText) external returns (uint256); + function promptUint(string calldata promptText) external returns (uint256); + function publicKeyP256(uint256 privateKey) external pure returns (uint256 publicKeyX, uint256 publicKeyY); + function randomAddress() external returns (address); + function randomBool() external view returns (bool); + function randomBytes(uint256 len) external view returns (bytes memory); + function randomBytes4() external view returns (bytes4); + function randomBytes8() external view returns (bytes8); + function randomInt() external view returns (int256); + function randomInt(uint256 bits) external view returns (int256); + function randomUint() external returns (uint256); + function randomUint(uint256 min, uint256 max) external returns (uint256); + function randomUint(uint256 bits) external view returns (uint256); + function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + function readDir(string calldata path) external view returns (DirEntry[] memory entries); + function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); + function readDir(string calldata path, uint64 maxDepth, bool followLinks) external view returns (DirEntry[] memory entries); + function readFile(string calldata path) external view returns (string memory data); + function readFileBinary(string calldata path) external view returns (bytes memory data); + function readLine(string calldata path) external view returns (string memory line); + function readLink(string calldata linkPath) external view returns (string memory targetPath); function record() external; - - // Gets all accessed reads and write slot from a recording session, for a given address - function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes); - - // Record all the transaction logs function recordLogs() external; - - // Gets all the recorded logs - function getRecordedLogs() external returns (Log[] memory); - - // Prepare an expected log with all four checks enabled. - // Call this function, then emit an event, then call a function. Internally after the call, we check if - // logs were emitted in the expected order with the expected topics and data. - // Second form also checks supplied address against emitting contract. - function expectEmit() external; - - // Prepare an expected log with all four checks enabled, and check supplied address against emitting contract. - function expectEmit(address) external; - - // Prepare an expected log with (bool checkTopic1, bool checkTopic2, bool checkTopic3, bool checkData). - // Call this function, then emit an event, then call a function. Internally after the call, we check if - // logs were emitted in the expected order with the expected topics and data (as specified by the booleans). - // Second form also checks supplied address against emitting contract. - function expectEmit(bool, bool, bool, bool) external; - - function expectEmit(bool, bool, bool, bool, address) external; - - // Mocks a call to an address, returning specified data. - // Calldata can either be strict or a partial match, e.g. if you only - // pass a Solidity selector to the expected calldata, then the entire Solidity - // function will be mocked. - function mockCall(address, bytes calldata, bytes calldata) external; - - // Mocks a call to an address with a specific msg.value, returning specified data. - // Calldata match takes precedence over msg.value in case of ambiguity. - function mockCall(address, uint256, bytes calldata, bytes calldata) external; - - // Reverts a call to an address with specified revert data. - function mockCallRevert(address, bytes calldata, bytes calldata) external; - - // Reverts a call to an address with a specific msg.value, with specified revert data. - function mockCallRevert(address, uint256 msgValue, bytes calldata, bytes calldata) external; - - // Clears all mocked calls - function clearMockedCalls() external; - - // Expect a call to an address with the specified calldata. - // Calldata can either be strict or a partial match - function expectCall(address, bytes calldata) external; - - // Expect given number of calls to an address with the specified calldata. - // Calldata can either be strict or a partial match - function expectCall(address, bytes calldata, uint64) external; - - // Expect a call to an address with the specified msg.value and calldata - function expectCall(address, uint256, bytes calldata) external; - - // Expect a given number of calls to an address with the specified msg.value and calldata - function expectCall(address, uint256, bytes calldata, uint64) external; - - // Expect a call to an address with the specified msg.value, gas, and calldata. - function expectCall(address, uint256, uint64, bytes calldata) external; - - // Expect a given number of calls to an address with the specified msg.value, gas, and calldata. - function expectCall(address, uint256, uint64, bytes calldata, uint64) external; - - // Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address, uint256, uint64, bytes calldata) external; - - // Expect a given number of calls to an address with the specified msg.value and calldata, and a *minimum* amount of gas. - function expectCallMinGas(address, uint256, uint64, bytes calldata, uint64) external; - - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the current subcontext. If any other - // memory is written to, the test will fail. - function expectSafeMemory(uint64, uint64) external; - - // Only allows memory writes to offsets [0x00, 0x60) ∪ [min, max) in the next created subcontext. - // If any other memory is written to, the test will fail. - function expectSafeMemoryCall(uint64, uint64) external; - - // Gets the bytecode from an artifact file. Takes in the relative path to the json file - function getCode(string calldata) external returns (bytes memory); - - // Gets the _deployed_ bytecode from an artifact file. Takes in the relative path to the json file - function getDeployedCode(string calldata) external returns (bytes memory); - - // Labels an address in call traces - function label(address, string calldata) external; - - // Retrieves a label by its address. - function getLabel(address) external returns (string memory); - - // If the condition is false, discard this run's fuzz inputs and generate new ones - function assume(bool) external; - - // Set nonce for an account - function setNonce(address, uint64) external; - - // Get nonce for an account - function getNonce(address) external returns (uint64); - - // Resets the nonce for an account - function resetNonce(address) external; - - // Set an arbitrary nonce for an account - function setNonceUnsafe(address, uint64) external; - - // Set block.chainid (newChainId) - function chainId(uint256) external; - - // Using the address that calls the test contract, has the next call (at this call depth only) create a transaction that can later be signed and sent onchain - function broadcast() external; - - // Has the next call (at this call depth only) create a transaction with the address provided as the sender that can later be signed and sent onchain - function broadcast(address) external; - - // Has the next call (at this call depth only) create a transaction with the private key provided as the sender that can later be signed and sent onchain - function broadcast(uint256) external; - - // Using the address that calls the test contract, has the all subsequent calls (at this call depth only) create transactions that can later be signed and sent onchain - function startBroadcast() external; - - // Has all subsequent calls (at this call depth only) create transactions with the address provided that can later be signed and sent onchain - function startBroadcast(address) external; - - // Has all subsequent calls (at this call depth only) create transactions with the private key provided that can later be signed and sent onchain - function startBroadcast(uint256) external; - - // Stops collecting onchain transactions - function stopBroadcast() external; - - // Get the path of the current project root - function projectRoot() external returns (string memory); - - // Reads the entire content of file to string. Path is relative to the project root. - // (path) => (data) - function readFile(string calldata) external returns (string memory); - - // Reads the entire content of file as binary. Path is relative to the project root. - // (path) => (data) - function readFileBinary(string calldata) external returns (bytes memory); - - // Reads next line of file to string. - // (path) => (line) - function readLine(string calldata) external returns (string memory); - - // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does. - // `path` is relative to the project root. - // (path, data) => () - function writeFile(string calldata, string calldata) external; - - // Writes binary data to a file, creating a file if it does not exist, and entirely replacing its contents if it does. - // `path` is relative to the project root. - // (path, data) => () - function writeFileBinary(string calldata, bytes calldata) external; - - // Writes line to file, creating a file if it does not exist. - // `path` is relative to the project root. - // (path, data) => () - function writeLine(string calldata, string calldata) external; - - // Copies the contents of one file to another. This function will **overwrite** the contents of `to`. - // On success, the total number of bytes copied is returned and it is equal to the length of the `to` file as reported by `metadata`. - // Both `from` and `to` are relative to the project root. - // (from, to) => (copied) - function copyFile(string calldata, string calldata) external returns (uint64); - - // Closes file for reading, resetting the offset and allowing to read it from beginning with readLine. - // `path` is relative to the project root. - // (path) => () - function closeFile(string calldata) external; - - // Removes a file from the filesystem. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - `path` points to a directory. - // - The file doesn't exist. - // - The user lacks permissions to remove the file. - // `path` is relative to the project root. - // (path) => () - function removeFile(string calldata) external; - - // Creates a new, empty directory at the provided path. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - User lacks permissions to modify `path`. - // - A parent of the given path doesn't exist and `recursive` is false. - // - `path` already exists and `recursive` is false. - // `path` is relative to the project root. - // (path, recursive) => () - function createDir(string calldata, bool) external; - - // Removes a directory at the provided path. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - `path` doesn't exist. - // - `path` isn't a directory. - // - User lacks permissions to modify `path`. - // - The directory is not empty and `recursive` is false. - // `path` is relative to the project root. - // (path, recursive) => () - function removeDir(string calldata, bool) external; - - // Reads the directory at the given path recursively, up to `max_depth`. - // `max_depth` defaults to 1, meaning only the direct children of the given directory will be returned. - // Follows symbolic links if `follow_links` is true. - // (path) => (entries) - function readDir(string calldata) external returns (DirEntry[] memory); - - // (path, max_depth) => (entries) - function readDir(string calldata, uint64) external returns (DirEntry[] memory); - - // (path, max_depth, follow_links) => (entries) - function readDir(string calldata, uint64, bool) external returns (DirEntry[] memory); - - // Reads a symbolic link, returning the path that the link points to. - // This cheatcode will revert in the following situations, but is not limited to just these cases: - // - `path` is not a symbolic link. - // - `path` does not exist. - // (link_path) => (path) - function readLink(string calldata) external returns (string memory); - - // Given a path, query the file system to get information about a file, directory, etc. - // (path) => (metadata) - function fsMetadata(string calldata) external returns (FsMetadata memory); - - function toString(address) external returns (string memory); - - function toString(bytes calldata) external returns (string memory); - - function toString(bytes32) external returns (string memory); - - function toString(bool) external returns (string memory); - - function toString(uint256) external returns (string memory); - - function toString(int256) external returns (string memory); - - function parseBytes(string memory) external returns (bytes memory); - - function parseAddress(string memory) external returns (address); - - function parseUint(string memory) external returns (uint256); - - function parseInt(string memory) external returns (int256); - - function parseBytes32(string memory) external returns (bytes32); - - function parseBool(string memory) external returns (bool); - - // Snapshot the current state of the evm. - // Returns the id of the snapshot that was created. - // To revert a snapshot use `revertTo` - function snapshot() external returns (uint256); - - // Revert the state of the evm to a previous snapshot - // Takes the snapshot id to revert to. - // This deletes the snapshot and all snapshots taken after the given snapshot id. - function revertTo(uint256) external returns (bool); - - // Creates a new fork with the given endpoint and block and returns the identifier of the fork - function createFork(string calldata, uint256) external returns (uint256); - - // Creates a new fork with the given endpoint and at the block the given transaction was mined in, and replays all transaction mined in the block before the transaction - function createFork(string calldata, bytes32) external returns (uint256); - - // Creates a new fork with the given endpoint and the _latest_ block and returns the identifier of the fork - function createFork(string calldata) external returns (uint256); - - // Creates _and_ also selects a new fork with the given endpoint and block and returns the identifier of the fork - function createSelectFork(string calldata, uint256) external returns (uint256); - - // Creates _and_ also selects new fork with the given endpoint and at the block the given transaction was mined in, and replays all transaction mined in the block before the transaction - function createSelectFork(string calldata, bytes32) external returns (uint256); - - // Creates _and_ also selects a new fork with the given endpoint and the latest block and returns the identifier of the fork - function createSelectFork(string calldata) external returns (uint256); - - // Takes a fork identifier created by `createFork` and sets the corresponding forked state as active. - function selectFork(uint256) external; - - // Fetches the given transaction from the active fork and executes it on the current state - function transact(bytes32) external; - - // Fetches the given transaction from the given fork and executes it on the current state - function transact(uint256, bytes32) external; - - // Returns the currently active fork - // Reverts if no fork is currently active - function activeFork() external returns (uint256); - - // In forking mode, explicitly grant the given address cheatcode access - function allowCheatcodes(address) external; - - // Marks that the account(s) should use persistent storage across fork swaps. - // Meaning, changes made to the state of this account will be kept when switching forks - function makePersistent(address) external; - - function makePersistent(address, address) external; - - function makePersistent(address, address, address) external; - - function makePersistent(address[] calldata) external; - - // Revokes persistent status from the address, previously added via `makePersistent` - function revokePersistent(address) external; - - function revokePersistent(address[] calldata) external; - - // Returns true if the account is marked as persistent - function isPersistent(address) external returns (bool); - - // Updates the currently active fork to given block number - // This is similar to `roll` but for the currently active fork - function rollFork(uint256) external; - - // Updates the currently active fork to given transaction - // this will `rollFork` with the number of the block the transaction was mined in and replays all transaction mined before it in the block - function rollFork(bytes32) external; - - // Updates the given fork to given block number - function rollFork(uint256 forkId, uint256 blockNumber) external; - - // Updates the given fork to block number of the given transaction and replays all transaction mined before it in the block - function rollFork(uint256 forkId, bytes32 transaction) external; - - /// Returns the RPC url for the given alias - function rpcUrl(string calldata) external returns (string memory); - - /// Returns all rpc urls and their aliases `[alias, url][]` - function rpcUrls() external returns (string[2][] memory); - - /// Returns all rpc urls and their aliases as an array of structs - function rpcUrlStructs() external returns (Rpc[] memory); - - function parseJson(string calldata, string calldata) external returns (bytes memory); - - function parseJson(string calldata) external returns (bytes memory); - - function parseJsonKeys(string calldata, string calldata) external returns (string[] memory); - - function parseJsonUint(string calldata, string calldata) external returns (uint256); - - function parseJsonUintArray(string calldata, string calldata) external returns (uint256[] memory); - - function parseJsonInt(string calldata, string calldata) external returns (int256); - - function parseJsonIntArray(string calldata, string calldata) external returns (int256[] memory); - - function parseJsonBool(string calldata, string calldata) external returns (bool); - - function parseJsonBoolArray(string calldata, string calldata) external returns (bool[] memory); - - function parseJsonAddress(string calldata, string calldata) external returns (address); - - function parseJsonAddressArray(string calldata, string calldata) external returns (address[] memory); - - function parseJsonString(string calldata, string calldata) external returns (string memory); - - function parseJsonStringArray(string calldata, string calldata) external returns (string[] memory); - - function parseJsonBytes(string calldata, string calldata) external returns (bytes memory); - - function parseJsonBytesArray(string calldata, string calldata) external returns (bytes[] memory); - - function parseJsonBytes32(string calldata, string calldata) external returns (bytes32); - - function parseJsonBytes32Array(string calldata, string calldata) external returns (bytes32[] memory); - - function serializeBool(string calldata, string calldata, bool) external returns (string memory); - - function serializeUint(string calldata, string calldata, uint256) external returns (string memory); - - function serializeInt(string calldata, string calldata, int256) external returns (string memory); - - function serializeAddress(string calldata, string calldata, address) external returns (string memory); - - function serializeBytes32(string calldata, string calldata, bytes32) external returns (string memory); - - function serializeString(string calldata, string calldata, string calldata) external returns (string memory); - - function serializeBytes(string calldata, string calldata, bytes calldata) external returns (string memory); - - function serializeBool(string calldata, string calldata, bool[] calldata) external returns (string memory); - - function serializeUint(string calldata, string calldata, uint256[] calldata) external returns (string memory); - - function serializeInt(string calldata, string calldata, int256[] calldata) external returns (string memory); - - function serializeAddress(string calldata, string calldata, address[] calldata) external returns (string memory); - - function serializeBytes32(string calldata, string calldata, bytes32[] calldata) external returns (string memory); - - function serializeString(string calldata, string calldata, string[] calldata) external returns (string memory); - - function serializeBytes(string calldata, string calldata, bytes[] calldata) external returns (string memory); - - function writeJson(string calldata, string calldata) external; - - function writeJson(string calldata, string calldata, string calldata) external; - - // Checks if a key exists in the given json string - function keyExists(string calldata, string calldata) external returns (bool); - - // Pauses gas metering (gas usage will not be counted) - function pauseGasMetering() external; - - // Resumes gas metering from where it left off + function rememberKey(uint256 privateKey) external returns (address keyAddr); + function rememberKeys(string calldata mnemonic, string calldata derivationPath, uint32 count) external returns (address[] memory keyAddrs); + function rememberKeys(string calldata mnemonic, string calldata derivationPath, string calldata language, uint32 count) external returns (address[] memory keyAddrs); + function removeDir(string calldata path, bool recursive) external; + function removeFile(string calldata path) external; + function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); + function resetGasMetering() external; + function resetNonce(address account) external; function resumeGasMetering() external; - - // Starts recording all map SSTOREs for later retrieval. + function resumeTracing() external view; + function revertTo(uint256 snapshotId) external returns (bool success); + function revertToAndDelete(uint256 snapshotId) external returns (bool success); + function revertToState(uint256 snapshotId) external returns (bool success); + function revertToStateAndDelete(uint256 snapshotId) external returns (bool success); + function revokePersistent(address account) external; + function revokePersistent(address[] calldata accounts) external; + function roll(uint256 newHeight) external; + function rollFork(uint256 blockNumber) external; + function rollFork(bytes32 txHash) external; + function rollFork(uint256 forkId, uint256 blockNumber) external; + function rollFork(uint256 forkId, bytes32 txHash) external; + 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 returns (string memory json); + function serializeAddress(string calldata objectKey, string calldata valueKey, address[] calldata values) external returns (string memory json); + function serializeBool(string calldata objectKey, string calldata valueKey, bool value) external returns (string memory json); + function serializeBool(string calldata objectKey, string calldata valueKey, bool[] calldata values) external returns (string memory json); + function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32 value) external returns (string memory json); + function serializeBytes32(string calldata objectKey, string calldata valueKey, bytes32[] calldata values) external returns (string memory json); + function serializeBytes(string calldata objectKey, string calldata valueKey, bytes calldata value) external returns (string memory json); + function serializeBytes(string calldata objectKey, string calldata valueKey, bytes[] calldata values) external returns (string memory json); + function serializeInt(string calldata objectKey, string calldata valueKey, int256 value) external returns (string memory json); + function serializeInt(string calldata objectKey, string calldata valueKey, int256[] calldata values) external returns (string memory json); + function serializeJson(string calldata objectKey, string calldata value) external returns (string memory json); + function serializeJsonType(string calldata typeDescription, bytes calldata value) external pure returns (string memory json); + function serializeJsonType(string calldata objectKey, string calldata valueKey, string calldata typeDescription, bytes calldata value) external returns (string memory json); + function serializeString(string calldata objectKey, string calldata valueKey, string calldata value) external returns (string memory json); + function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json); + function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); + function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); + function serializeUint(string calldata objectKey, string calldata valueKey, uint256[] calldata values) external returns (string memory json); + function setArbitraryStorage(address target) external; + 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; + function signAndAttachDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); + function signCompact(Wallet calldata wallet, bytes32 digest) external returns (bytes32 r, bytes32 vs); + function signCompact(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); + function signCompact(bytes32 digest) external pure returns (bytes32 r, bytes32 vs); + function signCompact(address signer, bytes32 digest) external pure returns (bytes32 r, bytes32 vs); + function signDelegation(address implementation, uint256 privateKey) external returns (SignedDelegation memory signedDelegation); + function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); + function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); + function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function skip(bool skipTest) external; + function skip(bool skipTest, string calldata reason) external; + function sleep(uint256 duration) external; + function snapshot() external returns (uint256 snapshotId); + function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed); + function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed); + function snapshotState() external returns (uint256 snapshotId); + function snapshotValue(string calldata name, uint256 value) external; + function snapshotValue(string calldata group, string calldata name, uint256 value) external; + function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); + function startBroadcast() external; + function startBroadcast(address signer) external; + function startBroadcast(uint256 privateKey) external; + function startDebugTraceRecording() external; function startMappingRecording() external; - - // Stops recording all map SSTOREs for later retrieval and clears the recorded data. + function startPrank(address msgSender) external; + function startPrank(address msgSender, address txOrigin) external; + function startPrank(address msgSender, bool delegateCall) external; + function startPrank(address msgSender, address txOrigin, bool delegateCall) external; + function startSnapshotGas(string calldata name) external; + function startSnapshotGas(string calldata group, string calldata name) external; + function startStateDiffRecording() external; + function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step); + function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); + function stopBroadcast() external; + function stopExpectSafeMemory() external; function stopMappingRecording() external; - - // Gets the length of a mapping at a given slot, for a given address. - function getMappingLength(address target, bytes32 slot) external returns (uint256); - - // Gets the element at index idx of a mapping at a given slot, for a given address. - function getMappingSlotAt(address target, bytes32 slot, uint256 idx) external returns (bytes32); - - // Gets the map key and parent of a mapping at a given slot, for a given address. - function getMappingKeyAndParentOf(address target, bytes32 slot) external returns (bool, bytes32, bytes32); + function stopPrank() external; + function stopSnapshotGas() external returns (uint256 gasUsed); + function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); + function store(address target, bytes32 slot, bytes32 value) external; + function toBase64URL(bytes calldata data) external pure returns (string memory); + function toBase64URL(string calldata data) external pure returns (string memory); + function toBase64(bytes calldata data) external pure returns (string memory); + function toBase64(string calldata data) external pure returns (string memory); + function toLowercase(string calldata input) external pure returns (string memory output); + function toString(address value) external pure returns (string memory stringifiedValue); + function toString(bytes calldata value) external pure returns (string memory stringifiedValue); + function toString(bytes32 value) external pure returns (string memory stringifiedValue); + function toString(bool value) external pure returns (string memory stringifiedValue); + function toString(uint256 value) external pure returns (string memory stringifiedValue); + function toString(int256 value) external pure returns (string memory stringifiedValue); + function toUppercase(string calldata input) external pure returns (string memory output); + function transact(bytes32 txHash) external; + function transact(uint256 forkId, bytes32 txHash) external; + function trim(string calldata input) external pure returns (string memory output); + function tryFfi(string[] calldata commandInput) external returns (FfiResult memory result); + function txGasPrice(uint256 newGasPrice) external; + function unixTime() external view returns (uint256 milliseconds); + function warp(uint256 newTimestamp) external; + function writeFile(string calldata path, string calldata data) external; + function writeFileBinary(string calldata path, bytes calldata data) external; + function writeJson(string calldata json, string calldata path) external; + function writeJson(string calldata json, string calldata path, string calldata valueKey) external; + function writeLine(string calldata path, string calldata data) external; + function writeToml(string calldata json, string calldata path) external; + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; } diff --git a/testdata/core/DSStyle.t.sol b/testdata/core/DSStyle.t.sol deleted file mode 100644 index fc08653338666..0000000000000 --- a/testdata/core/DSStyle.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; - -contract DSStyleTest is DSTest { - function testFailingAssertions() public { - emit log_string("assertionOne"); - assertEq(uint256(1), uint256(2)); - emit log_string("assertionTwo"); - assertEq(uint256(3), uint256(4)); - emit log_string("done"); - } -} diff --git a/testdata/core/FailingSetup.t.sol b/testdata/core/FailingSetup.t.sol deleted file mode 100644 index 19a79c266a190..0000000000000 --- a/testdata/core/FailingSetup.t.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; - -contract FailingSetupTest is DSTest { - event Test(uint256 n); - - function setUp() public { - emit Test(42); - require(false, "setup failed predictably"); - } - - function testFailShouldBeMarkedAsFailedBecauseOfSetup() public { - emit log("setup did not fail"); - } -} diff --git a/testdata/core/MultipleSetup.t.sol b/testdata/core/MultipleSetup.t.sol deleted file mode 100644 index 75ae20e856d6f..0000000000000 --- a/testdata/core/MultipleSetup.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; - -contract MultipleSetup is DSTest { - function setUp() public {} - - function setup() public {} - - function testFailShouldBeMarkedAsFailedBecauseOfSetup() public { - assert(true); - } -} diff --git a/testdata/core/Reverting.t.sol b/testdata/core/Reverting.t.sol deleted file mode 100644 index 7699755647bc2..0000000000000 --- a/testdata/core/Reverting.t.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -contract RevertingTest { - function testFailRevert() public pure { - require(false, "should revert here"); - } -} diff --git a/testdata/cheats/Addr.t.sol b/testdata/default/cheats/Addr.t.sol similarity index 60% rename from testdata/cheats/Addr.t.sol rename to testdata/default/cheats/Addr.t.sol index e326b5cac1029..b0b3fefbdba79 100644 --- a/testdata/cheats/Addr.t.sol +++ b/testdata/default/cheats/Addr.t.sol @@ -1,13 +1,15 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AddrTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function testFailPrivKeyZero() public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfPkZero() public { + vm.expectRevert("vm.addr: private key cannot be 0"); vm.addr(0); } diff --git a/testdata/default/cheats/ArbitraryStorage.t.sol b/testdata/default/cheats/ArbitraryStorage.t.sol new file mode 100644 index 0000000000000..afac010870159 --- /dev/null +++ b/testdata/default/cheats/ArbitraryStorage.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public a; + address public b; + int8 public c; + address[] public owners; + + function setA(uint256 _a) public { + a = _a; + } + + function setB(address _b) public { + b = _b; + } + + function getOwner(uint256 pos) public view returns (address) { + return owners[pos]; + } + + function setOwner(uint256 pos, address owner) public { + owners[pos] = owner; + } +} + +contract CounterArbitraryStorageWithSeedTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_fresh_storage() public { + uint256 index = 55; + Counter counter = new Counter(); + vm.setArbitraryStorage(address(counter)); + // Next call would fail with array out of bounds without arbitrary storage. + address owner = counter.getOwner(index); + // Subsequent calls should retrieve same value + assertEq(counter.getOwner(index), owner); + // Change slot and make sure new value retrieved + counter.setOwner(index, address(111)); + assertEq(counter.getOwner(index), address(111)); + } + + function test_arbitrary_storage_warm() public { + Counter counter = new Counter(); + vm.setArbitraryStorage(address(counter)); + assertGt(counter.a(), 0); + counter.setA(0); + // This should remain 0 if explicitly set. + assertEq(counter.a(), 0); + counter.setA(11); + assertEq(counter.a(), 11); + } + + function test_arbitrary_storage_multiple_read_writes() public { + Counter counter = new Counter(); + vm.setArbitraryStorage(address(counter)); + uint256 slot1 = vm.randomUint(0, 100); + uint256 slot2 = vm.randomUint(0, 100); + require(slot1 != slot2, "random positions should be different"); + address alice = counter.owners(slot1); + address bob = counter.owners(slot2); + require(alice != bob, "random storage values should be different"); + counter.setOwner(slot1, bob); + counter.setOwner(slot2, alice); + assertEq(alice, counter.owners(slot2)); + assertEq(bob, counter.owners(slot1)); + } +} + +contract AContract { + uint256[] public a; + address[] public b; + int8[] public c; + bytes32[] public d; +} + +contract AContractArbitraryStorageWithSeedTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_arbitrary_storage_with_seed() public { + AContract target = new AContract(); + vm.setArbitraryStorage(address(target)); + assertEq(target.a(11), 112807530564575719000382171275495171195982096112439764207649185248041477080234); + assertEq(target.b(22), 0x9dce87df97C81f2529877E8127b4b8c13E4b2b31); + assertEq(target.c(33), 85); + assertEq(target.d(44), 0x6ceda712fc9d694d72afeea6c44d370b789a18e1a3d640068c11069e421d25f6); + } +} + +contract SymbolicStore { + uint256 public testNumber = 1337; // slot 0 + + constructor() {} +} + +contract SymbolicStorageWithSeedTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_SymbolicStorage() public { + uint256 slot = vm.randomUint(0, 100); + address addr = 0xEA674fdDe714fd979de3EdF0F56AA9716B898ec8; + vm.setArbitraryStorage(addr); + bytes32 value = vm.load(addr, bytes32(slot)); + assertEq(uint256(value), 112807530564575719000382171275495171195982096112439764207649185248041477080234); + // Load slot again and make sure we get same value. + bytes32 value1 = vm.load(addr, bytes32(slot)); + assertEq(uint256(value), uint256(value1)); + } + + function test_SymbolicStorage1() public { + uint256 slot = vm.randomUint(0, 100); + SymbolicStore myStore = new SymbolicStore(); + vm.setArbitraryStorage(address(myStore)); + bytes32 value = vm.load(address(myStore), bytes32(uint256(slot))); + assertEq(uint256(value), 112807530564575719000382171275495171195982096112439764207649185248041477080234); + } + + function testEmptyInitialStorage(uint256 slot) public { + bytes32 storage_value = vm.load(address(vm), bytes32(slot)); + assertEq(uint256(storage_value), 0); + } +} diff --git a/testdata/default/cheats/Assert.t.sol b/testdata/default/cheats/Assert.t.sol new file mode 100644 index 0000000000000..d6765967c5faa --- /dev/null +++ b/testdata/default/cheats/Assert.t.sol @@ -0,0 +1,825 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract AssertionsTest is DSTest { + string constant errorMessage = "User provided message"; + uint256 constant maxDecimals = 77; + + Vm constant vm = Vm(HEVM_ADDRESS); + + function _abs(int256 a) internal pure returns (uint256) { + // Required or it will fail when `a = type(int256).min` + if (a == type(int256).min) { + return uint256(type(int256).max) + 1; + } + + return uint256(a > 0 ? a : -a); + } + + function _getDelta(uint256 a, uint256 b) internal pure returns (uint256) { + return a > b ? a - b : b - a; + } + + function _getDelta(int256 a, int256 b) internal pure returns (uint256) { + // a and b are of the same sign + // this works thanks to two's complement, the left-most bit is the sign bit + if ((a ^ b) > -1) { + return _getDelta(_abs(a), _abs(b)); + } + + // a and b are of opposite signs + return _abs(a) + _abs(b); + } + + function _prefixDecWithZeroes(string memory intPart, string memory decimalPart, uint256 decimals) + internal + returns (string memory) + { + while (bytes(decimalPart).length < decimals) { + decimalPart = string.concat("0", decimalPart); + } + + return string.concat(intPart, ".", decimalPart); + } + + function _formatWithDecimals(uint256 value, uint256 decimals) internal returns (string memory) { + string memory intPart = vm.toString(value / (10 ** decimals)); + string memory decimalPart = vm.toString(value % (10 ** decimals)); + + return _prefixDecWithZeroes(intPart, decimalPart, decimals); + } + + function _formatWithDecimals(int256 value, uint256 decimals) internal returns (string memory) { + string memory formatted = _formatWithDecimals(_abs(value), decimals); + if (value < 0) { + formatted = string.concat("-", formatted); + } + + return formatted; + } + + function testFuzzAssertEqNotEq(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(left != right); + vm.assume(decimals <= maxDecimals); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " != ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertEqDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " == ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertNotEqDecimal(left, left, decimals); + } + + function testFuzzAssertEqNotEq(int256 left, int256 right, uint256 decimals) public { + vm.assume(left != right); + vm.assume(decimals <= maxDecimals); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + _formatWithDecimals(left, decimals), + " != ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertEqDecimal(left, right, decimals, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, ": ", _formatWithDecimals(left, decimals), " == ", _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertNotEqDecimal(left, left, decimals, errorMessage); + } + + function testFuzzAssertEqNotEq(bool left, bool right) public { + vm.assume(left != right); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(address left, address right) public { + vm.assume(left != right); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(bytes32 left, bytes32 right) public { + vm.assume(left != right); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(string memory left, string memory right) public { + vm.assume(keccak256(abi.encodePacked(left)) != keccak256(abi.encodePacked(right))); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": ", left, " != ", right))); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": ", left, " == ", left))); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertEqNotEq(bytes memory left, bytes memory right) public { + vm.assume(keccak256(left) != keccak256(right)); + + vm.assertEq(left, left); + vm.assertEq(right, right); + vm.assertNotEq(left, right); + vm.assertNotEq(right, left); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " != ", vm.toString(right))) + ); + vm.assertEq(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " == ", vm.toString(left))) + ); + vm.assertNotEq(left, left, errorMessage); + } + + function testFuzzAssertGtLt(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGt(right, left); + vm.assertLt(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " <= ", vm.toString(right))) + ); + vm.assertGt(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " <= ", vm.toString(right))) + ); + vm.assertGt(right, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " >= ", vm.toString(left))) + ); + vm.assertLt(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " >= ", vm.toString(left))) + ); + vm.assertLt(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(right, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(left, left, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(right, left, decimals); + } + + function testFuzzAssertGtLt(int256 left, int256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGt(right, left); + vm.assertLt(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " <= ", vm.toString(right))) + ); + vm.assertGt(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " <= ", vm.toString(right))) + ); + vm.assertGt(right, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " >= ", vm.toString(left))) + ); + vm.assertLt(left, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " >= ", vm.toString(left))) + ); + vm.assertLt(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " <= ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGtDecimal(right, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(left, left, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " >= ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLtDecimal(right, left, decimals); + } + + function testFuzzAssertGeLe(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGe(left, left); + vm.assertLe(left, left); + vm.assertGe(right, right); + vm.assertLe(right, right); + vm.assertGe(right, left); + vm.assertLe(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " < ", vm.toString(right))) + ); + vm.assertGe(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " > ", vm.toString(left))) + ); + vm.assertLe(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " < ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGeDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " > ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLeDecimal(right, left, decimals); + } + + function testFuzzAssertGeLe(int256 left, int256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(decimals <= maxDecimals); + + vm.assertGe(left, left); + vm.assertLe(left, left); + vm.assertGe(right, right); + vm.assertLe(right, right); + vm.assertGe(right, left); + vm.assertLe(left, right); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(left), " < ", vm.toString(right))) + ); + vm.assertGe(left, right, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": ", vm.toString(right), " > ", vm.toString(left))) + ); + vm.assertLe(right, left, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " < ", + _formatWithDecimals(right, decimals) + ) + ) + ); + vm.assertGeDecimal(left, right, decimals); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(right, decimals), + " > ", + _formatWithDecimals(left, decimals) + ) + ) + ); + vm.assertLeDecimal(right, left, decimals); + } + + function testFuzzAssertApproxEqAbs(uint256 left, uint256 right, uint256 decimals) public { + uint256 delta = _getDelta(right, left); + vm.assume(decimals <= maxDecimals); + + vm.assertApproxEqAbs(left, right, delta); + + if (delta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + vm.toString(delta - 1), + ", real delta: ", + vm.toString(delta), + ")" + ) + ) + ); + vm.assertApproxEqAbs(left, right, delta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(delta - 1, decimals), + ", real delta: ", + _formatWithDecimals(delta, decimals), + ")" + ) + ) + ); + vm.assertApproxEqAbsDecimal(left, right, delta - 1, decimals); + } + } + + function testFuzzAssertApproxEqAbs(int256 left, int256 right, uint256 decimals) public { + uint256 delta = _getDelta(right, left); + vm.assume(decimals <= maxDecimals); + + vm.assertApproxEqAbs(left, right, delta); + + if (delta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + vm.toString(delta - 1), + ", real delta: ", + vm.toString(delta), + ")" + ) + ) + ); + vm.assertApproxEqAbs(left, right, delta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(delta - 1, decimals), + ", real delta: ", + _formatWithDecimals(delta, decimals), + ")" + ) + ) + ); + vm.assertApproxEqAbsDecimal(left, right, delta - 1, decimals); + } + } + + function testFuzzAssertApproxEqRel(uint256 left, uint256 right, uint256 decimals) public { + vm.assume(right != 0); + uint256 delta = _getDelta(right, left); + vm.assume(delta < type(uint256).max / (10 ** 18)); + vm.assume(decimals <= maxDecimals); + + uint256 percentDelta = delta * (10 ** 18) / right; + + vm.assertApproxEqRel(left, right, percentDelta); + + if (percentDelta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRel(left, right, percentDelta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRelDecimal(left, right, percentDelta - 1, decimals); + } + } + + function testFuzzAssertApproxEqRel(int256 left, int256 right, uint256 decimals) public { + vm.assume(left < right); + vm.assume(right != 0); + uint256 delta = _getDelta(right, left); + vm.assume(delta < type(uint256).max / (10 ** 18)); + vm.assume(decimals <= maxDecimals); + + uint256 percentDelta = delta * (10 ** 18) / _abs(right); + + vm.assertApproxEqRel(left, right, percentDelta); + + if (percentDelta > 0) { + vm._expectCheatcodeRevert( + bytes( + string.concat( + errorMessage, + ": ", + vm.toString(left), + " !~= ", + vm.toString(right), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRel(left, right, percentDelta - 1, errorMessage); + + vm._expectCheatcodeRevert( + bytes( + string.concat( + "assertion failed: ", + _formatWithDecimals(left, decimals), + " !~= ", + _formatWithDecimals(right, decimals), + " (max delta: ", + _formatWithDecimals(percentDelta - 1, 16), + "%, real delta: ", + _formatWithDecimals(percentDelta, 16), + "%)" + ) + ) + ); + vm.assertApproxEqRelDecimal(left, right, percentDelta - 1, decimals); + } + } + + function testAssertEqNotEqArrays() public { + { + uint256[] memory arr1 = new uint256[](1); + arr1[0] = 1; + uint256[] memory arr2 = new uint256[](2); + arr2[0] = 1; + arr2[1] = 2; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes("assertion failed: [1] != [1, 2]")); + vm.assertEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat("assertion failed: [1, 2] == [1, 2]"))); + vm.assertNotEq(arr2, arr2); + } + { + int256[] memory arr1 = new int256[](2); + int256[] memory arr2 = new int256[](1); + arr1[0] = 5; + arr2[0] = type(int256).max; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [5, 0] != [", vm.toString(arr2[0]), "]"))); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert(bytes(string.concat("assertion failed: [5, 0] == [5, 0]"))); + vm.assertNotEq(arr1, arr1); + } + { + bool[] memory arr1 = new bool[](2); + bool[] memory arr2 = new bool[](2); + arr1[0] = true; + arr2[1] = true; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [true, false] != [false, true]"))); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert(bytes(string("assertion failed: [true, false] == [true, false]"))); + vm.assertNotEq(arr1, arr1); + } + { + address[] memory arr1 = new address[](1); + address[] memory arr2 = new address[](0); + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [", vm.toString(arr1[0]), "] != []"))); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert(bytes(string("assertion failed: [] == []"))); + vm.assertNotEq(arr2, arr2); + } + { + bytes32[] memory arr1 = new bytes32[](1); + bytes32[] memory arr2 = new bytes32[](1); + arr1[0] = bytes32(uint256(1)); + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": [", vm.toString(arr1[0]), "] != [", vm.toString(arr2[0]), "]")) + ); + vm.assertEq(arr1, arr2, errorMessage); + + vm._expectCheatcodeRevert( + bytes(string.concat("assertion failed: [", vm.toString(arr2[0]), "] == [", vm.toString(arr2[0]), "]")) + ); + vm.assertNotEq(arr2, arr2); + } + { + string[] memory arr1 = new string[](1); + string[] memory arr2 = new string[](3); + + arr1[0] = "foo"; + arr2[2] = "bar"; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes("assertion failed: [foo] != [, , bar]")); + vm.assertEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [foo] == [foo]"))); + vm.assertNotEq(arr1, arr1, errorMessage); + } + { + bytes[] memory arr1 = new bytes[](1); + bytes[] memory arr2 = new bytes[](2); + + arr1[0] = hex"1111"; + arr2[1] = hex"1234"; + + vm.assertEq(arr1, arr1); + vm.assertEq(arr2, arr2); + vm.assertNotEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes("assertion failed: [0x1111] != [0x, 0x1234]")); + vm.assertEq(arr1, arr2); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": [0x1111] == [0x1111]"))); + vm.assertNotEq(arr1, arr1, errorMessage); + } + } + + function testAssertBool() public { + vm.assertTrue(true); + vm.assertFalse(false); + + vm._expectCheatcodeRevert(bytes("assertion failed")); + vm.assertTrue(false); + + vm._expectCheatcodeRevert(bytes(errorMessage)); + vm.assertTrue(false, errorMessage); + + vm._expectCheatcodeRevert(bytes("assertion failed")); + vm.assertFalse(true); + + vm._expectCheatcodeRevert(bytes(errorMessage)); + vm.assertFalse(true, errorMessage); + } + + function testAssertApproxEqRel() public { + vm._expectCheatcodeRevert(bytes("assertion failed: overflow in delta calculation")); + vm.assertApproxEqRel(type(int256).min, type(int256).max, 0); + + vm._expectCheatcodeRevert( + bytes(string.concat(errorMessage, ": 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)")) + ); + vm.assertApproxEqRel(int256(1), int256(0), 0, errorMessage); + + vm._expectCheatcodeRevert(bytes(string.concat(errorMessage, ": overflow in delta calculation"))); + vm.assertApproxEqRel(uint256(0), type(uint256).max, 0, errorMessage); + + vm._expectCheatcodeRevert( + bytes("assertion failed: 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)") + ); + vm.assertApproxEqRel(uint256(1), uint256(0), uint256(0)); + + vm.assertApproxEqRel(uint256(0), uint256(0), uint256(0)); + } +} diff --git a/testdata/cheats/Assume.t.sol b/testdata/default/cheats/Assume.t.sol similarity index 71% rename from testdata/cheats/Assume.t.sol rename to testdata/default/cheats/Assume.t.sol index d27afe5e0c32e..14ed341c9970e 100644 --- a/testdata/cheats/Assume.t.sol +++ b/testdata/default/cheats/Assume.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AssumeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/AssumeNoRevert.t.sol b/testdata/default/cheats/AssumeNoRevert.t.sol new file mode 100644 index 0000000000000..ea6d2d9747bdd --- /dev/null +++ b/testdata/default/cheats/AssumeNoRevert.t.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import {DSTest as Test} from "ds-test/test.sol"; +import {Vm} from "cheats/Vm.sol"; + +contract ReverterB { + /// @notice has same error selectors as contract below to test the `reverter` param + error MyRevert(); + error SpecialRevertWithData(uint256 x); + + function revertIf2(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithData() public pure returns (bool) { + revert SpecialRevertWithData(2); + } +} + +contract Reverter { + error MyRevert(); + error RevertWithData(uint256 x); + error UnusedError(); + + ReverterB public immutable subReverter; + + constructor() { + subReverter = new ReverterB(); + } + + function myFunction() public pure returns (bool) { + revert MyRevert(); + } + + function revertIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert MyRevert(); + } + return true; + } + + function revertWithDataIf2(uint256 value) public pure returns (bool) { + if (value == 2) { + revert RevertWithData(2); + } + return true; + } + + function twoPossibleReverts(uint256 x) public pure returns (bool) { + if (x == 2) { + revert MyRevert(); + } else if (x == 3) { + revert RevertWithData(3); + } + return true; + } +} + +contract ReverterTest is Test { + Reverter reverter; + Vm _vm = Vm(HEVM_ADDRESS); + + function setUp() public { + reverter = new Reverter(); + } + + /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector + function testAssumeSelector(uint256 x) public view { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + reverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector and data + function testAssumeWithDataSingle(uint256 x) public view { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 2), + partialMatch: false, + reverter: address(0) + }) + ); + reverter.revertWithDataIf2(x); + } + + /// @dev Test that `assumeNoRevert` anticipates and correctly rejects a specific error selector with any extra data (ie providing selector allows for arbitrary extra data) + function testAssumeWithDataPartial(uint256 x) public view { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector), + partialMatch: true, + reverter: address(0) + }) + ); + reverter.revertWithDataIf2(x); + } + + /// @dev Test that `assumeNoRevert` assumptions are not cleared after a cheatcode call + function testAssumeNotClearedAfterCheatcodeCall(uint256 x) public { + _vm.assumeNoRevert( + Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(0) + }) + ); + _vm.warp(block.timestamp + 1000); + reverter.revertIf2(x); + } + + /// @dev Test that `assumeNoRevert` correctly rejects two different error selectors + function testMultipleAssumesPasses(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(reverter) + }); + revertData[1] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector, 3), + partialMatch: false, + reverter: address(reverter) + }); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + } + + /// @dev Test that `assumeNoRevert` correctly interacts with itself when partially matching on the error selector + function testMultipleAssumes_Partial(uint256 x) public view { + Vm.PotentialRevert[] memory revertData = new Vm.PotentialRevert[](2); + revertData[0] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.RevertWithData.selector), + partialMatch: true, + reverter: address(reverter) + }); + revertData[1] = Vm.PotentialRevert({ + revertData: abi.encodeWithSelector(Reverter.MyRevert.selector), + partialMatch: false, + reverter: address(reverter) + }); + _vm.assumeNoRevert(revertData); + reverter.twoPossibleReverts(x); + } +} diff --git a/testdata/default/cheats/AttachDelegation.t.sol b/testdata/default/cheats/AttachDelegation.t.sol new file mode 100644 index 0000000000000..2b2e829ae350d --- /dev/null +++ b/testdata/default/cheats/AttachDelegation.t.sol @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract AttachDelegationTest is DSTest { + event ExecutedBy(uint256 id); + + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 alice_pk = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d; + address payable alice = payable(0x70997970C51812dc3A010C7d01b50e0d17dc79C8); + uint256 bob_pk = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a; + address bob = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; + + SimpleDelegateContract implementation; + SimpleDelegateContract implementation2; + ERC20 token; + + function setUp() public { + implementation = new SimpleDelegateContract(1); + implementation2 = new SimpleDelegateContract(2); + token = new ERC20(alice); + } + + function testCallSingleAttachDelegation() public { + Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), alice_pk); + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](1); + bytes memory data = abi.encodeCall(ERC20.mint, (100, bob)); + calls[0] = SimpleDelegateContract.Call({to: address(token), data: data, value: 0}); + // executing as bob to make clear that we don't need to execute the tx as alice + vm.broadcast(bob_pk); + vm.attachDelegation(signedDelegation); + + bytes memory code = address(alice).code; + require(code.length > 0, "no code written to alice"); + SimpleDelegateContract(alice).execute(calls); + + assertEq(token.balanceOf(bob), 100); + } + + function testMultiCallAttachDelegation() public { + Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), alice_pk); + vm.broadcast(bob_pk); + vm.attachDelegation(signedDelegation); + + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](2); + calls[0] = + SimpleDelegateContract.Call({to: address(token), data: abi.encodeCall(ERC20.mint, (50, bob)), value: 0}); + calls[1] = SimpleDelegateContract.Call({ + to: address(token), + data: abi.encodeCall(ERC20.mint, (50, address(this))), + value: 0 + }); + + SimpleDelegateContract(alice).execute(calls); + + assertEq(token.balanceOf(bob), 50); + assertEq(token.balanceOf(address(this)), 50); + } + + function testSwitchAttachDelegation() public { + Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), alice_pk); + + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](1); + bytes memory data = abi.encodeCall(ERC20.mint, (100, bob)); + calls[0] = SimpleDelegateContract.Call({to: address(token), data: data, value: 0}); + + vm.broadcast(bob_pk); + vm.attachDelegation(signedDelegation); + + vm.expectEmit(true, true, true, true); + emit ExecutedBy(1); + SimpleDelegateContract(alice).execute(calls); + + // switch to implementation2 + Vm.SignedDelegation memory signedDelegation2 = vm.signDelegation(address(implementation2), alice_pk); + vm.broadcast(bob_pk); + vm.attachDelegation(signedDelegation2); + + vm.expectEmit(true, true, true, true); + emit ExecutedBy(2); + SimpleDelegateContract(alice).execute(calls); + + // verify final state + assertEq(token.balanceOf(bob), 200); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testAttachDelegationRevertInvalidSignature() public { + Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), alice_pk); + // change v from 1 to 0 + signedDelegation.v = (signedDelegation.v + 1) % 2; + vm.attachDelegation(signedDelegation); + + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](1); + bytes memory data = abi.encodeCall(ERC20.mint, (100, bob)); + calls[0] = SimpleDelegateContract.Call({to: address(token), data: data, value: 0}); + + vm.broadcast(alice_pk); + // empty revert because no bytecode was set to Alice's account + vm.expectRevert(); + SimpleDelegateContract(alice).execute(calls); + } + + function testAttachDelegationRevertsAfterNonceChange() public { + Vm.SignedDelegation memory signedDelegation = vm.signDelegation(address(implementation), alice_pk); + + vm.broadcast(alice_pk); + // send tx to increment alice's nonce + token.mint(1, bob); + + vm._expectCheatcodeRevert("vm.attachDelegation: invalid nonce"); + vm.attachDelegation(signedDelegation); + } + + function testCallSingleSignAndAttachDelegation() public { + SimpleDelegateContract.Call[] memory calls = new SimpleDelegateContract.Call[](1); + bytes memory data = abi.encodeCall(ERC20.mint, (100, bob)); + calls[0] = SimpleDelegateContract.Call({to: address(token), data: data, value: 0}); + vm.signAndAttachDelegation(address(implementation), alice_pk); + bytes memory code = address(alice).code; + require(code.length > 0, "no code written to alice"); + vm.broadcast(bob_pk); + SimpleDelegateContract(alice).execute(calls); + + assertEq(token.balanceOf(bob), 100); + } +} + +contract SimpleDelegateContract { + event Executed(address indexed to, uint256 value, bytes data); + event ExecutedBy(uint256 id); + + struct Call { + bytes data; + address to; + uint256 value; + } + + uint256 public immutable id; + + constructor(uint256 _id) { + id = _id; + } + + function execute(Call[] memory calls) external payable { + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + (bool success, bytes memory result) = call.to.call{value: call.value}(call.data); + require(success, string(result)); + emit Executed(call.to, call.value, call.data); + emit ExecutedBy(id); + } + } + + receive() external payable {} +} + +contract ERC20 { + address public minter; + mapping(address => uint256) private _balances; + + constructor(address _minter) { + minter = _minter; + } + + function mint(uint256 amount, address to) public { + _mint(to, amount); + } + + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + + function _mint(address account, uint256 amount) internal { + require(msg.sender == minter, "ERC20: msg.sender is not minter"); + require(account != address(0), "ERC20: mint to the zero address"); + unchecked { + _balances[account] += amount; + } + } +} diff --git a/testdata/cheats/Bank.t.sol b/testdata/default/cheats/Bank.t.sol similarity index 82% rename from testdata/cheats/Bank.t.sol rename to testdata/default/cheats/Bank.t.sol index a4c48d5d6ce47..166fbb16ac8ea 100644 --- a/testdata/cheats/Bank.t.sol +++ b/testdata/default/cheats/Bank.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract CoinbaseTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Base64.t.sol b/testdata/default/cheats/Base64.t.sol new file mode 100644 index 0000000000000..fad7bbf4f297c --- /dev/null +++ b/testdata/default/cheats/Base64.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"; +import "../logs/console.sol"; + +contract Base64Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_toBase64() public { + bytes memory input = hex"00112233445566778899aabbccddeeff"; + string memory expected = "ABEiM0RVZneImaq7zN3u/w=="; + string memory actual = vm.toBase64(input); + assertEq(actual, expected); + } + + function test_toBase64URL() public { + bytes memory input = hex"00112233445566778899aabbccddeeff"; + string memory expected = "ABEiM0RVZneImaq7zN3u_w=="; + string memory actual = vm.toBase64URL(input); + assertEq(actual, expected); + } +} diff --git a/testdata/default/cheats/BlobBaseFee.t.sol b/testdata/default/cheats/BlobBaseFee.t.sol new file mode 100644 index 0000000000000..54fbc8f7f0616 --- /dev/null +++ b/testdata/default/cheats/BlobBaseFee.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobBaseFeeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_blob_base_fee() public { + vm.blobBaseFee(6969); + assertEq(vm.getBlobBaseFee(), 6969); + } +} diff --git a/testdata/default/cheats/Blobhashes.t.sol b/testdata/default/cheats/Blobhashes.t.sol new file mode 100644 index 0000000000000..4a589b45a38ff --- /dev/null +++ b/testdata/default/cheats/Blobhashes.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobhashesTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSetAndGetBlobhashes() public { + bytes32[] memory blobhashes = new bytes32[](2); + blobhashes[0] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000001); + blobhashes[1] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000002); + vm.blobhashes(blobhashes); + + bytes32[] memory gotBlobhashes = vm.getBlobhashes(); + assertEq(gotBlobhashes[0], blobhashes[0]); + assertEq(gotBlobhashes[1], blobhashes[1]); + } +} diff --git a/testdata/cheats/Broadcast.t.sol b/testdata/default/cheats/Broadcast.t.sol similarity index 83% rename from testdata/cheats/Broadcast.t.sol rename to testdata/default/cheats/Broadcast.t.sol index c7d05acb7c501..6486049b03b3e 100644 --- a/testdata/cheats/Broadcast.t.sol +++ b/testdata/default/cheats/Broadcast.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; library F { function t2() public pure returns (uint256) { @@ -155,9 +155,11 @@ contract BroadcastTest is DSTest { vm.stopBroadcast(); } - function testFailNoBroadcast() public { - vm.stopBroadcast(); - } + /// forge-config: default.allow_internal_expect_revert = true + // function testRevertIfNoBroadcast() public { + // vm.expectRevert(); + // vm.stopBroadcast(); + // } } contract NoLink is DSTest { @@ -219,6 +221,32 @@ contract BroadcastTestNoLinking is DSTest { vm.stopBroadcast(); } + function deployCreate2(address deployer) public { + vm.startBroadcast(); + bytes32 salt = bytes32(uint256(1338)); + NoLink test_c2 = new NoLink{salt: salt}(); + assert(test_c2.view_me() == 1337); + + address expectedAddress = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + bytes1(0xff), + deployer, + salt, + keccak256(abi.encodePacked(type(NoLink).creationCode, abi.encode())) + ) + ) + ) + ) + ); + require(address(test_c2) == expectedAddress, "Create2 address mismatch"); + + NoLink test2 = new NoLink(); + vm.stopBroadcast(); + } + function errorStaticCall() public { vm.broadcast(); NoLink test11 = new NoLink(); @@ -511,3 +539,62 @@ contract CheckOverrides is DSTest { vm.stopBroadcast(); } } + +contract Child {} + +contract Parent { + constructor() { + new Child(); + } +} + +contract ScriptAdditionalContracts is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function run() external { + vm.startBroadcast(); + new Parent(); + } +} + +contract SignatureTester { + address public immutable owner; + + constructor() { + owner = msg.sender; + } + + function verifySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { + require(ecrecover(digest, v, r, s) == owner, "Invalid signature"); + } +} + +contract ScriptSign is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + bytes32 digest = keccak256("something"); + + function run() external { + vm.startBroadcast(); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(digest); + + vm._expectCheatcodeRevert( + bytes(string.concat("signer with address ", vm.toString(address(this)), " is not available")) + ); + vm.sign(address(this), digest); + + SignatureTester tester = new SignatureTester(); + (, address caller,) = vm.readCallers(); + assertEq(tester.owner(), caller); + tester.verifySignature(digest, v, r, s); + } + + function run(address sender) external { + vm._expectCheatcodeRevert(bytes("could not determine signer")); + vm.sign(digest); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sender, digest); + address actual = ecrecover(digest, v, r, s); + + assertEq(actual, sender); + } +} diff --git a/testdata/default/cheats/BroadcastRawTransaction.t.sol b/testdata/default/cheats/BroadcastRawTransaction.t.sol new file mode 100644 index 0000000000000..36682bc893359 --- /dev/null +++ b/testdata/default/cheats/BroadcastRawTransaction.t.sol @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BroadcastRawTransactionTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_revert_not_a_tx() public { + vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: unexpected string"); + vm.broadcastRawTransaction(hex"0102"); + } + + function test_revert_missing_signature() public { + vm._expectCheatcodeRevert("failed to decode RLP-encoded transaction: input too short"); + vm.broadcastRawTransaction(hex"dd806483030d40940993863c19b0defb183ca2b502db7d1b331ded757b80"); + } + + function test_revert_wrong_chainid() public { + vm._expectCheatcodeRevert("transaction validation error: invalid chain ID"); + vm.broadcastRawTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + } + + function test_execute_signed_tx() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + + uint256 balance = 1 ether; + uint256 amountSent = 17; + + vm.deal(address(from), balance); + assertEq(address(from).balance, balance); + assertEq(address(to).balance, 0); + + /* + Signed transaction: + TransactionRequest { from: Some(0x5316812db67073c4d4af8bb3000c5b86c2877e94), to: Some(Address(0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6)), gas: Some(200000), gas_price: Some(100), value: Some(17), data: None, nonce: Some(0), chain_id: Some(1) } + */ + vm.broadcastRawTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + uint256 gasPrice = 100; + assertEq(address(from).balance, balance - (gasPrice * 21_000) - amountSent); + assertEq(address(to).balance, amountSent); + } + + function test_execute_signed_tx2() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + address random = address(uint160(uint256(keccak256(abi.encodePacked("random"))))); + + uint256 balance = 1 ether; + uint256 amountSent = 17; + + vm.deal(address(from), balance); + assertEq(address(from).balance, balance); + assertEq(address(to).balance, 0); + + /* + Signed transaction: + TransactionRequest { from: Some(0x5316812db67073c4d4af8bb3000c5b86c2877e94), to: Some(Address(0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6)), gas: Some(200000), gas_price: Some(100), value: Some(17), data: None, nonce: Some(0), chain_id: Some(1) } + */ + vm.broadcastRawTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + uint256 gasPrice = 100; + assertEq(address(from).balance, balance - (gasPrice * 21_000) - amountSent); + assertEq(address(to).balance, amountSent); + assertEq(address(random).balance, 0); + + uint256 value = 5; + + vm.prank(to); + (bool success,) = random.call{value: value}(""); + require(success); + assertEq(address(to).balance, amountSent - value); + assertEq(address(random).balance, value); + } + + // this test is to make sure that the journaledstate is correctly handled + // i ran into an issue where the test would fail after running `broadcastRawTransaction` + // because there was an issue in the journaledstate + function test_execute_signed_tx_with_revert() public { + vm.fee(1); + vm.chainId(1); + + address from = 0x5316812db67073C4d4af8BB3000C5B86c2877e94; + address to = 0x6Fd0A0CFF9A87aDF51695b40b4fA267855a8F4c6; + + uint256 balance = 1 ether; + uint256 amountSent = 17; + + vm.deal(address(from), balance); + assertEq(address(from).balance, balance); + assertEq(address(to).balance, 0); + + /* + Signed transaction: + TransactionRequest { from: Some(0x5316812db67073c4d4af8bb3000c5b86c2877e94), to: Some(Address(0x6fd0a0cff9a87adf51695b40b4fa267855a8f4c6)), gas: Some(200000), gas_price: Some(100), value: Some(17), data: None, nonce: Some(0), chain_id: Some(1) } + */ + vm.broadcastRawTransaction( + hex"f860806483030d40946fd0a0cff9a87adf51695b40b4fa267855a8f4c6118025a03ebeabbcfe43c2c982e99b376b5fb6e765059d7f215533c8751218cac99bbd80a00a56cf5c382442466770a756e81272d06005c9e90fb8dbc5b53af499d5aca856" + ); + + uint256 gasPrice = 100; + assertEq(address(from).balance, balance - (gasPrice * 21_000) - amountSent); + assertEq(address(to).balance, amountSent); + + vm._expectCheatcodeRevert(); + vm.assertFalse(true); + } + + function test_execute_multiple_signed_tx() public { + vm.fee(1); + vm.chainId(1); + + address alice = 0x7ED31830602f9F7419307235c0610Fb262AA0375; + address bob = 0x70CF146aB98ffD5dE24e75dd7423F16181Da8E13; + address charlie = 0xae0900Cf97f8C233c64F7089cEC7d5457215BB8d; + + // this is the runtime code of "MyERC20" (see below) + // this is equivalent to: + // type(MyERC20).runtimeCode + bytes memory code = + hex"608060405234801561001057600080fd5b50600436106100625760003560e01c8063095ea7b31461006757806323b872dd1461008f57806370a08231146100a257806394bf804d146100d9578063a9059cbb146100ee578063dd62ed3e14610101575b600080fd5b61007a61007536600461051d565b61013a565b60405190151581526020015b60405180910390f35b61007a61009d366004610547565b610152565b6100cb6100b0366004610583565b6001600160a01b031660009081526020819052604090205490565b604051908152602001610086565b6100ec6100e73660046105a5565b610176565b005b61007a6100fc36600461051d565b610184565b6100cb61010f3660046105d1565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b600033610148818585610192565b5060019392505050565b600033610160858285610286565b61016b858585610318565b506001949350505050565b6101808183610489565b5050565b600033610148818585610318565b6001600160a01b0383166101f95760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b60648201526084015b60405180910390fd5b6001600160a01b03821661025a5760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b60648201526084016101f0565b6001600160a01b0392831660009081526001602090815260408083209490951682529290925291902055565b6001600160a01b03838116600090815260016020908152604080832093861683529290522054600019811461031257818110156103055760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016101f0565b6103128484848403610192565b50505050565b6001600160a01b03831661037c5760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b60648201526084016101f0565b6001600160a01b0382166103de5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084016101f0565b6001600160a01b038316600090815260208190526040902054818110156104565760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b60648201526084016101f0565b6001600160a01b039384166000908152602081905260408082209284900390925592909316825291902080549091019055565b6001600160a01b0382166104df5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016101f0565b6001600160a01b03909116600090815260208190526040902080549091019055565b80356001600160a01b038116811461051857600080fd5b919050565b6000806040838503121561053057600080fd5b61053983610501565b946020939093013593505050565b60008060006060848603121561055c57600080fd5b61056584610501565b925061057360208501610501565b9150604084013590509250925092565b60006020828403121561059557600080fd5b61059e82610501565b9392505050565b600080604083850312156105b857600080fd5b823591506105c860208401610501565b90509250929050565b600080604083850312156105e457600080fd5b6105ed83610501565b91506105c86020840161050156fea2646970667358221220e1fee5cd1c5bbf066a9ce9228e1baf7e7fcb77b5050506c7d614aaf8608b42e364736f6c63430008110033"; + + // this is equivalent to: + // MyERC20 token = new MyERC20{ salt: bytes32(uint256(1)) }(); + // address: 0x5bf11839f61ef5cceeaf1f4153e44df5d02825f7 + MyERC20 token = MyERC20(address(uint160(uint256(keccak256(abi.encodePacked("mytoken")))))); + vm.etch(address(token), code); + + token.mint(100, alice); + + assertEq(token.balanceOf(alice), 100); + assertEq(token.balanceOf(bob), 0); + assertEq(token.balanceOf(charlie), 0); + + vm.deal(alice, 10 ether); + + /* + Signed transaction: + { + from: '0x7ED31830602f9F7419307235c0610Fb262AA0375', + to: '0x5bF11839F61EF5ccEEaf1F4153e44df5D02825f7', + value: 0, + data: '0x095ea7b300000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e130000000000000000000000000000000000000000000000000000000000000032', + nonce: 0, + gasPrice: 100, + gasLimit: 200000, + chainId: 1 + } + */ + // this would be equivalent to using those cheatcodes: + // vm.prank(alice); + // token.approve(bob, 50); + vm.broadcastRawTransaction( + hex"f8a5806483030d40945bf11839f61ef5cceeaf1f4153e44df5d02825f780b844095ea7b300000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e13000000000000000000000000000000000000000000000000000000000000003225a0e25b9ef561d9a413b21755cc0e4bb6e80f2a88a8a52305690956130d612074dfa07bfd418bc2ad3c3f435fa531cdcdc64887f64ed3fb0d347d6b0086e320ad4eb1" + ); + + assertEq(token.allowance(alice, bob), 50); + + vm.deal(bob, 1 ether); + vm.prank(bob); + token.transferFrom(alice, charlie, 20); + + assertEq(token.balanceOf(bob), 0); + assertEq(token.balanceOf(charlie), 20); + + vm.deal(charlie, 1 ether); + + /* + Signed transaction: + { + from: '0xae0900Cf97f8C233c64F7089cEC7d5457215BB8d', + to: '0x5bF11839F61EF5ccEEaf1F4153e44df5D02825f7', + value: 0, + data: '0xa9059cbb00000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e130000000000000000000000000000000000000000000000000000000000000005', + nonce: 0, + gasPrice: 100, + gasLimit: 200000, + chainId: 1 + } + */ + // this would be equivalent to using those cheatcodes: + // vm.prank(charlie); + // token.transfer(bob, 5); + vm.broadcastRawTransaction( + hex"f8a5806483030d40945bf11839f61ef5cceeaf1f4153e44df5d02825f780b844a9059cbb00000000000000000000000070cf146ab98ffd5de24e75dd7423f16181da8e13000000000000000000000000000000000000000000000000000000000000000525a0941562f519e33dfe5b44ebc2b799686cebeaeacd617dd89e393620b380797da2a0447dfd38d9444ccd571b000482c81674733761753430c81ee6669e9542c266a1" + ); + + assertEq(token.balanceOf(alice), 80); + assertEq(token.balanceOf(bob), 5); + assertEq(token.balanceOf(charlie), 15); + } +} + +contract MyERC20 { + mapping(address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + function mint(uint256 amount, address to) public { + _mint(to, amount); + } + + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + + function transfer(address to, uint256 amount) public returns (bool) { + address owner = msg.sender; + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public returns (bool) { + address owner = msg.sender; + _approve(owner, spender, amount); + return true; + } + + function transferFrom(address from, address to, uint256 amount) public returns (bool) { + address spender = msg.sender; + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function _transfer(address from, address to, uint256 amount) internal { + require(from != address(0), "ERC20: transfer from the zero address"); + require(to != address(0), "ERC20: transfer to the zero address"); + + uint256 fromBalance = _balances[from]; + require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); + unchecked { + _balances[from] = fromBalance - amount; + _balances[to] += amount; + } + } + + function _mint(address account, uint256 amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + unchecked { + _balances[account] += amount; + } + } + + function _approve(address owner, address spender, uint256 amount) internal { + require(owner != address(0), "ERC20: approve from the zero address"); + require(spender != address(0), "ERC20: approve to the zero address"); + _allowances[owner][spender] = amount; + } + + function _spendAllowance(address owner, address spender, uint256 amount) internal { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, "ERC20: insufficient allowance"); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } +} + +contract ScriptBroadcastRawTransactionBroadcast is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function runSignedTxBroadcast() public { + uint256 pk_to = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + vm.startBroadcast(pk_to); + + address from = 0x73E1A965542AFA4B412467761b1CED8A764E1D3B; + address to = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; + address random = address(uint160(uint256(keccak256(abi.encodePacked("random"))))); + + assert(address(from).balance == 1 ether); + assert(address(to).balance == 1 ether); + assert(address(random).balance == 0); + + /* + TransactionRequest { + from: Some( + 0x73e1a965542afa4b412467761b1ced8a764e1d3b, + ), + to: Some( + Address( + 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266, + ), + ), + gas: Some( + 200000, + ), + gas_price: Some( + 10000000000, + ), + value: Some( + 1234, + ), + data: None, + nonce: Some( + 0, + ), + chain_id: Some( + 31337, + ), + } + */ + vm.broadcastRawTransaction( + hex"f869808502540be40083030d4094f39fd6e51aad88f6f4ce6ab8827279cfffb922668204d28082f4f6a061ce3c0f4280cb79c1eb0060a9a491cca1ba48ed32f141e3421ccb60c9dbe444a07fcd35cbec5f81427ac20f60484f4da9d00f59652f5053cd13ee90b992e94ab3" + ); + + uint256 value = 34; + (bool success,) = random.call{value: value}(""); + require(success); + + vm.stopBroadcast(); + + uint256 gasPrice = 10 * 1e9; + assert(address(from).balance == 1 ether - (gasPrice * 21_000) - 1234); + assert(address(to).balance == 1 ether + 1234 - value); + assert(address(random).balance == value); + } + + function runDeployCreate2Deployer() public { + vm.startBroadcast(); + vm.broadcastRawTransaction( + hex"f8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222" + ); + vm.stopBroadcast(); + } +} diff --git a/testdata/cheats/ChainId.t.sol b/testdata/default/cheats/ChainId.t.sol similarity index 72% rename from testdata/cheats/ChainId.t.sol rename to testdata/default/cheats/ChainId.t.sol index ab6f7357e21cf..31a6e5cdf37eb 100644 --- a/testdata/cheats/ChainId.t.sol +++ b/testdata/default/cheats/ChainId.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DealTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/CloneAccount.t.sol b/testdata/default/cheats/CloneAccount.t.sol new file mode 100644 index 0000000000000..d584c747cb9b9 --- /dev/null +++ b/testdata/default/cheats/CloneAccount.t.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Source { + uint256 public a; + address public b; + uint256[3] public c; + bool public d; + + constructor() { + a = 100; + b = address(111); + c[0] = 222; + c[1] = 333; + c[2] = 444; + d = true; + } +} + +contract CloneAccountTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + address clone = address(777); + + function setUp() public { + Source src = new Source(); + vm.deal(address(src), 0.123 ether); + vm.cloneAccount(address(src), clone); + } + + function test_clone_account() public { + // Check clone balance. + assertEq(clone.balance, 0.123 ether); + // Check clone storage. + assertEq(Source(clone).a(), 100); + assertEq(Source(clone).b(), address(111)); + assertEq(Source(clone).c(0), 222); + assertEq(Source(clone).c(1), 333); + assertEq(Source(clone).c(2), 444); + assertEq(Source(clone).d(), true); + } +} diff --git a/testdata/default/cheats/Cool.t.sol b/testdata/default/cheats/Cool.t.sol new file mode 100644 index 0000000000000..d0750bebfa18e --- /dev/null +++ b/testdata/default/cheats/Cool.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "lib/ds-test/src/test.sol"; +import "cheats/Vm.sol"; + +contract CoolTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 public slot0 = 1; + + function testCool_SLOAD_normal() public { + uint256 startGas; + uint256 endGas; + uint256 val; + uint256 beforeCoolGas; + uint256 noCoolGas; + + startGas = gasleft(); + val = slot0; + endGas = gasleft(); + beforeCoolGas = startGas - endGas; + + startGas = gasleft(); + val = slot0; + endGas = gasleft(); + noCoolGas = startGas - endGas; + + assertGt(beforeCoolGas, noCoolGas); + } + + function testCool_SLOAD() public { + uint256 startGas; + uint256 endGas; + uint256 val; + uint256 beforeCoolGas; + uint256 afterCoolGas; + uint256 noCoolGas; + + startGas = gasleft(); + val = slot0; + endGas = gasleft(); + beforeCoolGas = startGas - endGas; + + vm.cool(address(this)); + + startGas = gasleft(); + val = slot0; + endGas = gasleft(); + afterCoolGas = startGas - endGas; + + assertEq(beforeCoolGas, afterCoolGas); + + startGas = gasleft(); + val = slot0; + endGas = gasleft(); + noCoolGas = startGas - endGas; + + assertGt(beforeCoolGas, noCoolGas); + } +} diff --git a/testdata/default/cheats/CopyStorage.t.sol b/testdata/default/cheats/CopyStorage.t.sol new file mode 100644 index 0000000000000..e9195949e49c9 --- /dev/null +++ b/testdata/default/cheats/CopyStorage.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public a; + address public b; + int256[] public c; + + function setA(uint256 _a) public { + a = _a; + } + + function setB(address _b) public { + b = _b; + } +} + +contract CounterWithSeedTest is DSTest { + Counter public counter; + Counter public counter1; + Vm vm = Vm(HEVM_ADDRESS); + + function test_copy_storage() public { + counter = new Counter(); + counter.setA(1000); + counter.setB(address(27)); + counter1 = new Counter(); + counter1.setA(11); + counter1.setB(address(50)); + + assertEq(counter.a(), 1000); + assertEq(counter.b(), address(27)); + assertEq(counter1.a(), 11); + assertEq(counter1.b(), address(50)); + vm.copyStorage(address(counter), address(counter1)); + assertEq(counter.a(), 1000); + assertEq(counter.b(), address(27)); + assertEq(counter1.a(), 1000); + assertEq(counter1.b(), address(27)); + } + + function test_copy_storage_from_arbitrary() public { + counter = new Counter(); + counter1 = new Counter(); + vm.setArbitraryStorage(address(counter)); + vm.copyStorage(address(counter), address(counter1)); + + // Make sure untouched storage has same values. + assertEq(counter.a(), counter1.a()); + assertEq(counter.b(), counter1.b()); + assertEq(counter.c(33), counter1.c(33)); + + // Change storage in source storage contract and make sure copy is not changed. + counter.setA(1000); + counter1.setB(address(50)); + assertEq(counter.a(), 1000); + assertEq(counter1.a(), 67350900536747027229585709178274816969402970928486983076982664581925078789474); + assertEq(counter.b(), 0x5A61ACa23C478d83A72425c386Eb5dB083FBd0e4); + assertEq(counter1.b(), address(50)); + } +} + +contract CopyStorageContract { + uint256 public x; +} + +contract CopyStorageTest is DSTest { + CopyStorageContract csc_1; + CopyStorageContract csc_2; + CopyStorageContract csc_3; + Vm vm = Vm(HEVM_ADDRESS); + + function _storeUInt256(address contractAddress, uint256 slot, uint256 value) internal { + vm.store(contractAddress, bytes32(slot), bytes32(value)); + } + + function setUp() public { + csc_1 = new CopyStorageContract(); + csc_2 = new CopyStorageContract(); + csc_3 = new CopyStorageContract(); + } + + function test_copy_storage() public { + // Make the storage of first contract symbolic + vm.setArbitraryStorage(address(csc_1)); + // and explicitly put a constrained symbolic value into the slot for `x` + uint256 x_1 = vm.randomUint(); + _storeUInt256(address(csc_1), 0, x_1); + // `x` of second contract is uninitialized + assert(csc_2.x() == 0); + // Copy storage from first to second contract + vm.copyStorage(address(csc_1), address(csc_2)); + // `x` of second contract is now the `x` of the first + assert(csc_2.x() == x_1); + } + + function test_copy_storage_same_values_on_load() public { + // Make the storage of first contract symbolic + vm.setArbitraryStorage(address(csc_1)); + vm.copyStorage(address(csc_1), address(csc_2)); + uint256 slot1 = vm.randomUint(0, 100); + uint256 slot2 = vm.randomUint(0, 100); + bytes32 value1 = vm.load(address(csc_1), bytes32(slot1)); + bytes32 value2 = vm.load(address(csc_1), bytes32(slot2)); + + bytes32 value3 = vm.load(address(csc_2), bytes32(slot1)); + bytes32 value4 = vm.load(address(csc_2), bytes32(slot2)); + + // Check storage values are the same for both source and target contracts. + assertEq(value1, value3); + assertEq(value2, value4); + } + + function test_copy_storage_consistent_values() public { + // Make the storage of first contract symbolic. + vm.setArbitraryStorage(address(csc_1)); + // Copy arbitrary storage to 2 contracts. + vm.copyStorage(address(csc_1), address(csc_2)); + vm.copyStorage(address(csc_1), address(csc_3)); + uint256 slot1 = vm.randomUint(0, 100); + uint256 slot2 = vm.randomUint(0, 100); + + // Load slot 1 from 1st copied contract and slot2 from symbolic contract. + bytes32 value3 = vm.load(address(csc_2), bytes32(slot1)); + bytes32 value2 = vm.load(address(csc_1), bytes32(slot2)); + + bytes32 value1 = vm.load(address(csc_1), bytes32(slot1)); + bytes32 value4 = vm.load(address(csc_2), bytes32(slot2)); + + // Make sure same values for both copied and symbolic contract. + assertEq(value3, value1); + assertEq(value2, value4); + + uint256 x_1 = vm.randomUint(); + // Change slot1 of 1st copied contract. + _storeUInt256(address(csc_2), slot1, x_1); + value3 = vm.load(address(csc_2), bytes32(slot1)); + bytes32 value5 = vm.load(address(csc_3), bytes32(slot1)); + // Make sure value for 1st contract copied is different than symbolic contract value. + assert(value3 != value1); + // Make sure same values for 2nd contract copied and symbolic contract. + assertEq(value5, value1); + + uint256 x_2 = vm.randomUint(); + // Change slot2 of symbolic contract. + _storeUInt256(address(csc_1), slot2, x_2); + value2 = vm.load(address(csc_1), bytes32(slot2)); + bytes32 value6 = vm.load(address(csc_3), bytes32(slot2)); + // Make sure value for symbolic contract value is different than 1st contract copied. + assert(value2 != value4); + // Make sure value for symbolic contract value is different than 2nd contract copied. + assert(value2 != value6); + assertEq(value4, value6); + } +} diff --git a/testdata/cheats/Deal.t.sol b/testdata/default/cheats/Deal.t.sol similarity index 85% rename from testdata/cheats/Deal.t.sol rename to testdata/default/cheats/Deal.t.sol index 2189bc0ac93b9..a46d9e7140e3e 100644 --- a/testdata/cheats/Deal.t.sol +++ b/testdata/default/cheats/Deal.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DealTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/DeployCode.t.sol b/testdata/default/cheats/DeployCode.t.sol new file mode 100644 index 0000000000000..edf4c78e6dab4 --- /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/Derive.t.sol b/testdata/default/cheats/Derive.t.sol new file mode 100644 index 0000000000000..c27456c6ec487 --- /dev/null +++ b/testdata/default/cheats/Derive.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract DeriveTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testDerive() public { + string memory mnemonic = "test test test test test test test test test test test junk"; + + uint256 privateKey = vm.deriveKey(mnemonic, 0); + assertEq(privateKey, 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); + + uint256 privateKeyDerivationPathChanged = vm.deriveKey(mnemonic, "m/44'/60'/0'/1/", 0); + assertEq(privateKeyDerivationPathChanged, 0x6abb89895f93b02c1b9470db0fa675297f6cca832a5fc66d5dfd7661a42b37be); + } +} diff --git a/testdata/default/cheats/EnsNamehash.t.sol b/testdata/default/cheats/EnsNamehash.t.sol new file mode 100644 index 0000000000000..965d505006060 --- /dev/null +++ b/testdata/default/cheats/EnsNamehash.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract EnsNamehashTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEnsNamehash() public { + assertEq(vm.ensNamehash(""), 0x0000000000000000000000000000000000000000000000000000000000000000); + assertEq(vm.ensNamehash("eth"), 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae); + assertEq(vm.ensNamehash("foo.eth"), 0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f); + } +} diff --git a/testdata/cheats/Env.t.sol b/testdata/default/cheats/Env.t.sol similarity index 92% rename from testdata/cheats/Env.t.sol rename to testdata/default/cheats/Env.t.sol index 9e4b4360d1b98..7edb35dff13ff 100644 --- a/testdata/cheats/Env.t.sol +++ b/testdata/default/cheats/Env.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract EnvTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -13,12 +13,20 @@ contract EnvTest is DSTest { vm.setEnv(key, val); } - uint256 constant numEnvBoolTests = 4; + function testEnvExists() public { + string memory key = "_foundryCheatcodeEnvExistsTestKey"; + string memory val = "_foundryCheatcodeEnvExistsTestVal"; + vm.setEnv(key, val); + require(vm.envExists(key), "envExists failed"); + require(!vm.envExists("nonexistent"), "envExists failed"); + } + + uint256 constant numEnvBoolTests = 2; function testEnvBool() public { string memory key = "_foundryCheatcodeEnvBoolTestKey"; - string[numEnvBoolTests] memory values = ["true", "false", "True", "False"]; - bool[numEnvBoolTests] memory expected = [true, false, true, false]; + string[numEnvBoolTests] memory values = ["true", "false"]; + bool[numEnvBoolTests] memory expected = [true, false]; for (uint256 i = 0; i < numEnvBoolTests; ++i) { vm.setEnv(key, values[i]); bool output = vm.envBool(key); @@ -91,9 +99,12 @@ contract EnvTest is DSTest { function testEnvBytes32() public { string memory key = "_foundryCheatcodeEnvBytes32TestKey"; - string[numEnvBytes32Tests] memory values = ["0x7109709ECfa91a80626fF3989D68f67F5b1DD12D", "0x00"]; + string[numEnvBytes32Tests] memory values = [ + "0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ]; bytes32[numEnvBytes32Tests] memory expected = [ - bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000), + bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) ]; for (uint256 i = 0; i < numEnvBytes32Tests; ++i) { @@ -135,8 +146,8 @@ contract EnvTest is DSTest { function testEnvBoolArr() public { string memory key = "_foundryCheatcodeEnvBoolArrTestKey"; - string memory value = "true, false, True, False"; - bool[4] memory expected = [true, false, true, false]; + string memory value = "true, false"; + bool[numEnvBoolTests] memory expected = [true, false]; vm.setEnv(key, value); string memory delimiter = ","; @@ -187,9 +198,10 @@ contract EnvTest is DSTest { function testEnvBytes32Arr() public { string memory key = "_foundryCheatcodeEnvBytes32ArrTestKey"; - string memory value = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D," "0x00"; + string memory value = "0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811," + "0x0000000000000000000000000000000000000000000000000000000000000000"; bytes32[2] memory expected = [ - bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000), + bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) ]; @@ -336,8 +348,8 @@ contract EnvTest is DSTest { function testEnvOrBoolKey() public { string memory key = "_foundryCheatcodeEnvOrBoolTestKey"; - string[numEnvBoolTests] memory values = ["true", "false", "True", "False"]; - bool[numEnvBoolTests] memory expected = [true, false, true, false]; + string[numEnvBoolTests] memory values = ["true", "false"]; + bool[numEnvBoolTests] memory expected = [true, false]; for (uint256 i = 0; i < numEnvBoolTests; ++i) { vm.setEnv(key, values[i]); bool output = vm.envOr(key, expected[i]); @@ -347,7 +359,7 @@ contract EnvTest is DSTest { function testEnvOrBoolDefault() public { string memory key = "_foundryCheatcodeEnvOrBoolTestDefault"; - bool[numEnvBoolTests] memory expected = [true, false, true, false]; + bool[numEnvBoolTests] memory expected = [true, false]; for (uint256 i = 0; i < numEnvBoolTests; ++i) { bool output = vm.envOr(key, expected[i]); require(output == expected[i], "envOrBoolDefault failed"); @@ -447,9 +459,12 @@ contract EnvTest is DSTest { function testEnvOrBytes32Key() public { string memory key = "_foundryCheatcodeEnvOrBytes32TestKey"; - string[numEnvBytes32Tests] memory values = ["0x7109709ECfa91a80626fF3989D68f67F5b1DD12D", "0x00"]; + string[numEnvBytes32Tests] memory values = [ + "0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811", + "0x0000000000000000000000000000000000000000000000000000000000000000" + ]; bytes32[numEnvBytes32Tests] memory expected = [ - bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000), + bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) ]; for (uint256 i = 0; i < numEnvBytes32Tests; ++i) { @@ -462,7 +477,7 @@ contract EnvTest is DSTest { function testEnvOrBytes32Default() public { string memory key = "_foundryCheatcodeEnvOrBytes32TestDefault"; bytes32[numEnvBytes32Tests] memory expected = [ - bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000), + bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) ]; for (uint256 i = 0; i < numEnvBytes32Tests; ++i) { @@ -523,13 +538,11 @@ contract EnvTest is DSTest { function testEnvOrBoolArrKey() public { string memory key = "_foundryCheatcodeEnvBoolWithDefaultBoolArrTestKey"; - string memory value = "true, false, True, False"; - bool[4] memory expected = [true, false, true, false]; - bool[] memory defaultValues = new bool[](4); + string memory value = "true, false"; + bool[numEnvBoolTests] memory expected = [true, false]; + bool[] memory defaultValues = new bool[](numEnvBoolTests); defaultValues[0] = true; defaultValues[1] = false; - defaultValues[2] = true; - defaultValues[3] = false; vm.setEnv(key, value); string memory delimiter = ","; @@ -541,13 +554,11 @@ contract EnvTest is DSTest { function testEnvOrBoolArrDefault() public { string memory key = "_foundryCheatcodeEnvBoolWithDefaultBoolArrTestDefault"; - string memory value = "true, false, True, False"; - bool[4] memory expected = [true, false, true, false]; - bool[] memory defaultValues = new bool[](4); + string memory value = "true, false"; + bool[numEnvBoolTests] memory expected = [true, false]; + bool[] memory defaultValues = new bool[](numEnvBoolTests); defaultValues[0] = true; defaultValues[1] = false; - defaultValues[2] = true; - defaultValues[3] = false; string memory delimiter = ","; bool[] memory output = vm.envOr(key, delimiter, defaultValues); @@ -676,9 +687,10 @@ contract EnvTest is DSTest { function testEnvOrBytes32ArrKey() public { string memory key = "_foundryCheatcodeEnvOrBytes32ArrTestKey"; - string memory value = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D," "0x00"; + string memory value = "0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811," + "0x0000000000000000000000000000000000000000000000000000000000000000"; bytes32[2] memory expected = [ - bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000), + bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) ]; bytes32[] memory defaultValues = new bytes32[](2); @@ -696,13 +708,14 @@ contract EnvTest is DSTest { function testEnvOrBytes32ArrDefault() public { string memory key = "_foundryCheatcodeEnvOrBytes32ArrTestDefault"; - string memory value = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D," "0x00"; + string memory value = "0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811," + "0x0000000000000000000000000000000000000000000000000000000000000000"; bytes32[2] memory expected = [ - bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000), + bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811), bytes32(0x0000000000000000000000000000000000000000000000000000000000000000) ]; bytes32[] memory defaultValues = new bytes32[](2); - defaultValues[0] = bytes32(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D000000000000000000000000); + defaultValues[0] = bytes32(0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811); defaultValues[1] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000000); string memory delimiter = ","; @@ -770,9 +783,9 @@ contract EnvTest is DSTest { function testEnvOrBytesArrDefault() public { string memory key = "_foundryCheatcodeEnvOrBytesArrTestDefault"; - string memory value = "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D," "0x00"; + string memory value = "0x463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811," "0x00"; bytes[] memory expected = new bytes[](2); - expected[0] = hex"7109709ECfa91a80626fF3989D68f67F5b1DD12D"; + expected[0] = hex"463df98a03418e6196421718c1b96779a6d4f0bcff1702a9e8f2323bb49f6811"; expected[1] = hex"00"; string memory delimiter = ","; @@ -963,8 +976,8 @@ contract EnvTest is DSTest { } } - function testEnvWithDefaulBytesEmptyArrKey() public { - string memory key = "_foundryCheatcodeEnvWithDefaulBytesEmptyArrTestKey"; + function testEnvWithDefaultBytesEmptyArrKey() public { + string memory key = "_foundryCheatcodeEnvWithDefaultBytesEmptyArrTestKey"; string memory value = ""; bytes[] memory expected = new bytes[](0); bytes[] memory defaultValues = new bytes[](0); @@ -975,13 +988,13 @@ contract EnvTest is DSTest { for (uint256 i = 0; i < expected.length; ++i) { require( keccak256(abi.encodePacked((output[i]))) == keccak256(abi.encodePacked((expected[i]))), - "envWithDefaulBytesEmptyArrKey failed" + "envWithDefaultBytesEmptyArrKey failed" ); } } - function testEnvWithDefaulBytesEmptyArrDefault() public { - string memory key = "_foundryCheatcodeEnvWithDefaulBytesEmptyArrTestDefault"; + function testEnvWithDefaultBytesEmptyArrDefault() public { + string memory key = "_foundryCheatcodeEnvWithDefaultBytesEmptyArrTestDefault"; string memory value = ""; bytes[] memory expected = new bytes[](0); bytes[] memory defaultValues = new bytes[](0); @@ -991,7 +1004,7 @@ contract EnvTest is DSTest { for (uint256 i = 0; i < expected.length; ++i) { require( keccak256(abi.encodePacked((output[i]))) == keccak256(abi.encodePacked((expected[i]))), - "envWithDefaulBytesEmptyArrDefault failed" + "envWithDefaultBytesEmptyArrDefault failed" ); } } diff --git a/testdata/cheats/Etch.t.sol b/testdata/default/cheats/Etch.t.sol similarity index 61% rename from testdata/cheats/Etch.t.sol rename to testdata/default/cheats/Etch.t.sol index 7df1e7200050e..f60ea4cad650e 100644 --- a/testdata/cheats/Etch.t.sol +++ b/testdata/default/cheats/Etch.t.sol @@ -1,14 +1,14 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract EtchTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testEtch() public { - address target = address(10); + address target = address(11); bytes memory code = hex"1010"; vm.etch(target, code); assertEq(string(code), string(target.code)); @@ -17,9 +17,7 @@ contract EtchTest is DSTest { function testEtchNotAvailableOnPrecompiles() public { address target = address(1); bytes memory code = hex"1010"; - vm.expectRevert( - bytes("Etch cannot be used on precompile addresses (N < 10). Please use an address bigger than 10 instead") - ); + vm._expectCheatcodeRevert("cannot use precompile 0x0000000000000000000000000000000000000001 as an argument"); vm.etch(target, code); } } diff --git a/testdata/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol similarity index 68% rename from testdata/cheats/ExpectCall.t.sol rename to testdata/default/cheats/ExpectCall.t.sol index 9508f63e6dbcd..01a95b4277e01 100644 --- a/testdata/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Contract { function numberA() public pure returns (uint256) { @@ -50,6 +50,16 @@ contract NestedContract { } } +contract SimpleCall { + function call() public {} +} + +contract ProxyWithDelegateCall { + function delegateCall(SimpleCall simpleCall) public { + address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); + } +} + contract ExpectCallTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -69,12 +79,6 @@ contract ExpectCallTest is DSTest { this.exposed_callTargetNTimes(target, 1, 2, 1); } - function testFailExpectCallDirectly() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); - target.add(1, 2); - } - function testExpectMultipleCallsWithData() public { Contract target = new Contract(); vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); @@ -96,21 +100,6 @@ contract ExpectCallTest is DSTest { this.exposed_callTargetNTimes(target, 1, 2, 3); } - function testFailExpectMultipleCallsWithDataAdditive() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - // Not enough calls to satisfy the additive expectCall, which expects 3 calls. - this.exposed_callTargetNTimes(target, 1, 2, 2); - } - - function testFailExpectCallWithData() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); - this.exposed_callTargetNTimes(target, 3, 3, 1); - } - function testExpectInnerCall() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); @@ -122,15 +111,6 @@ contract ExpectCallTest is DSTest { target.sum(); } - function testFailExpectInnerCall() public { - Contract inner = new Contract(); - NestedContract target = new NestedContract(inner); - - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector)); - - this.exposed_failExpectInnerCall(target); - } - function exposed_failExpectInnerCall(NestedContract target) public { // this function does not call inner target.hello(); @@ -169,29 +149,12 @@ contract ExpectCallTest is DSTest { this.exposed_callTargetNTimes(target, 5, 5, 1); } - function testFailExpectSelectorCall() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); - } - - function testFailExpectCallWithMoreParameters() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 3, 3, 3)); - target.add(3, 3); - this.exposed_callTargetNTimes(target, 3, 3, 1); - } - function testExpectCallWithValue() public { Contract target = new Contract(); vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); this.exposed_expectCallWithValue(target, 1, 2); } - function testFailExpectCallValue() public { - Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); - } - function testExpectCallWithValueWithoutParameters() public { Contract target = new Contract(); vm.expectCall(address(target), 3, abi.encodeWithSelector(target.pay.selector)); @@ -220,13 +183,6 @@ contract ExpectCallTest is DSTest { target.addHardGasLimit(); } - function testFailExpectCallWithNoValueAndWrongGas() public { - Contract inner = new Contract(); - NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); - this.exposed_addHardGasLimit(target); - } - function testExpectCallWithValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); @@ -241,19 +197,12 @@ contract ExpectCallTest is DSTest { this.exposed_addHardGasLimit(target); } - function testFailExpectCallWithNoValueAndWrongMinGas() public { - Contract inner = new Contract(); - NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1)); - this.exposed_addHardGasLimit(target); - } - - /// Ensure that you cannot use expectCall with an expectRevert. - function testFailExpectCallWithRevertDisallowed() public { - Contract target = new Contract(); - vm.expectRevert(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); - this.exposed_callTargetNTimes(target, 5, 5, 1); + /// Ensure expectCall works for Proxy DelegateCalls. Ref: + function testExpectCallForProxyDelegateCall() public { + ProxyWithDelegateCall proxyWithDelegateCall = new ProxyWithDelegateCall(); + SimpleCall simpleCall = new SimpleCall(); + vm.expectCall(address(simpleCall), abi.encodeWithSignature("call()")); + proxyWithDelegateCall.delegateCall(simpleCall); } } @@ -278,12 +227,6 @@ contract ExpectCallCountTest is DSTest { target.add(3, 3); } - function testFailExpectCallCountWithWrongCount() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); - target.add(1, 2); - } - function testExpectCountInnerCall() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); @@ -291,16 +234,6 @@ contract ExpectCallCountTest is DSTest { target.sum(); } - function testFailExpectCountInnerCall() public { - Contract inner = new Contract(); - NestedContract target = new NestedContract(inner); - - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 1); - - // this function does not call inner - target.hello(); - } - function testExpectCountInnerAndOuterCalls() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); @@ -329,12 +262,6 @@ contract ExpectCallCountTest is DSTest { this.exposed_pay{value: 2}(target, 2, 2); } - function testFailExpectCallCountValue() public { - Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 1); - this.exposed_pay{value: 2}(target, 2, 2); - } - function testExpectCallCountWithValueWithoutParameters() public { Contract target = new Contract(); vm.expectCall(address(target), 3, abi.encodeWithSelector(target.pay.selector), 3); @@ -379,13 +306,6 @@ contract ExpectCallCountTest is DSTest { this.exposed_addHardGasLimit(target, 1); } - function testFailExpectCallCountWithNoValueAndWrongGas() public { - Contract inner = new Contract(); - NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); - this.exposed_addHardGasLimit(target, 2); - } - function testExpectCallCountWithValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); @@ -410,13 +330,6 @@ contract ExpectCallCountTest is DSTest { vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); this.exposed_addHardGasLimit(target, 1); } - - function testFailExpectCallCountWithNoValueAndWrongMinGas() public { - Contract inner = new Contract(); - NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); - this.exposed_addHardGasLimit(target, 1); - } } contract ExpectCallMixedTest is DSTest { @@ -428,36 +341,10 @@ contract ExpectCallMixedTest is DSTest { } } - function testFailOverrideNoCountWithCount() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - // You should not be able to overwrite a expectCall that had no count with some count. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); - this.exposed_callTargetNTimes(target, 1, 2, 2); - } - - function testFailOverrideCountWithCount() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); - // You should not be able to overwrite a expectCall that had a count with some count. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); - target.add(1, 2); - target.add(1, 2); - } - - function testFailOverrideCountWithNoCount() public { - Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); - // You should not be able to overwrite a expectCall that had a count with no count. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - target.add(1, 2); - target.add(1, 2); - } - function testExpectMatchPartialAndFull() public { Contract target = new Contract(); vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector), 2); - // Even if a partial match is speciifed, you should still be able to look for full matches + // Even if a partial match is specified, you should still be able to look for full matches // as one does not override the other. vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); this.exposed_expectMatchPartialAndFull(target); @@ -471,7 +358,7 @@ contract ExpectCallMixedTest is DSTest { function testExpectMatchPartialAndFullFlipped() public { Contract target = new Contract(); vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); - // Even if a partial match is speciifed, you should still be able to look for full matches + // Even if a partial match is specified, you should still be able to look for full matches // as one does not override the other. vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); this.exposed_expectMatchPartialAndFullFlipped(target); diff --git a/testdata/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol similarity index 67% rename from testdata/cheats/ExpectEmit.t.sol rename to testdata/default/cheats/ExpectEmit.t.sol index 599a14e808c46..b6b09a8a8db70 100644 --- a/testdata/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { uint256 public thing; @@ -28,6 +28,12 @@ contract Emitter { emit Something(topic1, topic2, topic3, data); } + function emitNEvents(uint256 topic1, uint256 topic2, uint256 topic3, uint256 data, uint256 n) public { + for (uint256 i = 0; i < n; i++) { + emit Something(topic1, topic2, topic3, data); + } + } + function emitMultiple( uint256[2] memory topic1, uint256[2] memory topic2, @@ -133,15 +139,6 @@ contract ExpectEmitTest is DSTest { emit A(1); } - function testFailExpectEmitDanglingNoReference() public { - vm.expectEmit(false, false, false, false); - } - - function testFailExpectEmitDanglingWithReference() public { - vm.expectEmit(false, false, false, false); - emit Something(1, 2, 3, 4); - } - /// The topics that are not checked are altered to be incorrect /// compared to the reference. function testExpectEmit( @@ -165,31 +162,6 @@ contract ExpectEmitTest is DSTest { emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); } - /// The topics that are checked are altered to be incorrect - /// compared to the reference. - function testFailExpectEmit( - bool checkTopic1, - bool checkTopic2, - bool checkTopic3, - bool checkData, - uint128 topic1, - uint128 topic2, - uint128 topic3, - uint128 data - ) public { - vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); - - uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); - uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); - uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); - uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); - - vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); - - emit Something(topic1, topic2, topic3, data); - emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); - } - /// The topics that are checked are altered to be incorrect /// compared to the reference. function testExpectEmitNested( @@ -215,32 +187,6 @@ contract ExpectEmitTest is DSTest { emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); } - /// The topics that are checked are altered to be incorrect - /// compared to the reference. - function testFailExpectEmitNested( - bool checkTopic1, - bool checkTopic2, - bool checkTopic3, - bool checkData, - uint128 topic1, - uint128 topic2, - uint128 topic3, - uint128 data - ) public { - vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); - Emitter inner = new Emitter(); - - uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); - uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); - uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); - uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); - - vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); - - emit Something(topic1, topic2, topic3, data); - emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); - } - function testExpectEmitMultiple() public { vm.expectEmit(); emit Something(1, 2, 3, 4); @@ -281,19 +227,6 @@ contract ExpectEmitTest is DSTest { emitter.emitOutOfExactOrder(); } - function testFailExpectEmitCanMatchWithoutExactOrder() public { - vm.expectEmit(true, true, true, true); - emit Something(1, 2, 3, 4); - // This should fail, as this event is never emitted - // in between the other two Something events. - vm.expectEmit(true, true, true, true); - emit SomethingElse(1); - vm.expectEmit(true, true, true, true); - emit Something(1, 2, 3, 4); - - emitter.emitOutOfExactOrder(); - } - function testExpectEmitCanMatchWithoutExactOrder2() public { vm.expectEmit(true, true, true, true); emit SomethingNonIndexed(1); @@ -317,54 +250,6 @@ contract ExpectEmitTest is DSTest { emitter.emitEvent(1, 2, 3, 4); } - function testFailExpectEmitAddress() public { - vm.expectEmit(address(0)); - emit Something(1, 2, 3, 4); - - emitter.emitEvent(1, 2, 3, 4); - } - - function testFailExpectEmitAddressWithArgs() public { - vm.expectEmit(true, true, true, true, address(0)); - emit Something(1, 2, 3, 4); - - emitter.emitEvent(1, 2, 3, 4); - } - - /// Ref: issue #760 - function testFailLowLevelWithoutEmit() public { - LowLevelCaller caller = new LowLevelCaller(); - - vm.expectEmit(true, true, true, true); - emit Something(1, 2, 3, 4); - - // This does not emit an event, so this test should fail - caller.f(); - } - - function testFailNoEmitDirectlyOnNextCall() public { - LowLevelCaller caller = new LowLevelCaller(); - - vm.expectEmit(true, true, true, true); - emit Something(1, 2, 3, 4); - - // This call does not emit. As emit expects the next call to emit, this should fail. - caller.f(); - // This call does emit, but it is a call later than expected. - emitter.emitEvent(1, 2, 3, 4); - } - - /// Ref: issue #760 - function testFailDifferentIndexedParameters() public { - vm.expectEmit(true, false, false, false); - emit SomethingElse(1); - - // This should fail since `SomethingElse` in the test - // and in the `Emitter` contract have differing - // amounts of indexed topics. - emitter.emitSomethingElse(1); - } - function testCanDoStaticCall() public { vm.expectEmit(true, true, true, true); emit Something(emitter.getVar(), 2, 3, 4); @@ -383,19 +268,6 @@ contract ExpectEmitTest is DSTest { ); } - /// This test should fail, as the call to `changeThing` is not a static call. - /// While we can ignore static calls, we cannot ignore normal calls. - function testFailEmitOnlyAppliesToNextCall() public { - vm.expectEmit(true, true, true, true); - emit Something(1, 2, 3, 4); - // This works because it's a staticcall. - emitter.doesNothing(); - // This should make the test fail as it's a normal call. - emitter.changeThing(block.timestamp); - - emitter.emitEvent(1, 2, 3, 4); - } - /// emitWindow() emits events A, B, C, D, E. /// We should be able to match [A, B, C, D, E] in the correct order. function testCanMatchConsecutiveEvents() public { @@ -449,23 +321,6 @@ contract ExpectEmitTest is DSTest { emitter.emitWindow(); } - /// emitWindow() emits events A, B, C, D, E. - /// We should not be able to match [B, A, C, D, E] as B and A are flipped. - function testFailCanMatchConsecutiveEvents() public { - vm.expectEmit(true, false, false, true); - emit B(2); - vm.expectEmit(true, false, false, true); - emit A(1); - vm.expectEmit(true, false, false, true); - emit C(3); - vm.expectEmit(true, false, false, true); - emit D(4); - vm.expectEmit(true, false, false, true); - emit E(5); - - emitter.emitWindow(); - } - /// emitWindowNested() emits events A, C, E, A, B, C, D, E, the last 5 on an external call. /// We should be able to match the whole event sequence in order no matter if the events /// were emitted deeper into the call tree. @@ -530,34 +385,6 @@ contract ExpectEmitTest is DSTest { emitter.emitNestedWindow(); } - /// emitWindowNested() emits events A, C, E, A, B, C, D, E, the last 5 on an external call. - /// We should NOT be able to match [A, A, E, E], as while we're matching the correct amount - /// of events, they're not in the correct order. It should be [A, E, A, E]. - function testFailMatchRepeatedEventsOutOfOrder() public { - vm.expectEmit(true, false, false, true); - emit A(1); - vm.expectEmit(true, false, false, true); - emit A(1); - vm.expectEmit(true, false, false, true); - emit E(5); - vm.expectEmit(true, false, false, true); - emit E(5); - - emitter.emitNestedWindow(); - } - - /// emitWindow() emits events A, B, C, D, E. - /// We should not be able to match [A, A] even if emitWindow() is called twice, - /// as expectEmit() only works for the next call. - function testFailEventsOnTwoCalls() public { - vm.expectEmit(true, false, false, true); - emit A(1); - vm.expectEmit(true, false, false, true); - emit A(1); - emitter.emitWindow(); - emitter.emitWindow(); - } - /// emitWindowAndOnTest emits [[A, B, C, D, E], [A]]. The interesting bit is that the /// second call that emits [A] is on this same contract. We should still be able to match /// [A, A] as the call made to this contract is still external. @@ -569,15 +396,6 @@ contract ExpectEmitTest is DSTest { emitter.emitWindowAndOnTest(this); } - /// We should not be able to expect emits if we're expecting the function reverts, no matter - /// if the function reverts or not. - function testFailEmitWindowWithRevertDisallowed() public { - vm.expectRevert(); - vm.expectEmit(true, false, false, true); - emit A(1); - emitter.emitWindow(); - } - /// This test will fail if we check that all expected logs were emitted /// after every call from the same depth as the call that invoked the cheatcode. /// @@ -597,3 +415,48 @@ contract ExpectEmitTest is DSTest { // emitter.emitEvent(1, 2, 3, 4); // } } + +contract ExpectEmitCountTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Emitter emitter; + + event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); + + function setUp() public { + emitter = new Emitter(); + } + + function testCountNoEmit() public { + vm.expectEmit(0); + emit Something(1, 2, 3, 4); + emitter.doesNothing(); + } + + function testCountNEmits() public { + uint64 count = 2; + vm.expectEmit(count); + emit Something(1, 2, 3, 4); + emitter.emitNEvents(1, 2, 3, 4, count); + } + + function testCountMoreEmits() public { + uint64 count = 2; + vm.expectEmit(count); + emit Something(1, 2, 3, 4); + emitter.emitNEvents(1, 2, 3, 4, count + 1); + } + + /// Test zero emits from a specific address (emitter). + function testCountNoEmitFromAddress() public { + vm.expectEmit(address(emitter), 0); + emit Something(1, 2, 3, 4); + emitter.doesNothing(); + } + + function testCountEmitsFromAddress() public { + uint64 count = 2; + vm.expectEmit(address(emitter), count); + emit Something(1, 2, 3, 4); + emitter.emitNEvents(1, 2, 3, 4, count); + } +} diff --git a/testdata/default/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol new file mode 100644 index 0000000000000..249fcc7bb3f4c --- /dev/null +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Reverter { + error CustomError(); + + function revertWithMessage(string memory message) public pure { + revert(message); + } + + function doNotRevert() public pure {} + + function panic() public pure returns (uint256) { + return uint256(100) - uint256(101); + } + + function revertWithCustomError() public pure { + revert CustomError(); + } + + function nestedRevert(Reverter inner, string memory message) public pure { + inner.revertWithMessage(message); + } + + function callThenRevert(Dummy dummy, string memory message) public pure { + dummy.callMe(); + revert(message); + } + + function callThenNoRevert(Dummy dummy) public pure { + dummy.callMe(); + } + + function revertWithoutReason() public pure { + revert(); + } +} + +contract ConstructorReverter { + constructor(string memory message) { + revert(message); + } +} + +/// Used to ensure that the dummy data from `vm.expectRevert` +/// is large enough to decode big structs. +/// +/// The struct is based on issue #2454 +struct LargeDummyStruct { + address a; + uint256 b; + bool c; + address d; + address e; + string f; + address[8] g; + address h; + uint256 i; +} + +contract Dummy { + function callMe() public pure returns (string memory) { + return "thanks for calling"; + } + + function largeReturnType() public pure returns (LargeDummyStruct memory) { + revert("reverted with large return type"); + } +} + +contract ExpectRevertTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function shouldRevert() internal { + revert(); + } + + function testExpectRevertString() public { + Reverter reverter = new Reverter(); + vm.expectRevert("revert"); + reverter.revertWithMessage("revert"); + } + + function testShouldFailIfExpectRevertWrongString() public { + Reverter reverter = new Reverter(); + vm.expectRevert("my not so cool error", 0); + reverter.revertWithMessage("my cool error"); + } + + function testExpectRevertConstructor() public { + vm.expectRevert("constructor revert"); + new ConstructorReverter("constructor revert"); + } + + function testExpectRevertBuiltin() public { + Reverter reverter = new Reverter(); + vm.expectRevert(abi.encodeWithSignature("Panic(uint256)", 0x11)); + reverter.panic(); + } + + function testExpectRevertCustomError() public { + Reverter reverter = new Reverter(); + vm.expectRevert(abi.encodePacked(Reverter.CustomError.selector)); + reverter.revertWithCustomError(); + } + + function testExpectRevertNested() public { + Reverter reverter = new Reverter(); + Reverter inner = new Reverter(); + vm.expectRevert("nested revert"); + reverter.nestedRevert(inner, "nested revert"); + } + + function testExpectRevertCallsThenReverts() public { + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + vm.expectRevert("called a function and then reverted"); + reverter.callThenRevert(dummy, "called a function and then reverted"); + } + + function testDummyReturnDataForBigType() public { + Dummy dummy = new Dummy(); + vm.expectRevert("reverted with large return type"); + dummy.largeReturnType(); + } + + function testExpectRevertNoReason() public { + Reverter reverter = new Reverter(); + vm.expectRevert(bytes("")); + reverter.revertWithoutReason(); + } + + function testExpectRevertAnyRevert() public { + vm.expectRevert(); + new ConstructorReverter("hello this is a revert message"); + + Reverter reverter = new Reverter(); + vm.expectRevert(); + reverter.revertWithMessage("this is also a revert message"); + + vm.expectRevert(); + reverter.panic(); + + vm.expectRevert(); + reverter.revertWithCustomError(); + + Reverter reverter2 = new Reverter(); + vm.expectRevert(); + reverter.nestedRevert(reverter2, "this too is a revert message"); + + Dummy dummy = new Dummy(); + vm.expectRevert(); + reverter.callThenRevert(dummy, "revert message 4 i ran out of synonims for also"); + + vm.expectRevert(); + reverter.revertWithoutReason(); + } + + function testexpectCheatcodeRevert() public { + vm._expectCheatcodeRevert('JSON value at ".a" is not an object'); + vm.parseJsonKeys('{"a": "b"}', ".a"); + } +} + +contract AContract { + BContract bContract; + CContract cContract; + + constructor(BContract _bContract, CContract _cContract) { + bContract = _bContract; + cContract = _cContract; + } + + function callAndRevert() public pure { + require(1 > 2, "Reverted by AContract"); + } + + function callAndRevertInBContract() public { + bContract.callAndRevert(); + } + + function callAndRevertInCContract() public { + cContract.callAndRevert(); + } + + function callAndRevertInCContractThroughBContract() public { + bContract.callAndRevertInCContract(); + } + + function createDContract() public { + new DContract(); + } + + function createDContractThroughBContract() public { + bContract.createDContract(); + } + + function createDContractThroughCContract() public { + cContract.createDContract(); + } + + function doNotRevert() public {} +} + +contract BContract { + CContract cContract; + + constructor(CContract _cContract) { + cContract = _cContract; + } + + function callAndRevert() public pure { + require(1 > 2, "Reverted by BContract"); + } + + function callAndRevertInCContract() public { + this.doNotRevert(); + cContract.doNotRevert(); + cContract.callAndRevert(); + } + + function createDContract() public { + this.doNotRevert(); + cContract.doNotRevert(); + new DContract(); + } + + function createDContractThroughCContract() public { + this.doNotRevert(); + cContract.doNotRevert(); + cContract.createDContract(); + } + + function doNotRevert() public {} +} + +contract CContract { + error CContractError(string reason); + + function callAndRevert() public pure { + revert CContractError("Reverted by CContract"); + } + + function createDContract() public { + new DContract(); + } + + function doNotRevert() public {} +} + +contract DContract { + constructor() { + require(1 > 2, "Reverted by DContract"); + } +} + +contract ExpectRevertWithReverterTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + error CContractError(string reason); + + AContract aContract; + BContract bContract; + CContract cContract; + + function setUp() public { + cContract = new CContract(); + bContract = new BContract(cContract); + aContract = new AContract(bContract, cContract); + } + + function testExpectRevertsWithReverter() public { + // Test expect revert with reverter at first call. + vm.expectRevert(address(aContract)); + aContract.callAndRevert(); + // Test expect revert with reverter at second subcall. + vm.expectRevert(address(bContract)); + aContract.callAndRevertInBContract(); + // Test expect revert with partial data match and reverter at third subcall. + vm.expectPartialRevert(CContractError.selector, address(cContract)); + aContract.callAndRevertInCContractThroughBContract(); + // Test expect revert with exact data match and reverter at second subcall. + vm.expectRevert(abi.encodeWithSelector(CContractError.selector, "Reverted by CContract"), address(cContract)); + aContract.callAndRevertInCContract(); + } + + function testExpectRevertsWithReverterInConstructor() public { + // Test expect revert with reverter when constructor reverts. + vm.expectRevert(abi.encodePacked("Reverted by DContract"), address(cContract)); + cContract.createDContract(); + + vm.expectRevert(address(bContract)); + bContract.createDContract(); + vm.expectRevert(address(cContract)); + bContract.createDContractThroughCContract(); + + vm.expectRevert(address(aContract)); + aContract.createDContract(); + vm.expectRevert(address(bContract)); + aContract.createDContractThroughBContract(); + vm.expectRevert(address(cContract)); + aContract.createDContractThroughCContract(); + } +} + +contract ExpectRevertCount is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRevertCountAny() public { + uint64 count = 3; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert2"); + reverter.revertWithMessage("revert3"); + + vm.expectRevert("revert"); + reverter.revertWithMessage("revert"); + } + + function testNoRevert() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(count); + reverter.doNotRevert(); + } + + function testRevertCountSpecific() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert"); + } + + function testNoRevertSpecific() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.doNotRevert(); + } + + function testNoRevertSpecificButDiffRevert() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", count); + reverter.revertWithMessage("revert2"); + } + + function testRevertCountWithConstructor() public { + uint64 count = 1; + vm.expectRevert("constructor revert", count); + new ConstructorReverter("constructor revert"); + } + + function testNoRevertWithConstructor() public { + uint64 count = 0; + vm.expectRevert("constructor revert", count); + new CContract(); + } + + function testRevertCountNestedSpecific() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Reverter inner = new Reverter(); + + vm.expectRevert("nested revert", count); + reverter.revertWithMessage("nested revert"); + reverter.nestedRevert(inner, "nested revert"); + + vm.expectRevert("nested revert", count); + reverter.nestedRevert(inner, "nested revert"); + reverter.nestedRevert(inner, "nested revert"); + } + + function testRevertCountCallsThenReverts() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + + vm.expectRevert("called a function and then reverted", count); + reverter.callThenRevert(dummy, "called a function and then reverted"); + reverter.callThenRevert(dummy, "called a function and then reverted"); + } + + function testNoRevertCall() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + Dummy dummy = new Dummy(); + + vm.expectRevert("called a function and then reverted", count); + reverter.callThenNoRevert(dummy); + } +} + +contract ExpectRevertCountWithReverter is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRevertCountWithReverter() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert"); + } + + function testNoRevertWithReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter.doNotRevert(); + } + + function testNoRevertWithWrongReverter() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + Reverter reverter2 = new Reverter(); + vm.expectRevert(address(reverter), count); + reverter2.revertWithMessage("revert"); // revert from wrong reverter + } + + function testReverterCountWithData() public { + uint64 count = 2; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert"); + reverter.revertWithMessage("revert"); + } + + function testNoReverterCountWithData() public { + uint64 count = 0; + Reverter reverter = new Reverter(); + vm.expectRevert("revert", address(reverter), count); + reverter.doNotRevert(); + + vm.expectRevert("revert", address(reverter), count); + reverter.revertWithMessage("revert2"); + } +} diff --git a/testdata/cheats/Fee.t.sol b/testdata/default/cheats/Fee.t.sol similarity index 78% rename from testdata/cheats/Fee.t.sol rename to testdata/default/cheats/Fee.t.sol index d5104ead376f9..d258eaf13704e 100644 --- a/testdata/cheats/Fee.t.sol +++ b/testdata/default/cheats/Fee.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FeeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Ffi.t.sol b/testdata/default/cheats/Ffi.t.sol similarity index 90% rename from testdata/cheats/Ffi.t.sol rename to testdata/default/cheats/Ffi.t.sol index d6cdc02455113..23ac54e6ace12 100644 --- a/testdata/cheats/Ffi.t.sol +++ b/testdata/default/cheats/Ffi.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FfiTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Fork.t.sol b/testdata/default/cheats/Fork.t.sol similarity index 84% rename from testdata/cheats/Fork.t.sol rename to testdata/default/cheats/Fork.t.sol index f59556ad7f60f..2f2e627de131a 100644 --- a/testdata/cheats/Fork.t.sol +++ b/testdata/default/cheats/Fork.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; interface IWETH { function deposit() external payable; @@ -23,8 +23,8 @@ contract ForkTest is DSTest { // this will create two _different_ forks during setup function setUp() public { - forkA = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", mainblock); - forkB = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/9VWGraLx0tMiSWx05WH-ywgSVmMxs66W", mainblock - 1); + forkA = vm.createFork("mainnet", mainblock); + forkB = vm.createFork("mainnet2", mainblock - 1); testValue = 999; } @@ -35,7 +35,7 @@ contract ForkTest is DSTest { // ensures we can create and select in one step function testCreateSelect() public { - uint256 fork = vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf"); + uint256 fork = vm.createSelectFork("mainnet"); assertEq(fork, vm.activeFork()); } @@ -111,4 +111,15 @@ contract ForkTest is DSTest { uint256 expected = block.chainid; assertEq(newChainId, expected); } + + // ensures forks change chain ids automatically + function testCanAutoUpdateChainId() public { + vm.createSelectFork("sepolia"); + assertEq(block.chainid, 11155111); + } + + // ensures forks storage is cached at block + function testStorageCaching() public { + vm.createSelectFork("mainnet", 19800000); + } } diff --git a/testdata/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol similarity index 57% rename from testdata/cheats/Fork2.t.sol rename to testdata/default/cheats/Fork2.t.sol index c090255f2e1e4..d0703ce7fa6ce 100644 --- a/testdata/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -1,8 +1,9 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "../logs/console.sol"; +import "cheats/Vm.sol"; struct MyStruct { uint256 value; @@ -34,8 +35,8 @@ contract ForkTest is DSTest { // this will create two _different_ forks during setup function setUp() public { - mainnetFork = vm.createFork("rpcAlias"); - optimismFork = vm.createFork("https://opt-mainnet.g.alchemy.com/v2/UVatYU2Ax0rX6bDiqddeTRDdcCxzdpoE"); + mainnetFork = vm.createFork("mainnet"); + optimismFork = vm.createFork("optimism"); } // ensures forks use different ids @@ -56,12 +57,12 @@ contract ForkTest is DSTest { } function testCanCreateSelect() public { - uint256 anotherFork = vm.createSelectFork("rpcAlias"); + uint256 anotherFork = vm.createSelectFork("mainnet"); assertEq(anotherFork, vm.activeFork()); } // ensures forks have different block hashes - function testBlockNumbersMimatch() public { + function testBlockNumbersMismatch() public { vm.selectFork(mainnetFork); uint256 num = block.number; bytes32 mainHash = blockhash(block.number - 1); @@ -74,12 +75,12 @@ contract ForkTest is DSTest { // test that we can switch between forks, and "roll" blocks function testCanRollFork() public { vm.selectFork(mainnetFork); - uint256 otherMain = vm.createFork("rpcAlias", block.number - 1); + uint256 otherMain = vm.createFork("mainnet", block.number - 1); vm.selectFork(otherMain); uint256 mainBlock = block.number; uint256 forkedBlock = 14608400; - uint256 otherFork = vm.createFork("rpcAlias", forkedBlock); + uint256 otherFork = vm.createFork("mainnet", forkedBlock); vm.selectFork(otherFork); assertEq(block.number, forkedBlock); @@ -100,7 +101,7 @@ contract ForkTest is DSTest { uint256 block = 16261704; // fork until previous block - uint256 fork = vm.createSelectFork("rpcAlias", block - 1); + uint256 fork = vm.createSelectFork("mainnet", block - 1); // block transactions in order: https://beaconcha.in/block/16261704#transactions // run transactions from current block until tx @@ -165,6 +166,80 @@ contract ForkTest is DSTest { // this will revert since `dummy` does not exists on the currently active fork string memory msg2 = dummy.hello(); } + + struct EthGetLogsJsonParseable { + bytes32 blockHash; + bytes blockNumber; // Should be uint256, but is returned from RPC in 0x... format + bytes32 data; // Should be bytes, but in our particular example is bytes32 + address emitter; + bytes logIndex; // Should be uint256, but is returned from RPC in 0x... format + bool removed; + bytes32[] topics; + bytes32 transactionHash; + bytes transactionIndex; // Should be uint256, but is returned from RPC in 0x... format + } + + function testEthGetLogs() public { + vm.selectFork(mainnetFork); + address weth = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + bytes32 withdrawalTopic = 0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65; + uint256 blockNumber = 17623835; + + string memory path = "fixtures/Rpc/eth_getLogs.json"; + string memory file = vm.readFile(path); + bytes memory parsed = vm.parseJson(file); + EthGetLogsJsonParseable[] memory fixtureLogs = abi.decode(parsed, (EthGetLogsJsonParseable[])); + + bytes32[] memory topics = new bytes32[](1); + topics[0] = withdrawalTopic; + Vm.EthGetLogs[] memory logs = vm.eth_getLogs(blockNumber, blockNumber, weth, topics); + assertEq(logs.length, 3); + + for (uint256 i = 0; i < logs.length; i++) { + Vm.EthGetLogs memory log = logs[i]; + assertEq(log.emitter, fixtureLogs[i].emitter); + + string memory i_str; + if (i == 0) i_str = "0"; + if (i == 1) i_str = "1"; + if (i == 2) i_str = "2"; + + assertEq(log.blockNumber, vm.parseJsonUint(file, string.concat("[", i_str, "].blockNumber"))); + assertEq(log.logIndex, vm.parseJsonUint(file, string.concat("[", i_str, "].logIndex"))); + assertEq(log.transactionIndex, vm.parseJsonUint(file, string.concat("[", i_str, "].transactionIndex"))); + + assertEq(log.blockHash, fixtureLogs[i].blockHash); + assertEq(log.removed, fixtureLogs[i].removed); + assertEq(log.transactionHash, fixtureLogs[i].transactionHash); + + // In this specific example, the log.data is bytes32 + assertEq(bytes32(log.data), fixtureLogs[i].data); + assertEq(log.topics.length, 2); + assertEq(log.topics[0], withdrawalTopic); + assertEq(log.topics[1], fixtureLogs[i].topics[1]); + } + } + + function testRpc() public { + // balance at block + vm.selectFork(mainnetFork); + string memory path = "fixtures/Rpc/balance_params.json"; + string memory file = vm.readFile(path); + bytes memory result = vm.rpc("eth_getBalance", file); + assertEq(hex"10b7c11bcb51e6", result); + } + + function testRpcWithUrl() public { + bytes memory result = vm.rpc("mainnet", "eth_blockNumber", "[]"); + uint256 decodedResult = vm.parseUint(vm.toString(result)); + assertGt(decodedResult, 20_000_000); + } + + // + function testRpcTransactionByHash() public { + string memory param = string.concat('["0xe1a0fba63292976050b2fbf4379a1901691355ed138784b4e0d1854b4cf9193e"]'); + vm.rpc("sepolia", "eth_getTransactionByHash", param); + } } contract DummyContract { diff --git a/testdata/default/cheats/Fs.t.sol b/testdata/default/cheats/Fs.t.sol new file mode 100644 index 0000000000000..b4882525944cf --- /dev/null +++ b/testdata/default/cheats/Fs.t.sol @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract FsTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + bytes constant FOUNDRY_TOML_ACCESS_ERR = "access to foundry.toml is not allowed"; + bytes constant FOUNDRY_READ_ERR = "the path /etc/hosts is not allowed to be accessed for read operations"; + bytes constant FOUNDRY_READ_DIR_ERR = "the path /etc is not allowed to be accessed for read operations"; + bytes constant FOUNDRY_WRITE_ERR = "the path /etc/hosts is not allowed to be accessed for write operations"; + + function assertEntry(Vm.DirEntry memory entry, uint64 depth, bool dir) private { + assertEq(entry.errorMessage, ""); + assertEq(entry.depth, depth); + assertEq(entry.isDir, dir); + assertEq(entry.isSymlink, false); + } + + function testReadFile() public { + string memory path = "fixtures/File/read.txt"; + + assertEq(vm.readFile(path), "hello readable world\nthis is the second line!"); + + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); + vm.readFile("/etc/hosts"); + + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); + vm.readFileBinary("/etc/hosts"); + } + + function testReadLine() public { + string memory path = "fixtures/File/read.txt"; + + assertEq(vm.readLine(path), "hello readable world"); + assertEq(vm.readLine(path), "this is the second line!"); + assertEq(vm.readLine(path), ""); + + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); + vm.readLine("/etc/hosts"); + } + + function testWriteFile() public { + string memory path = "fixtures/File/write_file.txt"; + string memory data = "hello writable world"; + vm.writeFile(path, data); + + assertEq(vm.readFile(path), data); + + vm.removeFile(path); + + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); + vm.writeFile("/etc/hosts", "malicious stuff"); + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); + vm.writeFileBinary("/etc/hosts", "malicious stuff"); + } + + function testCopyFile() public { + string memory from = "fixtures/File/read.txt"; + string memory to = "fixtures/File/copy.txt"; + uint64 copied = vm.copyFile(from, to); + assertEq(vm.fsMetadata(to).length, uint256(copied)); + assertEq(vm.readFile(from), vm.readFile(to)); + vm.removeFile(to); + } + + function testWriteLine() public { + string memory path = "fixtures/File/write_line.txt"; + + string memory line1 = "first line"; + vm.writeLine(path, line1); + + string memory line2 = "second line"; + vm.writeLine(path, line2); + + assertEq(vm.readFile(path), string.concat(line1, "\n", line2, "\n")); + + vm.removeFile(path); + + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); + vm.writeLine("/etc/hosts", "malicious stuff"); + } + + function testCloseFile() public { + string memory path = "fixtures/File/read.txt"; + + assertEq(vm.readLine(path), "hello readable world"); + vm.closeFile(path); + assertEq(vm.readLine(path), "hello readable world"); + } + + function testRemoveFile() public { + string memory path = "fixtures/File/remove_file.txt"; + string memory data = "hello writable world"; + + vm.writeFile(path, data); + assertEq(vm.readLine(path), data); + + vm.removeFile(path); + vm.writeLine(path, data); + assertEq(vm.readLine(path), data); + + vm.removeFile(path); + + vm._expectCheatcodeRevert(FOUNDRY_WRITE_ERR); + vm.removeFile("/etc/hosts"); + } + + function testWriteLineFoundrytoml() public { + string memory root = vm.projectRoot(); + string memory foundryToml = string.concat(root, "/", "foundry.toml"); + + vm._expectCheatcodeRevert(); + vm.writeLine(foundryToml, "\nffi = true\n"); + + vm._expectCheatcodeRevert(); + vm.writeLine("foundry.toml", "\nffi = true\n"); + + vm._expectCheatcodeRevert(); + vm.writeLine("./foundry.toml", "\nffi = true\n"); + + vm._expectCheatcodeRevert(); + vm.writeLine("./Foundry.toml", "\nffi = true\n"); + } + + function testWriteFoundrytoml() public { + string memory root = vm.projectRoot(); + string memory foundryToml = string.concat(root, "/", "foundry.toml"); + + vm._expectCheatcodeRevert(); + vm.writeFile(foundryToml, "\nffi = true\n"); + + vm._expectCheatcodeRevert(); + vm.writeFile("foundry.toml", "\nffi = true\n"); + + vm._expectCheatcodeRevert(); + vm.writeFile("./foundry.toml", "\nffi = true\n"); + + vm._expectCheatcodeRevert(); + vm.writeFile("./Foundry.toml", "\nffi = true\n"); + } + + function testReadDir() public { + string memory path = "fixtures/Dir"; + + { + Vm.DirEntry[] memory entries = vm.readDir(path); + assertEq(entries.length, 2); + assertEntry(entries[0], 1, false); + assertEntry(entries[1], 1, true); + + Vm.DirEntry[] memory entries2 = vm.readDir(path, 1); + assertEq(entries2.length, 2); + assertEq(entries[0].path, entries2[0].path); + assertEq(entries[1].path, entries2[1].path); + + string memory contents = vm.readFile(entries[0].path); + assertEq(contents, unicode"Wow! 😀"); + } + + { + Vm.DirEntry[] memory entries = vm.readDir(path, 2); + assertEq(entries.length, 4); + assertEntry(entries[2], 2, false); + assertEntry(entries[3], 2, true); + } + + { + Vm.DirEntry[] memory entries = vm.readDir(path, 3); + assertEq(entries.length, 5); + assertEntry(entries[4], 3, false); + } + + vm._expectCheatcodeRevert(FOUNDRY_READ_DIR_ERR); + vm.readDir("/etc"); + } + + function testCreateRemoveDir() public { + string memory path = "fixtures/Dir/remove_dir"; + string memory child = string.concat(path, "/child"); + + vm.createDir(path, false); + assertEq(vm.fsMetadata(path).isDir, true); + + vm.removeDir(path, false); + vm._expectCheatcodeRevert(); + vm.fsMetadata(path); + + // reverts because not recursive + vm._expectCheatcodeRevert(); + vm.createDir(child, false); + + vm.createDir(child, true); + assertEq(vm.fsMetadata(child).isDir, true); + + // deleted both, recursively + vm.removeDir(path, true); + vm._expectCheatcodeRevert(); + vm.fsMetadata(path); + vm._expectCheatcodeRevert(); + vm.fsMetadata(child); + } + + function testFsMetadata() public { + Vm.FsMetadata memory metadata = vm.fsMetadata("fixtures/File"); + assertEq(metadata.isDir, true); + assertEq(metadata.isSymlink, false); + assertEq(metadata.readOnly, false); + // These fields aren't available on all platforms, default to zero + // assertGt(metadata.length, 0); + // assertGt(metadata.modified, 0); + // assertGt(metadata.accessed, 0); + // assertGt(metadata.created, 0); + + metadata = vm.fsMetadata("fixtures/File/read.txt"); + assertEq(metadata.isDir, false); + assertEq(metadata.isSymlink, false); + // This test will fail on windows if we compared to 45, as windows + // ends files with both line feed and carriage return, unlike + // unix which only uses the first one. + assertTrue(metadata.length == 45 || metadata.length == 46); + + metadata = vm.fsMetadata("fixtures/File/symlink"); + assertEq(metadata.isDir, false); + // TODO: symlinks are canonicalized away in `ensure_path_allowed` + // assertEq(metadata.isSymlink, true); + + vm._expectCheatcodeRevert(); + vm.fsMetadata("../not-found"); + + vm._expectCheatcodeRevert(FOUNDRY_READ_ERR); + vm.fsMetadata("/etc/hosts"); + } + + function testExists() public { + string memory validFilePath = "fixtures/File/read.txt"; + assertTrue(vm.exists(validFilePath)); + assertTrue(vm.exists(validFilePath)); + + string memory validDirPath = "fixtures/File"; + assertTrue(vm.exists(validDirPath)); + assertTrue(vm.exists(validDirPath)); + + string memory invalidPath = "fixtures/File/invalidfile.txt"; + assertTrue(vm.exists(invalidPath) == false); + assertTrue(vm.exists(invalidPath) == false); + } + + function testIsFile() public { + string memory validFilePath = "fixtures/File/read.txt"; + assertTrue(vm.isFile(validFilePath)); + assertTrue(vm.isFile(validFilePath)); + + string memory invalidFilePath = "fixtures/File/invalidfile.txt"; + assertTrue(vm.isFile(invalidFilePath) == false); + assertTrue(vm.isFile(invalidFilePath) == false); + + string memory dirPath = "fixtures/File"; + assertTrue(vm.isFile(dirPath) == false); + assertTrue(vm.isFile(dirPath) == false); + } + + function testIsDir() public { + string memory validDirPath = "fixtures/File"; + assertTrue(vm.isDir(validDirPath)); + assertTrue(vm.isDir(validDirPath)); + + string memory invalidDirPath = "fixtures/InvalidDir"; + assertTrue(vm.isDir(invalidDirPath) == false); + assertTrue(vm.isDir(invalidDirPath) == false); + + string memory filePath = "fixtures/File/read.txt"; + assertTrue(vm.isDir(filePath) == false); + assertTrue(vm.isDir(filePath) == false); + } +} diff --git a/testdata/cheats/GasMetering.t.sol b/testdata/default/cheats/GasMetering.t.sol similarity index 87% rename from testdata/cheats/GasMetering.t.sol rename to testdata/default/cheats/GasMetering.t.sol index 030e3e84e51b0..3cb105d236f02 100644 --- a/testdata/cheats/GasMetering.t.sol +++ b/testdata/default/cheats/GasMetering.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract B { function a() public returns (uint256) { @@ -16,21 +16,21 @@ contract GasMeteringTest is DSTest { function testGasMetering() public { uint256 gas_start = gasleft(); - addInLoop(); + consumeGas(); uint256 gas_end_normal = gas_start - gasleft(); vm.pauseGasMetering(); uint256 gas_start_not_metered = gasleft(); - addInLoop(); + consumeGas(); uint256 gas_end_not_metered = gas_start_not_metered - gasleft(); vm.resumeGasMetering(); uint256 gas_start_metered = gasleft(); - addInLoop(); + consumeGas(); uint256 gas_end_resume_metered = gas_start_metered - gasleft(); @@ -76,11 +76,9 @@ contract GasMeteringTest is DSTest { assertEq(gas_end_not_metered, 0); } - function addInLoop() internal returns (uint256) { - uint256 b; + function consumeGas() internal returns (uint256 x) { for (uint256 i; i < 10000; i++) { - b + i; + x += i; } - return b; } } diff --git a/testdata/default/cheats/GetArtifactPath.t.sol b/testdata/default/cheats/GetArtifactPath.t.sol new file mode 100644 index 0000000000000..4b0df4ba6e6ec --- /dev/null +++ b/testdata/default/cheats/GetArtifactPath.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"; + +contract DummyForGetArtifactPath {} + +contract GetArtifactPathTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetArtifactPathByCode() public { + DummyForGetArtifactPath dummy = new DummyForGetArtifactPath(); + bytes memory dummyCreationCode = type(DummyForGetArtifactPath).creationCode; + + string memory path = vm.getArtifactPathByCode(dummyCreationCode); + assertTrue(vm.contains(path, "/out/default/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); + } + + function testGetArtifactPathByDeployedCode() public { + DummyForGetArtifactPath dummy = new DummyForGetArtifactPath(); + bytes memory dummyRuntimeCode = address(dummy).code; + + string memory path = vm.getArtifactPathByDeployedCode(dummyRuntimeCode); + assertTrue(vm.contains(path, "/out/default/GetArtifactPath.t.sol/DummyForGetArtifactPath.json")); + } +} diff --git a/testdata/default/cheats/GetBlockTimestamp.t.sol b/testdata/default/cheats/GetBlockTimestamp.t.sol new file mode 100644 index 0000000000000..edeaa0de79841 --- /dev/null +++ b/testdata/default/cheats/GetBlockTimestamp.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetBlockTimestampTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetTimestamp() public { + uint256 timestamp = vm.getBlockTimestamp(); + assertEq(timestamp, 1, "timestamp should be 1"); + } + + function testGetTimestampWithWarp() public { + assertEq(vm.getBlockTimestamp(), 1, "timestamp should be 1"); + vm.warp(10); + assertEq(vm.getBlockTimestamp(), 10, "warp failed"); + } + + function testGetTimestampWithWarpFuzzed(uint128 jump) public { + uint256 pre = vm.getBlockTimestamp(); + vm.warp(pre + jump); + assertEq(vm.getBlockTimestamp(), pre + jump, "warp failed"); + } +} diff --git a/testdata/cheats/GetCode.t.sol b/testdata/default/cheats/GetCode.t.sol similarity index 74% rename from testdata/cheats/GetCode.t.sol rename to testdata/default/cheats/GetCode.t.sol index ee62fc44d61fd..6020e4f1fd5bb 100644 --- a/testdata/cheats/GetCode.t.sol +++ b/testdata/default/cheats/GetCode.t.sol @@ -1,8 +1,12 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity =0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} + +contract TestContractGetCode {} contract GetCodeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -43,6 +47,8 @@ contract GetCodeTest is DSTest { assertEq(string(fullPath), expected, "code for full path was incorrect"); } + // TODO: Huff uses its own ABI. + /* function testGetCodeHuffArtifact() public { string memory path = "fixtures/GetCode/HuffWorkingContract.json"; bytes memory bytecode = vm.getCode(path); @@ -63,8 +69,29 @@ contract GetCodeTest is DSTest { // compare the loaded code to the actual deployed code assertEq(string(deployedCode), string(deployed.code), "deployedCode for path was incorrect"); } + */ - function testFailGetUnlinked() public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfGetUnlinked() public { + vm.expectRevert("vm.getCode: no matching artifact found"); vm.getCode("UnlinkedContract.sol"); } + + function testWithVersion() public { + bytes memory code = vm.getCode("cheats/GetCode.t.sol:TestContract:0.8.18"); + assertEq(type(TestContract).creationCode, code); + + vm._expectCheatcodeRevert("no matching artifact found"); + vm.getCode("cheats/GetCode.t.sol:TestContract:0.8.19"); + } + + function testByName() public { + bytes memory code = vm.getCode("TestContractGetCode"); + assertEq(type(TestContractGetCode).creationCode, code); + } + + function testByNameAndVersion() public { + bytes memory code = vm.getCode("TestContractGetCode:0.8.18"); + assertEq(type(TestContractGetCode).creationCode, code); + } } diff --git a/testdata/cheats/GetDeployedCode.t.sol b/testdata/default/cheats/GetDeployedCode.t.sol similarity index 85% rename from testdata/cheats/GetDeployedCode.t.sol rename to testdata/default/cheats/GetDeployedCode.t.sol index 926945049bc0f..295d2ae8f3f38 100644 --- a/testdata/cheats/GetDeployedCode.t.sol +++ b/testdata/default/cheats/GetDeployedCode.t.sol @@ -1,8 +1,10 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity =0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} contract GetDeployedCodeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -36,6 +38,16 @@ contract GetDeployedCodeTest is DSTest { emit Payload(address(this), address(0), "hello"); over.emitPayload(address(0), "hello"); } + + function testWithVersion() public { + TestContract test = new TestContract(); + bytes memory code = vm.getDeployedCode("cheats/GetDeployedCode.t.sol:TestContract:0.8.18"); + + assertEq(address(test).code, code); + + vm._expectCheatcodeRevert("no matching artifact found"); + vm.getDeployedCode("cheats/GetDeployedCode.t.sol:TestContract:0.8.19"); + } } interface Override { diff --git a/testdata/default/cheats/GetFoundryVersion.t.sol b/testdata/default/cheats/GetFoundryVersion.t.sol new file mode 100644 index 0000000000000..ac30bb8491874 --- /dev/null +++ b/testdata/default/cheats/GetFoundryVersion.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetFoundryVersionTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetFoundryVersion() public view { + // (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug) + string memory fullVersionString = vm.getFoundryVersion(); + + // Step 1: Split the version at "+" + string[] memory plusSplit = vm.split(fullVersionString, "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + + // Step 2: Extract parts + string memory semanticVersion = plusSplit[0]; // "0.3.0-dev" + string memory metadata = plusSplit[1]; // "34389e7850.1737037814.debug" + + // Step 3: Further split metadata by "." + string[] memory metadataComponents = vm.split(metadata, "."); + require(metadataComponents.length == 3, "Invalid version format: Metadata should have 3 components"); + + // Step 4: Extract values + string memory commitHash = metadataComponents[0]; // "34389e7850" + string memory timestamp = metadataComponents[1]; // "1737037814" + string memory buildType = metadataComponents[2]; // "debug" + + // Validate semantic version (e.g., "0.3.0-stable" or "0.3.0-nightly") + require(bytes(semanticVersion).length > 0, "Semantic version is empty"); + + // Validate commit hash (should be exactly 10 characters) + require(bytes(commitHash).length == 10, "Invalid commit hash length"); + + // Validate UNIX timestamp (numeric) + uint256 buildUnixTimestamp = vm.parseUint(timestamp); + uint256 minimumAcceptableTimestamp = 1700000000; // Adjust as needed + require(buildUnixTimestamp >= minimumAcceptableTimestamp, "Build timestamp is too old"); + + // Validate build profile (e.g., "debug" or "release") + require(bytes(buildType).length > 0, "Build type is empty"); + } + + function testFoundryVersionCmp() public { + // Should return -1 if current version is less than argument + assertEq(vm.foundryVersionCmp("99.0.0"), -1); + + // (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug) + string memory fullVersionString = vm.getFoundryVersion(); + + // Step 1: Split the version at "+" + string[] memory plusSplit = vm.split(fullVersionString, "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + + // Step 2: Extract parts + string memory semanticVersion = plusSplit[0]; // "0.3.0-dev" + string[] memory semanticSplit = vm.split(semanticVersion, "-"); + + semanticVersion = semanticSplit[0]; // "0.3.0" + // Should return 0 if current version is equal to argument + assertEq(vm.foundryVersionCmp(semanticVersion), 0); + + // Should return 1 if current version is greater than argument + assertEq(vm.foundryVersionCmp("0.0.1"), 1); + } + + function testFoundryVersionAtLeast() public { + // Should return false for future versions + assertEq(vm.foundryVersionAtLeast("99.0.0"), false); + + // (e.g. 0.3.0-nightly+3cb96bde9b.1737036656.debug) + string memory fullVersionString = vm.getFoundryVersion(); + + // Step 1: Split the version at "+" + string[] memory plusSplit = vm.split(fullVersionString, "+"); + require(plusSplit.length == 2, "Invalid version format: Missing '+' separator"); + + // Step 2: Extract parts + string memory semanticVersion = plusSplit[0]; // "0.3.0-dev" + string[] memory semanticSplit = vm.split(semanticVersion, "-"); + + semanticVersion = semanticSplit[0]; // "0.3.0" + assertTrue(vm.foundryVersionAtLeast(semanticVersion)); + + // Should return true for past versions + assertTrue(vm.foundryVersionAtLeast("0.2.0")); + } +} diff --git a/testdata/cheats/GetLabel.t.sol b/testdata/default/cheats/GetLabel.t.sol similarity index 79% rename from testdata/cheats/GetLabel.t.sol rename to testdata/default/cheats/GetLabel.t.sol index 0cf3d02423558..c5a5638d36752 100644 --- a/testdata/cheats/GetLabel.t.sol +++ b/testdata/default/cheats/GetLabel.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract GetLabelTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/GetNonce.t.sol b/testdata/default/cheats/GetNonce.t.sol similarity index 78% rename from testdata/cheats/GetNonce.t.sol rename to testdata/default/cheats/GetNonce.t.sol index eb45ef874011b..d4043a59992a8 100644 --- a/testdata/cheats/GetNonce.t.sol +++ b/testdata/default/cheats/GetNonce.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo {} diff --git a/testdata/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol similarity index 51% rename from testdata/cheats/Json.t.sol rename to testdata/default/cheats/Json.t.sol index fca9d8a546a71..ff1b62c6ee8a2 100644 --- a/testdata/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -1,11 +1,93 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.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; @@ -14,39 +96,25 @@ contract ParseJsonTest is DSTest { json = vm.readFile(path); } - function test_uintArray() public { - bytes memory data = vm.parseJson(json, ".uintArray"); - uint256[] memory decodedData = abi.decode(data, (uint256[])); - assertEq(42, decodedData[0]); - assertEq(43, decodedData[1]); - } - - function test_str() public { - bytes memory data = vm.parseJson(json, ".str"); + function test_basicString() public { + bytes memory data = vm.parseJson(json, ".basicString"); string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } - function test_strArray() public { - bytes memory data = vm.parseJson(json, ".strArray"); + function test_null() public { + bytes memory data = vm.parseJson(json, ".null"); + bytes memory decodedData = abi.decode(data, (bytes)); + assertEq(new bytes(0), decodedData); + } + + function test_stringArray() public { + bytes memory data = vm.parseJson(json, ".stringArray"); string[] memory decodedData = abi.decode(data, (string[])); assertEq("hai", decodedData[0]); assertEq("there", decodedData[1]); } - function test_bool() public { - bytes memory data = vm.parseJson(json, ".bool"); - bool decodedData = abi.decode(data, (bool)); - assertTrue(decodedData); - } - - function test_boolArray() public { - bytes memory data = vm.parseJson(json, ".boolArray"); - bool[] memory decodedData = abi.decode(data, (bool[])); - assertTrue(decodedData[0]); - assertTrue(!decodedData[1]); - } - function test_address() public { bytes memory data = vm.parseJson(json, ".address"); address decodedData = abi.decode(data, (address)); @@ -65,18 +133,31 @@ contract ParseJsonTest is DSTest { assertEq("0000000000000000000000000000000000001337", data); } - struct Nested { - uint256 number; - string str; + function test_bool() public { + bytes memory data = vm.parseJson(json, ".boolTrue"); + bool decodedData = abi.decode(data, (bool)); + assertTrue(decodedData); + + data = vm.parseJson(json, ".boolFalse"); + decodedData = abi.decode(data, (bool)); + assertTrue(!decodedData); } - function test_nestedObject() public { - bytes memory data = vm.parseJson(json, ".nestedObject"); - Nested memory nested = abi.decode(data, (Nested)); - assertEq(nested.number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - assertEq(nested.str, "NEST"); + function test_boolArray() public { + bytes memory data = vm.parseJson(json, ".boolArray"); + bool[] memory decodedData = abi.decode(data, (bool[])); + assertTrue(decodedData[0]); + assertTrue(!decodedData[1]); } + function test_uintArray() public { + bytes memory data = vm.parseJson(json, ".uintArray"); + uint256[] memory decodedData = abi.decode(data, (uint256[])); + assertEq(42, decodedData[0]); + assertEq(43, decodedData[1]); + } + + // Object keys are sorted alphabetically, regardless of input. struct Whole { string str; string[] strArray; @@ -85,7 +166,7 @@ contract ParseJsonTest is DSTest { function test_wholeObject() public { // we need to make the path relative to the crate that's running tests for it (forge crate) - string memory path = "fixtures/Json/wholeJson.json"; + string memory path = "fixtures/Json/whole_json.json"; console.log(path); json = vm.readFile(path); bytes memory data = vm.parseJson(json); @@ -98,42 +179,69 @@ contract ParseJsonTest is DSTest { } function test_coercionRevert() public { - vm.expectRevert( - "You can only coerce values or arrays, not JSON objects. The key '.nestedObject' returns an object" - ); - uint256 number = this.parseJsonUint(json, ".nestedObject"); - } - - function parseJsonUint(string memory json, string memory path) public returns (uint256) { - uint256 data = vm.parseJsonUint(json, path); + vm._expectCheatcodeRevert("expected uint256, found JSON object"); + vm.parseJsonUint(json, ".nestedObject"); } function test_coercionUint() public { - uint256 number = vm.parseJsonUint(json, ".hexUint"); + uint256 number = vm.parseJsonUint(json, ".uintHex"); assertEq(number, 1231232); - number = vm.parseJsonUint(json, ".stringUint"); + number = vm.parseJsonUint(json, ".uintString"); assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - number = vm.parseJsonUint(json, ".numberUint"); + number = vm.parseJsonUint(json, ".uintNumber"); assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - uint256[] memory numbers = vm.parseJsonUintArray(json, ".arrayUint"); + uint256[] memory numbers = vm.parseJsonUintArray(json, ".uintArray"); + assertEq(numbers[0], 42); + assertEq(numbers[1], 43); + numbers = vm.parseJsonUintArray(json, ".uintStringArray"); assertEq(numbers[0], 1231232); assertEq(numbers[1], 1231232); assertEq(numbers[2], 1231232); } function test_coercionInt() public { - int256 number = vm.parseJsonInt(json, ".hexInt"); + int256 number = vm.parseJsonInt(json, ".intNumber"); assertEq(number, -12); - number = vm.parseJsonInt(json, ".stringInt"); + number = vm.parseJsonInt(json, ".intString"); + assertEq(number, -12); + number = vm.parseJsonInt(json, ".intHex"); assertEq(number, -12); } function test_coercionBool() public { - bool boolean = vm.parseJsonBool(json, ".booleanString"); + bool boolean = vm.parseJsonBool(json, ".boolTrue"); + assertTrue(boolean); + bool boolFalse = vm.parseJsonBool(json, ".boolFalse"); + assertTrue(!boolFalse); + boolean = vm.parseJsonBool(json, ".boolString"); assertEq(boolean, true); - bool[] memory booleans = vm.parseJsonBoolArray(json, ".booleanArray"); - assert(booleans[0]); - assert(!booleans[1]); + bool[] memory booleans = vm.parseJsonBoolArray(json, ".boolArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + booleans = vm.parseJsonBoolArray(json, ".boolStringArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + } + + function test_coercionBytes() public { + bytes memory bytes_ = vm.parseJsonBytes(json, ".bytesString"); + assertEq(bytes_, hex"01"); + + bytes[] memory bytesArray = vm.parseJsonBytesArray(json, ".bytesStringArray"); + assertEq(bytesArray[0], hex"01"); + assertEq(bytesArray[1], hex"02"); + } + + struct Nested { + uint256 number; + string str; + } + + function test_nestedObject() public { + bytes memory data = vm.parseJson(json, ".nestedObject"); + Nested memory nested = abi.decode(data, (Nested)); + assertEq(nested.number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + assertEq(nested.str, "NEST"); } function test_advancedJsonPath() public { @@ -144,7 +252,7 @@ contract ParseJsonTest is DSTest { } function test_canonicalizePath() public { - bytes memory data = vm.parseJson(json, "$.str"); + bytes memory data = vm.parseJson(json, "$.basicString"); string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } @@ -152,34 +260,72 @@ contract ParseJsonTest is DSTest { function test_nonExistentKey() public { bytes memory data = vm.parseJson(json, ".thisKeyDoesNotExist"); assertEq(0, data.length); - - data = vm.parseJson(json, ".this.path.does.n.0.t.exist"); - assertEq(0, data.length); - - data = vm.parseJson("", "."); - assertEq(0, data.length); } function test_parseJsonKeys() public { string memory jsonString = '{"some_key_to_value": "some_value", "some_key_to_array": [1, 2, 3], "some_key_to_object": {"key1": "value1", "key2": 2}}'; + string[] memory keys = vm.parseJsonKeys(jsonString, "$"); - assertEq(abi.encode(keys), abi.encode(["some_key_to_value", "some_key_to_array", "some_key_to_object"])); + string[] memory expected = new string[](3); + expected[0] = "some_key_to_value"; + expected[1] = "some_key_to_array"; + expected[2] = "some_key_to_object"; + assertEq(abi.encode(keys), abi.encode(expected)); keys = vm.parseJsonKeys(jsonString, ".some_key_to_object"); - assertEq(abi.encode(keys), abi.encode(["key1", "key2"])); + expected = new string[](2); + expected[0] = "key1"; + expected[1] = "key2"; + assertEq(abi.encode(keys), abi.encode(expected)); - vm.expectRevert("You can only get keys for JSON-object. The key '.some_key_to_array' does not return an object"); + vm._expectCheatcodeRevert("JSON value at \".some_key_to_array\" is not an object"); vm.parseJsonKeys(jsonString, ".some_key_to_array"); - vm.expectRevert("You can only get keys for JSON-object. The key '.some_key_to_value' does not return an object"); + vm._expectCheatcodeRevert("JSON value at \".some_key_to_value\" is not an object"); vm.parseJsonKeys(jsonString, ".some_key_to_value"); - vm.expectRevert( - "You can only get keys for a single JSON-object. The key '.*' returns a value or an array of JSON-objects" - ); + 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 { @@ -198,6 +344,7 @@ contract WriteJsonTest is DSTest { vm.serializeBool(json1, "boolean", true); vm.serializeInt(json2, "key2", -234); vm.serializeUint(json2, "deploy", uint256(254)); + vm.serializeUintToHex(json2, "hexUint", uint256(255)); string memory data = vm.serializeBool(json2, "boolean", true); vm.serializeString(json2, "json1", data); emit log(data); @@ -246,6 +393,19 @@ contract WriteJsonTest is DSTest { vm.removeFile(path); } + // The serializeJson cheatcode was added to support assigning an existing json string to an object key. + // 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"}'); + 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}'); + } + struct simpleJson { uint256 a; string b; @@ -279,17 +439,25 @@ contract WriteJsonTest is DSTest { assertEq(decodedData.a, 123); } - function test_checkKeyExists() public { + function test_checkKeyExistsJson() public { string memory path = "fixtures/Json/write_complex_test.json"; string memory json = vm.readFile(path); - bool exists = vm.keyExists(json, "a"); + bool exists = vm.keyExistsJson(json, ".a"); + assertTrue(exists); + + // TODO: issue deprecation warning + exists = vm.keyExists(json, ".a"); assertTrue(exists); } - function test_checkKeyDoesNotExist() public { + function test_checkKeyDoesNotExistJson() public { string memory path = "fixtures/Json/write_complex_test.json"; string memory json = vm.readFile(path); - bool exists = vm.keyExists(json, "d"); + bool exists = vm.keyExistsJson(json, ".d"); + assertTrue(!exists); + + // TODO: issue deprecation warning + exists = vm.keyExists(json, ".d"); assertTrue(!exists); } diff --git a/testdata/cheats/Label.t.sol b/testdata/default/cheats/Label.t.sol similarity index 67% rename from testdata/cheats/Label.t.sol rename to testdata/default/cheats/Label.t.sol index a0fbe3ab4ef00..4ff5d3860bed0 100644 --- a/testdata/cheats/Label.t.sol +++ b/testdata/default/cheats/Label.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract LabelTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Load.t.sol b/testdata/default/cheats/Load.t.sol similarity index 63% rename from testdata/cheats/Load.t.sol rename to testdata/default/cheats/Load.t.sol index a8a9ed2ffab82..06f4b5bd52718 100644 --- a/testdata/cheats/Load.t.sol +++ b/testdata/default/cheats/Load.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Storage { uint256 slot0 = 10; @@ -27,14 +27,8 @@ contract LoadTest is DSTest { } function testLoadNotAvailableOnPrecompiles() public { - vm.expectRevert( - bytes("Load cannot be used on precompile addresses (N < 10). Please use an address bigger than 10 instead") - ); - uint256 val = this.load(address(1), bytes32(0)); - } - - function load(address target, bytes32 slot) public returns (uint256) { - return uint256(vm.load(target, slot)); + vm._expectCheatcodeRevert("cannot use precompile 0x0000000000000000000000000000000000000001 as an argument"); + vm.load(address(1), bytes32(0)); } function testLoadOtherStorage() public { diff --git a/testdata/cheats/Mapping.t.sol b/testdata/default/cheats/Mapping.t.sol similarity index 96% rename from testdata/cheats/Mapping.t.sol rename to testdata/default/cheats/Mapping.t.sol index 36678b413113b..82477150ae9ca 100644 --- a/testdata/cheats/Mapping.t.sol +++ b/testdata/default/cheats/Mapping.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RecordMapping { int256 length; diff --git a/testdata/cheats/MemSafety.t.sol b/testdata/default/cheats/MemSafety.t.sol similarity index 65% rename from testdata/cheats/MemSafety.t.sol rename to testdata/default/cheats/MemSafety.t.sol index d29b895bf6dc7..b18673e93e081 100644 --- a/testdata/cheats/MemSafety.t.sol +++ b/testdata/default/cheats/MemSafety.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract MemSafetyTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -37,30 +37,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that writing to memory before the range given to `expectSafeMemory` - /// will cause the test to fail while using the `MSTORE` opcode. - function testFailExpectSafeMemory_MSTORE_Low() public { - // Allow memory writes in the range of [0x80, 0xA0) within this context - vm.expectSafeMemory(0x80, 0xA0); - - // Attempt to write to memory outside of the range using `MSTORE` - assembly { - mstore(0x60, 0xc0ffee) - } - } - - /// @dev Tests that writing to memory after the range given to `expectSafeMemory` - /// will cause the test to fail while using the `MSTORE` opcode. - function testFailExpectSafeMemory_MSTORE_High() public { - // Allow memory writes in the range of [0x80, 0xA0) within this context - vm.expectSafeMemory(0x80, 0xA0); - - // Attempt to write to memory outside of the range using `MSTORE` - assembly { - mstore(0xA0, 0xc0ffee) - } - } - //////////////////////////////////////////////////////////////// // MSTORE8 // //////////////////////////////////////////////////////////////// @@ -91,30 +67,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that writing to memory before the range given to `expectSafeMemory` - /// will cause the test to fail while using the `MSTORE8` opcode. - function testFailExpectSafeMemory_MSTORE8_Low() public { - // Allow memory writes in the range of [0x80, 0x81) within this context - vm.expectSafeMemory(0x80, 0x81); - - // Attempt to write to memory outside of the range using `MSTORE8` - assembly { - mstore8(0x60, 0xFF) - } - } - - /// @dev Tests that writing to memory after the range given to `expectSafeMemory` - /// will cause the test to fail while using the `MSTORE8` opcode. - function testFailExpectSafeMemory_MSTORE8_High() public { - // Allow memory writes in the range of [0x80, 0x81) within this context - vm.expectSafeMemory(0x80, 0x81); - - // Attempt to write to memory outside of the range using `MSTORE8` - assembly { - mstore8(0x81, 0xFF) - } - } - //////////////////////////////////////////////////////////////// // CALLDATACOPY // //////////////////////////////////////////////////////////////// @@ -131,18 +83,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `CALLDATACOPY` opcode. - function testFailExpectSafeMemory_CALLDATACOPY(uint256 _x) public { - // Allow memory writes in the range of [0x80, 0xA0) within this context - vm.expectSafeMemory(0x80, 0xA0); - - // Write to memory outside the range using `CALLDATACOPY` - assembly { - calldatacopy(0xA0, 0x04, 0x20) - } - } - //////////////////////////////////////////////////////////////// // CODECOPY // //////////////////////////////////////////////////////////////// @@ -160,19 +100,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `CODECOPY` opcode. - function testFailExpectSafeMemory_CODECOPY() public { - // Allow memory writes in the range of [0x80, 0xA0) within this context - vm.expectSafeMemory(0x80, 0xA0); - - // Attempt to write to memory outside of the range using `CODECOPY` - assembly { - let size := extcodesize(address()) - codecopy(0x80, 0x00, size) - } - } - //////////////////////////////////////////////////////////////// // RETURNDATACOPY // //////////////////////////////////////////////////////////////// @@ -198,27 +125,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `RETURNDATACOPY` opcode. - function testFailExpectSafeMemory_RETURNDATACOPY() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - - // Create a payload to call `giveReturndata` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); - - // Allow memory writes in the range of [0x80, 0x100) within this context - vm.expectSafeMemory(0x80, 0x100); - - // Create a new SubContext contract and call `giveReturndata` on it. - _doCallReturnData(address(sc), payload, 0x80, 0x60); - - // Write to memory outside of the range using `RETURNDATACOPY` - assembly { - returndatacopy(0x100, 0x00, 0x60) - } - } - //////////////////////////////////////////////////////////////// // EXTCODECOPY // //////////////////////////////////////////////////////////////// @@ -236,19 +142,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will not cause the test to fail while using the `EXTCODECOPY` opcode. - function testFailExpectSafeMemory_EXTCODECOPY() public { - // Allow memory writes in the range of [0x80, 0xA0) within this context - vm.expectSafeMemory(0x80, 0xA0); - - // Attempt to write to memory outside of the range using `EXTCODECOPY` - assembly { - let size := extcodesize(address()) - extcodecopy(address(), 0xA0, 0x00, 0x20) - } - } - //////////////////////////////////////////////////////////////// // CALL // //////////////////////////////////////////////////////////////// @@ -269,22 +162,6 @@ contract MemSafetyTest is DSTest { _doCallReturnData(address(sc), payload, 0x80, 0x60); } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `CALL` opcode. - function testFailExpectSafeMemory_CALL() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - - // Create a payload to call `giveReturndata` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); - - // Allow memory writes in the range of [0x80, 0x100) within this context - vm.expectSafeMemory(0x80, 0x100); - - // Create a new SubContext contract and call `giveReturndata` on it. - _doCallReturnData(address(sc), payload, 0x100, 0x60); - } - //////////////////////////////////////////////////////////////// // CALLCODE // //////////////////////////////////////////////////////////////// @@ -305,22 +182,6 @@ contract MemSafetyTest is DSTest { _doCallCodeReturnData(address(sc), payload, 0x80, 0x60); } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `CALLCODE` opcode. - function testFailExpectSafeMemory_CALLCODE() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - - // Create a payload to call `giveReturndata` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); - - // Allow memory writes in the range of [0x80, 0x100) within this context - vm.expectSafeMemory(0x80, 0x100); - - // Create a new SubContext contract and call `giveReturndata` on it. - _doCallCodeReturnData(address(sc), payload, 0x100, 0x60); - } - //////////////////////////////////////////////////////////////// // STATICCALL // //////////////////////////////////////////////////////////////// @@ -341,22 +202,6 @@ contract MemSafetyTest is DSTest { _doStaticCallReturnData(address(sc), payload, 0x80, 0x60); } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `STATICCALL` opcode. - function testFailExpectSafeMemory_STATICCALL() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - - // Create a payload to call `giveReturndata` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); - - // Allow memory writes in the range of [0x80, 0x100) within this context - vm.expectSafeMemory(0x80, 0x100); - - // Create a new SubContext contract and call `giveReturndata` on it. - _doStaticCallReturnData(address(sc), payload, 0x100, 0x60); - } - //////////////////////////////////////////////////////////////// // DELEGATECALL // //////////////////////////////////////////////////////////////// @@ -377,22 +222,6 @@ contract MemSafetyTest is DSTest { _doDelegateCallReturnData(address(sc), payload, 0x80, 0x60); } - /// @dev Tests that writing to memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `DELEGATECALL` opcode. - function testFailExpectSafeMemory_DELEGATECALL() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - - // Create a payload to call `giveReturndata` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.giveReturndata.selector); - - // Allow memory writes in the range of [0x80, 0x100) within this context - vm.expectSafeMemory(0x80, 0x100); - - // Create a new SubContext contract and call `giveReturndata` on it. - _doDelegateCallReturnData(address(sc), payload, 0x100, 0x60); - } - //////////////////////////////////////////////////////////////// // MLOAD (Read Expansion) // //////////////////////////////////////////////////////////////// @@ -413,9 +242,12 @@ contract MemSafetyTest is DSTest { /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` /// will cause the test to fail while using the `MLOAD` opcode. - function testFailExpectSafeMemory_MLOAD() public { + /// forge-config: default.allow_internal_expect_revert = true + function testExpectSafeMemory_MLOAD_REVERT() public { vm.expectSafeMemory(0x80, 0x100); + vm.expectRevert(); + // This should revert. Ugly hack to make sure the mload isn't optimized // out. uint256 a; @@ -443,20 +275,6 @@ contract MemSafetyTest is DSTest { uint256 b = a + 1; } - /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `SHA3` opcode. - function testFailExpectSafeMemory_SHA3() public { - vm.expectSafeMemory(0x80, 0x100); - - // This should revert. Ugly hack to make sure the sha3 isn't optimized - // out. - uint256 a; - assembly { - a := keccak256(0x100, 0x20) - } - uint256 b = a + 1; - } - //////////////////////////////////////////////////////////////// // LOG(0-4) (Read Expansion) // //////////////////////////////////////////////////////////////// @@ -477,9 +295,10 @@ contract MemSafetyTest is DSTest { /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` /// will cause the test to fail while using the `LOG0` opcode. - function testFailExpectSafeMemory_LOG0() public { + /// forge-config: default.allow_internal_expect_revert = true + function testExpectSafeMemory_LOG0_REVERT() public { vm.expectSafeMemory(0x80, 0x100); - + vm.expectRevert(); // This should revert. assembly { log0(0x100, 0x20) @@ -501,17 +320,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `CREATE` opcode. - function testFailExpectSafeMemory_CREATE() public { - vm.expectSafeMemory(0x80, 0x100); - - // This should revert. - assembly { - pop(create(0, 0x100, 0x20)) - } - } - /// @dev Tests that expanding memory within the range given to `expectSafeMemory` /// will not cause the test to fail while using the `CREATE2` opcode. function testExpectSafeMemory_CREATE2() public { @@ -523,17 +331,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `CREATE2` opcode. - function testFailExpectSafeMemory_CREATE2() public { - vm.expectSafeMemory(0x80, 0x100); - - // This should revert. - assembly { - pop(create2(0, 0x100, 0x20, 0x00)) - } - } - //////////////////////////////////////////////////////////////// // RETURN/REVERT (Read Expansion) // //////////////////////////////////////////////////////////////// @@ -549,17 +346,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `RETURN` opcode. - function testFailExpectSafeMemory_RETURN() public { - vm.expectSafeMemory(0x80, 0x100); - - // This should revert. - assembly { - return(0x100, 0x20) - } - } - /// @dev Tests that expanding memory within the range given to `expectSafeMemory` /// will not cause the test to fail while using the `REVERT` opcode. function testExpectSafeMemory_REVERT() public { @@ -580,26 +366,6 @@ contract MemSafetyTest is DSTest { } } - /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` - /// will cause the test to fail while using the `REVERT` opcode. - function testFailExpectSafeMemory_REVERT() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - - // Create a payload to call `doRevert` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.doRevert.selector, 0x120, 0x20); - - // Expect memory in the range of [0x00, 0x120] to be safe in the next subcontext - vm.expectSafeMemoryCall(0x00, 0x120); - - // Call `doRevert` on the SubContext contract and ensure it did not revert with - // zero data. - _doCallReturnData(address(sc), payload, 0x200, 0x20); - assembly { - if iszero(eq(keccak256(0x60, 0x20), keccak256(0x200, returndatasize()))) { revert(0x00, 0x00) } - } - } - //////////////////////////////////////////////////////////////// // Context Depth Tests // //////////////////////////////////////////////////////////////// @@ -654,20 +420,47 @@ contract MemSafetyTest is DSTest { _doCall(address(sc), payload); } - /// @dev Tests that the `expectSafeMemoryCall` cheatcode works as expected. - function testFailExpectSafeMemoryCall() public { - // Create a new SubContext contract - SubContext sc = new SubContext(); - // Create a payload to call `doMstore8` on the SubContext contract - bytes memory payload = abi.encodeWithSelector(SubContext.doMstore.selector, 0xA0, 0xc0ffee); + //////////////////////////////////////////////////////////////// + // `stopExpectSafeMemory` cheatcode // + //////////////////////////////////////////////////////////////// - // Allow memory writes in the range of [0x80, 0xA0) within the next created subcontext - vm.expectSafeMemoryCall(0x80, 0xA0); + /// @dev Tests that the `stopExpectSafeMemory` cheatcode works as expected. + function testStopExpectSafeMemory() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } + + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write to allowed range + mstore(initPtr, 0x01) + } + + vm.stopExpectSafeMemory(); + + assembly { + // write ouside allowed range, this should be fine + mstore(add(initPtr, 0x20), 0x01) + } + } + + /// @dev Tests that the `stopExpectSafeMemory` cheatcode can still be called if the free memory pointer was + /// updated to the exclusive upper boundary during execution. + function testStopExpectSafeMemory_freeMemUpdate() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } - // Should revert. The memory write in this subcontext is outside of the allowed range. - if (!_doCall(address(sc), payload)) { - revert("Expected call to fail"); + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write outside of allowed range, this should revert + mstore(initPtr, 0x01) + mstore(0x40, add(initPtr, 0x20)) } + + vm.stopExpectSafeMemory(); } //////////////////////////////////////////////////////////////// diff --git a/testdata/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol similarity index 76% rename from testdata/cheats/MockCall.t.sol rename to testdata/default/cheats/MockCall.t.sol index 8203b9de1b0e4..f11fd20984571 100644 --- a/testdata/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Mock { uint256 state = 0; @@ -42,6 +42,20 @@ contract NestedMock { } } +contract NestedMockDelegateCall { + Mock private inner; + + constructor(Mock _inner) { + inner = _inner; + } + + function sum() public returns (uint256) { + (, bytes memory dataA) = address(inner).delegatecall(abi.encodeWithSelector(Mock.numberA.selector)); + (, bytes memory dataB) = address(inner).delegatecall(abi.encodeWithSelector(Mock.numberB.selector)); + return abi.decode(dataA, (uint256)) + abi.decode(dataB, (uint256)); + } +} + contract MockCallTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -72,6 +86,18 @@ contract MockCallTest is DSTest { assertEq(target.sum(), 10); } + // Ref: https://github.com/foundry-rs/foundry/issues/8066 + function testMockNestedDelegate() public { + Mock inner = new Mock(); + NestedMockDelegateCall target = new NestedMockDelegateCall(inner); + + assertEq(target.sum(), 3); + + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), abi.encode(9)); + + assertEq(target.sum(), 10); + } + function testMockSelector() public { Mock target = new Mock(); assertEq(target.add(5, 5), 10); @@ -150,7 +176,7 @@ contract MockCallTest is DSTest { Mock mock = Mock(address(100)); vm.mockCall(address(mock), abi.encodeWithSelector(mock.add.selector), abi.encode(10)); - vm.mockCall(address(mock), abi.encodeWithSelector(mock.noReturnValue.selector), abi.encode()); + vm.mockCall(address(mock), mock.noReturnValue.selector, abi.encode()); assertEq(mock.add(1, 2), 10); mock.noReturnValue(); @@ -171,12 +197,15 @@ contract MockCallRevertTest is DSTest { assertEq(target.numberA(), 1); assertEq(target.numberB(), 2); - vm.mockCallRevert(address(target), abi.encodeWithSelector(target.numberB.selector), ERROR_MESSAGE); + vm.mockCallRevert(address(target), target.numberB.selector, ERROR_MESSAGE); // post-mock assertEq(target.numberA(), 1); - vm.expectRevert(); - target.numberB(); + try target.numberB() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockRevertWithCustomError() public { @@ -190,8 +219,11 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(target), abi.encodeWithSelector(target.numberB.selector), customError); assertEq(target.numberA(), 1); - vm.expectRevert(customError); - target.numberB(); + try target.numberB() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(customError)); + } } function testMockNestedRevert() public { @@ -202,8 +234,11 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(inner), abi.encodeWithSelector(inner.numberB.selector), ERROR_MESSAGE); - vm.expectRevert(ERROR_MESSAGE); - target.sum(); + try target.sum() { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCalldataRevert() public { @@ -215,8 +250,11 @@ contract MockCallRevertTest is DSTest { assertEq(target.add(6, 4), 10); - vm.expectRevert(ERROR_MESSAGE); - target.add(5, 5); + try target.add(5, 5) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testClearMockRevertedCalls() public { @@ -237,8 +275,11 @@ contract MockCallRevertTest is DSTest { assertEq(mock.add(1, 2), 3); - vm.expectRevert(ERROR_MESSAGE); - mock.add(2, 3); + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCallRevertWithValue() public { @@ -249,8 +290,11 @@ contract MockCallRevertTest is DSTest { assertEq(mock.pay(1), 1); assertEq(mock.pay(2), 2); - vm.expectRevert(ERROR_MESSAGE); - mock.pay{value: 10}(1); + try mock.pay{value: 10}(1) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCallResetsMockCallRevert() public { @@ -270,8 +314,11 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); - vm.expectRevert(ERROR_MESSAGE); - mock.add(2, 3); + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } function testMockCallRevertWithCall() public { @@ -291,7 +338,10 @@ contract MockCallRevertTest is DSTest { vm.mockCallRevert(address(mock), abi.encodeWithSelector(mock.add.selector), ERROR_MESSAGE); - vm.expectRevert(ERROR_MESSAGE); - mock.add(1, 2); + try mock.add(2, 3) { + revert(); + } catch (bytes memory err) { + require(keccak256(err) == keccak256(ERROR_MESSAGE)); + } } } diff --git a/testdata/default/cheats/MockCalls.t.sol b/testdata/default/cheats/MockCalls.t.sol new file mode 100644 index 0000000000000..2bd4d8bd9ea2e --- /dev/null +++ b/testdata/default/cheats/MockCalls.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract MockCallsTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testMockCallsLastShouldPersist() public { + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](2); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(7.219 ether); + vm.mockCalls(mockErc20, data, mocks); + (, bytes memory ret1) = mockErc20.call(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call(data); + assertEq(abi.decode(ret2, (uint256)), 7.219 ether); + (, bytes memory ret3) = mockErc20.call(data); + assertEq(abi.decode(ret3, (uint256)), 7.219 ether); + } + + function testMockCallsWithValue() public { + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](3); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(1 ether); + mocks[2] = abi.encode(6.423 ether); + vm.mockCalls(mockErc20, 1 ether, data, mocks); + (, bytes memory ret1) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret2, (uint256)), 1 ether); + (, bytes memory ret3) = mockErc20.call{value: 1 ether}(data); + assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + } + + function testMockCalls() public { + address mockUser = vm.addr(vm.randomUint()); + address mockErc20 = vm.addr(vm.randomUint()); + bytes memory data = abi.encodeWithSignature("balanceOf(address)", mockUser); + bytes[] memory mocks = new bytes[](3); + mocks[0] = abi.encode(2 ether); + mocks[1] = abi.encode(1 ether); + mocks[2] = abi.encode(6.423 ether); + vm.mockCalls(mockErc20, data, mocks); + (, bytes memory ret1) = mockErc20.call(data); + assertEq(abi.decode(ret1, (uint256)), 2 ether); + (, bytes memory ret2) = mockErc20.call(data); + assertEq(abi.decode(ret2, (uint256)), 1 ether); + (, bytes memory ret3) = mockErc20.call(data); + assertEq(abi.decode(ret3, (uint256)), 6.423 ether); + } +} diff --git a/testdata/default/cheats/MockFunction.t.sol b/testdata/default/cheats/MockFunction.t.sol new file mode 100644 index 0000000000000..6d670024b2053 --- /dev/null +++ b/testdata/default/cheats/MockFunction.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract MockFunctionContract { + uint256 public a; + + function mocked_function() public { + a = 321; + } + + function mocked_args_function(uint256 x) public { + a = 321 + x; + } +} + +contract ModelMockFunctionContract { + uint256 public a; + + function mocked_function() public { + a = 123; + } + + function mocked_args_function(uint256 x) public { + a = 123 + x; + } +} + +contract MockFunctionTest is DSTest { + MockFunctionContract my_contract; + ModelMockFunctionContract model_contract; + Vm vm = Vm(HEVM_ADDRESS); + + function setUp() public { + my_contract = new MockFunctionContract(); + model_contract = new ModelMockFunctionContract(); + } + + function test_mock_function() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_function.selector) + ); + my_contract.mocked_function(); + assertEq(my_contract.a(), 123); + } + + function test_mock_function_concrete_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector, 456) + ); + my_contract.mocked_args_function(456); + assertEq(my_contract.a(), 123 + 456); + my_contract.mocked_args_function(567); + assertEq(my_contract.a(), 321 + 567); + } + + function test_mock_function_all_args() public { + vm.mockFunction( + address(my_contract), + address(model_contract), + abi.encodeWithSelector(MockFunctionContract.mocked_args_function.selector) + ); + my_contract.mocked_args_function(678); + assertEq(my_contract.a(), 123 + 678); + my_contract.mocked_args_function(789); + assertEq(my_contract.a(), 123 + 789); + } +} diff --git a/testdata/default/cheats/Nonce.t.sol b/testdata/default/cheats/Nonce.t.sol new file mode 100644 index 0000000000000..312c2b4d7edcc --- /dev/null +++ b/testdata/default/cheats/Nonce.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public count; + + function increment() public { + count += 1; + } +} + +contract NonceIsolatedTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testIncrementNonce() public { + address bob = address(14); + vm.startPrank(bob); + Counter counter = new Counter(); + assertEq(vm.getNonce(bob), 1); + counter.increment(); + assertEq(vm.getNonce(bob), 2); + new Counter(); + assertEq(vm.getNonce(bob), 3); + counter.increment(); + assertEq(vm.getNonce(bob), 4); + } +} diff --git a/testdata/cheats/Parse.t.sol b/testdata/default/cheats/Parse.t.sol similarity index 97% rename from testdata/cheats/Parse.t.sol rename to testdata/default/cheats/Parse.t.sol index dc43174e0faba..65e7561d104de 100644 --- a/testdata/cheats/Parse.t.sol +++ b/testdata/default/cheats/Parse.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ParseTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Prank.t.sol b/testdata/default/cheats/Prank.t.sol similarity index 74% rename from testdata/cheats/Prank.t.sol rename to testdata/default/cheats/Prank.t.sol index 01b3f2ccbcb1d..3a6bf756fb176 100644 --- a/testdata/cheats/Prank.t.sol +++ b/testdata/default/cheats/Prank.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Victim { function assertCallerAndOrigin( @@ -85,9 +85,125 @@ contract NestedPranker { } } +contract ImplementationTest { + uint256 public num; + address public sender; + + function assertCorrectCaller(address expectedSender) public { + require(msg.sender == expectedSender); + } + + function assertCorrectOrigin(address expectedOrigin) public { + require(tx.origin == expectedOrigin); + } + + function setNum(uint256 _num) public { + num = _num; + } +} + +contract ProxyTest { + uint256 public num; + address public sender; +} + contract PrankTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + function testPrankDelegateCallPrank2() public { + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.prank(address(proxy), true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "prank2: delegate call failed assertCorrectCaller"); + + // Assert storage updates + uint256 num = 42; + vm.prank(address(proxy), true); + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successTwo, "prank2: delegate call failed setNum"); + require(proxy.num() == num, "prank2: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + function testPrankDelegateCallStartPrank2() public { + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.startPrank(address(proxy), true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "startPrank2: delegate call failed assertCorrectCaller"); + + // Assert storage updates + uint256 num = 42; + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successTwo, "startPrank2: delegate call failed setNum"); + require(proxy.num() == num, "startPrank2: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + function testPrankDelegateCallPrank3(address origin) public { + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.prank(address(proxy), origin, true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "prank3: delegate call failed assertCorrectCaller"); + + // Assert correct `tx.origin` + vm.prank(address(proxy), origin, true); + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("assertCorrectOrigin(address)", origin)); + require(successTwo, "prank3: delegate call failed assertCorrectOrigin"); + + // Assert storage updates + uint256 num = 42; + vm.prank(address(proxy), address(origin), true); + (bool successThree,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successThree, "prank3: delegate call failed setNum"); + require(proxy.num() == num, "prank3: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + function testPrankDelegateCallStartPrank3(address origin) public { + ProxyTest proxy = new ProxyTest(); + ImplementationTest impl = new ImplementationTest(); + vm.startPrank(address(proxy), origin, true); + + // Assert correct `msg.sender` + (bool success,) = + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", address(proxy))); + require(success, "startPrank3: delegate call failed assertCorrectCaller"); + + // Assert correct `tx.origin` + (bool successTwo,) = address(impl).delegatecall(abi.encodeWithSignature("assertCorrectOrigin(address)", origin)); + require(successTwo, "startPrank3: delegate call failed assertCorrectOrigin"); + + // Assert storage updates + uint256 num = 42; + (bool successThree,) = address(impl).delegatecall(abi.encodeWithSignature("setNum(uint256)", num)); + require(successThree, "startPrank3: delegate call failed setNum"); + require(proxy.num() == num, "startPrank3: proxy's storage was not set correctly"); + vm.stopPrank(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfPrankDelegateCalltoEOA() public { + uint256 privateKey = uint256(keccak256(abi.encodePacked("alice"))); + address alice = vm.addr(privateKey); + ImplementationTest impl = new ImplementationTest(); + vm.expectRevert("vm.prank: cannot `prank` delegate call from an EOA"); + vm.prank(alice, true); + // Should fail when EOA pranked with delegatecall. + address(impl).delegatecall(abi.encodeWithSignature("assertCorrectCaller(address)", alice)); + } + function testPrankSender(address sender) public { // Perform the prank Victim victim = new Victim(); @@ -222,16 +338,19 @@ contract PrankTest is DSTest { ); } - function testFailOverwriteUnusedPrank(address sender, address origin) public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfOverwriteUnusedPrank(address sender, address origin) public { // Set the prank, but not use it address oldOrigin = tx.origin; Victim victim = new Victim(); vm.startPrank(sender, origin); // try to overwrite the prank. This should fail. + vm.expectRevert("vm.startPrank: cannot overwrite a prank until it is applied at least once"); vm.startPrank(address(this), origin); } - function testFailOverwriteUnusedPrankAfterSuccessfulPrank(address sender, address origin) public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfOverwriteUnusedPrankAfterSuccessfulPrank(address sender, address origin) public { // Set the prank, but not use it address oldOrigin = tx.origin; Victim victim = new Victim(); @@ -241,6 +360,7 @@ contract PrankTest is DSTest { ); vm.startPrank(address(this), origin); // try to overwrite the prank. This should fail. + vm.expectRevert("vm.startPrank: cannot overwrite a prank until it is applied at least once"); vm.startPrank(sender, origin); } @@ -274,10 +394,7 @@ contract PrankTest is DSTest { function testPrankConstructorSender(address sender) public { vm.prank(sender); ConstructorVictim victim = new ConstructorVictim( - sender, - "msg.sender was not set during prank", - tx.origin, - "tx.origin invariant failed" + sender, "msg.sender was not set during prank", tx.origin, "tx.origin invariant failed" ); // Ensure we cleaned up correctly @@ -290,10 +407,7 @@ contract PrankTest is DSTest { // Perform the prank vm.prank(sender, origin); ConstructorVictim victim = new ConstructorVictim( - sender, - "msg.sender was not set during prank", - origin, - "tx.origin was not set during prank" + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" ); // Ensure we cleaned up correctly @@ -329,10 +443,7 @@ contract PrankTest is DSTest { // Perform the prank vm.startPrank(sender, origin); ConstructorVictim victim = new ConstructorVictim( - sender, - "msg.sender was not set during prank", - origin, - "tx.origin was not set during prank" + sender, "msg.sender was not set during prank", origin, "tx.origin was not set during prank" ); new ConstructorVictim( sender, diff --git a/testdata/cheats/Prevrandao.t.sol b/testdata/default/cheats/Prevrandao.t.sol similarity index 71% rename from testdata/cheats/Prevrandao.t.sol rename to testdata/default/cheats/Prevrandao.t.sol index beed5d71f73b2..aab8e326c43ce 100644 --- a/testdata/cheats/Prevrandao.t.sol +++ b/testdata/default/cheats/Prevrandao.t.sol @@ -1,34 +1,34 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract PrevrandaoTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testPrevrandao() public { assertEq(block.prevrandao, 0); - vm.prevrandao(bytes32(uint256(10))); + vm.prevrandao(uint256(10)); assertEq(block.prevrandao, 10, "prevrandao cheatcode failed"); } function testPrevrandaoFuzzed(uint256 newPrevrandao) public { vm.assume(newPrevrandao != block.prevrandao); assertEq(block.prevrandao, 0); - vm.prevrandao(bytes32(newPrevrandao)); + vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); } function testPrevrandaoSnapshotFuzzed(uint256 newPrevrandao) public { vm.assume(newPrevrandao != block.prevrandao); uint256 oldPrevrandao = block.prevrandao; - uint256 snapshot = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); - vm.prevrandao(bytes32(newPrevrandao)); + vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); - assert(vm.revertTo(snapshot)); + assert(vm.revertToState(snapshotId)); assertEq(block.prevrandao, oldPrevrandao); } } diff --git a/testdata/cheats/ProjectRoot.t.sol b/testdata/default/cheats/ProjectRoot.t.sol similarity index 68% rename from testdata/cheats/ProjectRoot.t.sol rename to testdata/default/cheats/ProjectRoot.t.sol index 49bc7be018e56..cff3d83751d66 100644 --- a/testdata/cheats/ProjectRoot.t.sol +++ b/testdata/default/cheats/ProjectRoot.t.sol @@ -1,15 +1,18 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ProjectRootTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + bytes public manifestDirBytes; function testProjectRoot() public { - bytes memory manifestDirBytes = bytes(vm.envString("CARGO_MANIFEST_DIR")); - + manifestDirBytes = bytes(vm.envString("CARGO_MANIFEST_DIR")); + for (uint256 i = 0; i < 7; i++) { + manifestDirBytes.pop(); + } // replace "forge" suffix with "testdata" suffix to get expected project root from manifest dir bytes memory expectedRootSuffix = bytes("testd"); for (uint256 i = 1; i < 6; i++) { diff --git a/testdata/default/cheats/Prompt.t.sol b/testdata/default/cheats/Prompt.t.sol new file mode 100644 index 0000000000000..2e623a28ef22c --- /dev/null +++ b/testdata/default/cheats/Prompt.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract PromptTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPrompt_revertNotATerminal() public { + // should revert in CI and testing environments either with timout or because no terminal is available + vm._expectCheatcodeRevert(); + vm.prompt("test"); + + vm._expectCheatcodeRevert(); + vm.promptSecret("test"); + + vm._expectCheatcodeRevert(); + uint256 test = vm.promptSecretUint("test"); + } + + function testPrompt_Address() public { + vm._expectCheatcodeRevert(); + address test = vm.promptAddress("test"); + } + + function testPrompt_Uint() public { + vm._expectCheatcodeRevert(); + uint256 test = vm.promptUint("test"); + } +} diff --git a/testdata/default/cheats/RandomAddress.t.sol b/testdata/default/cheats/RandomAddress.t.sol new file mode 100644 index 0000000000000..61510ed4eaaac --- /dev/null +++ b/testdata/default/cheats/RandomAddress.t.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RandomAddress is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRandomAddress() public { + vm.randomAddress(); + } +} diff --git a/testdata/default/cheats/RandomBytes.t.sol b/testdata/default/cheats/RandomBytes.t.sol new file mode 100644 index 0000000000000..dbc03a6ccfb8f --- /dev/null +++ b/testdata/default/cheats/RandomBytes.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RandomBytes is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRandomBytes4() public { + vm.randomBytes4(); + } + + function testRandomBytes8() public { + vm.randomBytes8(); + } + + function testFillrandomBytes() public view { + uint256 len = 16; + vm.randomBytes(len); + } +} diff --git a/testdata/default/cheats/RandomCheatcodes.t.sol b/testdata/default/cheats/RandomCheatcodes.t.sol new file mode 100644 index 0000000000000..c42b4310012f1 --- /dev/null +++ b/testdata/default/cheats/RandomCheatcodes.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RandomCheatcodesTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + int128 constant min = -170141183460469231731687303715884105728; + int128 constant max = 170141183460469231731687303715884105727; + + function test_int128() public { + vm._expectCheatcodeRevert("vm.randomInt: number of bits cannot exceed 256"); + int256 val = vm.randomInt(type(uint256).max); + + val = vm.randomInt(128); + assertGe(val, min); + assertLe(val, max); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testReverttIf_int128() public { + int256 val = vm.randomInt(128); + vm.expectRevert("Error: a > b not satisfied [int]"); + require(val > max, "Error: a > b not satisfied [int]"); + } + + function test_address() public { + address fresh_address = vm.randomAddress(); + assert(fresh_address != address(this)); + assert(fresh_address != address(vm)); + } + + function test_randomUintLimit() public { + vm._expectCheatcodeRevert("vm.randomUint: number of bits cannot exceed 256"); + uint256 val = vm.randomUint(type(uint256).max); + } + + function test_randomUints(uint256 x) public { + x = vm.randomUint(0, 256); + uint256 freshUint = vm.randomUint(x); + + assert(0 <= freshUint); + if (x == 256) { + assert(freshUint <= type(uint256).max); + } else { + assert(freshUint <= 2 ** x - 1); + } + } + + function test_randomSymbolicWord() public { + uint256 freshUint192 = vm.randomUint(192); + + assert(0 <= freshUint192); + assert(freshUint192 <= type(uint192).max); + } +} + +contract RandomBytesTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + bytes1 local_byte; + bytes local_bytes; + + function manip_symbolic_bytes(bytes memory b) public { + uint256 middle = b.length / 2; + b[middle] = hex"aa"; + } + + function test_symbolic_bytes_revert() public { + vm._expectCheatcodeRevert(); + bytes memory val = vm.randomBytes(type(uint256).max); + } + + function test_symbolic_bytes_1() public { + uint256 length = uint256(vm.randomUint(1, type(uint8).max)); + bytes memory fresh_bytes = vm.randomBytes(length); + uint256 index = uint256(vm.randomUint(1)); + + local_byte = fresh_bytes[index]; + assertEq(fresh_bytes[index], local_byte); + } + + function test_symbolic_bytes_2() public { + uint256 length = uint256(vm.randomUint(1, type(uint8).max)); + bytes memory fresh_bytes = vm.randomBytes(length); + + local_bytes = fresh_bytes; + assertEq(fresh_bytes, local_bytes); + } + + function test_symbolic_bytes_3() public { + uint256 length = uint256(vm.randomUint(1, type(uint8).max)); + bytes memory fresh_bytes = vm.randomBytes(length); + + manip_symbolic_bytes(fresh_bytes); + assertEq(hex"aa", fresh_bytes[length / 2]); + } + + function test_symbolic_bytes_length(uint8 l) public { + vm.assume(0 < l); + bytes memory fresh_bytes = vm.randomBytes(l); + assertEq(fresh_bytes.length, l); + } +} diff --git a/testdata/default/cheats/RandomUint.t.sol b/testdata/default/cheats/RandomUint.t.sol new file mode 100644 index 0000000000000..c0021030d0d1e --- /dev/null +++ b/testdata/default/cheats/RandomUint.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RandomUint is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRandomUint() public { + 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"); + } +} diff --git a/testdata/cheats/ReadCallers.t.sol b/testdata/default/cheats/ReadCallers.t.sol similarity index 98% rename from testdata/cheats/ReadCallers.t.sol rename to testdata/default/cheats/ReadCallers.t.sol index e733c57a53510..dbd198a2d93e8 100644 --- a/testdata/cheats/ReadCallers.t.sol +++ b/testdata/default/cheats/ReadCallers.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Target { function consumeNewCaller() external {} diff --git a/testdata/cheats/Record.t.sol b/testdata/default/cheats/Record.t.sol similarity index 94% rename from testdata/cheats/Record.t.sol rename to testdata/default/cheats/Record.t.sol index 6aed8a5f5983d..c2907ebb84316 100644 --- a/testdata/cheats/Record.t.sol +++ b/testdata/default/cheats/Record.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RecordAccess { function record() public returns (NestedRecordAccess) { diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol new file mode 100644 index 0000000000000..63100f51f1284 --- /dev/null +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -0,0 +1,1343 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +/// @notice Helper contract with a construction that makes a call to itself then +/// optionally reverts if zero-length data is passed +contract SelfCaller { + constructor(bytes memory) payable { + assembly { + // call self to test that the cheatcode correctly reports the + // account as initialized even when there is no code at the + // contract address + pop(call(gas(), address(), div(callvalue(), 10), 0, 0, 0, 0)) + if eq(calldataload(0x04), 1) { revert(0, 0) } + } + } +} + +/// @notice Helper contract with a constructor that stores a value in storage +/// and then optionally reverts. +contract ConstructorStorer { + constructor(bool shouldRevert) { + assembly { + sstore(0x00, 0x01) + if shouldRevert { revert(0, 0) } + } + } +} + +/// @notice Helper contract that calls itself from the run method +contract Doer { + uint256[10] spacer; + mapping(bytes32 key => uint256 value) slots; + + function run() public payable { + slots[bytes32("doer 1")]++; + this.doStuff{value: msg.value / 10}(); + } + + function doStuff() external payable { + slots[bytes32("doer 2")]++; + } +} + +/// @notice Helper contract that selfdestructs to a target address within its +/// constructor +contract SelfDestructor { + constructor(address target) payable { + selfdestruct(payable(target)); + } +} + +/// @notice Helper contract that calls a Doer from the run method +contract Create2or { + function create2(bytes32 salt, bytes memory initcode) external payable returns (address result) { + assembly { + result := create2(callvalue(), add(initcode, 0x20), mload(initcode), salt) + } + } +} + +/// @notice Helper contract that calls a Doer from the run method and then +/// reverts +contract Reverter { + Doer immutable doer; + mapping(bytes32 key => uint256 value) slots; + + constructor(Doer _doer) { + doer = _doer; + } + + function run() public payable { + slots[bytes32("reverter")]++; + doer.run{value: msg.value / 10}(); + revert(); + } +} + +/// @notice Helper contract that calls a Doer from the run method +contract Succeeder { + Doer immutable doer; + mapping(bytes32 key => uint256 value) slots; + + constructor(Doer _doer) { + doer = _doer; + } + + function run() public payable { + slots[bytes32("succeeder")]++; + doer.run{value: msg.value / 10}(); + } +} + +/// @notice Helper contract that calls a Reverter and Succeeder from the run +/// method +contract NestedRunner { + Doer public immutable doer; + Reverter public immutable reverter; + Succeeder public immutable succeeder; + mapping(bytes32 key => uint256 value) slots; + + constructor() { + doer = new Doer(); + reverter = new Reverter(doer); + succeeder = new Succeeder(doer); + } + + function run(bool shouldRevert) public payable { + slots[bytes32("runner")]++; + try reverter.run{value: msg.value / 10}() { + if (shouldRevert) { + revert(); + } + } catch {} + succeeder.run{value: msg.value / 10}(); + if (shouldRevert) { + revert(); + } + } +} + +/// Helper contract that uses all three EXT* opcodes on a given address +contract ExtChecker { + function checkExts(address a) external { + assembly { + let x := extcodesize(a) + let y := extcodehash(a) + extcodecopy(a, x, y, 0) + // sstore to check that storage accesses are correctly stored in a new access with a "resume" context + sstore(0, balance(a)) + } + } +} + +/// @notice Helper contract that writes to storage in a nested call +contract NestedStorer { + mapping(bytes32 key => uint256 value) slots; + + constructor() {} + + function run() public payable { + slots[bytes32("nested_storer 1")]++; + this.run2(); + slots[bytes32("nested_storer 2")]++; + } + + function run2() external payable { + slots[bytes32("nested_storer 3")]++; + slots[bytes32("nested_storer 4")]++; + } +} + +/// @notice Helper contract that directly reads from and writes to storage +contract StorageAccessor { + function read(bytes32 slot) public view returns (bytes32 value) { + assembly { + value := sload(slot) + } + } + + function write(bytes32 slot, bytes32 value) public { + assembly { + sstore(slot, value) + } + } +} + +/// @notice Proxy contract +contract Proxy { + bytes32 public constant IMPL_ADDR = bytes32(uint256(keccak256("ekans implementation"))); + + constructor(address _delegate) { + bytes32 impl = IMPL_ADDR; + assembly { + sstore(impl, _delegate) + } + } + + receive() external payable { + doProxyCall(); + } + + fallback() external payable { + doProxyCall(); + } + + function doProxyCall() internal { + address _target; + bytes32 impl = IMPL_ADDR; + assembly { + _target := sload(impl) + calldatacopy(0x0, 0x0, calldatasize()) + let result := delegatecall(gas(), _target, 0x0, calldatasize(), 0x0, 0) + returndatacopy(0x0, 0x0, returndatasize()) + switch result + case 0 { revert(0, 0) } + default { return(0, returndatasize()) } + } + } +} + +/// @notice Test that the cheatcode correctly records account accesses +contract RecordAccountAccessesTest is DSTest { + Vm constant cheats = Vm(HEVM_ADDRESS); + NestedRunner runner; + NestedStorer nestedStorer; + Create2or create2or; + StorageAccessor test1; + StorageAccessor test2; + ExtChecker extChecker; + + function setUp() public { + runner = new NestedRunner(); + nestedStorer = new NestedStorer(); + create2or = new Create2or(); + test1 = new StorageAccessor(); + test2 = new StorageAccessor(); + extChecker = new ExtChecker(); + } + + function testStorageAccessDelegateCall() public { + StorageAccessor one = test1; + Proxy proxy = new Proxy(address(one)); + + cheats.startStateDiffRecording(); + address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + + assertEq(called.length, 2, "incorrect length"); + + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Call), "incorrect kind"); + assertEq(called[0].accessor, address(this)); + assertEq(called[0].account, address(proxy)); + + assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.DelegateCall), "incorrect kind"); + assertEq(called[1].account, address(one), "incorrect account"); + assertEq(called[1].accessor, address(this), "incorrect accessor"); + assertEq( + called[1].storageAccesses[0], + Vm.StorageAccess({ + account: address(proxy), + slot: bytes32(uint256(1234)), + isWrite: false, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(0)), + reverted: false + }) + ); + } + + /// @notice Test normal, non-nested storage accesses + function testStorageAccesses() public { + StorageAccessor one = test1; + StorageAccessor two = test2; + cheats.startStateDiffRecording(); + + one.read(bytes32(uint256(1234))); + one.write(bytes32(uint256(1235)), bytes32(uint256(5678))); + two.write(bytes32(uint256(5678)), bytes32(uint256(123469))); + two.write(bytes32(uint256(5678)), bytes32(uint256(1234))); + + string memory diffs = cheats.getStateDiff(); + assertEq( + "0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n", + diffs + ); + string memory diffsJson = cheats.getStateDiffJson(); + assertEq( + "{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}", + diffsJson + ); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 4, "incorrect length"); + + assertEq(called[0].storageAccesses.length, 1, "incorrect storage length"); + Vm.StorageAccess memory access = called[0].storageAccesses[0]; + assertEq( + access, + Vm.StorageAccess({ + account: address(one), + slot: bytes32(uint256(1234)), + isWrite: false, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(0)), + reverted: false + }) + ); + + assertEq(called[1].storageAccesses.length, 1, "incorrect storage length"); + access = called[1].storageAccesses[0]; + assertEq( + access, + Vm.StorageAccess({ + account: address(one), + slot: bytes32(uint256(1235)), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(5678)), + reverted: false + }) + ); + + assertEq(called[2].storageAccesses.length, 1, "incorrect storage length"); + access = called[2].storageAccesses[0]; + assertEq( + access, + Vm.StorageAccess({ + account: address(two), + slot: bytes32(uint256(5678)), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(123469)), + reverted: false + }) + ); + + assertEq(called[3].storageAccesses.length, 1, "incorrect storage length"); + access = called[3].storageAccesses[0]; + assertEq( + access, + Vm.StorageAccess({ + account: address(two), + slot: bytes32(uint256(5678)), + isWrite: true, + previousValue: bytes32(uint256(123469)), + newValue: bytes32(uint256(1234)), + reverted: false + }) + ); + } + + /// @notice Test that basic account accesses are correctly recorded + function testRecordAccountAccesses() public { + cheats.startStateDiffRecording(); + + (bool succ,) = address(1234).call(""); + (succ,) = address(5678).call{value: 1 ether}(""); + (succ,) = address(123469).call("hello world"); + (succ,) = address(5678).call(""); + // contract calls to self in constructor + SelfCaller caller = new SelfCaller{value: 2 ether}("hello2 world2"); + + string memory callerAddress = cheats.toString(address(caller)); + string memory expectedStateDiff = + "0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n"; + expectedStateDiff = string.concat(expectedStateDiff, callerAddress); + expectedStateDiff = string.concat(expectedStateDiff, "\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n\n"); + assertEq(expectedStateDiff, cheats.getStateDiff()); + + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 6); + assertEq( + called[0], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(1234), + kind: Vm.AccountAccessKind.Call, + initialized: false, + oldBalance: 0, + newBalance: 0, + deployedCode: hex"", + value: 0, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 + }) + ); + + assertEq( + called[1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(5678), + kind: Vm.AccountAccessKind.Call, + initialized: false, + oldBalance: 0, + newBalance: 1 ether, + deployedCode: hex"", + value: 1 ether, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 + }) + ); + assertEq( + called[2], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(123469), + kind: Vm.AccountAccessKind.Call, + initialized: false, + oldBalance: 0, + newBalance: 0, + deployedCode: hex"", + value: 0, + data: "hello world", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 + }) + ); + assertEq( + called[3], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(5678), + kind: Vm.AccountAccessKind.Call, + initialized: true, + oldBalance: 1 ether, + newBalance: 1 ether, + deployedCode: hex"", + value: 0, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 + }) + ); + assertEq( + called[4], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(caller), + kind: Vm.AccountAccessKind.Create, + initialized: true, + oldBalance: 0, + newBalance: 2 ether, + deployedCode: address(caller).code, + value: 2 ether, + data: abi.encodePacked(type(SelfCaller).creationCode, abi.encode("hello2 world2")), + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 + }) + ); + assertEq( + called[5], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(caller), + account: address(caller), + kind: Vm.AccountAccessKind.Call, + initialized: true, + oldBalance: 2 ether, + newBalance: 2 ether, + deployedCode: hex"", + value: 0.2 ether, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 + }) + ); + } + + /// @notice Test that account accesses are correctly recorded when a call + /// reverts + function testRevertingCall() public { + uint256 initBalance = address(this).balance; + cheats.startStateDiffRecording(); + try this.revertingCall{value: 1 ether}(address(1234), "") {} catch {} + assertEq( + "0x00000000000000000000000000000000000004d2\n- balance diff: 0 \xE2\x86\x92 100000000000000000\n\n", + cheats.getStateDiff() + ); + assertEq( + "{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"stateDiff\":{}}}", + cheats.getStateDiffJson() + ); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 2); + assertEq( + called[0], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(this), + kind: Vm.AccountAccessKind.Call, + initialized: true, + oldBalance: initBalance, + newBalance: initBalance, + deployedCode: hex"", + value: 1 ether, + data: abi.encodeCall(this.revertingCall, (address(1234), "")), + reverted: true, + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 + }) + ); + assertEq( + called[1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(1234), + kind: Vm.AccountAccessKind.Call, + initialized: false, + oldBalance: 0, + newBalance: 0.1 ether, + deployedCode: hex"", + value: 0.1 ether, + data: "", + reverted: true, + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 + }) + ); + } + + /// @notice Test that nested account accesses are correctly recorded + function testNested() public { + cheats.startStateDiffRecording(); + runNested(false, false); + } + + /// @notice Test that nested account accesses are correctly recorded when + /// the first call reverts + function testNested_Revert() public { + cheats.startStateDiffRecording(); + runNested(true, false); + } + + /// @notice Helper function to test nested account accesses + /// @param shouldRevert Whether the first call should revert + function runNested(bool shouldRevert, bool expectFirstCall) public { + try runner.run{value: 1 ether}(shouldRevert) {} catch {} + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 7 + toUint(expectFirstCall), "incorrect length"); + + uint64 startingIndex = uint64(toUint(expectFirstCall)); + if (expectFirstCall) { + assertEq( + called[0], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(1234), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: false, + value: 0, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + }) + ); + } + + assertEq(called[startingIndex].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex].storageAccesses[0], + called[startingIndex].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner), + slot: keccak256(abi.encodePacked(bytes32("runner"), bytes32(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: shouldRevert + }) + ); + assertEq( + called[startingIndex], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(runner), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: shouldRevert ? 0 : 0.9 ether, + deployedCode: "", + initialized: true, + value: 1 ether, + data: abi.encodeCall(NestedRunner.run, (shouldRevert)), + reverted: shouldRevert, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + }), + false + ); + + assertEq(called[startingIndex + 1].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex + 1].storageAccesses[0], + called[startingIndex + 1].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner.reverter()), + slot: keccak256(abi.encodePacked(bytes32("reverter"), bytes32(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: true + }) + ); + assertEq( + called[startingIndex + 1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(runner), + account: address(runner.reverter()), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 0.1 ether, + data: abi.encodeCall(Reverter.run, ()), + reverted: true, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 1 + }), + false + ); + + assertEq(called[startingIndex + 2].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex + 2].storageAccesses[0], + called[startingIndex + 2].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner.doer()), + slot: keccak256(abi.encodePacked(bytes32("doer 1"), uint256(10))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: true + }) + ); + assertEq( + called[startingIndex + 2], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(runner.reverter()), + account: address(runner.doer()), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0.01 ether, + deployedCode: "", + initialized: true, + value: 0.01 ether, + data: abi.encodeCall(Doer.run, ()), + reverted: true, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 2 + }), + false + ); + + assertEq(called[startingIndex + 3].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex + 3].storageAccesses[0], + called[startingIndex + 3].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner.doer()), + slot: keccak256(abi.encodePacked(bytes32("doer 2"), uint256(10))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: true + }) + ); + assertEq( + called[startingIndex + 3], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(runner.doer()), + account: address(runner.doer()), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0.01 ether, + newBalance: 0.01 ether, + deployedCode: "", + initialized: true, + value: 0.001 ether, + data: abi.encodeCall(Doer.doStuff, ()), + reverted: true, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 3 + }), + false + ); + + assertEq(called[startingIndex + 4].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex + 4].storageAccesses[0], + called[startingIndex + 4].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner.succeeder()), + slot: keccak256(abi.encodePacked(bytes32("succeeder"), uint256(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: shouldRevert + }) + ); + assertEq( + called[startingIndex + 4], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(runner), + account: address(runner.succeeder()), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0.09 ether, + deployedCode: "", + initialized: true, + value: 0.1 ether, + data: abi.encodeCall(Succeeder.run, ()), + reverted: shouldRevert, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 4 + }), + false + ); + + assertEq(called[startingIndex + 5].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex + 5].storageAccesses[0], + called[startingIndex + 5].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner.doer()), + slot: keccak256(abi.encodePacked(bytes32("doer 1"), uint256(10))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: shouldRevert + }) + ); + assertEq( + called[startingIndex + 5], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(runner.succeeder()), + account: address(runner.doer()), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0.01 ether, + deployedCode: "", + initialized: true, + value: 0.01 ether, + data: abi.encodeCall(Doer.run, ()), + reverted: shouldRevert, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 5 + }), + false + ); + + assertEq(called[startingIndex + 3].storageAccesses.length, 2, "incorrect length"); + assertIncrementEq( + called[startingIndex + 6].storageAccesses[0], + called[startingIndex + 6].storageAccesses[1], + Vm.StorageAccess({ + account: address(runner.doer()), + slot: keccak256(abi.encodePacked(bytes32("doer 2"), uint256(10))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: shouldRevert + }) + ); + assertEq( + called[startingIndex + 6], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(runner.doer()), + account: address(runner.doer()), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0.01 ether, + newBalance: 0.01 ether, + deployedCode: "", + initialized: true, + value: 0.001 ether, + data: abi.encodeCall(Doer.doStuff, ()), + reverted: shouldRevert, + storageAccesses: new Vm.StorageAccess[](0), + depth: startingIndex + 6 + }), + false + ); + } + + function testNestedStorage() public { + cheats.startStateDiffRecording(); + nestedStorer.run(); + cheats.label(address(nestedStorer), "NestedStorer"); + assertEq( + "0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n", + cheats.getStateDiff() + ); + assertEq( + "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"balanceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}", + cheats.getStateDiffJson() + ); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 3, "incorrect account access length"); + + assertEq(called[0].storageAccesses.length, 2, "incorrect run storage length"); + assertIncrementEq( + called[0].storageAccesses[0], + called[0].storageAccesses[1], + Vm.StorageAccess({ + account: address(nestedStorer), + slot: keccak256(abi.encodePacked(bytes32("nested_storer 1"), bytes32(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: false + }) + ); + assertEq( + called[0], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(nestedStorer), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 0, + data: abi.encodeCall(NestedStorer.run, ()), + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 + }), + false + ); + + assertEq(called[1].storageAccesses.length, 4, "incorrect run2 storage length"); + assertIncrementEq( + called[1].storageAccesses[0], + called[1].storageAccesses[1], + Vm.StorageAccess({ + account: address(nestedStorer), + slot: keccak256(abi.encodePacked(bytes32("nested_storer 3"), bytes32(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: false + }) + ); + assertIncrementEq( + called[1].storageAccesses[2], + called[1].storageAccesses[3], + Vm.StorageAccess({ + account: address(nestedStorer), + slot: keccak256(abi.encodePacked(bytes32("nested_storer 4"), bytes32(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: false + }) + ); + assertEq( + called[1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(nestedStorer), + account: address(nestedStorer), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 0, + data: abi.encodeCall(NestedStorer.run2, ()), + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 + }), + false + ); + + assertEq(called[2].storageAccesses.length, 2, "incorrect resume storage length"); + assertIncrementEq( + called[2].storageAccesses[0], + called[2].storageAccesses[1], + Vm.StorageAccess({ + account: address(nestedStorer), + slot: keccak256(abi.encodePacked(bytes32("nested_storer 2"), bytes32(0))), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: false + }) + ); + assertEq( + called[2], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(nestedStorer), + kind: Vm.AccountAccessKind.Resume, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 0, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 + }), + false + ); + } + + /// @notice Test that constructor account and storage accesses are recorded, including reverts + function testConstructorStorage() public { + cheats.startStateDiffRecording(); + address storer = address(new ConstructorStorer(false)); + try create2or.create2(bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) {} + catch {} + bytes memory creationCode = abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true)); + address hypotheticalStorer = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); + + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 3, "incorrect account access length"); + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create), "incorrect kind"); + assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call), "incorrect kind"); + assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Create), "incorrect kind"); + + assertEq(called[0].storageAccesses.length, 1, "incorrect storage access length"); + Vm.StorageAccess[] memory storageAccesses = new Vm.StorageAccess[](1); + storageAccesses[0] = Vm.StorageAccess({ + account: storer, + slot: bytes32(uint256(0)), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: false + }); + assertEq( + called[0], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(storer), + kind: Vm.AccountAccessKind.Create, + oldBalance: 0, + newBalance: 0, + deployedCode: storer.code, + initialized: true, + value: 0, + data: abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(false)), + reverted: false, + storageAccesses: storageAccesses, + depth: 0 + }) + ); + + assertEq(called[1].storageAccesses.length, 0, "incorrect storage access length"); + assertEq( + called[1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: address(create2or), + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 0, + data: abi.encodeCall( + Create2or.create2, + (bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) + ), + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 + }) + ); + + assertEq(called[2].storageAccesses.length, 1, "incorrect storage access length"); + storageAccesses = new Vm.StorageAccess[](1); + storageAccesses[0] = Vm.StorageAccess({ + account: hypotheticalStorer, + slot: bytes32(uint256(0)), + isWrite: true, + previousValue: bytes32(uint256(0)), + newValue: bytes32(uint256(1)), + reverted: true + }); + assertEq( + called[2], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(create2or), + account: hypotheticalStorer, + kind: Vm.AccountAccessKind.Create, + oldBalance: 0, + newBalance: 0, + deployedCode: address(hypotheticalStorer).code, + initialized: true, + value: 0, + data: creationCode, + reverted: true, + storageAccesses: storageAccesses, + depth: 2 + }) + ); + } + + /// @notice Test that account accesses are correctly recorded when the + /// recording is started from a lower depth than they are + /// retrieved + function testNested_LowerDepth() public { + this.startRecordingFromLowerDepth(); + runNested(false, true); + } + + /// @notice Test that account accesses are correctly recorded when + /// the first call reverts the and recording is started from + /// a lower depth than they are retrieved. + function testNested_LowerDepth_Revert() public { + this.startRecordingFromLowerDepth(); + runNested(true, true); + } + + /// @notice Test that constructor calls and calls made within a constructor + /// are correctly recorded, even if it reverts + function testCreateRevert() public { + cheats.startStateDiffRecording(); + bytes memory creationCode = abi.encodePacked(type(SelfCaller).creationCode, abi.encode("")); + try create2or.create2(bytes32(0), creationCode) {} catch {} + address hypotheticalAddress = deriveCreate2Address(address(create2or), bytes32(0), keccak256(creationCode)); + + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 3, "incorrect length"); + assertEq( + called[1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(create2or), + account: hypotheticalAddress, + kind: Vm.AccountAccessKind.Create, + oldBalance: 0, + newBalance: 0, + deployedCode: address(hypotheticalAddress).code, + initialized: true, + value: 0, + data: creationCode, + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 + }) + ); + assertEq( + called[2], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: hypotheticalAddress, + account: hypotheticalAddress, + kind: Vm.AccountAccessKind.Call, + oldBalance: 0, + newBalance: 0, + deployedCode: hex"", + initialized: true, + value: 0, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 + }) + ); + } + + /// @notice It is important to test SELFDESTRUCT behavior as long as there + /// are public networks that support the opcode, regardless of whether + /// or not Ethereum mainnet does. + function testSelfDestruct() public { + uint256 startingBalance = address(this).balance; + this.startRecordingFromLowerDepth(); + address a = address(new SelfDestructor{value: 1 ether}(address(this))); + address b = address(new SelfDestructor{value: 1 ether}(address(bytes20("doesn't exist yet")))); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 5, "incorrect length"); + assertEq( + called[1], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: a, + kind: Vm.AccountAccessKind.Create, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 1 ether, + data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(this))), + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 1 + }) + ); + assertEq( + called[2], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(a), + account: address(this), + kind: Vm.AccountAccessKind.SelfDestruct, + oldBalance: startingBalance - 1 ether, + newBalance: startingBalance, + deployedCode: "", + initialized: true, + value: 1 ether, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 2 + }) + ); + assertEq( + called[3], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(this), + account: b, + kind: Vm.AccountAccessKind.Create, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: true, + value: 1 ether, + data: abi.encodePacked(type(SelfDestructor).creationCode, abi.encode(address(bytes20("doesn't exist yet")))), + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 3 + }) + ); + assertEq( + called[4], + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: address(b), + account: address(bytes20("doesn't exist yet")), + kind: Vm.AccountAccessKind.SelfDestruct, + oldBalance: 0, + newBalance: 1 ether, + deployedCode: hex"", + initialized: false, + value: 1 ether, + data: "", + reverted: false, + storageAccesses: new Vm.StorageAccess[](0), + depth: 4 + }) + ); + } + + /// @notice Asserts interaction between broadcast and recording cheatcodes + function testIssue6514() public { + cheats.startStateDiffRecording(); + cheats.startBroadcast(); + + StorageAccessor a = new StorageAccessor(); + + cheats.stopBroadcast(); + Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); + assertEq(called.length, 1, "incorrect length"); + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Create)); + assertEq(called[0].account, address(a)); + } + + /// @notice Test that EXT* opcodes are recorded as account accesses + function testExtOpcodes() public { + cheats.startStateDiffRecording(); + extChecker.checkExts(address(1234)); + Vm.AccountAccess[] memory called = cheats.stopAndReturnStateDiff(); + assertEq(called.length, 7, "incorrect length"); + // initial solidity extcodesize check for calling extChecker + assertEq(toUint(called[0].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + // call to extChecker + assertEq(toUint(called[1].kind), toUint(Vm.AccountAccessKind.Call)); + // extChecker checks + assertEq(toUint(called[2].kind), toUint(Vm.AccountAccessKind.Extcodesize)); + assertEq(toUint(called[3].kind), toUint(Vm.AccountAccessKind.Extcodehash)); + assertEq(toUint(called[4].kind), toUint(Vm.AccountAccessKind.Extcodecopy)); + assertEq(toUint(called[5].kind), toUint(Vm.AccountAccessKind.Balance)); + // resume of extChecker to hold SSTORE access + assertEq(toUint(called[6].kind), toUint(Vm.AccountAccessKind.Resume)); + assertEq(called[6].storageAccesses.length, 1, "incorrect length"); + } + + /** + * @notice Filter out extcodesize account accesses for legacy tests written before + * EXT* opcodes were supported. + */ + function filterExtcodesizeForLegacyTests(Vm.AccountAccess[] memory inArr) + internal + pure + returns (Vm.AccountAccess[] memory out) + { + // allocate max length for out array + out = new Vm.AccountAccess[](inArr.length); + // track end size + uint256 size; + for (uint256 i = 0; i < inArr.length; ++i) { + // only append if not extcodesize + if (inArr[i].kind != Vm.AccountAccessKind.Extcodesize) { + out[size] = inArr[i]; + ++size; + } + } + // manually truncate out array + assembly { + mstore(out, size) + } + } + + function startRecordingFromLowerDepth() external { + cheats.startStateDiffRecording(); + assembly { + pop(call(gas(), 1234, 0, 0, 0, 0, 0)) + } + } + + function revertingCall(address target, bytes memory data) external payable { + assembly { + pop(call(gas(), target, div(callvalue(), 10), add(data, 0x20), mload(data), 0, 0)) + } + revert(); + } + + /// Asserts that the given account access is a resume of the given parent + function assertResumeEq(Vm.AccountAccess memory actual, Vm.AccountAccess memory expected) internal { + assertEq( + actual, + Vm.AccountAccess({ + chainInfo: Vm.ChainInfo({forkId: 0, chainId: 0}), + accessor: expected.accessor, + account: expected.account, + kind: Vm.AccountAccessKind.Resume, + oldBalance: 0, + newBalance: 0, + deployedCode: "", + initialized: expected.initialized, + value: 0, + data: "", + reverted: expected.reverted, + storageAccesses: new Vm.StorageAccess[](0), + depth: 0 + }), + false + ); + } + + function assertIncrementEq( + Vm.StorageAccess memory read, + Vm.StorageAccess memory write, + Vm.StorageAccess memory expected + ) internal { + assertEq( + read, + Vm.StorageAccess({ + account: expected.account, + slot: expected.slot, + isWrite: false, + previousValue: expected.previousValue, + newValue: expected.previousValue, + reverted: expected.reverted + }) + ); + assertEq( + write, + Vm.StorageAccess({ + account: expected.account, + slot: expected.slot, + isWrite: true, + previousValue: expected.previousValue, + newValue: expected.newValue, + reverted: expected.reverted + }) + ); + } + + function assertEq(Vm.AccountAccess memory actualAccess, Vm.AccountAccess memory expectedAccess) internal { + assertEq(actualAccess, expectedAccess, true); + } + + function assertEq(Vm.AccountAccess memory actualAccess, Vm.AccountAccess memory expectedAccess, bool checkStorage) + internal + { + assertEq(toUint(actualAccess.kind), toUint(expectedAccess.kind), "incorrect kind"); + assertEq(actualAccess.account, expectedAccess.account, "incorrect account"); + assertEq(actualAccess.accessor, expectedAccess.accessor, "incorrect accessor"); + assertEq(toUint(actualAccess.initialized), toUint(expectedAccess.initialized), "incorrect initialized"); + assertEq(actualAccess.oldBalance, expectedAccess.oldBalance, "incorrect oldBalance"); + assertEq(actualAccess.newBalance, expectedAccess.newBalance, "incorrect newBalance"); + assertEq(actualAccess.deployedCode, expectedAccess.deployedCode, "incorrect deployedCode"); + assertEq(actualAccess.value, expectedAccess.value, "incorrect value"); + assertEq(actualAccess.data, expectedAccess.data, "incorrect data"); + assertEq(toUint(actualAccess.reverted), toUint(expectedAccess.reverted), "incorrect reverted"); + if (checkStorage) { + assertEq( + actualAccess.storageAccesses.length, + expectedAccess.storageAccesses.length, + "incorrect storageAccesses length" + ); + for (uint256 i = 0; i < actualAccess.storageAccesses.length; i++) { + assertEq(actualAccess.storageAccesses[i], expectedAccess.storageAccesses[i]); + } + } + } + + function assertEq(Vm.StorageAccess memory actual, Vm.StorageAccess memory expected) internal { + assertEq(actual.account, expected.account, "incorrect storageAccess account"); + assertEq(actual.slot, expected.slot, "incorrect storageAccess slot"); + assertEq(toUint(actual.isWrite), toUint(expected.isWrite), "incorrect storageAccess isWrite"); + assertEq(actual.previousValue, expected.previousValue, "incorrect storageAccess previousValue"); + assertEq(actual.newValue, expected.newValue, "incorrect storageAccess newValue"); + assertEq(toUint(actual.reverted), toUint(expected.reverted), "incorrect storageAccess reverted"); + } + + function toUint(Vm.AccountAccessKind kind) internal pure returns (uint256 value) { + assembly { + value := and(kind, 0xff) + } + } + + function toUint(bool a) internal pure returns (uint256) { + return a ? 1 : 0; + } + + function deriveCreate2Address(address deployer, bytes32 salt, bytes32 codeHash) internal pure returns (address) { + return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, codeHash))))); + } +} diff --git a/testdata/default/cheats/RecordDebugTrace.t.sol b/testdata/default/cheats/RecordDebugTrace.t.sol new file mode 100644 index 0000000000000..ade2e7aafb7e1 --- /dev/null +++ b/testdata/default/cheats/RecordDebugTrace.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract MStoreAndMLoadCaller { + uint256 public constant expectedValueInMemory = 999; + + uint256 public memPtr; // the memory pointer being used + + function storeAndLoadValueFromMemory() public returns (uint256) { + uint256 mPtr; + assembly { + mPtr := mload(0x40) // load free pointer + mstore(mPtr, expectedValueInMemory) + mstore(0x40, add(mPtr, 0x20)) + } + + // record & expose the memory pointer location + memPtr = mPtr; + + uint256 result = 123; + assembly { + // override with `expectedValueInMemory` + result := mload(mPtr) + } + return result; + } +} + +contract FirstLayer { + SecondLayer secondLayer; + + constructor(SecondLayer _secondLayer) { + secondLayer = _secondLayer; + } + + function callSecondLayer() public view returns (uint256) { + return secondLayer.endHere(); + } +} + +contract SecondLayer { + uint256 public constant endNumber = 123; + + function endHere() public view returns (uint256) { + return endNumber; + } +} + +contract OutOfGas { + uint256 dummyVal = 0; + + function consumeGas() public { + dummyVal += 1; + } + + function triggerOOG() public { + bytes memory encodedFunctionCall = abi.encodeWithSignature("consumeGas()", ""); + uint256 notEnoughGas = 50; + (bool success,) = address(this).call{gas: notEnoughGas}(encodedFunctionCall); + require(!success, "it should error out of gas"); + } +} + +contract RecordDebugTraceTest is DSTest { + Vm constant cheats = Vm(HEVM_ADDRESS); + /** + * The goal of this test is to ensure the debug steps provide the correct OPCODE with its stack + * and memory input used. The test checke MSTORE and MLOAD and ensure it records the expected + * stack and memory inputs. + */ + + function testDebugTraceCanRecordOpcodeWithStackAndMemoryData() public { + MStoreAndMLoadCaller testContract = new MStoreAndMLoadCaller(); + + cheats.startDebugTraceRecording(); + + uint256 val = testContract.storeAndLoadValueFromMemory(); + assertTrue(val == testContract.expectedValueInMemory()); + + Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + + bool mstoreCalled = false; + bool mloadCalled = false; + + for (uint256 i = 0; i < steps.length; i++) { + Vm.DebugStep memory step = steps[i]; + if ( + step.opcode == 0x52 /*MSTORE*/ && step.stack[0] == testContract.memPtr() // MSTORE offset + && step.stack[1] == testContract.expectedValueInMemory() // MSTORE val + ) { + mstoreCalled = true; + } + + if ( + step.opcode == 0x51 /*MLOAD*/ && step.stack[0] == testContract.memPtr() // MLOAD offset + && step.memoryInput.length == 32 // MLOAD should always load 32 bytes + && uint256(bytes32(step.memoryInput)) == testContract.expectedValueInMemory() // MLOAD value + ) { + mloadCalled = true; + } + } + + assertTrue(mstoreCalled); + assertTrue(mloadCalled); + } + + /** + * This test tests that the cheatcode can correctly record the depth of the debug steps. + * This is test by test -> FirstLayer -> SecondLayer and check that the + * depth of the FirstLayer and SecondLayer are all as expected. + */ + function testDebugTraceCanRecordDepth() public { + SecondLayer second = new SecondLayer(); + FirstLayer first = new FirstLayer(second); + + cheats.startDebugTraceRecording(); + + first.callSecondLayer(); + + Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + + bool goToDepthTwo = false; + bool goToDepthThree = false; + for (uint256 i = 0; i < steps.length; i++) { + Vm.DebugStep memory step = steps[i]; + + if (step.depth == 2) { + assertTrue(step.contractAddr == address(first), "must be first layer on depth 2"); + goToDepthTwo = true; + } + + if (step.depth == 3) { + assertTrue(step.contractAddr == address(second), "must be second layer on depth 3"); + goToDepthThree = true; + } + } + assertTrue(goToDepthTwo && goToDepthThree, "must have been to both first and second layer"); + } + + /** + * The goal of this test is to ensure it can return expected `isOutOfGas` flag. + * It is tested with out of gas result here. + */ + function testDebugTraceCanRecordOutOfGas() public { + OutOfGas testContract = new OutOfGas(); + + cheats.startDebugTraceRecording(); + + testContract.triggerOOG(); + + Vm.DebugStep[] memory steps = cheats.stopAndReturnDebugTraceRecording(); + + bool isOOG = false; + for (uint256 i = 0; i < steps.length; i++) { + Vm.DebugStep memory step = steps[i]; + + if (step.isOutOfGas) { + isOOG = true; + } + } + assertTrue(isOOG, "should OOG"); + } +} diff --git a/testdata/cheats/RecordLogs.t.sol b/testdata/default/cheats/RecordLogs.t.sol similarity index 98% rename from testdata/cheats/RecordLogs.t.sol rename to testdata/default/cheats/RecordLogs.t.sol index 34337c6df1ce0..14ca8dde35e99 100644 --- a/testdata/cheats/RecordLogs.t.sol +++ b/testdata/default/cheats/RecordLogs.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { event LogAnonymous(bytes data) anonymous; diff --git a/testdata/cheats/Remember.t.sol b/testdata/default/cheats/Remember.t.sol similarity index 84% rename from testdata/cheats/Remember.t.sol rename to testdata/default/cheats/Remember.t.sol index e17b43c421ebf..b8dbe7e38007c 100644 --- a/testdata/cheats/Remember.t.sol +++ b/testdata/default/cheats/Remember.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RememberTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/ResetNonce.t.sol b/testdata/default/cheats/ResetNonce.t.sol similarity index 91% rename from testdata/cheats/ResetNonce.t.sol rename to testdata/default/cheats/ResetNonce.t.sol index 5c0bd77b68419..d8c911587095c 100644 --- a/testdata/cheats/ResetNonce.t.sol +++ b/testdata/default/cheats/ResetNonce.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/Roll.t.sol b/testdata/default/cheats/Roll.t.sol similarity index 89% rename from testdata/cheats/Roll.t.sol rename to testdata/default/cheats/Roll.t.sol index b7bd9ab9b5d7d..87f909cdd373f 100644 --- a/testdata/cheats/Roll.t.sol +++ b/testdata/default/cheats/Roll.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RollTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/RpcUrls.t.sol b/testdata/default/cheats/RpcUrls.t.sol new file mode 100644 index 0000000000000..9425d59631b59 --- /dev/null +++ b/testdata/default/cheats/RpcUrls.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RpcUrlTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + // returns the correct url + function testCanGetRpcUrl() public { + string memory url = vm.rpcUrl("mainnet"); + assertTrue(bytes(url).length == 61 || bytes(url).length == 69); + } + + // returns an error if env alias does not exist + function testRevertsOnMissingEnv() public { + vm._expectCheatcodeRevert("invalid rpc url: rpcUrlEnv"); + string memory url = vm.rpcUrl("rpcUrlEnv"); + } + + // can set env and return correct url + function testCanSetAndGetURLAndAllUrls() public { + // this will fail because alias is not set + vm._expectCheatcodeRevert("environment variable `RPC_ENV_ALIAS` not found"); + string[2][] memory _urls = vm.rpcUrls(); + + string memory url = vm.rpcUrl("mainnet"); + vm.setEnv("RPC_ENV_ALIAS", url); + string memory envUrl = vm.rpcUrl("rpcEnvAlias"); + assertEq(url, envUrl); + + string[2][] memory allUrls = vm.rpcUrls(); + assertGe(allUrls.length, 2); + } +} diff --git a/testdata/default/cheats/SetBlockhash.t.sol b/testdata/default/cheats/SetBlockhash.t.sol new file mode 100644 index 0000000000000..1274620df41a6 --- /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/cheats/SetNonce.t.sol b/testdata/default/cheats/SetNonce.t.sol similarity index 63% rename from testdata/cheats/SetNonce.t.sol rename to testdata/default/cheats/SetNonce.t.sol index f94a7c1c5fd44..e0fda6aaec688 100644 --- a/testdata/cheats/SetNonce.t.sol +++ b/testdata/default/cheats/SetNonce.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { @@ -26,9 +26,13 @@ contract SetNonceTest is DSTest { foo.f(); } - function testFailInvalidNonce() public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfInvalidNonce() public { vm.setNonce(address(foo), 10); // set lower nonce should fail + vm.expectRevert( + "vm.setNonce: new nonce (5) must be strictly equal to or higher than the account's current nonce (10)" + ); vm.setNonce(address(foo), 5); } } diff --git a/testdata/cheats/SetNonceUnsafe.t.sol b/testdata/default/cheats/SetNonceUnsafe.t.sol similarity index 88% rename from testdata/cheats/SetNonceUnsafe.t.sol rename to testdata/default/cheats/SetNonceUnsafe.t.sol index b1989d3312374..0caf2b4ce7421 100644 --- a/testdata/cheats/SetNonceUnsafe.t.sol +++ b/testdata/default/cheats/SetNonceUnsafe.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/Setup.t.sol b/testdata/default/cheats/Setup.t.sol similarity index 87% rename from testdata/cheats/Setup.t.sol rename to testdata/default/cheats/Setup.t.sol index 6cf240f0370d7..4d6e5954b5fe1 100644 --- a/testdata/cheats/Setup.t.sol +++ b/testdata/default/cheats/Setup.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Victim { function assertSender(address sender) external { @@ -21,7 +21,7 @@ contract VmSetupTest is DSTest { vm.chainId(99); vm.roll(100); vm.fee(1000); - vm.prevrandao(bytes32(uint256(10000))); + vm.prevrandao(uint256(10000)); vm.startPrank(address(1337)); } diff --git a/testdata/default/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol new file mode 100644 index 0000000000000..937ebc00a9222 --- /dev/null +++ b/testdata/default/cheats/Sign.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SignTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSignDigest(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); + address expected = vm.addr(pk); + address actual = ecrecover(digest, v, r, s); + assertEq(actual, expected, "digest signer did not match"); + } + + function testSignCompactDigest(uint248 pk, bytes32 digest) public { + vm.assume(pk != 0); + + (bytes32 r, bytes32 vs) = vm.signCompact(pk, digest); + + // Extract `s` from `vs`. + // Shift left by 1 bit to clear the leftmost bit, then shift right by 1 bit to restore the original position. + // This effectively clears the leftmost bit of `vs`, giving us `s`. + bytes32 s = bytes32((uint256(vs) << 1) >> 1); + + // Extract `v` from `vs`. + // We shift `vs` right by 255 bits to isolate the leftmost bit. + // Converting this to uint8 gives us the parity bit (0 or 1). + // Adding 27 converts this parity bit to the correct `v` value (27 or 28). + uint8 v = uint8(uint256(vs) >> 255) + 27; + + address expected = vm.addr(pk); + address actual = ecrecover(digest, v, r, s); + assertEq(actual, expected, "digest signer did not match"); + } + + function testSignMessage(uint248 pk, bytes memory message) public { + testSignDigest(pk, keccak256(message)); + } + + function testSignCompactMessage(uint248 pk, bytes memory message) public { + testSignCompactDigest(pk, keccak256(message)); + } +} diff --git a/testdata/default/cheats/SignP256.t.sol b/testdata/default/cheats/SignP256.t.sol new file mode 100644 index 0000000000000..b92588ce9f823 --- /dev/null +++ b/testdata/default/cheats/SignP256.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SignTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSignP256() public { + bytes32 pk = hex"A8568B74282DCC66FF70F10B4CE5CC7B391282F5381BBB4F4D8DD96974B16E6B"; + bytes32 digest = hex"54705ba3baafdbdfba8c5f9a70f7a89bee98d906b53e31074da7baecdc0da9ad"; + + (bytes32 r, bytes32 s) = vm.signP256(uint256(pk), digest); + assertEq(r, hex"7C11C3641B19E7822DB644CBF76ED0420A013928C2FD3E36D8EF983B103BDFE1"); + assertEq(s, hex"317D89879868D484810D4E508A96109F8C87617B7BE9337411348D7B786F945F"); + } +} diff --git a/testdata/cheats/Skip.t.sol b/testdata/default/cheats/Skip.t.sol similarity index 59% rename from testdata/cheats/Skip.t.sol rename to testdata/default/cheats/Skip.t.sol index 3107d5ec9e723..d7e75fa0f51af 100644 --- a/testdata/cheats/Skip.t.sol +++ b/testdata/default/cheats/Skip.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SkipTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -12,8 +12,10 @@ contract SkipTest is DSTest { revert("Should not reach this revert"); } - function testFailNotSkip() public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfNotSkip() public { vm.skip(false); + vm.expectRevert("This test should fail"); revert("This test should fail"); } @@ -22,8 +24,10 @@ contract SkipTest is DSTest { revert("Should not reach revert"); } - function testFailFuzzSkip(uint256 x) public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfFuzzSkip(uint256 x) public { vm.skip(false); + vm.expectRevert("This test should fail"); revert("This test should fail"); } diff --git a/testdata/cheats/Sleep.t.sol b/testdata/default/cheats/Sleep.t.sol similarity index 90% rename from testdata/cheats/Sleep.t.sol rename to testdata/default/cheats/Sleep.t.sol index 3efc8b127f68f..7af548e742573 100644 --- a/testdata/cheats/Sleep.t.sol +++ b/testdata/default/cheats/Sleep.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SleepTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -27,7 +27,7 @@ contract SleepTest is DSTest { assertGe(end - start, milliseconds / 1000 * 1000, "sleep failed"); } - /// forge-config: default.fuzz.runs = 10 + /// forge-config: default.fuzz.runs = 2 function testSleepFuzzed(uint256 _milliseconds) public { // Limit sleep time to 2 seconds to decrease test time uint256 milliseconds = _milliseconds % 2000; diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol new file mode 100644 index 0000000000000..8751a04094129 --- /dev/null +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct Storage { + uint256 slot0; + uint256 slot1; +} + +contract StateSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Storage store; + + function setUp() public { + store.slot0 = 10; + store.slot1 = 20; + } + + function testStateSnapshot() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToState(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + } + + function testStateSnapshotRevertDelete() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToStateAndDelete(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + // nothing to revert to anymore + assert(!vm.revertToState(snapshotId)); + } + + function testStateSnapshotDelete() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteStateSnapshot(snapshotId); + // nothing to revert to anymore + assert(!vm.revertToState(snapshotId)); + } + + function testStateSnapshotDeleteAll() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteStateSnapshots(); + // nothing to revert to anymore + assert(!vm.revertToState(snapshotId)); + } + + // + function testStateSnapshotsMany() public { + uint256 snapshotId; + for (uint256 c = 0; c < 10; c++) { + for (uint256 cc = 0; cc < 10; cc++) { + snapshotId = vm.snapshotState(); + vm.revertToStateAndDelete(snapshotId); + assert(!vm.revertToState(snapshotId)); + } + } + } + + // tests that snapshots can also revert changes to `block` + function testBlockValues() public { + uint256 num = block.number; + uint256 time = block.timestamp; + uint256 prevrandao = block.prevrandao; + + uint256 snapshotId = vm.snapshotState(); + + vm.warp(1337); + assertEq(block.timestamp, 1337); + + vm.roll(99); + assertEq(block.number, 99); + + vm.prevrandao(uint256(123)); + assertEq(block.prevrandao, 123); + + assert(vm.revertToState(snapshotId)); + + assertEq(block.number, num, "snapshot revert for block.number unsuccessful"); + assertEq(block.timestamp, time, "snapshot revert for block.timestamp unsuccessful"); + assertEq(block.prevrandao, prevrandao, "snapshot revert for block.prevrandao unsuccessful"); + } +} + +// TODO: remove this test suite once `snapshot*` has been deprecated in favor of `snapshotState*`. +contract DeprecatedStateSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Storage store; + + function setUp() public { + store.slot0 = 10; + store.slot1 = 20; + } + + function testSnapshotState() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertTo(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + } + + function testSnapshotStateRevertDelete() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToAndDelete(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + // nothing to revert to anymore + assert(!vm.revertTo(snapshotId)); + } + + function testSnapshotStateDelete() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteSnapshot(snapshotId); + // nothing to revert to anymore + assert(!vm.revertTo(snapshotId)); + } + + function testSnapshotStateDeleteAll() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteSnapshots(); + // nothing to revert to anymore + assert(!vm.revertTo(snapshotId)); + } + + // + function testSnapshotStatesMany() public { + uint256 snapshotId; + for (uint256 c = 0; c < 10; c++) { + for (uint256 cc = 0; cc < 10; cc++) { + snapshotId = vm.snapshot(); + vm.revertToAndDelete(snapshotId); + assert(!vm.revertTo(snapshotId)); + } + } + } + + // tests that snapshots can also revert changes to `block` + function testBlockValues() public { + uint256 num = block.number; + uint256 time = block.timestamp; + uint256 prevrandao = block.prevrandao; + + uint256 snapshotId = vm.snapshot(); + + vm.warp(1337); + assertEq(block.timestamp, 1337); + + vm.roll(99); + assertEq(block.number, 99); + + vm.prevrandao(uint256(123)); + assertEq(block.prevrandao, 123); + + assert(vm.revertTo(snapshotId)); + + assertEq(block.number, num, "snapshot revert for block.number unsuccessful"); + assertEq(block.timestamp, time, "snapshot revert for block.timestamp unsuccessful"); + assertEq(block.prevrandao, prevrandao, "snapshot revert for block.prevrandao unsuccessful"); + } +} diff --git a/testdata/cheats/Store.t.sol b/testdata/default/cheats/Store.t.sol similarity index 75% rename from testdata/cheats/Store.t.sol rename to testdata/default/cheats/Store.t.sol index 5d609ff26e4dd..9a1ce6101c1b0 100644 --- a/testdata/cheats/Store.t.sol +++ b/testdata/default/cheats/Store.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Storage { uint256 public slot0 = 10; @@ -30,14 +30,8 @@ contract StoreTest is DSTest { assertEq(store.slot0(), 10, "initial value for slot 0 is incorrect"); assertEq(store.slot1(), 20, "initial value for slot 1 is incorrect"); - vm.expectRevert( - bytes("Store cannot be used on precompile addresses (N < 10). Please use an address bigger than 10 instead") - ); - this._store(address(1), bytes32(0), bytes32(uint256(1))); - } - - function _store(address target, bytes32 slot, bytes32 value) public { - vm.store(target, slot, value); + vm._expectCheatcodeRevert("cannot use precompile 0x0000000000000000000000000000000000000001 as an argument"); + vm.store(address(1), bytes32(0), bytes32(uint256(1))); } function testStoreFuzzed(uint256 slot0, uint256 slot1) public { diff --git a/testdata/default/cheats/StringUtils.t.sol b/testdata/default/cheats/StringUtils.t.sol new file mode 100644 index 0000000000000..256d65302a445 --- /dev/null +++ b/testdata/default/cheats/StringUtils.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract StringManipulationTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testToLowercase() public { + string memory original = "Hello World"; + string memory lowercased = vm.toLowercase(original); + assertEq("hello world", lowercased); + } + + function testToUppercase() public { + string memory original = "Hello World"; + string memory uppercased = vm.toUppercase(original); + assertEq("HELLO WORLD", uppercased); + } + + function testTrim() public { + string memory original = " Hello World "; + string memory trimmed = vm.trim(original); + assertEq("Hello World", trimmed); + } + + function testReplace() public { + string memory original = "Hello World"; + string memory replaced = vm.replace(original, "World", "Reth"); + assertEq("Hello Reth", replaced); + } + + function testSplit() public { + string memory original = "Hello,World,Reth"; + string[] memory splitResult = vm.split(original, ","); + assertEq(3, splitResult.length); + assertEq("Hello", splitResult[0]); + assertEq("World", splitResult[1]); + assertEq("Reth", splitResult[2]); + } + + function testIndexOf() public { + string memory input = "Hello, World!"; + string memory key1 = "Hello,"; + string memory key2 = "World!"; + string memory key3 = ""; + string memory key4 = "foundry"; + assertEq(vm.indexOf(input, key1), 0); + assertEq(vm.indexOf(input, key2), 7); + assertEq(vm.indexOf(input, key3), 0); + assertEq(vm.indexOf(input, key4), type(uint256).max); + } + + function testContains() public { + string memory subject = "this is a test"; + assert(vm.contains(subject, "test")); + assert(!vm.contains(subject, "foundry")); + } +} diff --git a/testdata/cheats/ToString.t.sol b/testdata/default/cheats/ToString.t.sol similarity index 93% rename from testdata/cheats/ToString.t.sol rename to testdata/default/cheats/ToString.t.sol index 0b08db4a888e7..f19110e3e8655 100644 --- a/testdata/cheats/ToString.t.sol +++ b/testdata/default/cheats/ToString.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ToStringTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol new file mode 100644 index 0000000000000..5f0ef5b43c09e --- /dev/null +++ b/testdata/default/cheats/Toml.t.sol @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +library TomlStructs { + address constant HEVM_ADDRESS = address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))); + Vm constant vm = Vm(HEVM_ADDRESS); + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^FlatToml + string constant schema_FlatToml = + "FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^NestedToml + string constant schema_NestedToml = + "NestedToml(FlatToml[] members,AnotherFlatToml inner,string name)AnotherFlatToml(bytes4 fixedBytes)FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + function deserializeFlatToml(string memory toml) internal pure returns (ParseTomlTest.FlatToml memory) { + return abi.decode(vm.parseTomlType(toml, schema_FlatToml), (ParseTomlTest.FlatToml)); + } + + function deserializeFlatToml(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.FlatToml memory) + { + return abi.decode(vm.parseTomlType(toml, path, schema_FlatToml), (ParseTomlTest.FlatToml)); + } + + function deserializeFlatTomlArray(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.FlatToml[] memory) + { + return abi.decode(vm.parseTomlTypeArray(toml, path, schema_FlatToml), (ParseTomlTest.FlatToml[])); + } + + function deserializeNestedToml(string memory toml) internal pure returns (ParseTomlTest.NestedToml memory) { + return abi.decode(vm.parseTomlType(toml, schema_NestedToml), (ParseTomlTest.NestedToml)); + } + + function deserializeNestedToml(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.NestedToml memory) + { + return abi.decode(vm.parseTomlType(toml, path, schema_NestedToml), (ParseTomlTest.NestedToml)); + } + + function deserializeNestedTomlArray(string memory toml, string memory path) + internal + pure + returns (ParseTomlTest.NestedToml[] memory) + { + return abi.decode(vm.parseTomlType(toml, path, schema_NestedToml), (ParseTomlTest.NestedToml[])); + } +} + +contract ParseTomlTest is DSTest { + using TomlStructs for *; + + struct FlatToml { + uint256 a; + int24[][] arr; + string str; + bytes b; + address addr; + bytes32 fixedBytes; + } + + struct AnotherFlatToml { + bytes4 fixedBytes; + } + + struct NestedToml { + FlatToml[] members; + AnotherFlatToml inner; + string name; + } + + Vm constant vm = Vm(HEVM_ADDRESS); + string toml; + + function setUp() public { + string memory path = "fixtures/Toml/test.toml"; + toml = vm.readFile(path); + } + + function test_basicString() public { + bytes memory data = vm.parseToml(toml, ".basicString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai", decodedData); + } + + function test_nullString() public { + bytes memory data = vm.parseToml(toml, ".nullString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("", decodedData); + } + + function test_stringMultiline() public { + bytes memory data = vm.parseToml(toml, ".multilineString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai\nthere\n", decodedData); + } + + function test_stringArray() public { + bytes memory data = vm.parseToml(toml, ".stringArray"); + string[] memory decodedData = abi.decode(data, (string[])); + assertEq("hai", decodedData[0]); + assertEq("there", decodedData[1]); + } + + function test_address() public { + bytes memory data = vm.parseToml(toml, ".address"); + address decodedData = abi.decode(data, (address)); + assertEq(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, decodedData); + } + + function test_addressArray() public { + bytes memory data = vm.parseToml(toml, ".addressArray"); + address[] memory decodedData = abi.decode(data, (address[])); + assertEq(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, decodedData[0]); + assertEq(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, decodedData[1]); + } + + function test_H160ButNotaddress() public { + string memory data = abi.decode(vm.parseToml(toml, ".H160NotAddress"), (string)); + assertEq("0000000000000000000000000000000000001337", data); + } + + function test_bool() public { + bytes memory data = vm.parseToml(toml, ".boolTrue"); + bool decodedData = abi.decode(data, (bool)); + assertTrue(decodedData); + + data = vm.parseToml(toml, ".boolFalse"); + decodedData = abi.decode(data, (bool)); + assertTrue(!decodedData); + } + + function test_boolArray() public { + bytes memory data = vm.parseToml(toml, ".boolArray"); + bool[] memory decodedData = abi.decode(data, (bool[])); + assertTrue(decodedData[0]); + assertTrue(!decodedData[1]); + } + + function test_dateTime() public { + bytes memory data = vm.parseToml(toml, ".datetime"); + string memory decodedData = abi.decode(data, (string)); + assertEq(decodedData, "2021-08-10T14:48:00Z"); + } + + function test_dateTimeArray() public { + bytes memory data = vm.parseToml(toml, ".datetimeArray"); + string[] memory decodedData = abi.decode(data, (string[])); + assertEq(decodedData[0], "2021-08-10T14:48:00Z"); + assertEq(decodedData[1], "2021-08-10T14:48:00Z"); + } + + function test_uintArray() public { + bytes memory data = vm.parseToml(toml, ".uintArray"); + uint256[] memory decodedData = abi.decode(data, (uint256[])); + assertEq(42, decodedData[0]); + assertEq(43, decodedData[1]); + } + + // Object keys are sorted alphabetically, regardless of input. + struct Whole { + string str; + string[] strArray; + uint256[] uintArray; + } + + function test_wholeToml() public { + // we need to make the path relative to the crate that's running tests for it (forge crate) + string memory path = "fixtures/Toml/whole_toml.toml"; + console.log(path); + toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + Whole memory whole = abi.decode(data, (Whole)); + assertEq(whole.str, "hai"); + assertEq(whole.strArray[0], "hai"); + assertEq(whole.strArray[1], "there"); + assertEq(whole.uintArray[0], 42); + assertEq(whole.uintArray[1], 43); + } + + function test_coercionRevert() public { + vm._expectCheatcodeRevert("expected uint256, found JSON object"); + vm.parseTomlUint(toml, ".nestedObject"); + } + + function test_coercionUint() public { + uint256 number = vm.parseTomlUint(toml, ".uintNumber"); + assertEq(number, 9223372036854775807); // TOML is limited to 64-bit integers + number = vm.parseTomlUint(toml, ".uintString"); + assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + number = vm.parseTomlUint(toml, ".uintHex"); + assertEq(number, 1231232); + uint256[] memory numbers = vm.parseTomlUintArray(toml, ".uintArray"); + assertEq(numbers[0], 42); + assertEq(numbers[1], 43); + numbers = vm.parseTomlUintArray(toml, ".uintStringArray"); + assertEq(numbers[0], 1231232); + assertEq(numbers[1], 1231232); + assertEq(numbers[2], 1231232); + } + + function test_coercionInt() public { + int256 number = vm.parseTomlInt(toml, ".intNumber"); + assertEq(number, -12); + number = vm.parseTomlInt(toml, ".intString"); + assertEq(number, -12); + number = vm.parseTomlInt(toml, ".intHex"); + assertEq(number, -12); + } + + function test_coercionBool() public { + bool boolean = vm.parseTomlBool(toml, ".boolTrue"); + assertTrue(boolean); + bool boolFalse = vm.parseTomlBool(toml, ".boolFalse"); + assertTrue(!boolFalse); + boolean = vm.parseTomlBool(toml, ".boolString"); + assertEq(boolean, true); + bool[] memory booleans = vm.parseTomlBoolArray(toml, ".boolArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + booleans = vm.parseTomlBoolArray(toml, ".boolStringArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + } + + function test_coercionBytes() public { + bytes memory bytes_ = vm.parseTomlBytes(toml, ".bytesString"); + assertEq(bytes_, hex"01"); + + bytes[] memory bytesArray = vm.parseTomlBytesArray(toml, ".bytesStringArray"); + assertEq(bytesArray[0], hex"01"); + assertEq(bytesArray[1], hex"02"); + } + + struct NestedStruct { + uint256 number; + string str; + } + + function test_nestedObject() public { + bytes memory data = vm.parseToml(toml, ".nestedObject"); + NestedStruct memory nested = abi.decode(data, (NestedStruct)); + assertEq(nested.number, 9223372036854775807); // TOML is limited to 64-bit integers + assertEq(nested.str, "NEST"); + } + + function test_advancedTomlPath() public { + bytes memory data = vm.parseToml(toml, ".advancedTomlPath[*].id"); + uint256[] memory numbers = abi.decode(data, (uint256[])); + assertEq(numbers[0], 1); + assertEq(numbers[1], 2); + } + + function test_canonicalizePath() public { + bytes memory data = vm.parseToml(toml, "$.basicString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai", decodedData); + } + + function test_nonExistentKey() public { + bytes memory data = vm.parseToml(toml, ".thisKeyDoesNotExist"); + assertEq(0, data.length); + } + + function test_parseTomlKeys() public { + string memory tomlString = + "some_key_to_value = \"some_value\"\n some_key_to_array = [1, 2, 3]\n [some_key_to_object]\n key1 = \"value1\"\n key2 = 2"; + + string[] memory keys = vm.parseTomlKeys(tomlString, "$"); + string[] memory expected = new string[](3); + expected[0] = "some_key_to_value"; + expected[1] = "some_key_to_array"; + expected[2] = "some_key_to_object"; + assertEq(abi.encode(keys), abi.encode(expected)); + + keys = vm.parseTomlKeys(tomlString, ".some_key_to_object"); + expected = new string[](2); + expected[0] = "key1"; + expected[1] = "key2"; + assertEq(abi.encode(keys), abi.encode(expected)); + + vm._expectCheatcodeRevert("JSON value at \".some_key_to_array\" is not an object"); + vm.parseTomlKeys(tomlString, ".some_key_to_array"); + + vm._expectCheatcodeRevert("JSON value at \".some_key_to_value\" is not an object"); + vm.parseTomlKeys(tomlString, ".some_key_to_value"); + + vm._expectCheatcodeRevert("key \".*\" must return exactly one JSON object"); + vm.parseTomlKeys(tomlString, ".*"); + } + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^FlatToml + string constant schema_FlatToml = + "FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + // forge eip712 testdata/default/cheats/Toml.t.sol -R 'cheats=testdata/cheats' -R 'ds-test=testdata/lib/ds-test/src' | grep ^NestedToml + string constant schema_NestedToml = + "NestedToml(FlatToml[] members,AnotherFlatToml inner,string name)AnotherFlatToml(bytes4 fixedBytes)FlatToml(uint256 a,int24[][] arr,string str,bytes b,address addr,bytes32 fixedBytes)"; + + function test_parseTomlType() public { + string memory readToml = vm.readFile("fixtures/Toml/nested_toml_struct.toml"); + NestedToml memory data = readToml.deserializeNestedToml(); + assertEq(data.members.length, 2); + + FlatToml memory expected = FlatToml({ + 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))); + + FlatToml[] memory members = TomlStructs.deserializeFlatTomlArray(readToml, ".members"); + + assertEq(keccak256(abi.encode(members)), keccak256(abi.encode(data.members))); + } +} + +contract WriteTomlTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + string json1; + string json2; + + function setUp() public { + json1 = "example"; + json2 = "example2"; + } + + struct simpleStruct { + uint256 a; + string b; + } + + struct nestedStruct { + uint256 a; + string b; + simpleStruct c; + } + + function test_serializeNestedStructToml() public { + string memory json3 = "json3"; + string memory path = "fixtures/Toml/write_complex_test.toml"; + vm.serializeUint(json3, "a", uint256(123)); + string memory semiFinal = vm.serializeString(json3, "b", "test"); + string memory finalJson = vm.serializeString(json3, "c", semiFinal); + console.log(finalJson); + vm.writeToml(finalJson, path); + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + nestedStruct memory decodedData = abi.decode(data, (nestedStruct)); + console.log(decodedData.a); + assertEq(decodedData.a, 123); + } + + function test_retrieveEntireToml() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml, "."); + nestedStruct memory decodedData = abi.decode(data, (nestedStruct)); + console.log(decodedData.a); + assertEq(decodedData.a, 123); + } + + function test_checkKeyExists() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bool exists = vm.keyExistsToml(toml, ".a"); + assertTrue(exists); + } + + function test_checkKeyDoesNotExist() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bool exists = vm.keyExistsToml(toml, ".d"); + assertTrue(!exists); + } + + function test_writeToml() public { + string memory json3 = "json3"; + string memory path = "fixtures/Toml/write_test.toml"; + vm.serializeUint(json3, "a", uint256(123)); + string memory finalJson = vm.serializeString(json3, "b", "test"); + vm.writeToml(finalJson, path); + + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + simpleStruct memory decodedData = abi.decode(data, (simpleStruct)); + assertEq(decodedData.a, 123); + assertEq(decodedData.b, "test"); + + // write json3 to key b + vm.writeToml(finalJson, path, ".b"); + // read again + toml = vm.readFile(path); + data = vm.parseToml(toml, ".b"); + decodedData = abi.decode(data, (simpleStruct)); + assertEq(decodedData.a, 123); + assertEq(decodedData.b, "test"); + + // replace a single value to key b + address ex = address(0xBEEF); + vm.writeToml(vm.toString(ex), path, ".b"); + toml = vm.readFile(path); + data = vm.parseToml(toml, ".b"); + address decodedAddress = abi.decode(data, (address)); + assertEq(decodedAddress, ex); + } +} diff --git a/testdata/cheats/Travel.t.sol b/testdata/default/cheats/Travel.t.sol similarity index 80% rename from testdata/cheats/Travel.t.sol rename to testdata/default/cheats/Travel.t.sol index 01196aa1aba0c..b46d2e7ad7041 100644 --- a/testdata/cheats/Travel.t.sol +++ b/testdata/default/cheats/Travel.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ChainIdTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/TryFfi.sol b/testdata/default/cheats/TryFfi.sol new file mode 100644 index 0000000000000..58d93a48b4fea --- /dev/null +++ b/testdata/default/cheats/TryFfi.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract TryFfiTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testTryFfi() public { + string[] memory inputs = new string[](3); + inputs[0] = "bash"; + inputs[1] = "-c"; + inputs[2] = + "echo -n 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000"; + + Vm.FfiResult memory f = vm.tryFfi(inputs); + (string memory output) = abi.decode(f.stdout, (string)); + assertEq(output, "ffi works", "ffi failed"); + assertEq(f.exitCode, 0, "ffi failed"); + } + + function testTryFfiFail() public { + string[] memory inputs = new string[](2); + inputs[0] = "ls"; + inputs[1] = "wad"; + + Vm.FfiResult memory f = vm.tryFfi(inputs); + assertTrue(f.exitCode != 0); + } +} diff --git a/testdata/default/cheats/UnixTime.t.sol b/testdata/default/cheats/UnixTime.t.sol new file mode 100644 index 0000000000000..29d86699f64d1 --- /dev/null +++ b/testdata/default/cheats/UnixTime.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract UnixTimeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + // This is really wide because CI sucks. + 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. + inputs[1] = "+%s000"; + + bytes memory res = vm.ffi(inputs); + uint256 date = vm.parseUint(string(res)); + + // Limit precision to 1000 ms. + uint256 time = vm.unixTime() / 1000 * 1000; + + vm.assertApproxEqAbs(date, time, errMargin, ".unixTime() is inaccurate vs date"); + } + + function testUnixTime() public { + uint256 sleepTime = 2000; + + uint256 start = vm.unixTime(); + vm.sleep(sleepTime); + uint256 end = vm.unixTime(); + uint256 interval = end - start; + + vm.assertApproxEqAbs(interval, sleepTime, errMargin, ".unixTime() is inaccurate"); + } +} diff --git a/testdata/cheats/Wallet.t.sol b/testdata/default/cheats/Wallet.t.sol similarity index 79% rename from testdata/cheats/Wallet.t.sol rename to testdata/default/cheats/Wallet.t.sol index cccb874bdb0fa..d061b55ae45c5 100644 --- a/testdata/cheats/Wallet.t.sol +++ b/testdata/default/cheats/Wallet.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo {} @@ -104,10 +104,36 @@ contract WalletTest is DSTest { assertEq(recovered, wallet.addr); } + function testSignCompactWithWalletDigest(uint256 pkSeed, bytes32 digest) public { + uint256 pk = bound(pkSeed, 1, Q - 1); + + Vm.Wallet memory wallet = vm.createWallet(pk); + + (bytes32 r, bytes32 vs) = vm.signCompact(wallet, digest); + + // Extract `s` from `vs`. + // Shift left by 1 bit to clear the leftmost bit, then shift right by 1 bit to restore the original position. + // This effectively clears the leftmost bit of `vs`, giving us `s`. + bytes32 s = bytes32((uint256(vs) << 1) >> 1); + + // Extract `v` from `vs`. + // We shift `vs` right by 255 bits to isolate the leftmost bit. + // Converting this to uint8 gives us the parity bit (0 or 1). + // Adding 27 converts this parity bit to the correct `v` value (27 or 28). + uint8 v = uint8(uint256(vs) >> 255) + 27; + + address recovered = ecrecover(digest, v, r, s); + assertEq(recovered, wallet.addr); + } + function testSignWithWalletMessage(uint256 pkSeed, bytes memory message) public { testSignWithWalletDigest(pkSeed, keccak256(message)); } + function testSignCompactWithWalletMessage(uint256 pkSeed, bytes memory message) public { + testSignCompactWithWalletDigest(pkSeed, keccak256(message)); + } + function testGetNonceWallet(uint256 pkSeed) public { uint256 pk = bound(pkSeed, 1, Q - 1); diff --git a/testdata/cheats/Warp.t.sol b/testdata/default/cheats/Warp.t.sol similarity index 85% rename from testdata/cheats/Warp.t.sol rename to testdata/default/cheats/Warp.t.sol index 0d103080263e4..42f373c6172f7 100644 --- a/testdata/cheats/Warp.t.sol +++ b/testdata/default/cheats/Warp.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract WarpTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/dumpState.t.sol b/testdata/default/cheats/dumpState.t.sol new file mode 100644 index 0000000000000..8a8675ca5eace --- /dev/null +++ b/testdata/default/cheats/dumpState.t.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SimpleContract { + constructor() { + assembly { + sstore(1, 2) + } + } +} + +contract DumpStateTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testDumpStateCheatAccount() public { + // Path to temporary file that is deleted after the test + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_cheat.json"); + + // Define some values to set in the state using cheatcodes + address target = address(1001); + bytes memory bytecode = hex"11223344"; + uint256 balance = 1.2 ether; + uint64 nonce = 45; + + vm.etch(target, bytecode); + vm.deal(target, balance); + vm.setNonce(target, nonce); + vm.store(target, bytes32(uint256(0x20)), bytes32(uint256(0x40))); + vm.store(target, bytes32(uint256(0x40)), bytes32(uint256(0x60))); + + // Write the state to disk + vm.dumpState(path); + + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 1); + + string memory key = keys[0]; + assertEq(nonce, vm.parseJsonUint(json, string.concat(".", key, ".nonce"))); + assertEq(balance, vm.parseJsonUint(json, string.concat(".", key, ".balance"))); + assertEq(bytecode, vm.parseJsonBytes(json, string.concat(".", key, ".code"))); + + string[] memory slots = vm.parseJsonKeys(json, string.concat(".", key, ".storage")); + assertEq(slots.length, 2); + + assertEq( + bytes32(uint256(0x40)), + vm.parseJsonBytes32(json, string.concat(".", key, ".storage.", vm.toString(bytes32(uint256(0x20))))) + ); + assertEq( + bytes32(uint256(0x60)), + vm.parseJsonBytes32(json, string.concat(".", key, ".storage.", vm.toString(bytes32(uint256(0x40))))) + ); + + vm.removeFile(path); + } + + function testDumpStateMultipleAccounts() public { + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_multiple_accounts.json"); + + vm.setNonce(address(0x100), 1); + vm.deal(address(0x200), 1 ether); + vm.setNonce(address(0x300), 1); + vm.store(address(0x300), bytes32(uint256(1)), bytes32(uint256(2))); + vm.etch(address(0x400), hex"af"); + + vm.dumpState(path); + + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 4); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x100)))).length); + assertEq(1, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x100)), ".nonce"))); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x100)), ".balance"))); + assertEq(hex"", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x100)), ".code"))); + assertEq(0, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x100)), ".storage")).length); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x200)))).length); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x200)), ".nonce"))); + assertEq(1 ether, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x200)), ".balance"))); + assertEq(hex"", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x200)), ".code"))); + assertEq(0, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x200)), ".storage")).length); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x300)))).length); + assertEq(1, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x300)), ".nonce"))); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x300)), ".balance"))); + assertEq(hex"", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x300)), ".code"))); + assertEq(1, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x300)), ".storage")).length); + assertEq( + 2, + vm.parseJsonUint( + json, string.concat(".", vm.toString(address(0x300)), ".storage.", vm.toString(bytes32(uint256(1)))) + ) + ); + + assertEq(4, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x400)))).length); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x400)), ".nonce"))); + assertEq(0, vm.parseJsonUint(json, string.concat(".", vm.toString(address(0x400)), ".balance"))); + assertEq(hex"af", vm.parseJsonBytes(json, string.concat(".", vm.toString(address(0x400)), ".code"))); + assertEq(0, vm.parseJsonKeys(json, string.concat(".", vm.toString(address(0x400)), ".storage")).length); + + vm.removeFile(path); + } + + function testDumpStateDeployment() public { + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_deployment.json"); + + SimpleContract s = new SimpleContract(); + vm.dumpState(path); + + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 1); + assertEq(address(s), vm.parseAddress(keys[0])); + assertEq(1, vm.parseJsonKeys(json, string.concat(".", keys[0], ".storage")).length); + assertEq(2, vm.parseJsonUint(json, string.concat(".", keys[0], ".storage.", vm.toString(bytes32(uint256(1)))))); + + vm.removeFile(path); + } + + function testDumpStateEmptyAccount() public { + string memory path = string.concat(vm.projectRoot(), "/fixtures/Json/test_dump_state_empty_account.json"); + + SimpleContract s = new SimpleContract(); + vm.etch(address(s), hex""); + vm.resetNonce(address(s)); + + vm.dumpState(path); + string memory json = vm.readFile(path); + string[] memory keys = vm.parseJsonKeys(json, ""); + assertEq(keys.length, 0); + + vm.removeFile(path); + } +} diff --git a/testdata/default/cheats/getBlockNumber.t.sol b/testdata/default/cheats/getBlockNumber.t.sol new file mode 100644 index 0000000000000..ebf240dd811a1 --- /dev/null +++ b/testdata/default/cheats/getBlockNumber.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GetBlockNumberTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetBlockNumber() public { + uint256 height = vm.getBlockNumber(); + assertEq(height, uint256(block.number), "height should be equal to block.number"); + } + + function testGetBlockNumberWithRoll() public { + vm.roll(10); + assertEq(vm.getBlockNumber(), 10, "could not get correct block height after roll"); + } + + function testGetBlockNumberWithRollFuzzed(uint128 jump) public { + uint256 pre = vm.getBlockNumber(); + vm.roll(pre + jump); + assertEq(vm.getBlockNumber(), pre + jump, "could not get correct block height after roll"); + } +} diff --git a/testdata/default/cheats/loadAllocs.t.sol b/testdata/default/cheats/loadAllocs.t.sol new file mode 100644 index 0000000000000..94ce6804c1260 --- /dev/null +++ b/testdata/default/cheats/loadAllocs.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract LoadAllocsTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + address constant ALLOCD = address(0x420); + address constant ALLOCD_B = address(0x421); + + uint256 snapshotId; + string allocsPath; + + function setUp() public { + allocsPath = string.concat(vm.projectRoot(), "/fixtures/Json/test_allocs.json"); + + // Snapshot the state; We'll restore it in each test that loads allocs inline. + snapshotId = vm.snapshotState(); + + // Load the allocs file. + vm.loadAllocs(allocsPath); + } + + /// @dev Checks that the `loadAllocs` cheatcode persists account info if called in `setUp` + function testLoadAllocsStaticSetup() public { + // Balance should be `0xabcd` + assertEq(ALLOCD.balance, 0xabcd); + + // Code should be a simple store / return, returning `0x42` + (bool success, bytes memory rd) = ALLOCD.staticcall(""); + assertTrue(success); + uint256 ret = abi.decode(rd, (uint256)); + assertEq(ret, 0x42); + + // Storage should have been set in slot 0x1, equal to `0xbeef` + assertEq(uint256(vm.load(ALLOCD, bytes32(uint256(0x10 << 248)))), 0xbeef); + } + + /// @dev Checks that the `loadAllocs` cheatcode persists account info if called inline + function testLoadAllocsStatic() public { + // Restore the state snapshot prior to the allocs file being loaded. + vm.revertToState(snapshotId); + + // Load the allocs file + vm.loadAllocs(allocsPath); + + // Balance should be `0xabcd` + assertEq(ALLOCD.balance, 0xabcd); + + // Code should be a simple store / return, returning `0x42` + (bool success, bytes memory rd) = ALLOCD.staticcall(""); + assertTrue(success); + uint256 ret = abi.decode(rd, (uint256)); + assertEq(ret, 0x42); + + // Storage should have been set in slot 0x1, equal to `0xbeef` + assertEq(uint256(vm.load(ALLOCD, bytes32(uint256(0x10 << 248)))), 0xbeef); + } + + /// @dev Checks that the `loadAllocs` cheatcode overrides existing account information (if present) + function testLoadAllocsOverride() public { + // Restore the state snapshot prior to the allocs file being loaded. + vm.revertToState(snapshotId); + + // Populate the alloc'd account's code. + vm.etch(ALLOCD, hex"FF"); + assertEq(ALLOCD.code, hex"FF"); + + // Store something in the alloc'd storage slot. + bytes32 slot = bytes32(uint256(0x10 << 248)); + vm.store(ALLOCD, slot, bytes32(uint256(0xBADC0DE))); + assertEq(uint256(vm.load(ALLOCD, slot)), 0xBADC0DE); + + // Populate balance. + vm.deal(ALLOCD, 0x1234); + assertEq(ALLOCD.balance, 0x1234); + + vm.loadAllocs(allocsPath); + + // Info should have changed. + assertTrue(keccak256(ALLOCD.code) != keccak256(hex"FF")); + assertEq(uint256(vm.load(ALLOCD, slot)), 0xbeef); + assertEq(ALLOCD.balance, 0xabcd); + } + + /// @dev Checks that the `loadAllocs` cheatcode does not override existing account information if there is no data + /// within the allocs/genesis file for the account field (i.e., partial overrides) + function testLoadAllocsPartialOverride() public { + // Restore the state snapshot prior to the allocs file being loaded. + vm.revertToState(snapshotId); + + // Populate the alloc'd account's code. + vm.etch(ALLOCD_B, hex"FF"); + assertEq(ALLOCD_B.code, hex"FF"); + + // Populate balance. + vm.deal(ALLOCD_B, 0x1234); + assertEq(ALLOCD_B.balance, 0x1234); + + vm.loadAllocs(allocsPath); + + assertEq(ALLOCD_B.code, hex"FF"); + assertEq(ALLOCD_B.balance, 0); + } +} diff --git a/testdata/core/Abstract.t.sol b/testdata/default/core/Abstract.t.sol similarity index 84% rename from testdata/core/Abstract.t.sol rename to testdata/default/core/Abstract.t.sol index 043875be6f33f..d04d0ff778b7c 100644 --- a/testdata/core/Abstract.t.sol +++ b/testdata/default/core/Abstract.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; contract TestFixture { function something() public pure returns (string memory) { diff --git a/testdata/default/core/BadSigAfterInvariant.t.sol b/testdata/default/core/BadSigAfterInvariant.t.sol new file mode 100644 index 0000000000000..7b485e24f4a04 --- /dev/null +++ b/testdata/default/core/BadSigAfterInvariant.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +contract BadSigAfterInvariant is DSTest { + function afterinvariant() public {} + + function testShouldPassWithWarning() public { + assert(true); + } +} diff --git a/testdata/core/ContractEnvironment.t.sol b/testdata/default/core/ContractEnvironment.t.sol similarity index 93% rename from testdata/core/ContractEnvironment.t.sol rename to testdata/default/core/ContractEnvironment.t.sol index 6abc3714286da..452fa88022557 100644 --- a/testdata/core/ContractEnvironment.t.sol +++ b/testdata/default/core/ContractEnvironment.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/core/FailingTestAfterFailedSetup.t.sol b/testdata/default/core/FailingTestAfterFailedSetup.t.sol similarity index 80% rename from testdata/core/FailingTestAfterFailedSetup.t.sol rename to testdata/default/core/FailingTestAfterFailedSetup.t.sol index 17b9b191e28b8..c56f4ba5de605 100644 --- a/testdata/core/FailingTestAfterFailedSetup.t.sol +++ b/testdata/default/core/FailingTestAfterFailedSetup.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/default/core/LegacyAssertions.t.sol b/testdata/default/core/LegacyAssertions.t.sol new file mode 100644 index 0000000000000..c35a63417efc3 --- /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/core/PaymentFailure.t.sol b/testdata/default/core/PaymentFailure.t.sol similarity index 76% rename from testdata/core/PaymentFailure.t.sol rename to testdata/default/core/PaymentFailure.t.sol index 3aa3540114525..52c42fd376052 100644 --- a/testdata/core/PaymentFailure.t.sol +++ b/testdata/default/core/PaymentFailure.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Payable { function pay() public payable {} diff --git a/testdata/default/core/Reverting.t.sol b/testdata/default/core/Reverting.t.sol new file mode 100644 index 0000000000000..73877cab0b542 --- /dev/null +++ b/testdata/default/core/Reverting.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RevertingTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + /// forge-config: default.allow_internal_expect_revert = true + function testRevert() public { + vm.expectRevert("should revert here"); + require(false, "should revert here"); + } +} diff --git a/testdata/core/SetupConsistency.t.sol b/testdata/default/core/SetupConsistency.t.sol similarity index 87% rename from testdata/core/SetupConsistency.t.sol rename to testdata/default/core/SetupConsistency.t.sol index cce58ed5c7684..08d766f0f9242 100644 --- a/testdata/core/SetupConsistency.t.sol +++ b/testdata/default/core/SetupConsistency.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/fork/DssExecLib.sol b/testdata/default/fork/DssExecLib.sol similarity index 100% rename from testdata/fork/DssExecLib.sol rename to testdata/default/fork/DssExecLib.sol diff --git a/testdata/fork/ForkSame_1.t.sol b/testdata/default/fork/ForkSame_1.t.sol similarity index 65% rename from testdata/fork/ForkSame_1.t.sol rename to testdata/default/fork/ForkSame_1.t.sol index aa06eeeb04a0e..949c7ea9ec17d 100644 --- a/testdata/fork/ForkSame_1.t.sol +++ b/testdata/default/fork/ForkSame_1.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ForkTest is DSTest { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; @@ -11,7 +11,7 @@ contract ForkTest is DSTest { // this will create two _different_ forks during setup function setUp() public { - forkA = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", 15_977_624); + forkA = vm.createFork("mainnet", 15_977_624); } function testDummy() public { diff --git a/testdata/fork/ForkSame_2.t.sol b/testdata/default/fork/ForkSame_2.t.sol similarity index 65% rename from testdata/fork/ForkSame_2.t.sol rename to testdata/default/fork/ForkSame_2.t.sol index aa06eeeb04a0e..949c7ea9ec17d 100644 --- a/testdata/fork/ForkSame_2.t.sol +++ b/testdata/default/fork/ForkSame_2.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ForkTest is DSTest { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; @@ -11,7 +11,7 @@ contract ForkTest is DSTest { // this will create two _different_ forks during setup function setUp() public { - forkA = vm.createFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", 15_977_624); + forkA = vm.createFork("mainnet", 15_977_624); } function testDummy() public { diff --git a/testdata/fork/LaunchFork.t.sol b/testdata/default/fork/LaunchFork.t.sol similarity index 92% rename from testdata/fork/LaunchFork.t.sol rename to testdata/default/fork/LaunchFork.t.sol index 46e803f9e264c..710ac97f51d97 100644 --- a/testdata/fork/LaunchFork.t.sol +++ b/testdata/default/fork/LaunchFork.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.6.12; import "ds-test/test.sol"; @@ -72,7 +72,8 @@ contract ForkTest is DSTest { function testDepositWeth() public { IWETH WETH = IWETH(WETH_TOKEN_ADDR); + uint256 current = WETH.balanceOf(address(this)); WETH.deposit{value: 1000}(); - assertEq(WETH.balanceOf(address(this)), 1000, "WETH balance is not equal to deposited amount."); + assertEq(WETH.balanceOf(address(this)) - current, 1000, "WETH balance is not equal to deposited amount."); } } diff --git a/testdata/default/fs/Default.t.sol b/testdata/default/fs/Default.t.sol new file mode 100644 index 0000000000000..e1524963f6c3e --- /dev/null +++ b/testdata/default/fs/Default.t.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract DefaultAccessTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testReadFile() public { + string memory path = "fixtures/File/read.txt"; + vm.readFile(path); + + vm.readFileBinary(path); + } + + function testReadLine() public { + string memory path = "fixtures/File/read.txt"; + vm.readLine(path); + } + + function testWriteFile() public { + string memory path = "fixtures/File/write_file.txt"; + string memory data = "hello writable world"; + + vm._expectCheatcodeRevert(); + vm.writeFile(path, data); + + vm._expectCheatcodeRevert(); + vm.writeFileBinary(path, bytes(data)); + } + + function testWriteLine() public { + string memory path = "fixtures/File/write_file.txt"; + string memory data = "hello writable world"; + + vm._expectCheatcodeRevert(); + vm.writeLine(path, data); + } + + function testRemoveFile() public { + string memory path = "fixtures/File/write_file.txt"; + + vm._expectCheatcodeRevert(); + vm.removeFile(path); + } +} diff --git a/testdata/default/fs/Disabled.t.sol b/testdata/default/fs/Disabled.t.sol new file mode 100644 index 0000000000000..36f05c211fcad --- /dev/null +++ b/testdata/default/fs/Disabled.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract DisabledTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testReadFile() public { + string memory path = "fixtures/File/read.txt"; + vm._expectCheatcodeRevert(); + vm.readFile(path); + } + + function testReadLine() public { + string memory path = "fixtures/File/read.txt"; + vm._expectCheatcodeRevert(); + vm.readLine(path); + } + + function testWriteFile() public { + string memory path = "fixtures/File/write_file.txt"; + string memory data = "hello writable world"; + vm._expectCheatcodeRevert(); + vm.writeFile(path, data); + } + + function testWriteLine() public { + string memory path = "fixtures/File/write_file.txt"; + string memory data = "hello writable world"; + vm._expectCheatcodeRevert(); + vm.writeLine(path, data); + } + + function testRemoveFile() public { + string memory path = "fixtures/File/write_file.txt"; + vm._expectCheatcodeRevert(); + vm.removeFile(path); + } +} diff --git a/testdata/fuzz/Fuzz.t.sol b/testdata/default/fuzz/Fuzz.t.sol similarity index 70% rename from testdata/fuzz/Fuzz.t.sol rename to testdata/default/fuzz/Fuzz.t.sol index 686ee9d45f163..b1cf54716be93 100644 --- a/testdata/fuzz/Fuzz.t.sol +++ b/testdata/default/fuzz/Fuzz.t.sol @@ -1,22 +1,21 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; - -interface Vm { - function toString(bytes32) external returns (string memory); -} +import "cheats/Vm.sol"; contract FuzzTest is DSTest { constructor() { emit log("constructor"); } + Vm constant vm = Vm(HEVM_ADDRESS); + function setUp() public { emit log("setUp"); } - function testFailFuzz(uint8 x) public { + function testShouldFailFuzz(uint8 x) public { emit log("testFailFuzz"); require(x > 128, "should revert"); } @@ -27,7 +26,6 @@ contract FuzzTest is DSTest { } function testToStringFuzz(bytes32 data) public { - Vm vm = Vm(HEVM_ADDRESS); vm.toString(data); } } diff --git a/testdata/fuzz/FuzzCollection.t.sol b/testdata/default/fuzz/FuzzCollection.t.sol similarity index 98% rename from testdata/fuzz/FuzzCollection.t.sol rename to testdata/default/fuzz/FuzzCollection.t.sol index 9be09bea88c75..0c98ddc66b6b2 100644 --- a/testdata/fuzz/FuzzCollection.t.sol +++ b/testdata/default/fuzz/FuzzCollection.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.15; +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/default/fuzz/FuzzFailurePersist.t.sol b/testdata/default/fuzz/FuzzFailurePersist.t.sol new file mode 100644 index 0000000000000..3787060411e5d --- /dev/null +++ b/testdata/default/fuzz/FuzzFailurePersist.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct TestTuple { + address user; + uint256 amount; +} + +contract FuzzFailurePersistTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_persist_fuzzed_failure( + uint256 x, + int256 y, + address addr, + bool cond, + string calldata test, + TestTuple calldata tuple, + address[] calldata addresses + ) public { + // dummy assume to trigger runs + vm.assume(x > 1 && x < 1111111111111111111111111111); + vm.assume(y > 1 && y < 1111111111111111111111111111); + require(false); + } +} diff --git a/testdata/fuzz/FuzzInt.t.sol b/testdata/default/fuzz/FuzzInt.t.sol similarity index 62% rename from testdata/fuzz/FuzzInt.t.sol rename to testdata/default/fuzz/FuzzInt.t.sol index c6340807fe771..a47ff2953331f 100644 --- a/testdata/fuzz/FuzzInt.t.sol +++ b/testdata/default/fuzz/FuzzInt.t.sol @@ -1,9 +1,10 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -// See https://github.com/foundry-rs/foundry/pull/735 for context +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined contract FuzzNumbersTest is DSTest { function testPositive(int256) public { assertTrue(true); @@ -14,31 +15,31 @@ contract FuzzNumbersTest is DSTest { } function testNegative0(int256 val) public { - assertTrue(val != 0); + assertTrue(val == 0); } function testNegative1(int256 val) public { - assertTrue(val != -1); + assertTrue(val == -1); } function testNegative2(int128 val) public { - assertTrue(val != 1); + assertTrue(val == 1); } function testNegativeMax0(int256 val) public { - assertTrue(val != type(int256).max); + assertTrue(val == type(int256).max); } function testNegativeMax1(int256 val) public { - assertTrue(val != type(int256).max - 2); + assertTrue(val == type(int256).max - 2); } function testNegativeMin0(int256 val) public { - assertTrue(val != type(int256).min); + assertTrue(val == type(int256).min); } function testNegativeMin1(int256 val) public { - assertTrue(val != type(int256).min + 2); + assertTrue(val == type(int256).min + 2); } function testEquality(int256 x, int256 y) public { diff --git a/testdata/default/fuzz/FuzzPositive.t.sol b/testdata/default/fuzz/FuzzPositive.t.sol new file mode 100644 index 0000000000000..7d3639dfe5ec2 --- /dev/null +++ b/testdata/default/fuzz/FuzzPositive.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +contract FuzzPositive is DSTest { + function testSuccessChecker(uint256 val) public { + assertTrue(true); + } + + function testSuccessChecker2(int256 val) public { + assert(val == val); + } + + function testSuccessChecker3(uint32 val) public { + assert(val + 0 == val); + } +} diff --git a/testdata/fuzz/FuzzUint.t.sol b/testdata/default/fuzz/FuzzUint.t.sol similarity index 63% rename from testdata/fuzz/FuzzUint.t.sol rename to testdata/default/fuzz/FuzzUint.t.sol index b4ba1e0e119d9..c0cbf6466c31b 100644 --- a/testdata/fuzz/FuzzUint.t.sol +++ b/testdata/default/fuzz/FuzzUint.t.sol @@ -1,9 +1,10 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -// See https://github.com/foundry-rs/foundry/pull/735 for context +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined contract FuzzNumbersTest is DSTest { function testPositive(uint256) public { assertTrue(true); @@ -14,19 +15,19 @@ contract FuzzNumbersTest is DSTest { } function testNegative0(uint256 val) public { - assertTrue(val != 0); + assertTrue(val == 0); } function testNegative2(uint256 val) public { - assertTrue(val != 2); + assertTrue(val == 2); } function testNegative2Max(uint256 val) public { - assertTrue(val != type(uint256).max - 2); + assertTrue(val == type(uint256).max - 2); } function testNegativeMax(uint256 val) public { - assertTrue(val != type(uint256).max); + assertTrue(val == type(uint256).max); } function testEquality(uint256 x, uint256 y) public { diff --git a/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol b/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol new file mode 100644 index 0000000000000..3030b43e077cc --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract AfterInvariantHandler { + uint256 public count; + + function inc() external { + count += 1; + } +} + +contract InvariantAfterInvariantTest is DSTest { + AfterInvariantHandler handler; + + function setUp() public { + handler = new AfterInvariantHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.inc.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function afterInvariant() public { + require(handler.count() < 10, "afterInvariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_after_invariant_failure() public view { + require(handler.count() < 20, "invariant after invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_failure() public view { + require(handler.count() < 9, "invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + function invariant_success() public view { + require(handler.count() < 11, "invariant should not fail"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol new file mode 100644 index 0000000000000..9808a870f7228 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function doSomething(uint256 param) public { + vm.assume(param == 0); + } +} + +contract InvariantAssume is DSTest { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function invariant_dummy() public {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol new file mode 100644 index 0000000000000..3d4c51eac41e5 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// https://github.com/foundry-rs/foundry/issues/5868 +contract Owned { + address public owner; + address private ownerCandidate; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + modifier onlyOwnerCandidate() { + require(msg.sender == ownerCandidate); + _; + } + + function transferOwnership(address candidate) external onlyOwner { + ownerCandidate = candidate; + } + + function acceptOwnership() external onlyOwnerCandidate { + owner = ownerCandidate; + } +} + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Owned owned; + + constructor(Owned _owned) { + owned = _owned; + } + + function transferOwnership(address sender, address candidate) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.transferOwnership(candidate); + } + + function acceptOwnership(address sender) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.acceptOwnership(); + } +} + +contract InvariantCalldataDictionary is DSTest { + address owner; + Owned owned; + Handler handler; + address[] actors; + + function setUp() public { + owner = address(this); + owned = new Owned(); + handler = new Handler(owned); + actors.push(owner); + actors.push(address(777)); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.transferOwnership.selector; + selectors[1] = handler.acceptOwnership.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function fixtureSender() external returns (address[] memory) { + return actors; + } + + function fixtureCandidate() external returns (address[] memory) { + return actors; + } + + function invariant_owner_never_changes() public { + assertEq(owned.owner(), owner); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol new file mode 100644 index 0000000000000..737cf5ba9dd05 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ContractWithCustomError { + error InvariantCustomError(uint256, string); + + function revertWithInvariantCustomError() external { + revert InvariantCustomError(111, "custom"); + } +} + +contract Handler is DSTest { + ContractWithCustomError target; + + constructor() { + target = new ContractWithCustomError(); + } + + function revertTarget() external { + target.revertWithInvariantCustomError(); + } +} + +contract InvariantCustomError is DSTest { + Handler handler; + + function setUp() external { + handler = new Handler(); + } + + function invariant_decode_error() public {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol b/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol new file mode 100644 index 0000000000000..8fe0bed2c6e7c --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +contract InvariantSenders { + function checkSender() external { + require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); + require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); + require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); + } +} + +contract InvariantExcludedSendersTest is DSTest { + InvariantSenders target; + + function setUp() public { + target = new InvariantSenders(); + } + + function invariant_check_sender() public view {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol new file mode 100644 index 0000000000000..b3f1e17cb2497 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; + +contract Target { + bool ownerFound; + bool amountFound; + bool magicFound; + bool keyFound; + bool backupFound; + bool extraStringFound; + + function fuzzWithFixtures( + address owner_, + uint256 _amount, + int32 magic, + bytes32 key, + bytes memory backup, + string memory extra + ) external { + if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { + ownerFound = true; + } + if (_amount == 1122334455) amountFound = true; + if (magic == -777) magicFound = true; + if (key == "abcd1234") keyFound = true; + if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; + if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { + extraStringFound = true; + } + } + + function isCompromised() public view returns (bool) { + return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; + } +} + +/// Try to compromise target contract by finding all accepted values using fixtures. +contract InvariantFixtures is DSTest { + Target target; + address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; + uint256[] public fixture_amount = [1, 2, 1122334455]; + + function setUp() public { + target = new Target(); + } + + function fixtureMagic() external returns (int32[2] memory) { + int32[2] memory magic; + magic[0] = -777; + magic[1] = 777; + return magic; + } + + function fixtureKey() external pure returns (bytes32[] memory) { + bytes32[] memory keyFixture = new bytes32[](1); + keyFixture[0] = "abcd1234"; + return keyFixture; + } + + function fixtureBackup() external pure returns (bytes[] memory) { + bytes[] memory backupFixture = new bytes[](1); + backupFixture[0] = "qwerty1234"; + return backupFixture; + } + + function fixtureExtra() external pure returns (string[] memory) { + string[] memory extraFixture = new string[](1); + extraFixture[0] = "112233aabbccdd"; + return extraFixture; + } + + function invariant_target_not_compromised() public { + assertEq(target.isCompromised(), false); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol b/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol new file mode 100644 index 0000000000000..5ff50e782ac55 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Handler is DSTest { + function doSomething() public { + require(false, "failed on revert"); + } +} + +contract InvariantHandlerFailure is DSTest { + bytes4[] internal selectors; + + Handler handler; + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.doSomething.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function setUp() public { + handler = new Handler(); + } + + function statefulFuzz_BrokenInvariant() public {} +} diff --git a/testdata/fuzz/invariant/common/InvariantInnerContract.t.sol b/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol similarity index 93% rename from testdata/fuzz/invariant/common/InvariantInnerContract.t.sol rename to testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol index 49e67e7d37d70..f8330a33cd6ec 100644 --- a/testdata/fuzz/invariant/common/InvariantInnerContract.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -45,6 +45,6 @@ contract InvariantInnerContract is DSTest { } function invariantHideJesus() public { - require(jesus.identity_revealed() == false, "jesus betrayed."); + require(jesus.identity_revealed() == false, "jesus betrayed"); } } diff --git a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol new file mode 100644 index 0000000000000..bd70dd3aeafba --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// https://github.com/foundry-rs/foundry/issues/7219 + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function thisFunctionReverts() external { + if (block.number < 10) {} else { + revert(); + } + } + + function advanceTime(uint256 blocks) external { + blocks = blocks % 10; + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 12); + } +} + +contract InvariantPreserveState is DSTest { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.thisFunctionReverts.selector; + selectors[1] = handler.advanceTime.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_preserve_state() public { + assertTrue(true); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantReentrancy.t.sol b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol similarity index 54% rename from testdata/fuzz/invariant/common/InvariantReentrancy.t.sol rename to testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol index 38bb930ced98c..74a01f1805de6 100644 --- a/testdata/fuzz/invariant/common/InvariantReentrancy.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol @@ -1,11 +1,13 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; contract Malicious { function world() public { - // Does not matter, since it will get overridden. + // add code so contract is accounted as valid sender + // see https://github.com/foundry-rs/foundry/issues/4245 + payable(msg.sender).call(""); } } @@ -39,7 +41,15 @@ contract InvariantReentrancy is DSTest { vuln = new Vulnerable(address(mal)); } + // do not include `mal` in identified contracts + // see https://github.com/foundry-rs/foundry/issues/4245 + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(vuln); + return targets; + } + function invariantNotStolen() public { - require(vuln.stolen() == false, "stolen."); + require(vuln.stolen() == false, "stolen"); } } diff --git a/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol b/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol new file mode 100644 index 0000000000000..d15619b635f18 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +contract RollForkHandler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 public totalSupply; + + function work() external { + vm.rollFork(block.number + 1); + totalSupply = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F).totalSupply(); + } +} + +contract InvariantRollForkBlockTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("mainnet", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 2 + /// forge-config: default.invariant.depth = 4 + function invariant_fork_handler_block() public { + require(block.number < 19812634, "too many blocks mined"); + } +} + +contract InvariantRollForkStateTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("mainnet", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 1 + function invariant_fork_handler_state() public { + require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol b/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol new file mode 100644 index 0000000000000..40824a2602e77 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract FindFromReturnValue { + bool public found = false; + + function seed() public returns (int256) { + int256 mystery = 13337; + return (1337 + mystery); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromReturnValueTest is DSTest { + FindFromReturnValue target; + + function setUp() public { + target = new FindFromReturnValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from return found"); + } +} + +contract FindFromLogValue { + event FindFromLog(int256 indexed mystery, bytes32 rand); + + bool public found = false; + + function seed() public { + int256 mystery = 13337; + emit FindFromLog(1337 + mystery, keccak256(abi.encodePacked("mystery"))); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromLogValueTest is DSTest { + FindFromLogValue target; + + function setUp() public { + target = new FindFromLogValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from logs found"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol b/testdata/default/fuzz/invariant/common/InvariantSequenceNoReverts.t.sol new file mode 100644 index 0000000000000..993d806f81b38 --- /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/InvariantShrinkBigSequence.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol new file mode 100644 index 0000000000000..5699d9c455e89 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShrinkBigSequence { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + } + + function checkCond() public view { + require(cond < 77, "condition met"); + } +} + +contract ShrinkBigSequenceTest is DSTest { + ShrinkBigSequence target; + + function setUp() public { + target = new ShrinkBigSequence(); + } + + function invariant_shrink_big_sequence() public view { + target.checkCond(); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol new file mode 100644 index 0000000000000..d971367b69988 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShrinkFailOnRevert { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + require(cond < 10, "condition met"); + } +} + +contract ShrinkFailOnRevertTest is DSTest { + ShrinkFailOnRevert target; + + function setUp() public { + target = new ShrinkFailOnRevert(); + } + + function invariant_shrink_fail_on_revert() public view {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol new file mode 100644 index 0000000000000..fa4a6e945804e --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +contract Counter { + uint256 public number; + + function increment() public { + number++; + } + + function decrement() public { + number--; + } +} + +contract InvariantShrinkWithAssert is DSTest { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function invariant_with_assert() public { + assertTrue(counter.number() < 2, "wrong counter"); + } + + function invariant_with_require() public { + require(counter.number() < 2, "wrong counter"); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantTest1.t.sol b/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol similarity index 82% rename from testdata/fuzz/invariant/common/InvariantTest1.t.sol rename to testdata/default/fuzz/invariant/common/InvariantTest1.t.sol index 775b2cffd43df..bb62f34c6a965 100644 --- a/testdata/fuzz/invariant/common/InvariantTest1.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -30,10 +30,10 @@ contract InvariantTest is DSTest { } function invariant_neverFalse() public { - require(inv.flag1(), "false."); + require(inv.flag1(), "false"); } function statefulFuzz_neverFalseWithInvariantAlias() public { - require(inv.flag1(), "false."); + require(inv.flag1(), "false"); } } diff --git a/testdata/fuzz/invariant/storage/InvariantStorageTest.t.sol b/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol similarity index 100% rename from testdata/fuzz/invariant/storage/InvariantStorageTest.t.sol rename to testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol diff --git a/testdata/fuzz/invariant/target/ExcludeContracts.t.sol b/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol similarity index 80% rename from testdata/fuzz/invariant/target/ExcludeContracts.t.sol rename to testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol index 4b44c26680ab3..e2e850e316d1e 100644 --- a/testdata/fuzz/invariant/target/ExcludeContracts.t.sol +++ b/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -26,6 +26,6 @@ contract ExcludeContracts is DSTest { } function invariantTrueWorld() public { - require(hello.world() == true, "false world."); + require(hello.world() == true, "false world"); } } diff --git a/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol new file mode 100644 index 0000000000000..e2251f42c8126 --- /dev/null +++ b/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Hello { + bool public world = false; + + function change() public { + world = true; + } + + function real_change() public { + world = false; + } +} + +contract ExcludeSelectors is DSTest { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function excludeSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hello.change.selector; + targets[0] = FuzzSelector(address(hello), selectors); + return targets; + } + + function invariantFalseWorld() public { + require(hello.world() == false, "true world"); + } +} diff --git a/testdata/fuzz/invariant/target/ExcludeSenders.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol similarity index 88% rename from testdata/fuzz/invariant/target/ExcludeSenders.t.sol rename to testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol index 34e01519932c3..dda07074d18c7 100644 --- a/testdata/fuzz/invariant/target/ExcludeSenders.t.sol +++ b/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -40,6 +40,6 @@ contract ExcludeSenders is DSTest { } function invariantTrueWorld() public { - require(hello.world() == true, "false world."); + require(hello.world() == true, "false world"); } } diff --git a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol new file mode 100644 index 0000000000000..759810611e95b --- /dev/null +++ b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +interface Vm { + function etch(address target, bytes calldata newRuntimeBytecode) external; +} + +// https://github.com/foundry-rs/foundry/issues/5625 +// https://github.com/foundry-rs/foundry/issues/6166 +// `Target.wrongSelector` is not called when handler added as `targetContract` +// `Target.wrongSelector` is called (and test fails) when no `targetContract` set +contract Target { + uint256 count; + + function wrongSelector() external { + revert("wrong target selector called"); + } + + function goodSelector() external { + count++; + } +} + +contract Handler is DSTest { + function increment() public { + Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); + } +} + +contract ExplicitTargetContract is DSTest { + Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function targetContracts() public returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(handler); + return addrs; + } + + function invariant_explicit_target() public {} +} + +contract DynamicTargetContract is DSTest { + Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function invariant_dynamic_targets() public {} +} diff --git a/testdata/fuzz/invariant/target/TargetContracts.t.sol b/testdata/default/fuzz/invariant/target/TargetContracts.t.sol similarity index 81% rename from testdata/fuzz/invariant/target/TargetContracts.t.sol rename to testdata/default/fuzz/invariant/target/TargetContracts.t.sol index 376e8ee15bcbd..d24c7eb5282a4 100644 --- a/testdata/fuzz/invariant/target/TargetContracts.t.sol +++ b/testdata/default/fuzz/invariant/target/TargetContracts.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -27,6 +27,6 @@ contract TargetContracts is DSTest { } function invariantTrueWorld() public { - require(hello2.world() == true, "false world."); + require(hello2.world() == true, "false world"); } } diff --git a/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol b/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol new file mode 100644 index 0000000000000..30b4a05e3eaf9 --- /dev/null +++ b/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +struct FuzzInterface { + address target; + string[] artifacts; +} + +contract Hello { + bool public world; + + function changeWorld() external { + world = true; + } +} + +interface IHello { + function world() external view returns (bool); + function changeWorld() external; +} + +contract HelloProxy { + address internal immutable _implementation; + + constructor(address implementation_) { + _implementation = implementation_; + } + + function _delegate(address implementation) internal { + assembly { + calldatacopy(0, 0, calldatasize()) + + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + fallback() external payable { + _delegate(_implementation); + } +} + +contract TargetWorldInterfaces is DSTest { + IHello proxy; + + function setUp() public { + Hello hello = new Hello(); + proxy = IHello(address(new HelloProxy(address(hello)))); + } + + function targetInterfaces() public returns (FuzzInterface[] memory) { + FuzzInterface[] memory targets = new FuzzInterface[](1); + + string[] memory artifacts = new string[](1); + artifacts[0] = "IHello"; + + targets[0] = FuzzInterface(address(proxy), artifacts); + + return targets; + } + + function invariantTrueWorld() public { + require(proxy.world() == false, "false world"); + } +} diff --git a/testdata/fuzz/invariant/target/TargetSelectors.t.sol b/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol similarity index 85% rename from testdata/fuzz/invariant/target/TargetSelectors.t.sol rename to testdata/default/fuzz/invariant/target/TargetSelectors.t.sol index 289937efb9bbf..c74ac7fa18114 100644 --- a/testdata/fuzz/invariant/target/TargetSelectors.t.sol +++ b/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -36,6 +36,6 @@ contract TargetSelectors is DSTest { } function invariantTrueWorld() public { - require(hello.world() == true, "false world."); + require(hello.world() == true, "false world"); } } diff --git a/testdata/fuzz/invariant/target/TargetSenders.t.sol b/testdata/default/fuzz/invariant/target/TargetSenders.t.sol similarity index 81% rename from testdata/fuzz/invariant/target/TargetSenders.t.sol rename to testdata/default/fuzz/invariant/target/TargetSenders.t.sol index 94735404bc8c9..6fa4c9a6387d5 100644 --- a/testdata/fuzz/invariant/target/TargetSenders.t.sol +++ b/testdata/default/fuzz/invariant/target/TargetSenders.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -26,6 +26,6 @@ contract TargetSenders is DSTest { } function invariantTrueWorld() public { - require(hello.world() == true, "false world."); + require(hello.world() == true, "false world"); } } diff --git a/testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol similarity index 78% rename from testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol rename to testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol index 80947360ba826..86ca6d5439b7a 100644 --- a/testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -35,11 +35,11 @@ contract ExcludeArtifacts is DSTest { function excludeArtifacts() public returns (string[] memory) { string[] memory abis = new string[](1); - abis[0] = "fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; + abis[0] = "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; return abis; } function invariantShouldPass() public { - require(excluded.world() == true, "false world."); + require(excluded.world() == true, "false world"); } } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol similarity index 52% rename from testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol index 69fa42161903b..440f6183f415c 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol @@ -1,10 +1,10 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -struct FuzzAbiSelector { - string contract_abi; +struct FuzzArtifactSelector { + string artifact; bytes4[] selectors; } @@ -27,15 +27,16 @@ contract TargetArtifactSelectors is DSTest { hello = new Hi(); } - function targetArtifactSelectors() public returns (FuzzAbiSelector[] memory) { - FuzzAbiSelector[] memory targets = new FuzzAbiSelector[](1); + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](1); bytes4[] memory selectors = new bytes4[](1); selectors[0] = Hi.no_change.selector; - targets[0] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); + targets[0] = + FuzzArtifactSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); return targets; } function invariantShouldPass() public { - require(hello.world() == true, "false world."); + require(hello.world() == true, "false world"); } } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol similarity index 63% rename from testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol index bd4c1c62bcc49..162d9cc2e1106 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol @@ -1,10 +1,10 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -struct FuzzAbiSelector { - string contract_abi; +struct FuzzArtifactSelector { + string artifact; bytes4[] selectors; } @@ -46,16 +46,20 @@ contract TargetArtifactSelectors2 is DSTest { parent = new Parent(); } - function targetArtifactSelectors() public returns (FuzzAbiSelector[] memory) { - FuzzAbiSelector[] memory targets = new FuzzAbiSelector[](2); + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](2); bytes4[] memory selectors_child = new bytes4[](1); selectors_child[0] = Child.change_parent.selector; - targets[0] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child); + targets[0] = FuzzArtifactSelector( + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child + ); bytes4[] memory selectors_parent = new bytes4[](1); selectors_parent[0] = Parent.create.selector; - targets[1] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent); + targets[1] = FuzzArtifactSelector( + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent + ); return targets; } @@ -63,6 +67,6 @@ contract TargetArtifactSelectors2 is DSTest { if (!parent.should_be_true()) { require(!Child(address(parent.child())).changed(), "should have not happened"); } - require(parent.should_be_true() == true, "its false."); + require(parent.should_be_true() == true, "it's false"); } } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol similarity index 79% rename from testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol index e99e3b8671c0a..28fa146059f92 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; @@ -30,15 +30,15 @@ contract TargetArtifacts is DSTest { function targetArtifacts() public returns (string[] memory) { string[] memory abis = new string[](1); - abis[0] = "fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; + abis[0] = "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; return abis; } function invariantShouldPass() public { - require(target2.world() == true || target1.world() == true || hello.world() == true, "false world."); + require(target2.world() == true || target1.world() == true || hello.world() == true, "false world"); } function invariantShouldFail() public { - require(target2.world() == true || target1.world() == true, "false world."); + require(target2.world() == true || target1.world() == true, "false world"); } } diff --git a/testdata/default/inline/FuzzInlineConf.t.sol b/testdata/default/inline/FuzzInlineConf.t.sol new file mode 100644 index 0000000000000..73d2de2fc4ed1 --- /dev/null +++ b/testdata/default/inline/FuzzInlineConf.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function testInlineConfFuzz(uint8 x) public { + 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/inline/InvariantInlineConf.t.sol b/testdata/default/inline/InvariantInlineConf.t.sol similarity index 94% rename from testdata/inline/InvariantInlineConf.t.sol rename to testdata/default/inline/InvariantInlineConf.t.sol index 41e534f44a90a..5ac81755e998d 100644 --- a/testdata/inline/InvariantInlineConf.t.sol +++ b/testdata/default/inline/InvariantInlineConf.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/default/linking/cycle/Cycle.t.sol b/testdata/default/linking/cycle/Cycle.t.sol new file mode 100644 index 0000000000000..010f55ac36b2c --- /dev/null +++ b/testdata/default/linking/cycle/Cycle.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +library Foo { + function foo() external { + Bar.bar(); + } + + function flum() external {} +} + +library Bar { + function bar() external { + Foo.flum(); + } +} diff --git a/testdata/linking/duplicate/Duplicate.t.sol b/testdata/default/linking/duplicate/Duplicate.t.sol similarity index 97% rename from testdata/linking/duplicate/Duplicate.t.sol rename to testdata/default/linking/duplicate/Duplicate.t.sol index 215e85e34eb5e..d1d0f32789238 100644 --- a/testdata/linking/duplicate/Duplicate.t.sol +++ b/testdata/default/linking/duplicate/Duplicate.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/linking/nested/Nested.t.sol b/testdata/default/linking/nested/Nested.t.sol similarity index 93% rename from testdata/linking/nested/Nested.t.sol rename to testdata/default/linking/nested/Nested.t.sol index b9b85c614a3a5..136cb36479cbf 100644 --- a/testdata/linking/nested/Nested.t.sol +++ b/testdata/default/linking/nested/Nested.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/linking/simple/Simple.t.sol b/testdata/default/linking/simple/Simple.t.sol similarity index 89% rename from testdata/linking/simple/Simple.t.sol rename to testdata/default/linking/simple/Simple.t.sol index af8363509b628..85be791fd2489 100644 --- a/testdata/linking/simple/Simple.t.sol +++ b/testdata/default/linking/simple/Simple.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/logs/DebugLogs.t.sol b/testdata/default/logs/DebugLogs.t.sol similarity index 86% rename from testdata/logs/DebugLogs.t.sol rename to testdata/default/logs/DebugLogs.t.sol index 59bb2a188b67b..b560fd2bfb9ca 100644 --- a/testdata/logs/DebugLogs.t.sol +++ b/testdata/default/logs/DebugLogs.t.sol @@ -1,8 +1,11 @@ -pragma solidity 0.8.18; +pragma solidity ^0.8.18; import "ds-test/test.sol"; +import "cheats/Vm.sol"; contract DebugLogsTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + constructor() { emit log_uint(0); } @@ -19,14 +22,17 @@ contract DebugLogsTest is DSTest { emit log_uint(3); } - function testFailWithRevert() public { + function testRevertIfWithRevert() public { Fails fails = new Fails(); emit log_uint(4); + vm.expectRevert(); fails.failure(); } - function testFailWithRequire() public { + /// forge-config: default.allow_internal_expect_revert = true + function testRevertIfWithRequire() public { emit log_uint(5); + vm.expectRevert(); require(false); } diff --git a/testdata/logs/HardhatLogs.t.sol b/testdata/default/logs/HardhatLogs.t.sol similarity index 95% rename from testdata/logs/HardhatLogs.t.sol rename to testdata/default/logs/HardhatLogs.t.sol index 0058a32bdd26a..a6226bbd665fe 100644 --- a/testdata/logs/HardhatLogs.t.sol +++ b/testdata/default/logs/HardhatLogs.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "./console.sol"; @@ -25,10 +25,10 @@ contract HardhatLogsTest { } function testInts() public view { - console.log(0); - console.log(1); - console.log(2); - console.log(3); + console.log(uint256(0)); + console.log(uint256(1)); + console.log(uint256(2)); + console.log(uint256(3)); } function testStrings() public view { @@ -37,7 +37,7 @@ contract HardhatLogsTest { function testMisc() public view { console.log("testMisc", address(1)); - console.log("testMisc", 42); + console.log("testMisc", uint256(42)); } function testConsoleLog() public view { diff --git a/testdata/logs/console.sol b/testdata/default/logs/console.sol similarity index 61% rename from testdata/logs/console.sol rename to testdata/default/logs/console.sol index b295acc8cf930..e6b267184b96d 100644 --- a/testdata/logs/console.sol +++ b/testdata/default/logs/console.sol @@ -1,1531 +1,1552 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.22 <0.9.0; library console { - address constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67); + address constant CONSOLE_ADDRESS = 0x000000000000000000636F6e736F6c652e6c6f67; - function _sendLogPayload(bytes memory payload) private view { - uint256 payloadLength = payload.length; + function _sendLogPayloadImplementation(bytes memory payload) internal view { address consoleAddress = CONSOLE_ADDRESS; + /// @solidity memory-safe-assembly assembly { - let payloadStart := add(payload, 32) - let r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0) + pop(staticcall(gas(), consoleAddress, add(payload, 32), mload(payload), 0, 0)) } } - function log() internal view { + function _castToPure(function(bytes memory) internal view fnIn) + internal + pure + returns (function(bytes memory) pure fnOut) + { + assembly { + fnOut := fnIn + } + } + + function _sendLogPayload(bytes memory payload) internal pure { + _castToPure(_sendLogPayloadImplementation)(payload); + } + + function log() internal pure { _sendLogPayload(abi.encodeWithSignature("log()")); } - function logInt(int256 p0) internal view { - _sendLogPayload(abi.encodeWithSignature("log(int)", p0)); + function logInt(int256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } - function logUint(uint256 p0) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); + function logUint(uint256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); } - function logString(string memory p0) internal view { + function logString(string memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } - function logBool(bool p0) internal view { + function logBool(bool p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } - function logAddress(address p0) internal view { + function logAddress(address p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } - function logBytes(bytes memory p0) internal view { + function logBytes(bytes memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes)", p0)); } - function logBytes1(bytes1 p0) internal view { + function logBytes1(bytes1 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes1)", p0)); } - function logBytes2(bytes2 p0) internal view { + function logBytes2(bytes2 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes2)", p0)); } - function logBytes3(bytes3 p0) internal view { + function logBytes3(bytes3 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes3)", p0)); } - function logBytes4(bytes4 p0) internal view { + function logBytes4(bytes4 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes4)", p0)); } - function logBytes5(bytes5 p0) internal view { + function logBytes5(bytes5 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes5)", p0)); } - function logBytes6(bytes6 p0) internal view { + function logBytes6(bytes6 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes6)", p0)); } - function logBytes7(bytes7 p0) internal view { + function logBytes7(bytes7 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes7)", p0)); } - function logBytes8(bytes8 p0) internal view { + function logBytes8(bytes8 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes8)", p0)); } - function logBytes9(bytes9 p0) internal view { + function logBytes9(bytes9 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes9)", p0)); } - function logBytes10(bytes10 p0) internal view { + function logBytes10(bytes10 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes10)", p0)); } - function logBytes11(bytes11 p0) internal view { + function logBytes11(bytes11 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes11)", p0)); } - function logBytes12(bytes12 p0) internal view { + function logBytes12(bytes12 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes12)", p0)); } - function logBytes13(bytes13 p0) internal view { + function logBytes13(bytes13 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes13)", p0)); } - function logBytes14(bytes14 p0) internal view { + function logBytes14(bytes14 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes14)", p0)); } - function logBytes15(bytes15 p0) internal view { + function logBytes15(bytes15 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes15)", p0)); } - function logBytes16(bytes16 p0) internal view { + function logBytes16(bytes16 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes16)", p0)); } - function logBytes17(bytes17 p0) internal view { + function logBytes17(bytes17 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes17)", p0)); } - function logBytes18(bytes18 p0) internal view { + function logBytes18(bytes18 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes18)", p0)); } - function logBytes19(bytes19 p0) internal view { + function logBytes19(bytes19 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes19)", p0)); } - function logBytes20(bytes20 p0) internal view { + function logBytes20(bytes20 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes20)", p0)); } - function logBytes21(bytes21 p0) internal view { + function logBytes21(bytes21 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes21)", p0)); } - function logBytes22(bytes22 p0) internal view { + function logBytes22(bytes22 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes22)", p0)); } - function logBytes23(bytes23 p0) internal view { + function logBytes23(bytes23 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes23)", p0)); } - function logBytes24(bytes24 p0) internal view { + function logBytes24(bytes24 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes24)", p0)); } - function logBytes25(bytes25 p0) internal view { + function logBytes25(bytes25 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes25)", p0)); } - function logBytes26(bytes26 p0) internal view { + function logBytes26(bytes26 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes26)", p0)); } - function logBytes27(bytes27 p0) internal view { + function logBytes27(bytes27 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes27)", p0)); } - function logBytes28(bytes28 p0) internal view { + function logBytes28(bytes28 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes28)", p0)); } - function logBytes29(bytes29 p0) internal view { + function logBytes29(bytes29 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes29)", p0)); } - function logBytes30(bytes30 p0) internal view { + function logBytes30(bytes30 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes30)", p0)); } - function logBytes31(bytes31 p0) internal view { + function logBytes31(bytes31 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes31)", p0)); } - function logBytes32(bytes32 p0) internal view { + function logBytes32(bytes32 p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bytes32)", p0)); } - function log(uint256 p0) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint)", p0)); + function log(uint256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256)", p0)); + } + + function log(int256 p0) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(int256)", p0)); } - function log(string memory p0) internal view { + function log(string memory p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string)", p0)); } - function log(bool p0) internal view { + function log(bool p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool)", p0)); } - function log(address p0) internal view { + function log(address p0) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address)", p0)); } - function log(uint256 p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint)", p0, p1)); + function log(uint256 p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256)", p0, p1)); + } + + function log(uint256 p0, string memory p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string)", p0, p1)); } - function log(uint256 p0, string memory p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string)", p0, p1)); + function log(uint256 p0, bool p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool)", p0, p1)); } - function log(uint256 p0, bool p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool)", p0, p1)); + function log(uint256 p0, address p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address)", p0, p1)); } - function log(uint256 p0, address p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address)", p0, p1)); + function log(string memory p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256)", p0, p1)); } - function log(string memory p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint)", p0, p1)); + function log(string memory p0, int256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,int256)", p0, p1)); } - function log(string memory p0, string memory p1) internal view { + function log(string memory p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string)", p0, p1)); } - function log(string memory p0, bool p1) internal view { + function log(string memory p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool)", p0, p1)); } - function log(string memory p0, address p1) internal view { + function log(string memory p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address)", p0, p1)); } - function log(bool p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint)", p0, p1)); + function log(bool p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256)", p0, p1)); } - function log(bool p0, string memory p1) internal view { + function log(bool p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string)", p0, p1)); } - function log(bool p0, bool p1) internal view { + function log(bool p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool)", p0, p1)); } - function log(bool p0, address p1) internal view { + function log(bool p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address)", p0, p1)); } - function log(address p0, uint256 p1) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint)", p0, p1)); + function log(address p0, uint256 p1) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256)", p0, p1)); } - function log(address p0, string memory p1) internal view { + function log(address p0, string memory p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string)", p0, p1)); } - function log(address p0, bool p1) internal view { + function log(address p0, bool p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool)", p0, p1)); } - function log(address p0, address p1) internal view { + function log(address p0, address p1) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address)", p0, p1)); } - function log(uint256 p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address)", p0, p1, p2)); + function log(uint256 p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint)", p0, p1, p2)); + function log(uint256 p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string)", p0, p1, p2)); + function log(uint256 p0, string memory p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool)", p0, p1, p2)); + function log(uint256 p0, string memory p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool)", p0, p1, p2)); } - function log(uint256 p0, string memory p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address)", p0, p1, p2)); + function log(uint256 p0, string memory p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address)", p0, p1, p2)); } - function log(uint256 p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint)", p0, p1, p2)); + function log(uint256 p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256)", p0, p1, p2)); } - function log(uint256 p0, bool p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string)", p0, p1, p2)); + function log(uint256 p0, bool p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string)", p0, p1, p2)); } - function log(uint256 p0, bool p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool)", p0, p1, p2)); + function log(uint256 p0, bool p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool)", p0, p1, p2)); } - function log(uint256 p0, bool p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address)", p0, p1, p2)); + function log(uint256 p0, bool p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address)", p0, p1, p2)); } - function log(uint256 p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint)", p0, p1, p2)); + function log(uint256 p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256)", p0, p1, p2)); } - function log(uint256 p0, address p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string)", p0, p1, p2)); + function log(uint256 p0, address p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string)", p0, p1, p2)); } - function log(uint256 p0, address p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool)", p0, p1, p2)); + function log(uint256 p0, address p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool)", p0, p1, p2)); } - function log(uint256 p0, address p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address)", p0, p1, p2)); + function log(uint256 p0, address p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint)", p0, p1, p2)); + function log(string memory p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string)", p0, p1, p2)); + function log(string memory p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool)", p0, p1, p2)); + function log(string memory p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool)", p0, p1, p2)); } - function log(string memory p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address)", p0, p1, p2)); + function log(string memory p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address)", p0, p1, p2)); } - function log(string memory p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint)", p0, p1, p2)); + function log(string memory p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256)", p0, p1, p2)); } - function log(string memory p0, string memory p1, string memory p2) internal view { + function log(string memory p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string)", p0, p1, p2)); } - function log(string memory p0, string memory p1, bool p2) internal view { + function log(string memory p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool)", p0, p1, p2)); } - function log(string memory p0, string memory p1, address p2) internal view { + function log(string memory p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address)", p0, p1, p2)); } - function log(string memory p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint)", p0, p1, p2)); + function log(string memory p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256)", p0, p1, p2)); } - function log(string memory p0, bool p1, string memory p2) internal view { + function log(string memory p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string)", p0, p1, p2)); } - function log(string memory p0, bool p1, bool p2) internal view { + function log(string memory p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool)", p0, p1, p2)); } - function log(string memory p0, bool p1, address p2) internal view { + function log(string memory p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address)", p0, p1, p2)); } - function log(string memory p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint)", p0, p1, p2)); + function log(string memory p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256)", p0, p1, p2)); } - function log(string memory p0, address p1, string memory p2) internal view { + function log(string memory p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string)", p0, p1, p2)); } - function log(string memory p0, address p1, bool p2) internal view { + function log(string memory p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool)", p0, p1, p2)); } - function log(string memory p0, address p1, address p2) internal view { + function log(string memory p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address)", p0, p1, p2)); } - function log(bool p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint)", p0, p1, p2)); + function log(bool p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256)", p0, p1, p2)); } - function log(bool p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string)", p0, p1, p2)); + function log(bool p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string)", p0, p1, p2)); } - function log(bool p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool)", p0, p1, p2)); + function log(bool p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool)", p0, p1, p2)); } - function log(bool p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address)", p0, p1, p2)); + function log(bool p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address)", p0, p1, p2)); } - function log(bool p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint)", p0, p1, p2)); + function log(bool p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256)", p0, p1, p2)); } - function log(bool p0, string memory p1, string memory p2) internal view { + function log(bool p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string)", p0, p1, p2)); } - function log(bool p0, string memory p1, bool p2) internal view { + function log(bool p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool)", p0, p1, p2)); } - function log(bool p0, string memory p1, address p2) internal view { + function log(bool p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address)", p0, p1, p2)); } - function log(bool p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint)", p0, p1, p2)); + function log(bool p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256)", p0, p1, p2)); } - function log(bool p0, bool p1, string memory p2) internal view { + function log(bool p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string)", p0, p1, p2)); } - function log(bool p0, bool p1, bool p2) internal view { + function log(bool p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool)", p0, p1, p2)); } - function log(bool p0, bool p1, address p2) internal view { + function log(bool p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address)", p0, p1, p2)); } - function log(bool p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint)", p0, p1, p2)); + function log(bool p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256)", p0, p1, p2)); } - function log(bool p0, address p1, string memory p2) internal view { + function log(bool p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string)", p0, p1, p2)); } - function log(bool p0, address p1, bool p2) internal view { + function log(bool p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool)", p0, p1, p2)); } - function log(bool p0, address p1, address p2) internal view { + function log(bool p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address)", p0, p1, p2)); } - function log(address p0, uint256 p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint)", p0, p1, p2)); + function log(address p0, uint256 p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256)", p0, p1, p2)); } - function log(address p0, uint256 p1, string memory p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string)", p0, p1, p2)); + function log(address p0, uint256 p1, string memory p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string)", p0, p1, p2)); } - function log(address p0, uint256 p1, bool p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool)", p0, p1, p2)); + function log(address p0, uint256 p1, bool p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool)", p0, p1, p2)); } - function log(address p0, uint256 p1, address p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address)", p0, p1, p2)); + function log(address p0, uint256 p1, address p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address)", p0, p1, p2)); } - function log(address p0, string memory p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint)", p0, p1, p2)); + function log(address p0, string memory p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256)", p0, p1, p2)); } - function log(address p0, string memory p1, string memory p2) internal view { + function log(address p0, string memory p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string)", p0, p1, p2)); } - function log(address p0, string memory p1, bool p2) internal view { + function log(address p0, string memory p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool)", p0, p1, p2)); } - function log(address p0, string memory p1, address p2) internal view { + function log(address p0, string memory p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address)", p0, p1, p2)); } - function log(address p0, bool p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint)", p0, p1, p2)); + function log(address p0, bool p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256)", p0, p1, p2)); } - function log(address p0, bool p1, string memory p2) internal view { + function log(address p0, bool p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string)", p0, p1, p2)); } - function log(address p0, bool p1, bool p2) internal view { + function log(address p0, bool p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool)", p0, p1, p2)); } - function log(address p0, bool p1, address p2) internal view { + function log(address p0, bool p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address)", p0, p1, p2)); } - function log(address p0, address p1, uint256 p2) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint)", p0, p1, p2)); + function log(address p0, address p1, uint256 p2) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256)", p0, p1, p2)); } - function log(address p0, address p1, string memory p2) internal view { + function log(address p0, address p1, string memory p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string)", p0, p1, p2)); } - function log(address p0, address p1, bool p2) internal view { + function log(address p0, address p1, bool p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool)", p0, p1, p2)); } - function log(address p0, address p1, address p2) internal view { + function log(address p0, address p1, address p2) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address)", p0, p1, p2)); } - function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,uint,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,uint256,address,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, string memory p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,string,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, string memory p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,string,address,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, bool p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,bool,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, bool p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,bool,address,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,uint,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,uint256,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,string,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,string,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,bool,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,bool,address)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,uint)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,uint256)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,string)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,string)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,bool)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,bool)", p0, p1, p2, p3)); } - function log(uint256 p0, address p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(uint,address,address,address)", p0, p1, p2, p3)); + function log(uint256 p0, address p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(uint256,address,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,string,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,bool,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,string)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,bool)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,uint,address,address)", p0, p1, p2, p3)); + function log(string memory p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,uint256,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, string memory p3) internal view { + function log(string memory p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, bool p3) internal view { + function log(string memory p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, string memory p2, address p3) internal view { + function log(string memory p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, string memory p3) internal view { + function log(string memory p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, bool p3) internal view { + function log(string memory p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, bool p2, address p3) internal view { + function log(string memory p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,string,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, string memory p3) internal view { + function log(string memory p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, bool p3) internal view { + function log(string memory p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, string memory p1, address p2, address p3) internal view { + function log(string memory p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,string,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, string memory p3) internal view { + function log(string memory p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, bool p3) internal view { + function log(string memory p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, string memory p2, address p3) internal view { + function log(string memory p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, string memory p3) internal view { + function log(string memory p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, bool p3) internal view { + function log(string memory p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, bool p2, address p3) internal view { + function log(string memory p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, string memory p3) internal view { + function log(string memory p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, bool p3) internal view { + function log(string memory p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, bool p1, address p2, address p3) internal view { + function log(string memory p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,bool,address,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,string)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,bool)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,uint,address)", p0, p1, p2, p3)); + function log(string memory p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,uint256,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,string,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, string memory p3) internal view { + function log(string memory p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, bool p3) internal view { + function log(string memory p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, string memory p2, address p3) internal view { + function log(string memory p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,string,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, string memory p3) internal view { + function log(string memory p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, bool p3) internal view { + function log(string memory p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, bool p2, address p3) internal view { + function log(string memory p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,bool,address)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint)", p0, p1, p2, p3)); + function log(string memory p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(string,address,address,uint256)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, string memory p3) internal view { + function log(string memory p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,string)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, bool p3) internal view { + function log(string memory p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,bool)", p0, p1, p2, p3)); } - function log(string memory p0, address p1, address p2, address p3) internal view { + function log(string memory p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(string,address,address,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,uint,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,string,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,string,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,bool,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,uint)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,string)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,string)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,bool)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,uint,address,address)", p0, p1, p2, p3)); + function log(bool p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,uint256,address,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,string)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint,address)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, string memory p3) internal view { + function log(bool p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, bool p3) internal view { + function log(bool p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, string memory p2, address p3) internal view { + function log(bool p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,string,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, string memory p3) internal view { + function log(bool p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, bool p3) internal view { + function log(bool p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, bool p2, address p3) internal view { + function log(bool p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint)", p0, p1, p2, p3)); + function log(bool p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, string memory p3) internal view { + function log(bool p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,string)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, bool p3) internal view { + function log(bool p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, string memory p1, address p2, address p3) internal view { + function log(bool p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,string,address,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,string)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint,address)", p0, p1, p2, p3)); + function log(bool p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, string memory p3) internal view { + function log(bool p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, bool p3) internal view { + function log(bool p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, string memory p2, address p3) internal view { + function log(bool p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,string,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, string memory p3) internal view { + function log(bool p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, bool p3) internal view { + function log(bool p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, bool p2, address p3) internal view { + function log(bool p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint)", p0, p1, p2, p3)); + function log(bool p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, string memory p3) internal view { + function log(bool p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,string)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, bool p3) internal view { + function log(bool p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, bool p1, address p2, address p3) internal view { + function log(bool p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,bool,address,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,string)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,bool)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint,address)", p0, p1, p2, p3)); + function log(bool p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,uint256,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, string memory p3) internal view { + function log(bool p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, bool p3) internal view { + function log(bool p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, string memory p2, address p3) internal view { + function log(bool p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,string,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, string memory p3) internal view { + function log(bool p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, bool p3) internal view { + function log(bool p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, bool p2, address p3) internal view { + function log(bool p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,bool,address)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint)", p0, p1, p2, p3)); + function log(bool p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,uint256)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, string memory p3) internal view { + function log(bool p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,string)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, bool p3) internal view { + function log(bool p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,bool)", p0, p1, p2, p3)); } - function log(bool p0, address p1, address p2, address p3) internal view { + function log(bool p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(bool,address,address,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,uint,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, string memory p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,string,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, string memory p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,string,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, bool p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,bool,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, bool p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,bool,address)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,uint)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,string)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,string)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,bool)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,bool)", p0, p1, p2, p3)); } - function log(address p0, uint256 p1, address p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,uint,address,address)", p0, p1, p2, p3)); + function log(address p0, uint256 p1, address p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,uint256,address,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,string)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,bool)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,uint,address)", p0, p1, p2, p3)); + function log(address p0, string memory p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, string memory p3) internal view { + function log(address p0, string memory p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, bool p3) internal view { + function log(address p0, string memory p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, string memory p2, address p3) internal view { + function log(address p0, string memory p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,string,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, string memory p3) internal view { + function log(address p0, string memory p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, bool p3) internal view { + function log(address p0, string memory p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, bool p2, address p3) internal view { + function log(address p0, string memory p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,bool,address)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint)", p0, p1, p2, p3)); + function log(address p0, string memory p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,string,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, string memory p3) internal view { + function log(address p0, string memory p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,string)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, bool p3) internal view { + function log(address p0, string memory p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,bool)", p0, p1, p2, p3)); } - function log(address p0, string memory p1, address p2, address p3) internal view { + function log(address p0, string memory p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,string,address,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,string)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,bool)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint,address)", p0, p1, p2, p3)); + function log(address p0, bool p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, string memory p3) internal view { + function log(address p0, bool p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, bool p3) internal view { + function log(address p0, bool p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, string memory p2, address p3) internal view { + function log(address p0, bool p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,string,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, string memory p3) internal view { + function log(address p0, bool p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, bool p3) internal view { + function log(address p0, bool p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, bool p2, address p3) internal view { + function log(address p0, bool p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,bool,address)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint)", p0, p1, p2, p3)); + function log(address p0, bool p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, string memory p3) internal view { + function log(address p0, bool p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,string)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, bool p3) internal view { + function log(address p0, bool p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,bool)", p0, p1, p2, p3)); } - function log(address p0, bool p1, address p2, address p3) internal view { + function log(address p0, bool p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,bool,address,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, string memory p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,string)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, string memory p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, bool p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,bool)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, bool p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, uint256 p2, address p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,uint,address)", p0, p1, p2, p3)); + function log(address p0, address p1, uint256 p2, address p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,uint256,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, string memory p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,string,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, string memory p3) internal view { + function log(address p0, address p1, string memory p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, bool p3) internal view { + function log(address p0, address p1, string memory p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, string memory p2, address p3) internal view { + function log(address p0, address p1, string memory p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,string,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, bool p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, string memory p3) internal view { + function log(address p0, address p1, bool p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, bool p3) internal view { + function log(address p0, address p1, bool p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, bool p2, address p3) internal view { + function log(address p0, address p1, bool p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,bool,address)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, uint256 p3) internal view { - _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint)", p0, p1, p2, p3)); + function log(address p0, address p1, address p2, uint256 p3) internal pure { + _sendLogPayload(abi.encodeWithSignature("log(address,address,address,uint256)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, string memory p3) internal view { + function log(address p0, address p1, address p2, string memory p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,string)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, bool p3) internal view { + function log(address p0, address p1, address p2, bool p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,bool)", p0, p1, p2, p3)); } - function log(address p0, address p1, address p2, address p3) internal view { + function log(address p0, address p1, address p2, address p3) internal pure { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } } diff --git a/testdata/repros/Issue2623.t.sol b/testdata/default/repros/Issue2623.t.sol similarity index 74% rename from testdata/repros/Issue2623.t.sol rename to testdata/default/repros/Issue2623.t.sol index d479860da02b2..31252cae36c3b 100644 --- a/testdata/repros/Issue2623.t.sol +++ b/testdata/default/repros/Issue2623.t.sol @@ -1,15 +1,15 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2623 contract Issue2623Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testRollFork() public { - uint256 fork = vm.createFork("rpcAlias", 10); + uint256 fork = vm.createFork("mainnet", 10); vm.selectFork(fork); assertEq(block.number, 10); diff --git a/testdata/repros/Issue2629.t.sol b/testdata/default/repros/Issue2629.t.sol similarity index 72% rename from testdata/repros/Issue2629.t.sol rename to testdata/default/repros/Issue2629.t.sol index ef7df205ba48c..d46868903a60e 100644 --- a/testdata/repros/Issue2629.t.sol +++ b/testdata/default/repros/Issue2629.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2629 contract Issue2629Test is DSTest { @@ -11,13 +11,13 @@ contract Issue2629Test is DSTest { function testSelectFork() public { address coinbase = 0x0193d941b50d91BE6567c7eE1C0Fe7AF498b4137; - uint256 f1 = vm.createSelectFork("rpcAlias", 9); + uint256 f1 = vm.createSelectFork("mainnet", 9); vm.selectFork(f1); assertEq(block.number, 9); assertEq(coinbase.balance, 11250000000000000000); - uint256 f2 = vm.createFork("rpcAlias", 10); + uint256 f2 = vm.createFork("mainnet", 10); vm.selectFork(f2); assertEq(block.number, 10); diff --git a/testdata/repros/Issue2723.t.sol b/testdata/default/repros/Issue2723.t.sol similarity index 77% rename from testdata/repros/Issue2723.t.sol rename to testdata/default/repros/Issue2723.t.sol index 0a89f289ee0f2..b7678df450cb8 100644 --- a/testdata/repros/Issue2723.t.sol +++ b/testdata/default/repros/Issue2723.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2723 contract Issue2723Test is DSTest { @@ -11,7 +11,7 @@ contract Issue2723Test is DSTest { function testRollFork() public { address coinbase = 0x0193d941b50d91BE6567c7eE1C0Fe7AF498b4137; - vm.createSelectFork("rpcAlias", 9); + vm.createSelectFork("mainnet", 9); assertEq(block.number, 9); assertEq(coinbase.balance, 11250000000000000000); diff --git a/testdata/default/repros/Issue2851.t.sol b/testdata/default/repros/Issue2851.t.sol new file mode 100644 index 0000000000000..f90a5f7c5dc7d --- /dev/null +++ b/testdata/default/repros/Issue2851.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.1; + +import "ds-test/test.sol"; + +contract Backdoor { + uint256 public number = 1; + + function backdoor(uint256 newNumber) public payable { + uint256 x = newNumber - 1; + if (x == 6912213124124531) { + number = 0; + } + } +} + +// https://github.com/foundry-rs/foundry/issues/2851 +contract Issue2851Test is DSTest { + Backdoor back; + + function setUp() public { + back = new Backdoor(); + } + + function invariantNotZero() public { + assertEq(back.number(), 1); + } +} diff --git a/testdata/repros/Issue2898.t.sol b/testdata/default/repros/Issue2898.t.sol similarity index 87% rename from testdata/repros/Issue2898.t.sol rename to testdata/default/repros/Issue2898.t.sol index 67a5abe8992c0..a16adf5a350ff 100644 --- a/testdata/repros/Issue2898.t.sol +++ b/testdata/default/repros/Issue2898.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/2898 diff --git a/testdata/repros/Issue2956.t.sol b/testdata/default/repros/Issue2956.t.sol similarity index 67% rename from testdata/repros/Issue2956.t.sol rename to testdata/default/repros/Issue2956.t.sol index 3fe90b72e4977..c57b46cc1f4bc 100644 --- a/testdata/repros/Issue2956.t.sol +++ b/testdata/default/repros/Issue2956.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2956 contract Issue2956Test is DSTest { @@ -11,8 +11,8 @@ contract Issue2956Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 7475589); - fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); + fork1 = vm.createFork("sepolia", 5565573); + fork2 = vm.createFork("avaxTestnet", 12880747); } function testForkNonce() public { @@ -28,7 +28,7 @@ contract Issue2956Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); + assertEq(vm.getNonce(user), 1); vm.prank(user); new Counter(); } diff --git a/testdata/default/repros/Issue2984.t.sol b/testdata/default/repros/Issue2984.t.sol new file mode 100644 index 0000000000000..fbcd1ab8c3c4a --- /dev/null +++ b/testdata/default/repros/Issue2984.t.sol @@ -0,0 +1,25 @@ +// 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/2984 +contract Issue2984Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 fork; + uint256 snapshot; + + function setUp() public { + fork = vm.createSelectFork("avaxTestnet", 12880747); + snapshot = vm.snapshotState(); + } + + function testForkRevertSnapshot() public { + vm.revertToState(snapshot); + } + + function testForkSelectSnapshot() public { + uint256 fork2 = vm.createSelectFork("avaxTestnet", 12880749); + } +} diff --git a/testdata/repros/Issue3055.t.sol b/testdata/default/repros/Issue3055.t.sol similarity index 64% rename from testdata/repros/Issue3055.t.sol rename to testdata/default/repros/Issue3055.t.sol index 1a4634ef52247..90ac8c3b08afd 100644 --- a/testdata/repros/Issue3055.t.sol +++ b/testdata/default/repros/Issue3055.t.sol @@ -1,23 +1,23 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3055 contract Issue3055Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function test_snapshot() external { - uint256 snapId = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); assertEq(uint256(0), uint256(1)); - vm.revertTo(snapId); + vm.revertToState(snapshotId); } function test_snapshot2() public { - uint256 snapshot = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); assertTrue(false); - vm.revertTo(snapshot); + vm.revertToState(snapshotId); assertTrue(true); } @@ -29,8 +29,8 @@ contract Issue3055Test is DSTest { } function exposed_snapshot3() public { - uint256 snapshot = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); assertTrue(false); - vm.revertTo(snapshot); + vm.revertToState(snapshotId); } } diff --git a/testdata/repros/Issue3077.t.sol b/testdata/default/repros/Issue3077.t.sol similarity index 88% rename from testdata/repros/Issue3077.t.sol rename to testdata/default/repros/Issue3077.t.sol index 9167dc70ddab5..3b5e4257a3ad4 100644 --- a/testdata/repros/Issue3077.t.sol +++ b/testdata/default/repros/Issue3077.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3077 abstract contract ZeroState is DSTest { @@ -15,7 +15,7 @@ abstract contract ZeroState is DSTest { function setUp() public virtual { vm.startPrank(deployer); - mainnetFork = vm.createFork("rpcAlias"); + mainnetFork = vm.createFork("mainnet"); vm.selectFork(mainnetFork); vm.rollFork(block.number - 20); // deploy tokens diff --git a/testdata/repros/Issue3110.t.sol b/testdata/default/repros/Issue3110.t.sol similarity index 88% rename from testdata/repros/Issue3110.t.sol rename to testdata/default/repros/Issue3110.t.sol index 02b1ff3d7c394..9f1da8d032ec3 100644 --- a/testdata/repros/Issue3110.t.sol +++ b/testdata/default/repros/Issue3110.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3110 abstract contract ZeroState is DSTest { @@ -17,7 +17,7 @@ abstract contract ZeroState is DSTest { vm.label(deployer, "Deployer"); vm.startPrank(deployer); - mainnetFork = vm.createFork("rpcAlias"); + mainnetFork = vm.createFork("mainnet"); vm.selectFork(mainnetFork); vm.rollFork(block.number - 20); diff --git a/testdata/repros/Issue3119.t.sol b/testdata/default/repros/Issue3119.t.sol similarity index 82% rename from testdata/repros/Issue3119.t.sol rename to testdata/default/repros/Issue3119.t.sol index c91fd78329ce9..6c0ceb429d6d6 100644 --- a/testdata/repros/Issue3119.t.sol +++ b/testdata/default/repros/Issue3119.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3119 contract Issue3119Test is DSTest { @@ -12,7 +12,7 @@ contract Issue3119Test is DSTest { address public alice = vm.addr(2); function testRollFork() public { - uint256 fork = vm.createFork("rpcAlias"); + uint256 fork = vm.createFork("mainnet"); vm.selectFork(fork); FortressSwap fortressSwap = new FortressSwap(address(owner)); diff --git a/testdata/repros/Issue3189.t.sol b/testdata/default/repros/Issue3189.t.sol similarity index 87% rename from testdata/repros/Issue3189.t.sol rename to testdata/default/repros/Issue3189.t.sol index e2d302256ccdd..0bcf5ddce8d76 100644 --- a/testdata/repros/Issue3189.t.sol +++ b/testdata/default/repros/Issue3189.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3189 contract MyContract { diff --git a/testdata/repros/Issue3190.t.sol b/testdata/default/repros/Issue3190.t.sol similarity index 81% rename from testdata/repros/Issue3190.t.sol rename to testdata/default/repros/Issue3190.t.sol index 30adada424587..ede3e50e2e3ec 100644 --- a/testdata/repros/Issue3190.t.sol +++ b/testdata/default/repros/Issue3190.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/3190 diff --git a/testdata/repros/Issue3192.t.sol b/testdata/default/repros/Issue3192.t.sol similarity index 66% rename from testdata/repros/Issue3192.t.sol rename to testdata/default/repros/Issue3192.t.sol index f333adedebc1b..9c5be8d89f3d6 100644 --- a/testdata/repros/Issue3192.t.sol +++ b/testdata/default/repros/Issue3192.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3192 contract Issue3192Test is DSTest { @@ -11,8 +11,8 @@ contract Issue3192Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("rpcAlias", 7475589); - fork2 = vm.createFork("rpcAlias", 12880747); + fork1 = vm.createFork("mainnet", 7475589); + fork2 = vm.createFork("mainnet", 12880747); vm.selectFork(fork1); } diff --git a/testdata/repros/Issue3220.t.sol b/testdata/default/repros/Issue3220.t.sol similarity index 81% rename from testdata/repros/Issue3220.t.sol rename to testdata/default/repros/Issue3220.t.sol index 760e25d6c0bf8..5235e44c79c60 100644 --- a/testdata/repros/Issue3220.t.sol +++ b/testdata/default/repros/Issue3220.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3220 contract Issue3220Test is DSTest { @@ -14,9 +14,9 @@ contract Issue3220Test is DSTest { uint256 counter; function setUp() public { - fork1 = vm.createFork("rpcAlias", 7475589); + fork1 = vm.createFork("mainnet", 7475589); vm.selectFork(fork1); - fork2 = vm.createFork("rpcAlias", 12880747); + fork2 = vm.createFork("mainnet", 12880747); } function testForkRevert() public { diff --git a/testdata/repros/Issue3221.sol b/testdata/default/repros/Issue3221.t.sol similarity index 66% rename from testdata/repros/Issue3221.sol rename to testdata/default/repros/Issue3221.t.sol index e4fc7490fd1a1..81398c41fc290 100644 --- a/testdata/repros/Issue3221.sol +++ b/testdata/default/repros/Issue3221.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3221 contract Issue3221Test is DSTest { @@ -11,8 +11,8 @@ contract Issue3221Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b1d3925804e74152b316ca7da97060d3", 7475589); - fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); + fork1 = vm.createFork("sepolia", 5565573); + fork2 = vm.createFork("avaxTestnet", 12880747); } function testForkNonce() public { @@ -27,7 +27,7 @@ contract Issue3221Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); + assertEq(vm.getNonce(user), 1); vm.prank(user); new Counter(); } diff --git a/testdata/repros/Issue3223.sol b/testdata/default/repros/Issue3223.t.sol similarity index 60% rename from testdata/repros/Issue3223.sol rename to testdata/default/repros/Issue3223.t.sol index 95728b95c9559..6c21b7b3d60b1 100644 --- a/testdata/repros/Issue3223.sol +++ b/testdata/default/repros/Issue3223.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3223 contract Issue3223Test is DSTest { @@ -11,8 +11,8 @@ contract Issue3223Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 7475589); - fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); + fork1 = vm.createFork("sepolia", 2362365); + fork2 = vm.createFork("avaxTestnet", 12880747); } function testForkNonce() public { @@ -25,9 +25,7 @@ contract Issue3223Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); - vm.prank(user); - new Counter(); + assertEq(vm.getNonce(user), 1); } } diff --git a/testdata/repros/Issue3347.sol b/testdata/default/repros/Issue3347.t.sol similarity index 69% rename from testdata/repros/Issue3347.sol rename to testdata/default/repros/Issue3347.t.sol index 76f8c2130d3bf..e48c1305db426 100644 --- a/testdata/repros/Issue3347.sol +++ b/testdata/default/repros/Issue3347.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3347 contract Issue3347Test is DSTest { diff --git a/testdata/repros/Issue3437.t.sol b/testdata/default/repros/Issue3437.t.sol similarity index 67% rename from testdata/repros/Issue3437.t.sol rename to testdata/default/repros/Issue3437.t.sol index 2c16ce999e4b8..69f56ca8283dd 100644 --- a/testdata/repros/Issue3437.t.sol +++ b/testdata/default/repros/Issue3437.t.sol @@ -1,19 +1,19 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3437 contract Issue3347Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function rever() internal { + function internalRevert() internal { revert(); } function testFailExample() public { vm.expectRevert(); - rever(); + internalRevert(); } } diff --git a/testdata/repros/Issue3596.t.sol b/testdata/default/repros/Issue3596.t.sol similarity index 86% rename from testdata/repros/Issue3596.t.sol rename to testdata/default/repros/Issue3596.t.sol index af1aa021fcbef..b0c6785874375 100644 --- a/testdata/repros/Issue3596.t.sol +++ b/testdata/default/repros/Issue3596.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3596 contract Issue3596Test is DSTest { diff --git a/testdata/repros/Issue3653.t.sol b/testdata/default/repros/Issue3653.t.sol similarity index 64% rename from testdata/repros/Issue3653.t.sol rename to testdata/default/repros/Issue3653.t.sol index d54acf3901333..26eb38e4a29f1 100644 --- a/testdata/repros/Issue3653.t.sol +++ b/testdata/default/repros/Issue3653.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3653 contract Issue3653Test is DSTest { @@ -11,13 +11,13 @@ contract Issue3653Test is DSTest { Token token; constructor() { - fork = vm.createSelectFork("rpcAlias", 10); + fork = vm.createSelectFork("mainnet", 1000000); token = new Token(); vm.makePersistent(address(token)); } function testDummy() public { - assertEq(block.number, 10); + assertEq(block.number, 1000000); } } diff --git a/testdata/repros/Issue3661.sol b/testdata/default/repros/Issue3661.t.sol similarity index 80% rename from testdata/repros/Issue3661.sol rename to testdata/default/repros/Issue3661.t.sol index 6a3e732cd9715..76b55a222ca00 100644 --- a/testdata/repros/Issue3661.sol +++ b/testdata/default/repros/Issue3661.t.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; diff --git a/testdata/repros/Issue3674.t.sol b/testdata/default/repros/Issue3674.t.sol similarity index 50% rename from testdata/repros/Issue3674.t.sol rename to testdata/default/repros/Issue3674.t.sol index 1b6985fff6bac..de5a960059f52 100644 --- a/testdata/repros/Issue3674.t.sol +++ b/testdata/default/repros/Issue3674.t.sol @@ -1,17 +1,17 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3674 contract Issue3674Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testNonceCreateSelect() public { - vm.createSelectFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001"); + vm.createSelectFork("sepolia"); - vm.createSelectFork("https://api.avax-test.network/ext/bc/C/rpc"); + vm.createSelectFork("avaxTestnet"); assert(vm.getNonce(msg.sender) > 0x17); } } diff --git a/testdata/repros/Issue3685.t.sol b/testdata/default/repros/Issue3685.t.sol similarity index 92% rename from testdata/repros/Issue3685.t.sol rename to testdata/default/repros/Issue3685.t.sol index 204ea677027dd..f1da5bf6997bd 100644 --- a/testdata/repros/Issue3685.t.sol +++ b/testdata/default/repros/Issue3685.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/3685 diff --git a/testdata/repros/Issue3703.t.sol b/testdata/default/repros/Issue3703.t.sol similarity index 78% rename from testdata/repros/Issue3703.t.sol rename to testdata/default/repros/Issue3703.t.sol index 517e59b7bcfb8..48651be24c669 100644 --- a/testdata/repros/Issue3703.t.sol +++ b/testdata/default/repros/Issue3703.t.sol @@ -1,18 +1,16 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3703 contract Issue3703Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { - uint256 fork = vm.createSelectFork( - "https://polygon-mainnet.g.alchemy.com/v2/bVjX9v-FpmUhf5R_oHIgwJx2kXvYPRbx", - bytes32(0xbed0c8c1b9ff8bf0452979d170c52893bb8954f18a904aa5bcbd0f709be050b9) - ); + uint256 fork = + vm.createSelectFork("polygon", bytes32(0xbed0c8c1b9ff8bf0452979d170c52893bb8954f18a904aa5bcbd0f709be050b9)); } function poolState(address poolAddr, uint256 expectedSqrtPriceX96, uint256 expectedLiquidity) private { diff --git a/testdata/repros/Issue3708.sol b/testdata/default/repros/Issue3708.t.sol similarity index 81% rename from testdata/repros/Issue3708.sol rename to testdata/default/repros/Issue3708.t.sol index 619614ea61bf1..53a7c461f873f 100644 --- a/testdata/repros/Issue3708.sol +++ b/testdata/default/repros/Issue3708.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3708 contract Issue3708Test is DSTest { @@ -11,8 +11,7 @@ contract Issue3708Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function setUp() public { - string memory RPC_URL = "https://mainnet.optimism.io"; - uint256 forkId = vm.createSelectFork(RPC_URL); + uint256 forkId = vm.createSelectFork("optimism"); bytes memory code = hex"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3"; diff --git a/testdata/repros/Issue3723.t.sol b/testdata/default/repros/Issue3723.t.sol similarity index 83% rename from testdata/repros/Issue3723.t.sol rename to testdata/default/repros/Issue3723.t.sol index 16172e1571acf..9ea3fe733c944 100644 --- a/testdata/repros/Issue3723.t.sol +++ b/testdata/default/repros/Issue3723.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3723 contract Issue3723Test is DSTest { diff --git a/testdata/repros/Issue3753.t.sol b/testdata/default/repros/Issue3753.t.sol similarity index 82% rename from testdata/repros/Issue3753.t.sol rename to testdata/default/repros/Issue3753.t.sol index 62b26858b8545..2c927c823dbb6 100644 --- a/testdata/repros/Issue3753.t.sol +++ b/testdata/default/repros/Issue3753.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3753 contract Issue3753Test is DSTest { diff --git a/testdata/repros/Issue3792.t.sol b/testdata/default/repros/Issue3792.t.sol similarity index 73% rename from testdata/repros/Issue3792.t.sol rename to testdata/default/repros/Issue3792.t.sol index 4ffa84d2e8834..37f62bc61fabe 100644 --- a/testdata/repros/Issue3792.t.sol +++ b/testdata/default/repros/Issue3792.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Config { address public test = 0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa; @@ -16,10 +16,10 @@ contract TestSetup is Config, DSTest { // We now check for keccak256("failed") on the hevm address. // This test should succeed. function testSnapshotStorageShift() public { - uint256 snapshotId = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); vm.prank(test); - vm.revertTo(snapshotId); + vm.revertToState(snapshotId); } } diff --git a/testdata/default/repros/Issue4232.t.sol b/testdata/default/repros/Issue4232.t.sol new file mode 100644 index 0000000000000..0ac6a77c77076 --- /dev/null +++ b/testdata/default/repros/Issue4232.t.sol @@ -0,0 +1,31 @@ +// 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/4232 +contract Issue4232Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testFork() public { + // Smoke test, worked previously as well + vm.createSelectFork("sepolia", 7215400); + vm.assertFalse(block.prevrandao == 0); + + // Would previously fail with: + // [FAIL: backend: failed while inspecting; header validation error: `prevrandao` not set; `prevrandao` not set; ] setUp() (gas: 0) + // + // Related fix: + // Moonbeam | Moonbase | Moonriver | MoonbeamDev => { + // if env.block.prevrandao.is_none() { + // // + // env.block.prevrandao = Some(B256::random()); + // } + // } + // + // Note: public RPC node used for `moonbeam` discards state quickly so we need to fork against the latest block + vm.createSelectFork("moonbeam"); + vm.assertFalse(block.prevrandao == 0); + } +} diff --git a/testdata/default/repros/Issue4402.t.sol b/testdata/default/repros/Issue4402.t.sol new file mode 100644 index 0000000000000..830b2926ef269 --- /dev/null +++ b/testdata/default/repros/Issue4402.t.sol @@ -0,0 +1,64 @@ +// 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/4402 +contract Issue4402Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testReadNonEmptyArray() public { + string memory path = "fixtures/Json/Issue4402.json"; + string memory json = vm.readFile(path); + address[] memory tokens = vm.parseJsonAddressArray(json, ".tokens"); + assertEq(tokens.length, 1); + + path = "fixtures/Toml/Issue4402.toml"; + string memory toml = vm.readFile(path); + tokens = vm.parseTomlAddressArray(toml, ".tokens"); + assertEq(tokens.length, 1); + } + + function testReadEmptyArray() public { + string memory path = "fixtures/Json/Issue4402.json"; + string memory json = vm.readFile(path); + + // Every one of these used to causes panic + address[] memory emptyAddressArray = vm.parseJsonAddressArray(json, ".empty"); + bool[] memory emptyBoolArray = vm.parseJsonBoolArray(json, ".empty"); + bytes[] memory emptyBytesArray = vm.parseJsonBytesArray(json, ".empty"); + bytes32[] memory emptyBytes32Array = vm.parseJsonBytes32Array(json, ".empty"); + string[] memory emptyStringArray = vm.parseJsonStringArray(json, ".empty"); + int256[] memory emptyIntArray = vm.parseJsonIntArray(json, ".empty"); + uint256[] memory emptyUintArray = vm.parseJsonUintArray(json, ".empty"); + + assertEq(emptyAddressArray.length, 0); + assertEq(emptyBoolArray.length, 0); + assertEq(emptyBytesArray.length, 0); + assertEq(emptyBytes32Array.length, 0); + assertEq(emptyStringArray.length, 0); + assertEq(emptyIntArray.length, 0); + assertEq(emptyUintArray.length, 0); + + path = "fixtures/Toml/Issue4402.toml"; + string memory toml = vm.readFile(path); + + // Every one of these used to causes panic + emptyAddressArray = vm.parseTomlAddressArray(toml, ".empty"); + emptyBoolArray = vm.parseTomlBoolArray(toml, ".empty"); + emptyBytesArray = vm.parseTomlBytesArray(toml, ".empty"); + emptyBytes32Array = vm.parseTomlBytes32Array(toml, ".empty"); + emptyStringArray = vm.parseTomlStringArray(toml, ".empty"); + emptyIntArray = vm.parseTomlIntArray(toml, ".empty"); + emptyUintArray = vm.parseTomlUintArray(toml, ".empty"); + + assertEq(emptyAddressArray.length, 0); + assertEq(emptyBoolArray.length, 0); + assertEq(emptyBytesArray.length, 0); + assertEq(emptyBytes32Array.length, 0); + assertEq(emptyStringArray.length, 0); + assertEq(emptyIntArray.length, 0); + assertEq(emptyUintArray.length, 0); + } +} diff --git a/testdata/repros/Issue4586.t.sol b/testdata/default/repros/Issue4586.t.sol similarity index 87% rename from testdata/repros/Issue4586.t.sol rename to testdata/default/repros/Issue4586.t.sol index 43bb3853581ca..c904af1e46abb 100644 --- a/testdata/repros/Issue4586.t.sol +++ b/testdata/default/repros/Issue4586.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4586 contract Issue4586Test is DSTest { @@ -13,7 +13,7 @@ contract Issue4586Test is DSTest { InvariantHandler handler; function setUp() public { - vm.createSelectFork("rpcAlias", initialBlock); + vm.createSelectFork("mainnet", initialBlock); handler = new InvariantHandler(); } diff --git a/testdata/repros/Issue4630.t.sol b/testdata/default/repros/Issue4630.t.sol similarity index 62% rename from testdata/repros/Issue4630.t.sol rename to testdata/default/repros/Issue4630.t.sol index 6df4a05eb0352..01eb626505cd1 100644 --- a/testdata/repros/Issue4630.t.sol +++ b/testdata/default/repros/Issue4630.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4630 contract Issue4630Test is DSTest { @@ -18,11 +18,7 @@ contract Issue4630Test is DSTest { function testMissingValue() public { string memory path = "fixtures/Json/Issue4630.json"; string memory json = vm.readFile(path); - vm.expectRevert(); - uint256 val = this.parseJsonUint(json, ".localempty.prop1"); - } - - function parseJsonUint(string memory json, string memory path) public returns (uint256) { - return vm.parseJsonUint(json, path); + vm._expectCheatcodeRevert(); + vm.parseJsonUint(json, ".localempty.prop1"); } } diff --git a/testdata/repros/Issue4640.t.sol b/testdata/default/repros/Issue4640.t.sol similarity index 67% rename from testdata/repros/Issue4640.t.sol rename to testdata/default/repros/Issue4640.t.sol index 00576122281b7..1e7d887a9b57d 100644 --- a/testdata/repros/Issue4640.t.sol +++ b/testdata/default/repros/Issue4640.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4640 contract Issue4640Test is DSTest { @@ -10,7 +10,7 @@ contract Issue4640Test is DSTest { function testArbitrumBlockNumber() public { // - vm.createSelectFork("https://rpc.ankr.com/arbitrum", 75219831); + vm.createSelectFork("arbitrum", 75219831); // L1 block number assertEq(block.number, 16939475); } diff --git a/testdata/repros/Issue4832.t.sol b/testdata/default/repros/Issue4832.t.sol similarity index 81% rename from testdata/repros/Issue4832.t.sol rename to testdata/default/repros/Issue4832.t.sol index dd2d83d39e7b4..192d805c1bc36 100644 --- a/testdata/repros/Issue4832.t.sol +++ b/testdata/default/repros/Issue4832.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4832 contract Issue4832Test is DSTest { diff --git a/testdata/repros/Issue5038.t.sol b/testdata/default/repros/Issue5038.t.sol similarity index 98% rename from testdata/repros/Issue5038.t.sol rename to testdata/default/repros/Issue5038.t.sol index bee48f0b7fb12..51a90bca10d4c 100644 --- a/testdata/repros/Issue5038.t.sol +++ b/testdata/default/repros/Issue5038.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.18; +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; struct Value { uint256 value; diff --git a/testdata/default/repros/Issue5529.t.sol b/testdata/default/repros/Issue5529.t.sol new file mode 100644 index 0000000000000..14ec7cfdbce60 --- /dev/null +++ b/testdata/default/repros/Issue5529.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + +contract Issue5529Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Counter public counter; + address public constant default_create2_factory = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + function testCreate2FactoryUsedInTests() public { + address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); + address b = address(new Counter{salt: 0}()); + require(a == b, "create2 address mismatch"); + } + + function testCreate2FactoryUsedWhenPranking() public { + vm.startPrank(address(1234)); + address a = vm.computeCreate2Address(0, keccak256(type(Counter).creationCode), address(default_create2_factory)); + address b = address(new Counter{salt: 0}()); + require(a == b, "create2 address mismatch"); + } +} diff --git a/testdata/default/repros/Issue5739.t.sol b/testdata/default/repros/Issue5739.t.sol new file mode 100644 index 0000000000000..6f3494b7e6e7d --- /dev/null +++ b/testdata/default/repros/Issue5739.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +// https://github.com/foundry-rs/foundry/issues/5739 +contract Issue5739Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + IERC20 dai; + + function setUp() public { + vm.createSelectFork("mainnet", 19000000); + dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + } + + function testRollForkStateUpdated() public { + // dai not persistent so state should be updated between rolls + assertEq(dai.totalSupply(), 3723031040751006502480211083); + vm.rollFork(19925849); + assertEq(dai.totalSupply(), 3320242279303699674318705475); + } + + function testRollForkStatePersisted() public { + // make dai persistent so state is preserved between rolls + vm.makePersistent(address(dai)); + assertEq(dai.totalSupply(), 3723031040751006502480211083); + vm.rollFork(19925849); + assertEq(dai.totalSupply(), 3723031040751006502480211083); + } +} diff --git a/testdata/default/repros/Issue5808.t.sol b/testdata/default/repros/Issue5808.t.sol new file mode 100644 index 0000000000000..40efe65a9ce69 --- /dev/null +++ b/testdata/default/repros/Issue5808.t.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/5808 +contract Issue5808Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testReadInt() public { + string memory str1 = '["ffffffff","00000010"]'; + vm._expectCheatcodeRevert(); + int256[] memory ints1 = vm.parseJsonIntArray(str1, ""); + + string memory str2 = '["0xffffffff","0x00000010"]'; + int256[] memory ints2 = vm.parseJsonIntArray(str2, ""); + assertEq(ints2[0], 0xffffffff); + assertEq(ints2[1], 16); + } +} diff --git a/testdata/default/repros/Issue5929.t.sol b/testdata/default/repros/Issue5929.t.sol new file mode 100644 index 0000000000000..ced9d6d9b4a39 --- /dev/null +++ b/testdata/default/repros/Issue5929.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"; + +// https://github.com/foundry-rs/foundry/issues/5929 +contract Issue5929Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_transact_not_working() public { + vm.createSelectFork("mainnet", 21134547); + // https://etherscan.io/tx/0x96a129768ec66fd7d65114bf182f4e173bf0b73a44219adaf71f01381a3d0143 + vm.transact(hex"7dcff74771babf9c23363c4228e55a27f50224d4596b1ba6608b0b45712f94ba"); + } +} diff --git a/testdata/default/repros/Issue5935.t.sol b/testdata/default/repros/Issue5935.t.sol new file mode 100644 index 0000000000000..8ef724412ce31 --- /dev/null +++ b/testdata/default/repros/Issue5935.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract SimpleStorage { + uint256 public value; + + function set(uint256 _value) external { + value = _value; + } +} + +contract Issue5935Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testFork() public { + uint256 forkId1 = vm.createFork("mainnet", 18234083); + uint256 forkId2 = vm.createFork("mainnet", 18234083); + vm.selectFork(forkId1); + SimpleStorage myContract = new SimpleStorage(); + myContract.set(42); + vm.selectFork(forkId2); + SimpleStorage myContract2 = new SimpleStorage(); + assertEq(myContract2.value(), 0); + + vm.selectFork(forkId1); + assertEq(myContract.value(), 42); + } +} diff --git a/testdata/default/repros/Issue5948.t.sol b/testdata/default/repros/Issue5948.t.sol new file mode 100644 index 0000000000000..ae6ee9d50d8ba --- /dev/null +++ b/testdata/default/repros/Issue5948.t.sol @@ -0,0 +1,32 @@ +// 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/5948 +contract Issue5948Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + /// forge-config: default.fuzz.runs = 2 + function testSleepFuzzed(uint256 _milliseconds) public { + // Limit sleep time to 2 seconds to decrease test time + uint256 milliseconds = _milliseconds % 2000; + + string[] memory inputs = new string[](2); + inputs[0] = "date"; + // OS X does not support precision more than 1 second + inputs[1] = "+%s000"; + + bytes memory res = vm.ffi(inputs); + uint256 start = vm.parseUint(string(res)); + + vm.sleep(milliseconds); + + res = vm.ffi(inputs); + uint256 end = vm.parseUint(string(res)); + + // Limit precision to 1000 ms + assertGe(end - start, milliseconds / 1000 * 1000, "sleep failed"); + } +} diff --git a/testdata/default/repros/Issue6006.t.sol b/testdata/default/repros/Issue6006.t.sol new file mode 100644 index 0000000000000..54f0d11376d79 --- /dev/null +++ b/testdata/default/repros/Issue6006.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6006 +contract Issue6066Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_parse_11e20_sci() public { + string memory json = '{"value": 1.1e20}'; + bytes memory parsed = vm.parseJson(json); + Value memory data = abi.decode(parsed, (Value)); + assertEq(data.value, 1.1e20); + } + + function test_parse_22e20_sci() public { + string memory json = '{"value": 2.2e20}'; + bytes memory parsed = vm.parseJson(json); + Value memory data = abi.decode(parsed, (Value)); + assertEq(data.value, 2.2e20); + } + + function test_parse_2e_sci() public { + string memory json = '{"value": 2e10}'; + bytes memory parsed = vm.parseJson(json); + Value memory data = abi.decode(parsed, (Value)); + assertEq(data.value, 2e10); + } +} + +struct Value { + uint256 value; +} diff --git a/testdata/default/repros/Issue6032.t.sol b/testdata/default/repros/Issue6032.t.sol new file mode 100644 index 0000000000000..2fa05222d5a54 --- /dev/null +++ b/testdata/default/repros/Issue6032.t.sol @@ -0,0 +1,48 @@ +// 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/6032 +contract Issue6032Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEtchFork() public { + // Deploy initial contract + Counter counter = new Counter(); + counter.setNumber(42); + + address counterAddress = address(counter); + // Enter the fork + vm.createSelectFork("mainnet"); + assert(counterAddress.code.length > 0); + // `Counter` is not deployed on the fork, which is expected. + + // Etch the contract into the fork. + bytes memory code = vm.getDeployedCode("Issue6032.t.sol:CounterEtched"); + vm.etch(counterAddress, code); + // `Counter` is now deployed on the fork. + assert(counterAddress.code.length > 0); + + // Now we can etch the code, but state will remain. + CounterEtched counterEtched = CounterEtched(counterAddress); + assertEq(counterEtched.numberHalf(), 21); + } +} + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } +} + +contract CounterEtched { + uint256 public number; + + function numberHalf() public view returns (uint256) { + return number / 2; + } +} diff --git a/testdata/default/repros/Issue6070.t.sol b/testdata/default/repros/Issue6070.t.sol new file mode 100644 index 0000000000000..ebf3c7ab15c7b --- /dev/null +++ b/testdata/default/repros/Issue6070.t.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6070 +contract Issue6066Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testNonPrefixed() public { + vm.setEnv("__FOUNDRY_ISSUE_6066", "abcd"); + vm._expectCheatcodeRevert( + "failed parsing $__FOUNDRY_ISSUE_6066 as type `uint256`: missing hex prefix (\"0x\") for hex string" + ); + uint256 x = vm.envUint("__FOUNDRY_ISSUE_6066"); + } +} diff --git a/testdata/default/repros/Issue6115.t.sol b/testdata/default/repros/Issue6115.t.sol new file mode 100644 index 0000000000000..ae65a7dae8647 --- /dev/null +++ b/testdata/default/repros/Issue6115.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} + +// https://github.com/foundry-rs/foundry/issues/6115 +contract Issue6115Test is DSTest { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + // We should be able to fuzz bytes4 + function testFuzz_SetNumber(uint256 x, bytes4 test) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + // We should be able to fuzz bytes8 + function testFuzz_SetNumber2(uint256 x, bytes8 test) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } + + // We should be able to fuzz bytes12 + function testFuzz_SetNumber3(uint256 x, bytes12 test) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/testdata/default/repros/Issue6170.t.sol b/testdata/default/repros/Issue6170.t.sol new file mode 100644 index 0000000000000..78511f4a2dc91 --- /dev/null +++ b/testdata/default/repros/Issue6170.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"; + +contract Emitter { + event Values(uint256 indexed a, uint256 indexed b); + + function plsEmit(uint256 a, uint256 b) external { + emit Values(a, b); + } +} + +// https://github.com/foundry-rs/foundry/issues/6170 +contract Issue6170Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + event Values(uint256 indexed a, uint256 b); + + Emitter e = new Emitter(); + + function test() public { + vm.expectEmit(true, true, false, true); + emit Values(69, 420); + e.plsEmit(69, 420); + } +} diff --git a/testdata/default/repros/Issue6180.t.sol b/testdata/default/repros/Issue6180.t.sol new file mode 100644 index 0000000000000..3d08ccbebac5d --- /dev/null +++ b/testdata/default/repros/Issue6180.t.sol @@ -0,0 +1,23 @@ +// 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/6180 +contract Issue6180Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_timebug() external { + uint256 start = block.timestamp; + uint256 count = 4; + uint256 duration = 15; + for (uint256 i; i < count; i++) { + vm.warp(block.timestamp + duration); + } + + uint256 end = block.timestamp; + assertEq(end, start + count * duration); + assertEq(end - start, count * duration); + } +} diff --git a/testdata/default/repros/Issue6293.t.sol b/testdata/default/repros/Issue6293.t.sol new file mode 100644 index 0000000000000..6d57d13850950 --- /dev/null +++ b/testdata/default/repros/Issue6293.t.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/6293 +contract Issue6293Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + constructor() { + require(address(this).balance > 0); + payable(address(1)).call{value: 1}(""); + } + + function test() public { + assertGt(address(this).balance, 0); + } +} diff --git a/testdata/default/repros/Issue6355.t.sol b/testdata/default/repros/Issue6355.t.sol new file mode 100644 index 0000000000000..bbc3a4a98d412 --- /dev/null +++ b/testdata/default/repros/Issue6355.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/6355 +contract Issue6355Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 snapshotId; + Target targ; + + function setUp() public { + snapshotId = vm.snapshotState(); + targ = new Target(); + } + + // this non-deterministically fails sometimes and passes sometimes + function test_shouldPass() public { + assertEq(2, targ.num()); + } + + // always fails + function test_shouldFailWithRevertToState() public { + assertEq(3, targ.num()); + vm.revertToState(snapshotId); + } + + // always fails + function test_shouldFail() public { + assertEq(3, targ.num()); + } +} + +contract Target { + function num() public pure returns (uint256) { + return 2; + } +} diff --git a/testdata/default/repros/Issue6437.t.sol b/testdata/default/repros/Issue6437.t.sol new file mode 100644 index 0000000000000..4cf27be7b233e --- /dev/null +++ b/testdata/default/repros/Issue6437.t.sol @@ -0,0 +1,32 @@ +// 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/6437 +contract Issue6437Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test0() public { + string memory json = "[]"; + address[] memory arr = vm.parseJsonAddressArray(json, "$"); + assertEq(arr.length, 0); + } + + function test1() public { + string memory json = "[\"0x1111111111111111111111111111111111111111\"]"; + address[] memory arr = vm.parseJsonAddressArray(json, "$"); + assertEq(arr.length, 1); + assertEq(arr[0], 0x1111111111111111111111111111111111111111); + } + + function test2() public { + string memory json = + "[\"0x1111111111111111111111111111111111111111\",\"0x2222222222222222222222222222222222222222\"]"; + address[] memory arr = vm.parseJsonAddressArray(json, "$"); + assertEq(arr.length, 2); + assertEq(arr[0], 0x1111111111111111111111111111111111111111); + assertEq(arr[1], 0x2222222222222222222222222222222222222222); + } +} diff --git a/testdata/default/repros/Issue6501.t.sol b/testdata/default/repros/Issue6501.t.sol new file mode 100644 index 0000000000000..5d631cbe3e0a8 --- /dev/null +++ b/testdata/default/repros/Issue6501.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "../logs/console.sol"; + +// https://github.com/foundry-rs/foundry/issues/6501 +contract Issue6501Test is DSTest { + function test_hhLogs() public { + console.log("a"); + console.log(uint256(1)); + console.log("b", uint256(2)); + } +} diff --git a/testdata/default/repros/Issue6538.t.sol b/testdata/default/repros/Issue6538.t.sol new file mode 100644 index 0000000000000..34c4e2253a68b --- /dev/null +++ b/testdata/default/repros/Issue6538.t.sol @@ -0,0 +1,17 @@ +// 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/6538 +contract Issue6538Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_transact() public { + bytes32 lastHash = 0x4b70ca8c5a0990b43df3064372d424d46efa41dfaab961754b86c5afb2df4f61; + vm.createSelectFork("mainnet", lastHash); + bytes32 txhash = 0x7dcff74771babf9c23363c4228e55a27f50224d4596b1ba6608b0b45712f94ba; + vm.transact(txhash); + } +} diff --git a/testdata/default/repros/Issue6554.t.sol b/testdata/default/repros/Issue6554.t.sol new file mode 100644 index 0000000000000..7a5fe7879c655 --- /dev/null +++ b/testdata/default/repros/Issue6554.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"; + +// https://github.com/foundry-rs/foundry/issues/6554 +contract Issue6554Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPermissions() public { + vm.writeFile("./out/default/Issue6554.t.sol/cachedFile.txt", "cached data"); + string memory content = vm.readFile("./out/default/Issue6554.t.sol/cachedFile.txt"); + assertEq(content, "cached data"); + } +} diff --git a/testdata/default/repros/Issue6616.t.sol b/testdata/default/repros/Issue6616.t.sol new file mode 100644 index 0000000000000..262721d86d118 --- /dev/null +++ b/testdata/default/repros/Issue6616.t.sol @@ -0,0 +1,22 @@ +// 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/6616 +contract Issue6616Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testCreateForkRollLatestBlock() public { + vm.createSelectFork("mainnet"); + uint256 startBlock = block.number; + // this will create new forks and exit once a new latest block is found + for (uint256 i; i < 10; i++) { + vm.sleep(5000); + vm.createSelectFork("mainnet"); + if (block.number > startBlock) break; + } + assertGt(block.number, startBlock); + } +} diff --git a/testdata/default/repros/Issue6634.t.sol b/testdata/default/repros/Issue6634.t.sol new file mode 100644 index 0000000000000..aa94922dd1ccb --- /dev/null +++ b/testdata/default/repros/Issue6634.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract Box { + uint256 public number; + + constructor(uint256 _number) { + number = _number; + } +} + +// https://github.com/foundry-rs/foundry/issues/6634 +contract Issue6634Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_Create2FactoryCallRecordedInStandardTest() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess account is incorrect"); + assertEq(called[0].accessor, address(this), "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } + + function test_Create2FactoryCallRecordedWhenPranking() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + address accessor = address(0x5555); + + vm.startPrank(accessor); + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess account is incorrect"); + assertEq(called[0].accessor, accessor, "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } + + function test_Create2FactoryCallRecordedWhenBroadcasting() public { + address CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + address accessor = address(0x5555); + + vm.startBroadcast(accessor); + vm.startStateDiffRecording(); + Box a = new Box{salt: 0}(1); + + Vm.AccountAccess[] memory called = vm.stopAndReturnStateDiff(); + address addr = vm.computeCreate2Address( + 0, keccak256(abi.encodePacked(type(Box).creationCode, uint256(1))), address(CREATE2_DEPLOYER) + ); + assertEq(addr, called[1].account, "state diff contract address is not correct"); + assertEq(address(a), called[1].account, "returned address is not correct"); + + assertEq(called.length, 2, "incorrect length"); + assertEq(uint256(called[0].kind), uint256(Vm.AccountAccessKind.Call), "first AccountAccess is incorrect kind"); + assertEq(called[0].account, CREATE2_DEPLOYER, "first AccountAccess account is incorrect"); + assertEq(called[0].accessor, accessor, "first AccountAccess accessor is incorrect"); + assertEq( + uint256(called[1].kind), uint256(Vm.AccountAccessKind.Create), "second AccountAccess is incorrect kind" + ); + assertEq(called[1].accessor, CREATE2_DEPLOYER, "second AccountAccess accessor is incorrect"); + assertEq(called[1].account, address(a), "second AccountAccess account is incorrect"); + } +} diff --git a/testdata/default/repros/Issue6643.t.sol b/testdata/default/repros/Issue6643.t.sol new file mode 100644 index 0000000000000..5c7e1c483a03a --- /dev/null +++ b/testdata/default/repros/Issue6643.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + event TestEvent(uint256 n); + event AnotherTestEvent(uint256 n); + + constructor() { + emit TestEvent(1); + } + + function f() external { + emit TestEvent(2); + } + + function g() external { + emit AnotherTestEvent(1); + this.f(); + emit AnotherTestEvent(2); + } +} + +// https://github.com/foundry-rs/foundry/issues/6643 +contract Issue6643Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter public counter; + + event TestEvent(uint256 n); + event AnotherTestEvent(uint256 n); + + function setUp() public { + counter = new Counter(); + } + + function test_Bug1() public { + // part1 + vm.expectEmit(); + emit TestEvent(1); + new Counter(); + // part2 + vm.expectEmit(); + emit TestEvent(2); + counter.f(); + // part3 + vm.expectEmit(); + emit AnotherTestEvent(1); + vm.expectEmit(); + emit TestEvent(2); + vm.expectEmit(); + emit AnotherTestEvent(2); + counter.g(); + } + + function test_Bug2() public { + vm.expectEmit(); + emit TestEvent(1); + new Counter(); + vm.expectEmit(); + emit TestEvent(1); + new Counter(); + } +} diff --git a/testdata/default/repros/Issue6759.t.sol b/testdata/default/repros/Issue6759.t.sol new file mode 100644 index 0000000000000..ffdcb88935a92 --- /dev/null +++ b/testdata/default/repros/Issue6759.t.sol @@ -0,0 +1,19 @@ +// 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/6759 +contract Issue6759Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testCreateMulti() public { + uint256 fork1 = vm.createFork("mainnet", 10); + uint256 fork2 = vm.createFork("mainnet", 10); + uint256 fork3 = vm.createFork("mainnet", 10); + assert(fork1 != fork2); + assert(fork1 != fork3); + assert(fork2 != fork3); + } +} diff --git a/testdata/default/repros/Issue6966.t.sol b/testdata/default/repros/Issue6966.t.sol new file mode 100644 index 0000000000000..7e35a869ed0fb --- /dev/null +++ b/testdata/default/repros/Issue6966.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +// https://github.com/foundry-rs/foundry/issues/6966 +// See also https://github.com/RustCrypto/elliptic-curves/issues/988#issuecomment-1817681013 +contract Issue6966Test is DSTest { + function testEcrecover() public { + bytes32 h = 0x0000000000000000000000000000000000000000000000000000000000000000; + uint8 v = 27; + bytes32 r = bytes32(0xf87fff3202dfeae34ce9cb8151ce2e176bee02a937baac6de85c4ea03d6a6618); + bytes32 s = bytes32(0xedf9ab5c7d3ec1df1c2b48600ab0a35f586e069e9a69c6cdeebc99920128d1a5); + assert(ecrecover(h, v, r, s) != address(0)); + } +} diff --git a/testdata/default/repros/Issue7238.t.sol b/testdata/default/repros/Issue7238.t.sol new file mode 100644 index 0000000000000..a2227fabed8bc --- /dev/null +++ b/testdata/default/repros/Issue7238.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 Reverter { + function doNotRevert() public {} + + function revertWithMessage(string calldata message) public { + revert(message); + } +} + +// https://github.com/foundry-rs/foundry/issues/7238 +contract Issue7238Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testExpectRevertString() public { + Reverter reverter = new Reverter(); + vm.expectRevert("revert"); + reverter.revertWithMessage("revert"); + } + + // FAIL + /// forge-config: default.allow_internal_expect_revert = true + function testShouldFailCheatcodeRevert() public { + // This expectRevert is hanging, as the next cheatcode call is ignored. + vm.expectRevert(); + vm.fsMetadata("something/something"); // try to go to some non-existent path to cause a revert + } + + /// forge-config: default.allow_internal_expect_revert = true + function testShouldFailEarlyRevert() public { + vm.expectRevert(); + rever(); + } + + function rever() internal { + revert(); + } +} diff --git a/testdata/default/repros/Issue7457.t.sol b/testdata/default/repros/Issue7457.t.sol new file mode 100644 index 0000000000000..13cd033afac9a --- /dev/null +++ b/testdata/default/repros/Issue7457.t.sol @@ -0,0 +1,100 @@ +// 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(); + } + + /// forge-config: default.allow_internal_expect_revert = true + function testEmitEventNonIndexedReverts() public { + vm.expectEmit(false, false, false, true); + vm.expectRevert("use vm.expectEmitAnonymous to match anonymous events"); + emit AnonymousEventNonIndexed(1); + } + + function testEmitEventNonIndexed() public { + vm.expectEmitAnonymous(false, false, false, false, true); + emit AnonymousEventNonIndexed(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/Issue7481.t.sol b/testdata/default/repros/Issue7481.t.sol new file mode 100644 index 0000000000000..c8116b8095aeb --- /dev/null +++ b/testdata/default/repros/Issue7481.t.sol @@ -0,0 +1,25 @@ +// 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/7481 +// This test ensures that we don't panic +contract Issue7481Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + /// forge-config: default.allow_internal_expect_revert = true + function testRevertTransact() public { + vm.expectRevert("vm.createSelectFork: invalid rpc url: mainnet"); + vm.createSelectFork("mainnet", 19514903); + + // Transfer some funds to sender of tx being transacted to ensure that it appears in journaled state + payable(address(0x5C60cD7a3D50877Bfebd484750FBeb245D936dAD)).call{value: 1}(""); + vm.transact(0xccfd66fc409a633a99b5b75b0e9a2040fcf562d03d9bee3fefc1a5c0eb49c999); + + // Revert the current call to ensure that revm can revert state journal + vm.expectRevert(); + revert("HERE"); + } +} diff --git a/testdata/default/repros/Issue8004.t.sol b/testdata/default/repros/Issue8004.t.sol new file mode 100644 index 0000000000000..278aa12125ff3 --- /dev/null +++ b/testdata/default/repros/Issue8004.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract NonPersistentHelper is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 public curState; + + function createSelectFork() external { + vm.createSelectFork("mainnet"); + curState += 1; + } + + function createSelectForkAtBlock() external { + vm.createSelectFork("mainnet", 19000000); + curState += 1; + } + + function createSelectForkAtTx() external { + vm.createSelectFork( + "mainnet", vm.parseBytes32("0xb5c978f15d01fcc9b4d78967e8189e35ecc21ff4e78315ea5d616f3467003c84") + ); + curState += 1; + } + + function selectFork(uint256 forkId) external { + vm.selectFork(forkId); + curState += 1; + } + + function rollForkAtBlock() external { + vm.rollFork(19000000); + curState += 1; + } + + function rollForkIdAtBlock(uint256 forkId) external { + vm.rollFork(forkId, 19000000); + curState += 1; + } +} + +// https://github.com/foundry-rs/foundry/issues/8004 +contract Issue8004CreateSelectForkTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + NonPersistentHelper helper; + + function setUp() public { + helper = new NonPersistentHelper(); + } + + function testNonPersistentHelperCreateFork() external { + helper.createSelectFork(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperCreateForkAtBlock() external { + helper.createSelectForkAtBlock(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperCreateForkAtTx() external { + helper.createSelectForkAtBlock(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperSelectFork() external { + uint256 forkId = vm.createFork("mainnet"); + helper.selectFork(forkId); + assertEq(helper.curState(), 1); + } +} + +contract Issue8004RollForkTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + NonPersistentHelper helper; + uint256 forkId; + + function setUp() public { + forkId = vm.createSelectFork("mainnet"); + helper = new NonPersistentHelper(); + } + + function testNonPersistentHelperRollForkAtBlock() external { + helper.rollForkAtBlock(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperRollForkIdAtBlock() external { + helper.rollForkIdAtBlock(forkId); + assertEq(helper.curState(), 1); + } +} diff --git a/testdata/default/repros/Issue8006.t.sol b/testdata/default/repros/Issue8006.t.sol new file mode 100644 index 0000000000000..efe339d9fef2b --- /dev/null +++ b/testdata/default/repros/Issue8006.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +contract Mock { + function totalSupply() external view returns (uint256 supply) { + return 1; + } +} + +// https://github.com/foundry-rs/foundry/issues/8006 +contract Issue8006Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + IERC20 dai; + bytes32 transaction = 0xb23f389b26eb6f95c08e275ec2c360ab3990169492ff0d3e7b7233a3f81d299f; + + function setUp() public { + vm.createSelectFork("mainnet", 21134541); + dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + } + + function testRollForkEtchNotCalled() public { + // dai not persistent so should not call mock code + vm.etch(address(dai), address(new Mock()).code); + assertEq(dai.totalSupply(), 1); + vm.rollFork(transaction); + assertEq(dai.totalSupply(), 3324657947511778619416491233); + } + + function testRollForkEtchCalled() public { + // make dai persistent so mock code is preserved + vm.etch(address(dai), address(new Mock()).code); + vm.makePersistent(address(dai)); + assertEq(dai.totalSupply(), 1); + vm.rollFork(transaction); + assertEq(dai.totalSupply(), 1); + } +} diff --git a/testdata/default/repros/Issue8168.t.sol b/testdata/default/repros/Issue8168.t.sol new file mode 100644 index 0000000000000..9a072ce4bbf8f --- /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("mainnet"); + uint256 fork2 = vm.createFork("mainnet"); + + 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 0000000000000..48a089575b402 --- /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 0000000000000..d1e372bda91d8 --- /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("mainnet", 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("mainnet", 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 0000000000000..339f5b518a30c --- /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/repros/Issue8566.t.sol b/testdata/default/repros/Issue8566.t.sol new file mode 100644 index 0000000000000..f300d096f7a22 --- /dev/null +++ b/testdata/default/repros/Issue8566.t.sol @@ -0,0 +1,18 @@ +// 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/8566 +contract Issue8566Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testParseJsonUint() public { + string memory json = + "{ \"1284\": { \"addRewardInfo\": { \"amount\": 74258.225772486694040708e18, \"rewardPerSec\": 0.03069536448928848133e20 } } }"; + + assertEq(74258225772486694040708, vm.parseJsonUint(json, ".1284.addRewardInfo.amount")); + assertEq(3069536448928848133, vm.parseJsonUint(json, ".1284.addRewardInfo.rewardPerSec")); + } +} diff --git a/testdata/default/repros/Issue8639.t.sol b/testdata/default/repros/Issue8639.t.sol new file mode 100644 index 0000000000000..6f0a7b526336f --- /dev/null +++ b/testdata/default/repros/Issue8639.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +library ExternalLibrary { + function doWork(uint256 a) public returns (uint256) { + return a++; + } +} + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + ExternalLibrary.doWork(1); + } + + function increment() public {} +} + +// https://github.com/foundry-rs/foundry/issues/8639 +contract Issue8639Test is DSTest { + Counter counter; + + function setUp() public { + counter = new Counter(); + } + + /// forge-config: default.fuzz.runs = 1000 + /// forge-config: default.fuzz.seed = '100' + function test_external_library_address(address test) public { + require(test != address(ExternalLibrary)); + } +} + +contract Issue8639AnotherTest is DSTest { + /// forge-config: default.fuzz.runs = 1000 + /// forge-config: default.fuzz.seed = '100' + function test_another_external_library_address(address test) public { + require(test != address(ExternalLibrary)); + } +} diff --git a/testdata/default/repros/Issue8971.t.sol b/testdata/default/repros/Issue8971.t.sol new file mode 100644 index 0000000000000..37861b483ec5d --- /dev/null +++ b/testdata/default/repros/Issue8971.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + uint256 public number; + + function increment() public { + number++; + } +} + +/// @notice Test is mostly related to --isolate. Ensures that state is not affected by reverted +/// call to handler. +contract Handler { + bool public locked; + Counter public counter = new Counter(); + + function doNothing() public {} + + function doSomething() public { + locked = true; + counter.increment(); + this.doRevert(); + } + + function doRevert() public { + revert(); + } +} + +contract Invariant is DSTest { + Handler h; + + function setUp() public { + h = new Handler(); + } + + function targetContracts() public view returns (address[] memory contracts) { + contracts = new address[](1); + contracts[0] = address(h); + } + + function invariant_unchanged() public { + assertEq(h.locked(), false); + assertEq(h.counter().number(), 0); + } +} diff --git a/testdata/default/repros/Issue9643.t.sol b/testdata/default/repros/Issue9643.t.sol new file mode 100644 index 0000000000000..7a985138dc335 --- /dev/null +++ b/testdata/default/repros/Issue9643.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Mock { + uint256 private counter; + + function setCounter(uint256 _counter) external { + counter = _counter; + } +} + +contract DelegateProxy { + address internal implementation; + + constructor(address mock) { + implementation = mock; + } + + fallback() external payable { + address addr = implementation; + + assembly { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), addr, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } +} + +contract Issue9643Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_storage_json_diff() public { + vm.startStateDiffRecording(); + Mock proxied = Mock(address(new DelegateProxy(address(new Mock())))); + proxied.setCounter(42); + string memory rawDiff = vm.getStateDiffJson(); + assertEq( + "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}}}", + rawDiff + ); + } +} diff --git a/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json new file mode 100644 index 0000000000000..1a8bccb392489 --- /dev/null +++ b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json @@ -0,0 +1 @@ +{"transactions":[{"hash":null,"type":"CREATE","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","gas":"0x54653","value":"0x0","data":"0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033","nonce":"0x0","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xef15","value":"0x0","data":"0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000","nonce":"0x1","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xdcde","value":"0x0","data":"0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b","nonce":"0x2","accessList":[]}}],"receipts":[],"libraries":[],"pending":[],"path":"broadcast/deploy.sol/31337/run-latest.json","returns":{},"timestamp":1658913881} \ No newline at end of file diff --git a/testdata/script/deploy.sol b/testdata/default/script/deploy.sol similarity index 82% rename from testdata/script/deploy.sol rename to testdata/default/script/deploy.sol index 619f76d6f86f6..7570c706a5cec 100644 --- a/testdata/script/deploy.sol +++ b/testdata/default/script/deploy.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; -import {DSTest} from "../lib/ds-test/src/test.sol"; -import {Vm} from "../cheats/Vm.sol"; +import {DSTest} from "lib/ds-test/src/test.sol"; +import {Vm} from "cheats/Vm.sol"; contract Greeter { string name; diff --git a/testdata/spec/ShanghaiCompat.t.sol b/testdata/default/spec/ShanghaiCompat.t.sol similarity index 87% rename from testdata/spec/ShanghaiCompat.t.sol rename to testdata/default/spec/ShanghaiCompat.t.sol index 8c6e7a20274de..fd7213b3d0702 100644 --- a/testdata/spec/ShanghaiCompat.t.sol +++ b/testdata/default/spec/ShanghaiCompat.t.sol @@ -1,8 +1,8 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.20; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ShanghaiCompat is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/trace/ConflictingSignatures.t.sol b/testdata/default/trace/ConflictingSignatures.t.sol similarity index 94% rename from testdata/trace/ConflictingSignatures.t.sol rename to testdata/default/trace/ConflictingSignatures.t.sol index 896390212db0e..c8b7066c7a2f1 100644 --- a/testdata/trace/ConflictingSignatures.t.sol +++ b/testdata/default/trace/ConflictingSignatures.t.sol @@ -1,7 +1,7 @@ -pragma solidity 0.8.18; +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ReturnsNothing { function func() public pure {} diff --git a/testdata/trace/Trace.t.sol b/testdata/default/trace/Trace.t.sol similarity index 97% rename from testdata/trace/Trace.t.sol rename to testdata/default/trace/Trace.t.sol index 2eefba7ba5dd9..19af6dd7c9fe7 100644 --- a/testdata/trace/Trace.t.sol +++ b/testdata/default/trace/Trace.t.sol @@ -1,7 +1,7 @@ -pragma solidity 0.8.18; +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract RecursiveCall { TraceTest factory; diff --git a/testdata/default/vyper/Counter.vy b/testdata/default/vyper/Counter.vy new file mode 100644 index 0000000000000..772bddd11919c --- /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 0000000000000..b6cc517d25dd6 --- /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 0000000000000..e600c71c87e19 --- /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/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json new file mode 100644 index 0000000000000..899a44c1514b7 --- /dev/null +++ b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x044b75f554b886a065b9567891e45c79542d7357","contractCreator":"0xf87bc5535602077d340806d71f805ea9907a843d","txHash":"0x9a89d2f5528bf07661e92f3f78a3311396f11f15da19e3ec4d880be1ad1a4bec"} \ No newline at end of file diff --git a/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json new file mode 100644 index 0000000000000..54aec7d6a61f2 --- /dev/null +++ b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/InputStream.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity 0.8.10;\n\nlibrary InputStream {\n function createStream(bytes memory data) internal pure returns (uint256 stream) {\n assembly {\n stream := mload(0x40)\n mstore(0x40, add(stream, 64))\n mstore(stream, data)\n let length := mload(data)\n mstore(add(stream, 32), add(data, length))\n }\n }\n\n function isNotEmpty(uint256 stream) internal pure returns (bool) {\n uint256 pos;\n uint256 finish;\n assembly {\n pos := mload(stream)\n finish := mload(add(stream, 32))\n }\n return pos < finish;\n }\n\n function readUint8(uint256 stream) internal pure returns (uint8 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 1)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint16(uint256 stream) internal pure returns (uint16 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 2)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint32(uint256 stream) internal pure returns (uint32 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 4)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint(uint256 stream) internal pure returns (uint256 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 32)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readAddress(uint256 stream) internal pure returns (address res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 20)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readBytes(uint256 stream) internal pure returns (bytes memory res) {\n assembly {\n let pos := mload(stream)\n res := add(pos, 32)\n let length := mload(res)\n mstore(stream, add(res, length))\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../extensions/draft-IERC20Permit.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n"},"interfaces/IBentoBoxMinimal.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity >=0.8.0;\n\nstruct Rebase {\n uint128 elastic;\n uint128 base;\n}\n\nstruct StrategyData {\n uint64 strategyStartDate;\n uint64 targetPercentage;\n uint128 balance; // the balance of the strategy that BentoBox thinks is in there\n}\n\n/// @notice A rebasing library\nlibrary RebaseLibrary {\n /// @notice Calculates the base value in relationship to `elastic` and `total`.\n function toBase(Rebase memory total, uint256 elastic) internal pure returns (uint256 base) {\n if (total.elastic == 0) {\n base = elastic;\n } else {\n base = (elastic * total.base) / total.elastic;\n }\n }\n\n /// @notice Calculates the elastic value in relationship to `base` and `total`.\n function toElastic(Rebase memory total, uint256 base) internal pure returns (uint256 elastic) {\n if (total.base == 0) {\n elastic = base;\n } else {\n elastic = (base * total.elastic) / total.base;\n }\n }\n}\n\n/// @notice Minimal BentoBox vault interface.\n/// @dev `token` is aliased as `address` from `IERC20` for simplicity.\ninterface IBentoBoxMinimal {\n /// @notice Balance per ERC-20 token per account in shares.\n function balanceOf(address, address) external view returns (uint256);\n\n /// @dev Helper function to represent an `amount` of `token` in shares.\n /// @param token The ERC-20 token.\n /// @param amount The `token` amount.\n /// @param roundUp If the result `share` should be rounded up.\n /// @return share The token amount represented in shares.\n function toShare(\n address token,\n uint256 amount,\n bool roundUp\n ) external view returns (uint256 share);\n\n /// @dev Helper function to represent shares back into the `token` amount.\n /// @param token The ERC-20 token.\n /// @param share The amount of shares.\n /// @param roundUp If the result should be rounded up.\n /// @return amount The share amount back into native representation.\n function toAmount(\n address token,\n uint256 share,\n bool roundUp\n ) external view returns (uint256 amount);\n\n /// @notice Registers this contract so that users can approve it for BentoBox.\n function registerProtocol() external;\n\n /// @notice Deposit an amount of `token` represented in either `amount` or `share`.\n /// @param token The ERC-20 token to deposit.\n /// @param from which account to pull the tokens.\n /// @param to which account to push the tokens.\n /// @param amount Token amount in native representation to deposit.\n /// @param share Token amount represented in shares to deposit. Takes precedence over `amount`.\n /// @return amountOut The amount deposited.\n /// @return shareOut The deposited amount represented in shares.\n function deposit(\n address token,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external payable returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Withdraws an amount of `token` from a user account.\n /// @param token_ The ERC-20 token to withdraw.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param amount of tokens. Either one of `amount` or `share` needs to be supplied.\n /// @param share Like above, but `share` takes precedence over `amount`.\n function withdraw(\n address token_,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Transfer shares from a user account to another one.\n /// @param token The ERC-20 token to transfer.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param share The amount of `token` in shares.\n function transfer(\n address token,\n address from,\n address to,\n uint256 share\n ) external;\n\n /// @dev Reads the Rebase `totals`from storage for a given token\n function totals(address token) external view returns (Rebase memory total);\n\n function strategyData(address token) external view returns (StrategyData memory total);\n\n /// @dev Approves users' BentoBox assets to a \"master\" contract.\n function setMasterContractApproval(\n address user,\n address masterContract,\n bool approved,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function harvest(\n address token,\n bool balance,\n uint256 maxChangeAmount\n ) external;\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n"},"interfaces/IPool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.5.0;\npragma experimental ABIEncoderV2;\n\n/// @notice Trident pool interface.\ninterface IPool {\n /// @notice Executes a swap from one token to another.\n /// @dev The input tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function swap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Executes a swap from one token to another with a callback.\n /// @dev This function allows borrowing the output tokens and sending the input tokens in the callback.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function flashSwap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Mints liquidity tokens.\n /// @param data ABI-encoded params that the pool requires.\n /// @return liquidity The amount of liquidity tokens that were minted for the user.\n function mint(bytes calldata data) external returns (uint256 liquidity);\n\n /// @notice Burns liquidity tokens.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return withdrawnAmounts The amount of various output tokens that were sent to the user.\n function burn(bytes calldata data) external returns (TokenAmount[] memory withdrawnAmounts);\n\n /// @notice Burns liquidity tokens for a single output token.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return amountOut The amount of output tokens that were sent to the user.\n function burnSingle(bytes calldata data) external returns (uint256 amountOut);\n\n /// @return A unique identifier for the pool type.\n function poolIdentifier() external pure returns (bytes32);\n\n /// @return An array of tokens supported by the pool.\n function getAssets() external view returns (address[] memory);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that will be sent to the user if the trade is executed.\n function getAmountOut(bytes calldata data) external view returns (uint256 finalAmountOut);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountIn The amount of input tokens that are required from the user if the trade is executed.\n function getAmountIn(bytes calldata data) external view returns (uint256 finalAmountIn);\n\n /// @dev This event must be emitted on all swaps.\n event Swap(address indexed recipient, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);\n\n /// @dev This struct frames output tokens for burns.\n struct TokenAmount {\n address token;\n uint256 amount;\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n"},"contracts/RouteProcessor2.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity 0.8.10;\n\nimport '../interfaces/IUniswapV2Pair.sol';\nimport '../interfaces/IUniswapV3Pool.sol';\nimport '../interfaces/ITridentCLPool.sol';\nimport '../interfaces/IBentoBoxMinimal.sol';\nimport '../interfaces/IPool.sol';\nimport '../interfaces/IWETH.sol';\nimport './InputStream.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\n\naddress constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\naddress constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001;\n\n/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)\nuint160 constant MIN_SQRT_RATIO = 4295128739;\n/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)\nuint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;\n\n/// @title A route processor for the Sushi Aggregator\n/// @author Ilya Lyalin\ncontract RouteProcessor2 {\n using SafeERC20 for IERC20;\n using InputStream for uint256;\n\n IBentoBoxMinimal public immutable bentoBox;\n address private lastCalledPool;\n\n uint private unlocked = 1;\n modifier lock() {\n require(unlocked == 1, 'RouteProcessor is locked');\n unlocked = 2;\n _;\n unlocked = 1;\n }\n\n constructor(address _bentoBox) {\n bentoBox = IBentoBoxMinimal(_bentoBox);\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n }\n\n /// @notice For native unwrapping\n receive() external payable {}\n\n /// @notice Processes the route generated off-chain. Has a lock\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function processRoute(\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) external payable lock returns (uint256 amountOut) {\n return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);\n }\n\n /// @notice Transfers some value to and then processes the route\n /// @param transferValueTo Address where the value should be transferred\n /// @param amountValueTransfer How much value to transfer\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function transferValueAndprocessRoute(\n address payable transferValueTo,\n uint256 amountValueTransfer,\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) external payable lock returns (uint256 amountOut) {\n (bool success, bytes memory returnBytes) = transferValueTo.call{value: amountValueTransfer}('');\n require(success, string(abi.encodePacked(returnBytes)));\n return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);\n }\n\n /// @notice Processes the route generated off-chain\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function processRouteInternal(\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) private returns (uint256 amountOut) {\n uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS ? address(this).balance : IERC20(tokenIn).balanceOf(msg.sender);\n uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to);\n\n uint256 stream = InputStream.createStream(route);\n while (stream.isNotEmpty()) {\n uint8 commandCode = stream.readUint8();\n if (commandCode == 1) processMyERC20(stream);\n else if (commandCode == 2) processUserERC20(stream, amountIn);\n else if (commandCode == 3) processNative(stream);\n else if (commandCode == 4) processOnePool(stream);\n else if (commandCode == 5) processInsideBento(stream);\n else revert('RouteProcessor: Unknown command code');\n }\n\n uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS ? address(this).balance : IERC20(tokenIn).balanceOf(msg.sender);\n require(balanceInFinal + amountIn >= balanceInInitial, 'RouteProcessor: Minimal imput balance violation');\n\n uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to);\n require(balanceOutFinal >= balanceOutInitial + amountOutMin, 'RouteProcessor: Minimal ouput balance violation');\n\n amountOut = balanceOutFinal - balanceOutInitial;\n }\n\n /// @notice Processes native coin: call swap for all pools that swap from native coin\n /// @param stream Streamed process program\n function processNative(uint256 stream) private {\n uint256 amountTotal = address(this).balance;\n distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal);\n }\n\n /// @notice Processes ERC20 token from this contract balance:\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processMyERC20(uint256 stream) private {\n address token = stream.readAddress();\n uint256 amountTotal = IERC20(token).balanceOf(address(this));\n unchecked {\n if (amountTotal > 0) amountTotal -= 1; // slot undrain protection\n }\n distributeAndSwap(stream, address(this), token, amountTotal);\n }\n \n /// @notice Processes ERC20 token from msg.sender balance:\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n /// @param amountTotal Amount of tokens to take from msg.sender\n function processUserERC20(uint256 stream, uint256 amountTotal) private {\n address token = stream.readAddress();\n distributeAndSwap(stream, msg.sender, token, amountTotal);\n }\n\n /// @notice Distributes amountTotal to several pools according to their shares and calls swap for each pool\n /// @param stream Streamed process program\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountTotal Total amount of tokenIn for swaps \n function distributeAndSwap(uint256 stream, address from, address tokenIn, uint256 amountTotal) private {\n uint8 num = stream.readUint8();\n unchecked {\n for (uint256 i = 0; i < num; ++i) {\n uint16 share = stream.readUint16();\n uint256 amount = (amountTotal * share) / 65535;\n amountTotal -= amount;\n swap(stream, from, tokenIn, amount);\n }\n }\n }\n\n /// @notice Processes ERC20 token for cases when the token has only one output pool\n /// @notice In this case liquidity is already at pool balance. This is an optimization\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processOnePool(uint256 stream) private {\n address token = stream.readAddress();\n swap(stream, address(this), token, 0);\n }\n\n /// @notice Processes Bento tokens \n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processInsideBento(uint256 stream) private {\n address token = stream.readAddress();\n uint8 num = stream.readUint8();\n\n uint256 amountTotal = bentoBox.balanceOf(token, address(this));\n unchecked {\n if (amountTotal > 0) amountTotal -= 1; // slot undrain protection\n for (uint256 i = 0; i < num; ++i) {\n uint16 share = stream.readUint16();\n uint256 amount = (amountTotal * share) / 65535;\n amountTotal -= amount;\n swap(stream, address(this), token, amount);\n }\n }\n }\n\n /// @notice Makes swap\n /// @param stream Streamed process program\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swap(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 poolType = stream.readUint8();\n if (poolType == 0) swapUniV2(stream, from, tokenIn, amountIn);\n else if (poolType == 1) swapUniV3(stream, from, tokenIn, amountIn);\n else if (poolType == 2) wrapNative(stream, from, tokenIn, amountIn);\n else if (poolType == 3) bentoBridge(stream, from, tokenIn, amountIn);\n else if (poolType == 4) swapTrident(stream, from, tokenIn, amountIn);\n else if (poolType == 5) swapTridentCL(stream, from, tokenIn, amountIn);\n else revert('RouteProcessor: Unknown pool type');\n }\n\n /// @notice Wraps/unwraps native token\n /// @param stream [direction & fake, recipient, wrapToken?]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function wrapNative(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 directionAndFake = stream.readUint8();\n address to = stream.readAddress();\n\n if (directionAndFake & 1 == 1) { // wrap native\n address wrapToken = stream.readAddress();\n if (directionAndFake & 2 == 0) IWETH(wrapToken).deposit{value: amountIn}();\n if (to != address(this)) IERC20(wrapToken).safeTransfer(to, amountIn);\n } else { // unwrap native\n if (directionAndFake & 2 == 0) {\n if (from != address(this)) IERC20(tokenIn).safeTransferFrom(from, address(this), amountIn);\n IWETH(tokenIn).withdraw(amountIn);\n }\n payable(to).transfer(address(this).balance);\n }\n }\n\n /// @notice Bridge/unbridge tokens to/from Bento\n /// @param stream [direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function bentoBridge(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 direction = stream.readUint8();\n address to = stream.readAddress();\n\n if (direction > 0) { // outside to Bento\n // deposit to arbitrary recipient is possible only from address(bentoBox)\n if (amountIn != 0) {\n if (from == address(this)) IERC20(tokenIn).safeTransfer(address(bentoBox), amountIn);\n else IERC20(tokenIn).safeTransferFrom(from, address(bentoBox), amountIn);\n } else {\n // tokens already are at address(bentoBox)\n amountIn = IERC20(tokenIn).balanceOf(address(bentoBox)) +\n bentoBox.strategyData(tokenIn).balance -\n bentoBox.totals(tokenIn).elastic;\n }\n bentoBox.deposit(tokenIn, address(bentoBox), to, amountIn, 0);\n } else { // Bento to outside\n if (amountIn > 0) {\n bentoBox.transfer(tokenIn, from, address(this), amountIn);\n } else amountIn = bentoBox.balanceOf(tokenIn, address(this));\n bentoBox.withdraw(tokenIn, address(this), to, 0, amountIn);\n }\n }\n\n /// @notice UniswapV2 pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapUniV2(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n uint8 direction = stream.readUint8();\n address to = stream.readAddress();\n\n (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves();\n require(r0 > 0 && r1 > 0, 'Wrong pool reserves');\n (uint256 reserveIn, uint256 reserveOut) = direction == 1 ? (r0, r1) : (r1, r0);\n\n if (amountIn != 0) {\n if (from == address(this)) IERC20(tokenIn).safeTransfer(pool, amountIn);\n else IERC20(tokenIn).safeTransferFrom(from, pool, amountIn);\n } else amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; // tokens already were transferred\n\n uint256 amountInWithFee = amountIn * 997;\n uint256 amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);\n (uint256 amount0Out, uint256 amount1Out) = direction == 1 ? (uint256(0), amountOut) : (amountOut, uint256(0));\n IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0));\n }\n\n /// @notice Trident pool swap\n /// @param stream [pool, swapData]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapTrident(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bytes memory swapData = stream.readBytes();\n\n if (amountIn != 0) {\n bentoBox.transfer(tokenIn, from, pool, amountIn);\n }\n \n IPool(pool).swap(swapData);\n }\n\n /// @notice UniswapV3 pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapUniV3(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bool zeroForOne = stream.readUint8() > 0;\n address recipient = stream.readAddress();\n\n lastCalledPool = pool;\n IUniswapV3Pool(pool).swap(\n recipient,\n zeroForOne,\n int256(amountIn),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n abi.encode(tokenIn, from)\n );\n require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapUniV3: unexpected'); // Just to be sure\n }\n\n /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call\n function uniswapV3SwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n require(msg.sender == lastCalledPool, 'RouteProcessor.uniswapV3SwapCallback: call from unknown source');\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n (address tokenIn, address from) = abi.decode(data, (address, address));\n int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;\n require(amount > 0, 'RouteProcessor.uniswapV3SwapCallback: not positive amount');\n\n if (from == address(this)) IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));\n else IERC20(tokenIn).safeTransferFrom(from, msg.sender, uint256(amount));\n }\n\n /// @notice TridentCL pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapTridentCL(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bool zeroForOne = stream.readUint8() > 0;\n address recipient = stream.readAddress();\n\n lastCalledPool = pool;\n ITridentCLPool(pool).swap(\n recipient,\n zeroForOne,\n int256(amountIn),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n false,\n abi.encode(tokenIn, from)\n );\n require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapTridentCL: unexpected'); // Just to be sure\n }\n\n /// @notice Called to `msg.sender` after executing a swap via ITridentCLPool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a TridentCLPool deployed by the canonical TridentCLFactory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the ITridentCLPoolActions#swap call\n function tridentCLSwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n require(msg.sender == lastCalledPool, 'RouteProcessor.TridentCLSwapCallback: call from unknown source');\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n (address tokenIn, address from) = abi.decode(data, (address, address));\n int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;\n require(amount > 0, 'RouteProcessor.TridentCLSwapCallback: not positive amount');\n\n if (from == address(this)) IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));\n else IERC20(tokenIn).safeTransferFrom(from, msg.sender, uint256(amount));\n }\n}\n"},"interfaces/ITridentCLPool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface ITridentCLPool {\n function token0() external returns (address);\n function token1() external returns (address);\n\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bool unwrapBento,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n}\n"},"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n"},"interfaces/IUniswapV2Pair.sol":{"content":"// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2Pair {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external pure returns (string memory);\n function symbol() external pure returns (string memory);\n function decimals() external pure returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n function PERMIT_TYPEHASH() external pure returns (bytes32);\n function nonces(address owner) external view returns (uint);\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;\n\n event Mint(address indexed sender, uint amount0, uint amount1);\n event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\n event Swap(\n address indexed sender,\n uint amount0In,\n uint amount1In,\n uint amount0Out,\n uint amount1Out,\n address indexed to\n );\n event Sync(uint112 reserve0, uint112 reserve1);\n\n function MINIMUM_LIQUIDITY() external pure returns (uint);\n function factory() external view returns (address);\n function token0() external view returns (address);\n function token1() external view returns (address);\n function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n function price0CumulativeLast() external view returns (uint);\n function price1CumulativeLast() external view returns (uint);\n function kLast() external view returns (uint);\n\n function mint(address to) external returns (uint liquidity);\n function burn(address to) external returns (uint amount0, uint amount1);\n function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;\n function skim(address to) external;\n function sync() external;\n\n function initialize(address, address) external;\n}"},"interfaces/IUniswapV3Pool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface IUniswapV3Pool {\n function token0() external returns (address);\n function token1() external returns (address);\n\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n}\n"},"interfaces/IWETH.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface IWETH {\n function deposit() external payable;\n\n function transfer(address to, uint256 value) external returns (bool);\n\n function withdraw(uint256) external;\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":10000000},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"metadata":{"useLiteralContent":true},"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_bentoBox\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"bentoBox\",\"outputs\":[{\"internalType\":\"contract IBentoBoxMinimal\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"route\",\"type\":\"bytes\"}],\"name\":\"processRoute\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"transferValueTo\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountValueTransfer\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"route\",\"type\":\"bytes\"}],\"name\":\"transferValueAndprocessRoute\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"tridentCLSwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"uniswapV3SwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"RouteProcessor2","CompilerVersion":"v0.8.10+commit.fc410830","OptimizationUsed":1,"Runs":10000000,"ConstructorArguments":"0x000000000000000000000000f5bce5077908a1b7370b9ae04adc565ebd643966","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json new file mode 100644 index 0000000000000..e3433ef20019a --- /dev/null +++ b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x35fb958109b70799a8f9bc2a8b1ee4cc62034193","contractCreator":"0x3e32324277e96b69750bc6f7c4ba27e122413e07","txHash":"0x41e3517f8262b55e1eb1707ba0760b603a70e89ea4a86eff56072fcc80c3d0a1"} \ No newline at end of file diff --git a/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json new file mode 100644 index 0000000000000..bd48a2efbea4c --- /dev/null +++ b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":"/**\r\n *Submitted for verification at Etherscan.io on 2022-02-19\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-18\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-14\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-10\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-09\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-08\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-05\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-01-22\r\n*/\r\n\r\n// SPDX-License-Identifier: UNLICENSED\r\n/*\r\nmade by cty0312\r\n2022.01.22\r\n**/\r\n\r\npragma solidity >=0.7.0 <0.9.0;\r\n\r\nlibrary StringsUpgradeable {\r\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\r\n */\r\n function toString(uint256 value) internal pure returns (string memory) {\r\n // Inspired by OraclizeAPI's implementation - MIT licence\r\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\r\n\r\n if (value == 0) {\r\n return \"0\";\r\n }\r\n uint256 temp = value;\r\n uint256 digits;\r\n while (temp != 0) {\r\n digits++;\r\n temp /= 10;\r\n }\r\n bytes memory buffer = new bytes(digits);\r\n while (value != 0) {\r\n digits -= 1;\r\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\r\n value /= 10;\r\n }\r\n return string(buffer);\r\n }\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\r\n */\r\n function toHexString(uint256 value) internal pure returns (string memory) {\r\n if (value == 0) {\r\n return \"0x00\";\r\n }\r\n uint256 temp = value;\r\n uint256 length = 0;\r\n while (temp != 0) {\r\n length++;\r\n temp >>= 8;\r\n }\r\n return toHexString(value, length);\r\n }\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\r\n */\r\n function toHexString(uint256 value, uint256 length)\r\n internal\r\n pure\r\n returns (string memory)\r\n {\r\n bytes memory buffer = new bytes(2 * length + 2);\r\n buffer[0] = \"0\";\r\n buffer[1] = \"x\";\r\n for (uint256 i = 2 * length + 1; i > 1; --i) {\r\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\r\n value >>= 4;\r\n }\r\n require(value == 0, \"Strings: hex length insufficient\");\r\n return string(buffer);\r\n }\r\n}\r\n\r\nlibrary AddressUpgradeable {\r\n /**\r\n * @dev Returns true if `account` is a contract.\r\n *\r\n * [IMPORTANT]\r\n * ====\r\n * It is unsafe to assume that an address for which this function returns\r\n * false is an externally-owned account (EOA) and not a contract.\r\n *\r\n * Among others, `isContract` will return false for the following\r\n * types of addresses:\r\n *\r\n * - an externally-owned account\r\n * - a contract in construction\r\n * - an address where a contract will be created\r\n * - an address where a contract lived, but was destroyed\r\n * ====\r\n */\r\n function isContract(address account) internal view returns (bool) {\r\n // This method relies on extcodesize, which returns 0 for contracts in\r\n // construction, since the code is only stored at the end of the\r\n // constructor execution.\r\n\r\n uint256 size;\r\n assembly {\r\n size := extcodesize(account)\r\n }\r\n return size > 0;\r\n }\r\n\r\n /**\r\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\r\n * `recipient`, forwarding all available gas and reverting on errors.\r\n *\r\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\r\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\r\n * imposed by `transfer`, making them unable to receive funds via\r\n * `transfer`. {sendValue} removes this limitation.\r\n *\r\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\r\n *\r\n * IMPORTANT: because control is transferred to `recipient`, care must be\r\n * taken to not create reentrancy vulnerabilities. Consider using\r\n * {ReentrancyGuard} or the\r\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\r\n */\r\n function sendValue(address payable recipient, uint256 amount) internal {\r\n require(\r\n address(this).balance >= amount,\r\n \"Address: insufficient balance\"\r\n );\r\n\r\n (bool success, ) = recipient.call{value: amount}(\"\");\r\n require(\r\n success,\r\n \"Address: unable to send value, recipient may have reverted\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Performs a Solidity function call using a low level `call`. A\r\n * plain `call` is an unsafe replacement for a function call: use this\r\n * function instead.\r\n *\r\n * If `target` reverts with a revert reason, it is bubbled up by this\r\n * function (like regular Solidity function calls).\r\n *\r\n * Returns the raw returned data. To convert to the expected return value,\r\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\r\n *\r\n * Requirements:\r\n *\r\n * - `target` must be a contract.\r\n * - calling `target` with `data` must not revert.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCall(address target, bytes memory data)\r\n internal\r\n returns (bytes memory)\r\n {\r\n return functionCall(target, data, \"Address: low-level call failed\");\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\r\n * `errorMessage` as a fallback revert reason when `target` reverts.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCall(\r\n address target,\r\n bytes memory data,\r\n string memory errorMessage\r\n ) internal returns (bytes memory) {\r\n return functionCallWithValue(target, data, 0, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\r\n * but also transferring `value` wei to `target`.\r\n *\r\n * Requirements:\r\n *\r\n * - the calling contract must have an ETH balance of at least `value`.\r\n * - the called Solidity function must be `payable`.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCallWithValue(\r\n address target,\r\n bytes memory data,\r\n uint256 value\r\n ) internal returns (bytes memory) {\r\n return\r\n functionCallWithValue(\r\n target,\r\n data,\r\n value,\r\n \"Address: low-level call with value failed\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\r\n * with `errorMessage` as a fallback revert reason when `target` reverts.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCallWithValue(\r\n address target,\r\n bytes memory data,\r\n uint256 value,\r\n string memory errorMessage\r\n ) internal returns (bytes memory) {\r\n require(\r\n address(this).balance >= value,\r\n \"Address: insufficient balance for call\"\r\n );\r\n require(isContract(target), \"Address: call to non-contract\");\r\n\r\n (bool success, bytes memory returndata) = target.call{value: value}(\r\n data\r\n );\r\n return verifyCallResult(success, returndata, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\r\n * but performing a static call.\r\n *\r\n * _Available since v3.3._\r\n */\r\n function functionStaticCall(address target, bytes memory data)\r\n internal\r\n view\r\n returns (bytes memory)\r\n {\r\n return\r\n functionStaticCall(\r\n target,\r\n data,\r\n \"Address: low-level static call failed\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\r\n * but performing a static call.\r\n *\r\n * _Available since v3.3._\r\n */\r\n function functionStaticCall(\r\n address target,\r\n bytes memory data,\r\n string memory errorMessage\r\n ) internal view returns (bytes memory) {\r\n require(isContract(target), \"Address: static call to non-contract\");\r\n\r\n (bool success, bytes memory returndata) = target.staticcall(data);\r\n return verifyCallResult(success, returndata, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\r\n * revert reason using the provided one.\r\n *\r\n * _Available since v4.3._\r\n */\r\n function verifyCallResult(\r\n bool success,\r\n bytes memory returndata,\r\n string memory errorMessage\r\n ) internal pure returns (bytes memory) {\r\n if (success) {\r\n return returndata;\r\n } else {\r\n // Look for revert reason and bubble it up if present\r\n if (returndata.length > 0) {\r\n // The easiest way to bubble the revert reason is using memory via assembly\r\n\r\n assembly {\r\n let returndata_size := mload(returndata)\r\n revert(add(32, returndata), returndata_size)\r\n }\r\n } else {\r\n revert(errorMessage);\r\n }\r\n }\r\n }\r\n}\r\n\r\nlibrary SafeMathUpgradeable {\r\n /**\r\n * @dev Returns the addition of two unsigned integers, reverting on\r\n * overflow.\r\n *\r\n * Counterpart to Solidity's `+` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Addition cannot overflow.\r\n */\r\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\r\n uint256 c = a + b;\r\n require(c >= a, \"SafeMath: addition overflow\");\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the subtraction of two unsigned integers, reverting on\r\n * overflow (when the result is negative).\r\n *\r\n * Counterpart to Solidity's `-` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Subtraction cannot overflow.\r\n */\r\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return sub(a, b, \"SafeMath: subtraction overflow\");\r\n }\r\n\r\n /**\r\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\r\n * overflow (when the result is negative).\r\n *\r\n * Counterpart to Solidity's `-` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Subtraction cannot overflow.\r\n */\r\n function sub(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b <= a, errorMessage);\r\n uint256 c = a - b;\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the multiplication of two unsigned integers, reverting on\r\n * overflow.\r\n *\r\n * Counterpart to Solidity's `*` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Multiplication cannot overflow.\r\n */\r\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\r\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\r\n // benefit is lost if 'b' is also tested.\r\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\r\n if (a == 0) {\r\n return 0;\r\n }\r\n\r\n uint256 c = a * b;\r\n require(c / a == b, \"SafeMath: multiplication overflow\");\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the integer division of two unsigned integers. Reverts on\r\n * division by zero. The result is rounded towards zero.\r\n *\r\n * Counterpart to Solidity's `/` operator. Note: this function uses a\r\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\r\n * uses an invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return div(a, b, \"SafeMath: division by zero\");\r\n }\r\n\r\n /**\r\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\r\n * division by zero. The result is rounded towards zero.\r\n *\r\n * Counterpart to Solidity's `/` operator. Note: this function uses a\r\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\r\n * uses an invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function div(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b > 0, errorMessage);\r\n uint256 c = a / b;\r\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\r\n * Reverts when dividing by zero.\r\n *\r\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\r\n * opcode (which leaves remaining gas untouched) while Solidity uses an\r\n * invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return mod(a, b, \"SafeMath: modulo by zero\");\r\n }\r\n\r\n /**\r\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\r\n * Reverts with custom message when dividing by zero.\r\n *\r\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\r\n * opcode (which leaves remaining gas untouched) while Solidity uses an\r\n * invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function mod(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b != 0, errorMessage);\r\n return a % b;\r\n }\r\n}\r\n\r\nabstract contract Initializable {\r\n /**\r\n * @dev Indicates that the contract has been initialized.\r\n */\r\n bool private _initialized;\r\n\r\n /**\r\n * @dev Indicates that the contract is in the process of being initialized.\r\n */\r\n bool private _initializing;\r\n\r\n /**\r\n * @dev Modifier to protect an initializer function from being invoked twice.\r\n */\r\n modifier initializer() {\r\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\r\n // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the\r\n // contract may have been reentered.\r\n require(\r\n _initializing ? _isConstructor() : !_initialized,\r\n \"Initializable: contract is already initialized\"\r\n );\r\n\r\n bool isTopLevelCall = !_initializing;\r\n if (isTopLevelCall) {\r\n _initializing = true;\r\n _initialized = true;\r\n }\r\n\r\n _;\r\n\r\n if (isTopLevelCall) {\r\n _initializing = false;\r\n }\r\n }\r\n\r\n /**\r\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\r\n * {initializer} modifier, directly or indirectly.\r\n */\r\n modifier onlyInitializing() {\r\n require(_initializing, \"Initializable: contract is not initializing\");\r\n _;\r\n }\r\n\r\n function _isConstructor() private view returns (bool) {\r\n return !AddressUpgradeable.isContract(address(this));\r\n }\r\n}\r\n\r\nabstract contract ContextUpgradeable is Initializable {\r\n function __Context_init() internal onlyInitializing {\r\n __Context_init_unchained();\r\n }\r\n\r\n function __Context_init_unchained() internal onlyInitializing {}\r\n\r\n function _msgSender() internal view virtual returns (address) {\r\n return msg.sender;\r\n }\r\n\r\n function _msgData() internal view virtual returns (bytes calldata) {\r\n return msg.data;\r\n }\r\n\r\n uint256[50] private __gap;\r\n}\r\n\r\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\r\n address private _owner;\r\n\r\n event OwnershipTransferred(\r\n address indexed previousOwner,\r\n address indexed newOwner\r\n );\r\n\r\n /**\r\n * @dev Initializes the contract setting the deployer as the initial owner.\r\n */\r\n function __Ownable_init() internal onlyInitializing {\r\n __Context_init_unchained();\r\n __Ownable_init_unchained();\r\n }\r\n\r\n function __Ownable_init_unchained() internal onlyInitializing {\r\n _transferOwnership(_msgSender());\r\n }\r\n\r\n /**\r\n * @dev Returns the address of the current owner.\r\n */\r\n function owner() public view virtual returns (address) {\r\n return _owner;\r\n }\r\n\r\n /**\r\n * @dev Throws if called by any account other than the owner.\r\n */\r\n modifier onlyOwner() {\r\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\r\n _;\r\n }\r\n\r\n /**\r\n * @dev Leaves the contract without owner. It will not be possible to call\r\n * `onlyOwner` functions anymore. Can only be called by the current owner.\r\n *\r\n * NOTE: Renouncing ownership will leave the contract without an owner,\r\n * thereby removing any functionality that is only available to the owner.\r\n */\r\n function renounceOwnership() public virtual onlyOwner {\r\n _transferOwnership(address(0));\r\n }\r\n\r\n /**\r\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\r\n * Can only be called by the current owner.\r\n */\r\n function transferOwnership(address newOwner) public virtual onlyOwner {\r\n require(\r\n newOwner != address(0),\r\n \"Ownable: new owner is the zero address\"\r\n );\r\n _transferOwnership(newOwner);\r\n }\r\n\r\n /**\r\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\r\n * Internal function without access restriction.\r\n */\r\n function _transferOwnership(address newOwner) internal virtual {\r\n address oldOwner = _owner;\r\n _owner = newOwner;\r\n emit OwnershipTransferred(oldOwner, newOwner);\r\n }\r\n\r\n uint256[49] private __gap;\r\n}\r\n\r\ninterface IERC165Upgradeable {\r\n /**\r\n * @dev Returns true if this contract implements the interface defined by\r\n * `interfaceId`. See the corresponding\r\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\r\n * to learn more about how these ids are created.\r\n *\r\n * This function call must use less than 30 000 gas.\r\n */\r\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\r\n}\r\n\r\ninterface IERC721Upgradeable is IERC165Upgradeable {\r\n /**\r\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\r\n */\r\n event Transfer(\r\n address indexed from,\r\n address indexed to,\r\n uint256 indexed tokenId\r\n );\r\n\r\n /**\r\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\r\n */\r\n event Approval(\r\n address indexed owner,\r\n address indexed approved,\r\n uint256 indexed tokenId\r\n );\r\n\r\n /**\r\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\r\n */\r\n event ApprovalForAll(\r\n address indexed owner,\r\n address indexed operator,\r\n bool approved\r\n );\r\n\r\n /**\r\n * @dev Returns the number of tokens in ``owner``'s account.\r\n */\r\n function balanceOf(address owner) external view returns (uint256 balance);\r\n\r\n /**\r\n * @dev Returns the owner of the `tokenId` token.\r\n *\r\n * Requirements:\r\n *\r\n * - `tokenId` must exist.\r\n */\r\n function ownerOf(uint256 tokenId) external view returns (address owner);\r\n\r\n /**\r\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\r\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must exist and be owned by `from`.\r\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\r\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function safeTransferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId\r\n ) external;\r\n\r\n /**\r\n * @dev Transfers `tokenId` token from `from` to `to`.\r\n *\r\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must be owned by `from`.\r\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId\r\n ) external;\r\n\r\n /**\r\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\r\n * The approval is cleared when the token is transferred.\r\n *\r\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\r\n *\r\n * Requirements:\r\n *\r\n * - The caller must own the token or be an approved operator.\r\n * - `tokenId` must exist.\r\n *\r\n * Emits an {Approval} event.\r\n */\r\n function approve(address to, uint256 tokenId) external;\r\n\r\n /**\r\n * @dev Returns the account approved for `tokenId` token.\r\n *\r\n * Requirements:\r\n *\r\n * - `tokenId` must exist.\r\n */\r\n function getApproved(uint256 tokenId)\r\n external\r\n view\r\n returns (address operator);\r\n\r\n /**\r\n * @dev Approve or remove `operator` as an operator for the caller.\r\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\r\n *\r\n * Requirements:\r\n *\r\n * - The `operator` cannot be the caller.\r\n *\r\n * Emits an {ApprovalForAll} event.\r\n */\r\n function setApprovalForAll(address operator, bool _approved) external;\r\n\r\n /**\r\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\r\n *\r\n * See {setApprovalForAll}\r\n */\r\n function isApprovedForAll(address owner, address operator)\r\n external\r\n view\r\n returns (bool);\r\n\r\n /**\r\n * @dev Safely transfers `tokenId` token from `from` to `to`.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must exist and be owned by `from`.\r\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\r\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function safeTransferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId,\r\n bytes calldata data\r\n ) external;\r\n}\r\n\r\ninterface IERC20Upgradeable {\r\n /**\r\n * @dev Returns the amount of tokens in existence.\r\n */\r\n function totalSupply() external view returns (uint256);\r\n\r\n /**\r\n * @dev Returns the amount of tokens owned by `account`.\r\n */\r\n function balanceOf(address account) external view returns (uint256);\r\n\r\n /**\r\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transfer(address recipient, uint256 amount)\r\n external\r\n returns (bool);\r\n\r\n /**\r\n * @dev Returns the remaining number of tokens that `spender` will be\r\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\r\n * zero by default.\r\n *\r\n * This value changes when {approve} or {transferFrom} are called.\r\n */\r\n function allowance(address owner, address spender)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n /**\r\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\r\n * that someone may use both the old and the new allowance by unfortunate\r\n * transaction ordering. One possible solution to mitigate this race\r\n * condition is to first reduce the spender's allowance to 0 and set the\r\n * desired value afterwards:\r\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\r\n *\r\n * Emits an {Approval} event.\r\n */\r\n function approve(address spender, uint256 amount) external returns (bool);\r\n\r\n /**\r\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\r\n * allowance mechanism. `amount` is then deducted from the caller's\r\n * allowance.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transferFrom(\r\n address sender,\r\n address recipient,\r\n uint256 amount\r\n ) external returns (bool);\r\n\r\n\r\n function mintToken(address _address, uint256 _amount) external;\r\n /**\r\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\r\n * another (`to`).\r\n *\r\n * Note that `value` may be zero.\r\n */\r\n event Transfer(address indexed from, address indexed to, uint256 value);\r\n\r\n /**\r\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\r\n * a call to {approve}. `value` is the new allowance.\r\n */\r\n event Approval(\r\n address indexed owner,\r\n address indexed spender,\r\n uint256 value\r\n );\r\n}\r\n\r\ninterface IUniswapV2Factory {\r\n event PairCreated(\r\n address indexed token0,\r\n address indexed token1,\r\n address pair,\r\n uint256\r\n );\r\n\r\n function feeTo() external view returns (address);\r\n\r\n function feeToSetter() external view returns (address);\r\n\r\n function getPair(address tokenA, address tokenB)\r\n external\r\n view\r\n returns (address pair);\r\n\r\n function allPairs(uint256) external view returns (address pair);\r\n\r\n function allPairsLength() external view returns (uint256);\r\n\r\n function createPair(address tokenA, address tokenB)\r\n external\r\n returns (address pair);\r\n\r\n function setFeeTo(address) external;\r\n\r\n function setFeeToSetter(address) external;\r\n}\r\n\r\ninterface IUniswapV2Pair {\r\n event Approval(\r\n address indexed owner,\r\n address indexed spender,\r\n uint256 value\r\n );\r\n event Transfer(address indexed from, address indexed to, uint256 value);\r\n\r\n function name() external pure returns (string memory);\r\n\r\n function symbol() external pure returns (string memory);\r\n\r\n function decimals() external pure returns (uint8);\r\n\r\n function totalSupply() external view returns (uint256);\r\n\r\n function balanceOf(address owner) external view returns (uint256);\r\n\r\n function allowance(address owner, address spender)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n function approve(address spender, uint256 value) external returns (bool);\r\n\r\n function transfer(address to, uint256 value) external returns (bool);\r\n\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 value\r\n ) external returns (bool);\r\n\r\n function DOMAIN_SEPARATOR() external view returns (bytes32);\r\n\r\n function PERMIT_TYPEHASH() external pure returns (bytes32);\r\n\r\n function nonces(address owner) external view returns (uint256);\r\n\r\n function permit(\r\n address owner,\r\n address spender,\r\n uint256 value,\r\n uint256 deadline,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external;\r\n\r\n event Mint(address indexed sender, uint256 amount0, uint256 amount1);\r\n event Burn(\r\n address indexed sender,\r\n uint256 amount0,\r\n uint256 amount1,\r\n address indexed to\r\n );\r\n event Swap(\r\n address indexed sender,\r\n uint256 amount0In,\r\n uint256 amount1In,\r\n uint256 amount0Out,\r\n uint256 amount1Out,\r\n address indexed to\r\n );\r\n event Sync(uint112 reserve0, uint112 reserve1);\r\n\r\n function MINIMUM_LIQUIDITY() external pure returns (uint256);\r\n\r\n function factory() external view returns (address);\r\n\r\n function token0() external view returns (address);\r\n\r\n function token1() external view returns (address);\r\n\r\n function getReserves()\r\n external\r\n view\r\n returns (\r\n uint112 reserve0,\r\n uint112 reserve1,\r\n uint32 blockTimestampLast\r\n );\r\n\r\n function price0CumulativeLast() external view returns (uint256);\r\n\r\n function price1CumulativeLast() external view returns (uint256);\r\n\r\n function kLast() external view returns (uint256);\r\n\r\n function mint(address to) external returns (uint256 liquidity);\r\n\r\n function burn(address to)\r\n external\r\n returns (uint256 amount0, uint256 amount1);\r\n\r\n function swap(\r\n uint256 amount0Out,\r\n uint256 amount1Out,\r\n address to,\r\n bytes calldata data\r\n ) external;\r\n\r\n function skim(address to) external;\r\n\r\n function sync() external;\r\n\r\n function initialize(address, address) external;\r\n}\r\n\r\ninterface IUniswapV2Router01 {\r\n function factory() external pure returns (address);\r\n\r\n function WETH() external pure returns (address);\r\n\r\n function addLiquidity(\r\n address tokenA,\r\n address tokenB,\r\n uint256 amountADesired,\r\n uint256 amountBDesired,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline\r\n )\r\n external\r\n returns (\r\n uint256 amountA,\r\n uint256 amountB,\r\n uint256 liquidity\r\n );\r\n\r\n function addLiquidityETH(\r\n address token,\r\n uint256 amountTokenDesired,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n )\r\n external\r\n payable\r\n returns (\r\n uint256 amountToken,\r\n uint256 amountETH,\r\n uint256 liquidity\r\n );\r\n\r\n function removeLiquidity(\r\n address tokenA,\r\n address tokenB,\r\n uint256 liquidity,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountA, uint256 amountB);\r\n\r\n function removeLiquidityETH(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountToken, uint256 amountETH);\r\n\r\n function removeLiquidityWithPermit(\r\n address tokenA,\r\n address tokenB,\r\n uint256 liquidity,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountA, uint256 amountB);\r\n\r\n function removeLiquidityETHWithPermit(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountToken, uint256 amountETH);\r\n\r\n function swapExactTokensForTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapTokensForExactTokens(\r\n uint256 amountOut,\r\n uint256 amountInMax,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapExactETHForTokens(\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable returns (uint256[] memory amounts);\r\n\r\n function swapTokensForExactETH(\r\n uint256 amountOut,\r\n uint256 amountInMax,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapExactTokensForETH(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapETHForExactTokens(\r\n uint256 amountOut,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable returns (uint256[] memory amounts);\r\n\r\n function quote(\r\n uint256 amountA,\r\n uint256 reserveA,\r\n uint256 reserveB\r\n ) external pure returns (uint256 amountB);\r\n\r\n function getAmountOut(\r\n uint256 amountIn,\r\n uint256 reserveIn,\r\n uint256 reserveOut\r\n ) external pure returns (uint256 amountOut);\r\n\r\n function getAmountIn(\r\n uint256 amountOut,\r\n uint256 reserveIn,\r\n uint256 reserveOut\r\n ) external pure returns (uint256 amountIn);\r\n\r\n function getAmountsOut(uint256 amountIn, address[] calldata path)\r\n external\r\n view\r\n returns (uint256[] memory amounts);\r\n\r\n function getAmountsIn(uint256 amountOut, address[] calldata path)\r\n external\r\n view\r\n returns (uint256[] memory amounts);\r\n}\r\n\r\ninterface IUniswapV2Router is IUniswapV2Router01 {\r\n function removeLiquidityETHSupportingFeeOnTransferTokens(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountETH);\r\n\r\n function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountETH);\r\n\r\n function swapExactTokensForTokensSupportingFeeOnTransferTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external;\r\n\r\n function swapExactETHForTokensSupportingFeeOnTransferTokens(\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable;\r\n\r\n function swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external;\r\n}\r\n\r\ninterface IERC20MetadataUpgradeable is IERC20Upgradeable {\r\n /**\r\n * @dev Returns the name of the token.\r\n */\r\n function name() external view returns (string memory);\r\n\r\n /**\r\n * @dev Returns the symbol of the token.\r\n */\r\n function symbol() external view returns (string memory);\r\n\r\n /**\r\n * @dev Returns the decimals places of the token.\r\n */\r\n function decimals() external view returns (uint8);\r\n}\r\n\r\nabstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {\r\n using SafeMathUpgradeable for uint256;\r\n\r\n mapping(address => uint256) private _balances;\r\n\r\n mapping(address => mapping(address => uint256)) private _allowances;\r\n\r\n uint256 private _totalSupply;\r\n\r\n string private _name;\r\n string private _symbol;\r\n\r\n /**\r\n * @dev Sets the values for {name} and {symbol}.\r\n *\r\n * The default value of {decimals} is 18. To select a different value for\r\n * {decimals} you should overload it.\r\n *\r\n * All two of these values are immutable: they can only be set once during\r\n * construction.\r\n */\r\n function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {\r\n __ERC20_init_unchained(name_, symbol_);\r\n }\r\n\r\n function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\r\n _name = name_;\r\n _symbol = symbol_;\r\n }\r\n\r\n /**\r\n * @dev Returns the name of the token.\r\n */\r\n function name() public view virtual override returns (string memory) {\r\n return _name;\r\n }\r\n\r\n /**\r\n * @dev Returns the symbol of the token, usually a shorter version of the\r\n * name.\r\n */\r\n function symbol() public view virtual override returns (string memory) {\r\n return _symbol;\r\n }\r\n\r\n /**\r\n * @dev Returns the number of decimals used to get its user representation.\r\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\r\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\r\n *\r\n * Tokens usually opt for a value of 18, imitating the relationship between\r\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\r\n * overridden;\r\n *\r\n * NOTE: This information is only used for _display_ purposes: it in\r\n * no way affects any of the arithmetic of the contract, including\r\n * {IERC20-balanceOf} and {IERC20-transfer}.\r\n */\r\n function decimals() public view virtual override returns (uint8) {\r\n return 18;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-totalSupply}.\r\n */\r\n function totalSupply() public view virtual override returns (uint256) {\r\n return _totalSupply;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-balanceOf}.\r\n */\r\n function balanceOf(address account) public view virtual override returns (uint256) {\r\n return _balances[account];\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-transfer}.\r\n *\r\n * Requirements:\r\n *\r\n * - `to` cannot be the zero address.\r\n * - the caller must have a balance of at least `amount`.\r\n */\r\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\r\n address owner = _msgSender();\r\n _transfer(owner, to, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-allowance}.\r\n */\r\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\r\n return _allowances[owner][spender];\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-approve}.\r\n *\r\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\r\n * `transferFrom`. This is semantically equivalent to an infinite approval.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n */\r\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\r\n address owner = _msgSender();\r\n _approve(owner, spender, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-transferFrom}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance. This is not\r\n * required by the EIP. See the note at the beginning of {ERC20}.\r\n *\r\n * NOTE: Does not update the allowance if the current allowance\r\n * is the maximum `uint256`.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` and `to` cannot be the zero address.\r\n * - `from` must have a balance of at least `amount`.\r\n * - the caller must have allowance for ``from``'s tokens of at least\r\n * `amount`.\r\n */\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) public virtual override returns (bool) {\r\n address spender = _msgSender();\r\n _spendAllowance(from, spender, amount);\r\n _transfer(from, to, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Atomically increases the allowance granted to `spender` by the caller.\r\n *\r\n * This is an alternative to {approve} that can be used as a mitigation for\r\n * problems described in {IERC20-approve}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n */\r\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\r\n address owner = _msgSender();\r\n _approve(owner, spender, _allowances[owner][spender] + addedValue);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\r\n *\r\n * This is an alternative to {approve} that can be used as a mitigation for\r\n * problems described in {IERC20-approve}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n * - `spender` must have allowance for the caller of at least\r\n * `subtractedValue`.\r\n */\r\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\r\n address owner = _msgSender();\r\n uint256 currentAllowance = _allowances[owner][spender];\r\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\r\n unchecked {\r\n _approve(owner, spender, currentAllowance - subtractedValue);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Moves `amount` of tokens from `sender` to `recipient`.\r\n *\r\n * This internal function is equivalent to {transfer}, and can be used to\r\n * e.g. implement automatic token fees, slashing mechanisms, etc.\r\n *\r\n * Emits a {Transfer} event.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `from` must have a balance of at least `amount`.\r\n */\r\n function _transfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {\r\n require(from != address(0), \"ERC20: transfer from the zero address\");\r\n require(to != address(0), \"ERC20: transfer to the zero address\");\r\n\r\n _beforeTokenTransfer(from, to, amount);\r\n\r\n uint256 fromBalance = _balances[from];\r\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\r\n unchecked {\r\n _balances[from] = fromBalance - amount;\r\n }\r\n _balances[to] += amount;\r\n\r\n emit Transfer(from, to, amount);\r\n\r\n _afterTokenTransfer(from, to, amount);\r\n }\r\n\r\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\r\n * the total supply.\r\n *\r\n * Emits a {Transfer} event with `from` set to the zero address.\r\n *\r\n * Requirements:\r\n *\r\n * - `account` cannot be the zero address.\r\n */\r\n function _mint(address account, uint256 amount) internal virtual {\r\n require(account != address(0), \"ERC20: mint to the zero address\");\r\n\r\n _beforeTokenTransfer(address(0), account, amount);\r\n\r\n _totalSupply += amount;\r\n _balances[account] += amount;\r\n emit Transfer(address(0), account, amount);\r\n\r\n _afterTokenTransfer(address(0), account, amount);\r\n }\r\n\r\n /**\r\n * @dev Destroys `amount` tokens from `account`, reducing the\r\n * total supply.\r\n *\r\n * Emits a {Transfer} event with `to` set to the zero address.\r\n *\r\n * Requirements:\r\n *\r\n * - `account` cannot be the zero address.\r\n * - `account` must have at least `amount` tokens.\r\n */\r\n function _burn(uint256 amount) public virtual {\r\n // require(_balances[msg.sender] >= amount,'insufficient balance!');\r\n\r\n // _beforeTokenTransfer(msg.sender, address(0x000000000000000000000000000000000000dEaD), amount);\r\n\r\n // _balances[msg.sender] = _balances[msg.sender].sub(amount, \"ERC20: burn amount exceeds balance\");\r\n // _totalSupply = _totalSupply.sub(amount);\r\n // emit Transfer(msg.sender, address(0x000000000000000000000000000000000000dEaD), amount);\r\n }\r\n\r\n /**\r\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\r\n *\r\n * This internal function is equivalent to `approve`, and can be used to\r\n * e.g. set automatic allowances for certain subsystems, etc.\r\n *\r\n * Emits an {Approval} event.\r\n *\r\n * Requirements:\r\n *\r\n * - `owner` cannot be the zero address.\r\n * - `spender` cannot be the zero address.\r\n */\r\n function _approve(\r\n address owner,\r\n address spender,\r\n uint256 amount\r\n ) internal virtual {\r\n require(owner != address(0), \"ERC20: approve from the zero address\");\r\n require(spender != address(0), \"ERC20: approve to the zero address\");\r\n\r\n _allowances[owner][spender] = amount;\r\n emit Approval(owner, spender, amount);\r\n }\r\n\r\n /**\r\n * @dev Spend `amount` form the allowance of `owner` toward `spender`.\r\n *\r\n * Does not update the allowance amount in case of infinite allowance.\r\n * Revert if not enough allowance is available.\r\n *\r\n * Might emit an {Approval} event.\r\n */\r\n function _spendAllowance(\r\n address owner,\r\n address spender,\r\n uint256 amount\r\n ) internal virtual {\r\n uint256 currentAllowance = allowance(owner, spender);\r\n if (currentAllowance != type(uint256).max) {\r\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\r\n unchecked {\r\n _approve(owner, spender, currentAllowance - amount);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * @dev Hook that is called before any transfer of tokens. This includes\r\n * minting and burning.\r\n *\r\n * Calling conditions:\r\n *\r\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\r\n * will be transferred to `to`.\r\n * - when `from` is zero, `amount` tokens will be minted for `to`.\r\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\r\n * - `from` and `to` are never both zero.\r\n *\r\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\r\n */\r\n function _beforeTokenTransfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {}\r\n\r\n /**\r\n * @dev Hook that is called after any transfer of tokens. This includes\r\n * minting and burning.\r\n *\r\n * Calling conditions:\r\n *\r\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\r\n * has been transferred to `to`.\r\n * - when `from` is zero, `amount` tokens have been minted for `to`.\r\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\r\n * - `from` and `to` are never both zero.\r\n *\r\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\r\n */\r\n function _afterTokenTransfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {}\r\n\r\n /**\r\n * This empty reserved space is put in place to allow future versions to add new\r\n * variables without shifting down storage in the inheritance chain.\r\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\r\n */\r\n uint256[45] private __gap;\r\n}\r\n\r\ncontract BearXNFTStaking is OwnableUpgradeable {\r\n using SafeMathUpgradeable for uint256;\r\n using AddressUpgradeable for address;\r\n\r\n //-------------constant value------------------//\r\n address private UNISWAP_V2_ROUTER;\r\n address private WETH;\r\n uint256 DURATION_FOR_REWARDS;\r\n uint256 DURATION_FOR_STOP_REWARDS;\r\n\r\n address public BearXNFTAddress;\r\n address public ROOTxTokenAddress;\r\n address public SROOTxTokenAddress;\r\n uint256 public totalStakes;\r\n\r\n uint256 public MaxSROOTXrate;\r\n uint256 public MinSROOTXrate;\r\n uint256 public MaxRate;\r\n uint256 public MinRate;\r\n uint256 public maxprovision;\r\n uint256 public RateValue;\r\n uint256 public SROOTRateValue;\r\n\r\n struct stakingInfo {\r\n uint256 nft_id;\r\n uint256 stakedDate;\r\n uint256 claimedDate_SROOT;\r\n uint256 claimedDate_WETH;\r\n uint256 claimedDate_ROOTX;\r\n }\r\n\r\n mapping(address => stakingInfo[]) internal stakes;\r\n address[] internal stakers;\r\n uint256 public RootX_Store;\r\n\r\n // 1.29 update\r\n mapping(address => bool) internal m_stakers;\r\n bool ismaptransfered;\r\n\r\n // 2.15 update\r\n bool public islocked;\r\n\r\n function initialize() public initializer{\r\n __Ownable_init();\r\n UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;\r\n WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;\r\n DURATION_FOR_REWARDS = 1 days;\r\n DURATION_FOR_STOP_REWARDS = 3650 days;\r\n BearXNFTAddress = 0xE22e1e620dffb03065CD77dB0162249c0c91bf01;\r\n ROOTxTokenAddress = 0xd718Ad25285d65eF4D79262a6CD3AEA6A8e01023;\r\n SROOTxTokenAddress = 0x99CFDf48d0ba4885A73786148A2f89d86c702170;\r\n totalStakes = 0; \r\n MaxSROOTXrate = 100*10**18;\r\n MinSROOTXrate = 50*10**18;\r\n MaxRate = 11050*10**18;\r\n MinRate = 500*10**18; \r\n maxprovision = 3000;\r\n RateValue = ((MaxRate - MinRate) / maxprovision);\r\n SROOTRateValue = (( MaxSROOTXrate - MinSROOTXrate ) / maxprovision);\r\n RootX_Store=0;\r\n ismaptransfered = false;\r\n }\r\n // 1.29 update\r\n function transferStakers() public {\r\n if(!ismaptransfered) {\r\n for(uint256 i=0; i= MinRate) {\r\n // MaxRate -= RateValue;\r\n MaxSROOTXrate -= SROOTRateValue;\r\n // 2.1 updated\r\n MaxRate = MaxRate.sub(10*(10**18));\r\n }\r\n require(\r\n IERC721Upgradeable(BearXNFTAddress).ownerOf(_id) == _addr,\r\n \"You are not a owner of the nft\"\r\n );\r\n require(\r\n IERC721Upgradeable(BearXNFTAddress).isApprovedForAll(msg.sender, address(this)) ==\r\n true,\r\n \"You should approve nft to the staking contract\"\r\n );\r\n IERC721Upgradeable(BearXNFTAddress).transferFrom(\r\n _addr,\r\n address(this),\r\n _id\r\n );\r\n // (bool _isStaker, ) = isStaker(_addr);\r\n (bool _isStaker, ) = is_m_Staker(_addr);\r\n stakingInfo memory sInfo = stakingInfo(\r\n _id,\r\n block.timestamp,\r\n block.timestamp,\r\n block.timestamp,\r\n block.timestamp\r\n );\r\n totalStakes = totalStakes.add(1);\r\n stakes[_addr].push(sInfo);\r\n if (_isStaker == false) {\r\n // addStaker(_addr);\r\n m_stakers[_addr] = true;\r\n }\r\n }\r\n\r\n function unStake(uint256[] memory _ids) public isOnlyStaker {\r\n // 2.15 update\r\n\r\n require(!islocked, \"Locked\");\r\n uint256[] memory ownerIds = stakeOf(msg.sender);\r\n require(_ids.length <= ownerIds.length, \"Id errors\");\r\n uint256 temp = 0;\r\n\r\n for(uint256 i=0; i<_ids.length;i++) {\r\n for(uint256 j=0;j 0) {\r\n IERC20Upgradeable(ROOTxTokenAddress).transfer(_addr, Rootamount);\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_ROOTX = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function _claimOfSROOTxToken(address _addr) internal {\r\n (, uint256 SROOTamount, ) = claimOf(_addr);\r\n if (SROOTamount > 0) {\r\n IERC20Upgradeable(SROOTxTokenAddress).transfer(_addr, SROOTamount);\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_SROOT = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function _claimOfWETH(address _addr) internal {\r\n (, uint256 ETHamount, ) = claimOf(_addr);\r\n\r\n if (ETHamount > 0) {\r\n IERC20Upgradeable(SROOTxTokenAddress).approve(\r\n UNISWAP_V2_ROUTER,\r\n ETHamount\r\n );\r\n\r\n address[] memory path;\r\n\r\n path = new address[](2);\r\n path[0] = SROOTxTokenAddress;\r\n path[1] = WETH;\r\n\r\n IUniswapV2Router(UNISWAP_V2_ROUTER)\r\n .swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n ETHamount,\r\n 0,\r\n path,\r\n _addr,\r\n block.timestamp\r\n );\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_WETH = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function claimOfROOTxToken() external isOnlyStaker {\r\n _claimOfROOTxToken(msg.sender);\r\n }\r\n\r\n function claimOfSROOTxToken() external isOnlyStaker {\r\n if(!isVested(msg.sender)){\r\n _claimOfSROOTxToken(msg.sender);\r\n }\r\n }\r\n\r\n function claimOfWETH() external isOnlyStaker {\r\n if(!isVested(msg.sender)) {\r\n _claimOfWETH(msg.sender);\r\n }\r\n\r\n }\r\n\r\n // 2.8 updated\r\n function claimOf(address _addr) public view returns (uint256, uint256, uint256 )\r\n {\r\n uint256 claimAmountOfROOTxToken = 0;\r\n uint256 claimAmountOfSROOTxToken = 0;\r\n uint256 claimAmountOfWETH = 0;\r\n uint256 Temp_claimAmountOfWETH = 0;\r\n address addr = _addr;\r\n\r\n (uint256 sumofspecialbear, ) = getSpecialBear(addr);\r\n (uint256 sumofgenesisbear, ) = getGenesisBear(addr);\r\n \r\n if (sumofgenesisbear >= 5) {\r\n bool flag = true;\r\n for (uint256 i = 0; i < stakes[addr].length; i++) {\r\n ///ROOTX\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n if ((isGenesisBear(stakes[addr][i].nft_id) || isSpecialBear(stakes[addr][i].nft_id)) && dd_root <1) {\r\n flag = false;\r\n }\r\n if (flag && stakes[addr].length != 0) {\r\n claimAmountOfROOTxToken = RootX_Store.div(10**18).add(10*dd_root*sumofgenesisbear).add(10*dd_root*sumofspecialbear);\r\n }\r\n else if(!flag) {\r\n claimAmountOfROOTxToken = RootX_Store.div(10**18);\r\n }\r\n /// SROOTX and WETH\r\n if (isSpecialBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_srootx = calDay(stakes[addr][i].claimedDate_SROOT);\r\n uint256 dd_weth = dd_srootx;\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add(200 * (10**18) * dd_srootx);\r\n claimAmountOfWETH = claimAmountOfWETH.add(200 * (10**18) * dd_weth);\r\n } else if (isGenesisBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_srootx = calDay(stakes[addr][i].claimedDate_SROOT);\r\n uint256 dd_weth = dd_srootx;\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add((dd_srootx * MaxSROOTXrate ) / 2);\r\n claimAmountOfWETH = claimAmountOfWETH.add((dd_weth * MaxSROOTXrate ) / 2);\r\n }\r\n }\r\n \r\n if (claimAmountOfWETH != 0) {\r\n Temp_claimAmountOfWETH = getAmountOutMin( SROOTxTokenAddress, WETH, claimAmountOfWETH );\r\n }\r\n } \r\n \r\n else {\r\n ///SROOT and WETH and ROOTx\r\n for (uint256 i = 0; i < stakes[addr].length; i++) {\r\n if (isSpecialBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n claimAmountOfROOTxToken = claimAmountOfROOTxToken.add(dd_root * 10);\r\n uint256 dd_sroot = calDay(stakes[addr][i].claimedDate_SROOT);\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add(200 * (10**18) * dd_sroot);\r\n uint256 dd_weth = calDay(stakes[addr][i].claimedDate_WETH);\r\n claimAmountOfWETH = claimAmountOfWETH.add( 200 * (10**18) * dd_weth );\r\n }\r\n if (isGenesisBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n claimAmountOfROOTxToken = claimAmountOfROOTxToken.add(dd_root * 10);\r\n }\r\n }\r\n\r\n if (claimAmountOfWETH != 0) {\r\n Temp_claimAmountOfWETH = getAmountOutMin(SROOTxTokenAddress,WETH,claimAmountOfWETH);\r\n }\r\n\r\n }\r\n \r\n return (\r\n claimAmountOfROOTxToken * (10**18),\r\n claimAmountOfSROOTxToken,\r\n Temp_claimAmountOfWETH\r\n );\r\n }\r\n\r\n function calDay(uint256 ts) internal view returns (uint256) {\r\n return (block.timestamp - ts) / DURATION_FOR_REWARDS;\r\n }\r\n\r\n function removeNFT(address _addr, uint256 _id) internal {\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (stakes[_addr][i].nft_id == _id) {\r\n stakes[_addr][i] = stakes[_addr][stakes[_addr].length - 1];\r\n stakes[_addr].pop();\r\n }\r\n }\r\n if (stakes[_addr].length <= 0) {\r\n delete stakes[_addr];\r\n removeStaker(_addr);\r\n }\r\n }\r\n\r\n function isGenesisBear(uint256 _id) internal pure returns (bool) {\r\n bool returned;\r\n if (_id >= 0 && _id <= 3700) {\r\n returned = true;\r\n } else {\r\n returned = false;\r\n }\r\n return returned;\r\n }\r\n\r\n function isSpecialBear(uint256 _id) internal pure returns (bool) {\r\n bool returned;\r\n if (_id >= 1000000000000 && _id <= 1000000000005) {\r\n returned = true;\r\n }\r\n return returned;\r\n }\r\n\r\n function getSpecialBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofspecialbear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isSpecialBear(stakes[_addr][i].nft_id)) {\r\n sumofspecialbear += 1;\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofspecialbear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isSpecialBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofspecialbear, nft_ids);\r\n }\r\n\r\n function getGenesisBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofgenesisbear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isGenesisBear(stakes[_addr][i].nft_id)) {\r\n sumofgenesisbear = sumofgenesisbear.add(1);\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofgenesisbear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isGenesisBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofgenesisbear, nft_ids);\r\n }\r\n\r\n function isMiniBear(uint256 _id) internal pure returns (bool) {\r\n if (_id < 10000000000006) return false;\r\n if (_id > 10000000005299) return false;\r\n else return true;\r\n }\r\n\r\n function getMiniBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofminibear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isMiniBear(stakes[_addr][i].nft_id)) {\r\n sumofminibear += 1;\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofminibear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isMiniBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofminibear, nft_ids);\r\n }\r\n\r\n modifier isOnlyStaker() {\r\n // (bool _isStaker, ) = isStaker(msg.sender);\r\n (bool _isStaker, ) = is_m_Staker(msg.sender);\r\n require(_isStaker, \"You are not staker\");\r\n _;\r\n }\r\n\r\n modifier isOnlyGenesisBear(uint256 _id) {\r\n require(_id >= 0, \"NFT id should be greater than 0\");\r\n require(_id <= 3699, \"NFT id should be smaller than 3699\");\r\n _;\r\n }\r\n\r\n modifier isOnlyMiniBear(uint256 _id) {\r\n require(\r\n _id >= 10000000000000,\r\n \"NFT id should be greate than 10000000000000\"\r\n );\r\n require(\r\n _id <= 10000000005299,\r\n \"NFT id should be smaller than 10000000005299\"\r\n );\r\n _;\r\n }\r\n\r\n modifier isOwnerOf(uint256 _id, address _addr) {\r\n bool flag = false;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (stakes[_addr][i].nft_id == _id) flag = true;\r\n }\r\n if (flag) _;\r\n }\r\n\r\n function isVested(address _addr) public view returns (bool) {\r\n bool status = true;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n uint256 dd = calDay(stakes[_addr][i].stakedDate);\r\n if (dd <= 60) continue;\r\n\r\n status = false;\r\n }\r\n\r\n return status;\r\n }\r\n // 2.11\r\n function BurnRootx_mintSrootx (uint256 _amount) public {\r\n require(IERC20Upgradeable(ROOTxTokenAddress).allowance(msg.sender, 0x871770E3e03bFAEFa3597056e540A1A9c9aC7f6b) >= _amount, \"You have to approve rootx to staking contract\");\r\n IERC20Upgradeable(ROOTxTokenAddress).transferFrom(msg.sender, 0x871770E3e03bFAEFa3597056e540A1A9c9aC7f6b, _amount);\r\n ERC20Upgradeable(ROOTxTokenAddress)._burn(_amount);\r\n IERC20Upgradeable(SROOTxTokenAddress).mintToken(msg.sender, _amount.mul(5));\r\n }\r\n\r\n function lock () public {\r\n if(msg.sender == 0xd0d725208fd36BE1561050Fc1DD6a651d7eA7C89) {\r\n islocked = !islocked;\r\n }\r\n }\r\n}","ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BearXNFTAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"BurnRootx_mintSrootx\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MaxRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MaxSROOTXrate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MinRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MinSROOTXrate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ROOTxTokenAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RateValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RootX_Store\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SROOTRateValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SROOTxTokenAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"claimOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfROOTxToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfSROOTxToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfWETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_ids\",\"type\":\"uint256[]\"}],\"name\":\"createStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAPR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getGenesisBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getMiniBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getSpecialBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get_APR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"isVested\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"is_m_Staker\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"islocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lock\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxprovision\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setBearXNFTAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setROOTxTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setSROOTxTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"stakeOf\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transferStakers\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_ids\",\"type\":\"uint256[]\"}],\"name\":\"unStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"BearXNFTStaking","CompilerVersion":"v0.8.11+commit.d7f03943","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"Unlicense","Proxy":0,"SwarmSource":"ipfs://8225f1f0e5a2f3fe96c24aa279f677e9fe9917e9144ec29a9c0abce7aaa8f9f0"}] \ No newline at end of file diff --git a/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json new file mode 100644 index 0000000000000..ba804703e9e2c --- /dev/null +++ b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x3a23f943181408eac424116af7b7790c94cb97a5","contractCreator":"0xe8dd38e673a93ccfc2e3d7053efccb5c93f49365","txHash":"0x29328ac0edf7b080320bc8ed998fcd3e866f7eec3775b0d91a86f5d02ab83c28"} \ No newline at end of file diff --git a/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json new file mode 100644 index 0000000000000..d82efef5a707e --- /dev/null +++ b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"src/bridges/hop/interfaces/amm.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/**\n * @title HopAMM\n * @notice Interface to handle the token bridging to L2 chains.\n */\ninterface HopAMM {\n /**\n * @notice To send funds L2->L1 or L2->L2, call the swapAndSend on the L2 AMM Wrapper contract\n * @param chainId chainId of the L2 contract\n * @param recipient receiver address\n * @param amount amount is the amount the user wants to send plus the Bonder fee\n * @param bonderFee fees\n * @param amountOutMin minimum amount\n * @param deadline deadline for bridging\n * @param destinationAmountOutMin minimum amount expected to be bridged on L2\n * @param destinationDeadline destination time before which token is to be bridged on L2\n */\n function swapAndSend(\n uint256 chainId,\n address recipient,\n uint256 amount,\n uint256 bonderFee,\n uint256 amountOutMin,\n uint256 deadline,\n uint256 destinationAmountOutMin,\n uint256 destinationDeadline\n ) external payable;\n}\n"},"src/swap/oneinch/OneInchImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {ONEINCH} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title OneInch-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via OneInch-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of OneInchImplementation\n * @author Socket dot tech.\n */\ncontract OneInchImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable OneInchIdentifier = ONEINCH;\n\n /// @notice address of OneInchAggregator to swap the tokens on Chain\n address public immutable ONEINCH_AGGREGATOR;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @dev ensure _oneinchAggregator are set properly for the chainId in which the contract is being deployed\n constructor(\n address _oneinchAggregator,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n ONEINCH_AGGREGATOR = _oneinchAggregator;\n }\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * via OneInch-Middleware-Aggregator\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param receiverAddress address of toToken recipient\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n uint256 returnAmount;\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(ONEINCH_AGGREGATOR, amount);\n {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(\n swapExtraData\n );\n token.safeApprove(ONEINCH_AGGREGATOR, 0);\n\n if (!success) {\n revert SwapFailed();\n }\n\n returnAmount = abi.decode(result, (uint256));\n }\n } else {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{\n value: amount\n }(swapExtraData);\n if (!success) {\n revert SwapFailed();\n }\n returnAmount = abi.decode(result, (uint256));\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n OneInchIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * via OneInch-Middleware-Aggregator\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n uint256 returnAmount;\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(ONEINCH_AGGREGATOR, amount);\n {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(\n swapExtraData\n );\n token.safeApprove(ONEINCH_AGGREGATOR, 0);\n\n if (!success) {\n revert SwapFailed();\n }\n\n returnAmount = abi.decode(result, (uint256));\n }\n } else {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{\n value: amount\n }(swapExtraData);\n if (!success) {\n revert SwapFailed();\n }\n returnAmount = abi.decode(result, (uint256));\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n OneInchIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/libraries/LibUtil.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./LibBytes.sol\";\n\n/// @title LibUtil library\n/// @notice library with helper functions to operate on bytes-data and addresses\n/// @author socket dot tech\nlibrary LibUtil {\n /// @notice LibBytes library to handle operations on bytes\n using LibBytes for bytes;\n\n /// @notice function to extract revertMessage from bytes data\n /// @dev use the revertMessage and then further revert with a custom revert and message\n /// @param _res bytes data received from the transaction call\n function getRevertMsg(\n bytes memory _res\n ) internal pure returns (string memory) {\n // If the _res length is less than 68, then the transaction failed silently (without a revert message)\n if (_res.length < 68) {\n return \"Transaction reverted silently\";\n }\n bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes\n return abi.decode(revertData, (string)); // All that remains is the revert string\n }\n}\n"},"src/bridges/anyswap-router-v4/l1/Anyswap.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {ANYSWAP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Anyswap-V4-Route L1 Implementation\n * @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation\n * This is the L1 implementation, so this is used when transferring from l1 to supported l1s or L1.\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\n\n/// @notice Interface to interact with AnyswapV4-Router Implementation\ninterface AnyswapV4Router {\n function anySwapOutUnderlying(\n address token,\n address to,\n uint256 amount,\n uint256 toChainID\n ) external;\n}\n\ncontract AnyswapImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AnyswapIdentifier = ANYSWAP;\n\n /// @notice Function-selector for ERC20-token bridging on Anyswap-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ANYSWAP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,address)\"\n )\n );\n\n bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))\"\n )\n );\n\n /// @notice AnSwapV4Router Contract instance used to deposit ERC20 on to Anyswap-Bridge\n /// @dev contract instance is to be initialized in the constructor using the router-address passed as constructor argument\n AnyswapV4Router public immutable router;\n\n /**\n * @notice Constructor sets the router address and socketGateway address.\n * @dev anyswap 4 router is immutable. so no setter function required.\n */\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = AnyswapV4Router(_router);\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeDataNoToken {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeData {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AnyswapBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AnyswapBridgeData memory anyswapBridgeData = abi.decode(\n bridgeData,\n (AnyswapBridgeData)\n );\n ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n amount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n amount,\n anyswapBridgeData.token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param anyswapBridgeData encoded data for AnyswapBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AnyswapBridgeDataNoToken calldata anyswapBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n ERC20(token).safeApprove(address(router), bridgeAmount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n bridgeAmount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address wrapperTokenAddress\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n wrapperTokenAddress,\n receiverAddress,\n amount,\n toChainId\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AnyswapIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/cbridge/CelerStorageWrapper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\nimport {OnlySocketGateway, TransferIdExists, TransferIdDoesnotExist} from \"../../errors/SocketErrors.sol\";\n\n/**\n * @title CelerStorageWrapper\n * @notice handle storageMappings used while bridging ERC20 and native on CelerBridge\n * @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway\n * @author Socket dot tech.\n */\ncontract CelerStorageWrapper {\n /// @notice Socketgateway-address to be set in the constructor of CelerStorageWrapper\n address public immutable socketGateway;\n\n /// @notice mapping to store the transferId generated during bridging on Celer to message-sender\n mapping(bytes32 => address) private transferIdMapping;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(address _socketGateway) {\n socketGateway = _socketGateway;\n }\n\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @param transferIdAddress message sender who is making the bridging on CelerBridge\n */\n function setAddressForTransferId(\n bytes32 transferId,\n address transferIdAddress\n ) external {\n if (msg.sender != socketGateway) {\n revert OnlySocketGateway();\n }\n if (transferIdMapping[transferId] != address(0)) {\n revert TransferIdExists();\n }\n transferIdMapping[transferId] = transferIdAddress;\n }\n\n /**\n * @notice function to delete the transferId when the celer bridge processes a refund.\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n */\n function deleteTransferId(bytes32 transferId) external {\n if (msg.sender != socketGateway) {\n revert OnlySocketGateway();\n }\n if (transferIdMapping[transferId] == address(0)) {\n revert TransferIdDoesnotExist();\n }\n\n delete transferIdMapping[transferId];\n }\n\n /**\n * @notice function to lookup the address mapped to the transferId\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @return address of account mapped to transferId\n */\n function getAddressFromTransferId(\n bytes32 transferId\n ) external view returns (address) {\n return transferIdMapping[transferId];\n }\n}\n"},"src/bridges/polygon/interfaces/polygon.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/**\n * @title RootChain Manager Interface for Polygon Bridge.\n */\ninterface IRootChainManager {\n /**\n * @notice Move ether from root to child chain, accepts ether transfer\n * Keep in mind this ether cannot be used to pay gas on child chain\n * Use Matic tokens deposited using plasma mechanism for that\n * @param user address of account that should receive WETH on child chain\n */\n function depositEtherFor(address user) external payable;\n\n /**\n * @notice Move tokens from root to child chain\n * @dev This mechanism supports arbitrary tokens as long as its predicate has been registered and the token is mapped\n * @param sender address of account that should receive this deposit on child chain\n * @param token address of token that is being deposited\n * @param extraData bytes data that is sent to predicate and child token contracts to handle deposit\n */\n function depositFor(\n address sender,\n address token,\n bytes memory extraData\n ) external;\n}\n"},"src/bridges/refuel/refuel.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/refuel.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {REFUEL} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Refuel-Route Implementation\n * @notice Route implementation with functions to bridge Native via Refuel-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of RefuelImplementation\n * @author Socket dot tech.\n */\ncontract RefuelBridgeImpl is BridgeImplBase {\n bytes32 public immutable RefuelIdentifier = REFUEL;\n\n /// @notice refuelBridge-Contract address used to deposit Native on Refuel-Bridge\n address public immutable refuelBridge;\n\n /// @notice Function-selector for Native bridging via Refuel-Bridge\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable REFUEL_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,address,uint256,bytes32)\"));\n\n bytes4 public immutable REFUEL_NATIVE_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\"swapAndBridge(uint32,address,uint256,bytes32,bytes)\")\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure _refuelBridge are set properly for the chainId in which the contract is being deployed\n constructor(\n address _refuelBridge,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n refuelBridge = _refuelBridge;\n }\n\n // Function to receive Ether. msg.data must be empty\n receive() external payable {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct RefuelBridgeData {\n address receiverAddress;\n uint256 toChainId;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct\n * @param amount amount of tokens being bridged. this must be only native\n * @param bridgeData encoded data for RefuelBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n RefuelBridgeData memory refuelBridgeData = abi.decode(\n bridgeData,\n (RefuelBridgeData)\n );\n IRefuel(refuelBridge).depositNativeToken{value: amount}(\n refuelBridgeData.toChainId,\n refuelBridgeData.receiverAddress\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n refuelBridgeData.toChainId,\n RefuelIdentifier,\n msg.sender,\n refuelBridgeData.receiverAddress,\n refuelBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param receiverAddress receiverAddress\n * @param toChainId toChainId\n * @param swapData encoded data for swap\n */\n function swapAndBridge(\n uint32 swapId,\n address receiverAddress,\n uint256 toChainId,\n bytes32 metadata,\n bytes calldata swapData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, ) = abi.decode(result, (uint256, address));\n IRefuel(refuelBridge).depositNativeToken{value: bridgeAmount}(\n toChainId,\n receiverAddress\n );\n\n emit SocketBridge(\n bridgeAmount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n RefuelIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Refuel-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of native being refuelled to destination chain\n * @param receiverAddress recipient address of the refuelled native\n * @param toChainId destinationChainId\n */\n function bridgeNativeTo(\n uint256 amount,\n address receiverAddress,\n uint256 toChainId,\n bytes32 metadata\n ) external payable {\n IRefuel(refuelBridge).depositNativeToken{value: amount}(\n toChainId,\n receiverAddress\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n RefuelIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/across/interfaces/across.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/// @notice interface with functions to interact with SpokePool contract of Across-Bridge\ninterface SpokePool {\n /**************************************\n * DEPOSITOR FUNCTIONS *\n **************************************/\n\n /**\n * @notice Called by user to bridge funds from origin to destination chain. Depositor will effectively lock\n * tokens in this contract and receive a destination token on the destination chain. The origin => destination\n * token mapping is stored on the L1 HubPool.\n * @notice The caller must first approve this contract to spend amount of originToken.\n * @notice The originToken => destinationChainId must be enabled.\n * @notice This method is payable because the caller is able to deposit native token if the originToken is\n * wrappedNativeToken and this function will handle wrapping the native token to wrappedNativeToken.\n * @param recipient Address to receive funds at on destination chain.\n * @param originToken Token to lock into this contract to initiate deposit.\n * @param amount Amount of tokens to deposit. Will be amount of tokens to receive less fees.\n * @param destinationChainId Denotes network where user will receive funds from SpokePool by a relayer.\n * @param relayerFeePct % of deposit amount taken out to incentivize a fast relayer.\n * @param quoteTimestamp Timestamp used by relayers to compute this deposit's realizedLPFeePct which is paid\n * to LP pool on HubPool.\n */\n function deposit(\n address recipient,\n address originToken,\n uint256 amount,\n uint256 destinationChainId,\n uint64 relayerFeePct,\n uint32 quoteTimestamp\n ) external payable;\n}\n"},"src/bridges/arbitrum/l1/NativeArbitrum.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {L1GatewayRouter} from \"../interfaces/arbitrum.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {NATIVE_ARBITRUM} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Native Arbitrum-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 via NativeArbitrum-Bridge\n * @notice Called via SocketGateway if the routeId in the request maps to the routeId of NativeArbitrum-Implementation\n * @notice This is used when transferring from ethereum chain to arbitrum via their native bridge.\n * @notice Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * @notice RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract NativeArbitrumImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativeArbitrumIdentifier = NATIVE_ARBITRUM;\n\n uint256 public constant DESTINATION_CHAIN_ID = 42161;\n\n /// @notice Function-selector for ERC20-token bridging on NativeArbitrum\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_ARBITRUM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,uint256,uint256,bytes32,address,address,address,bytes)\"\n )\n );\n\n bytes4 public immutable NATIVE_ARBITRUM_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,uint256,uint256,address,address,bytes32,bytes))\"\n )\n );\n\n /// @notice router address of NativeArbitrum Bridge\n /// @notice GatewayRouter looks up ERC20Token's gateway, and finding that it's Standard ERC20 gateway (the L1ERC20Gateway contract).\n address public immutable router;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = _router;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct NativeArbitrumBridgeDataNoToken {\n uint256 value;\n /// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 maxGas;\n /// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 gasPriceBid;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of Gateway which handles the token bridging for the token\n /// @notice gatewayAddress is unique for each token\n address gatewayAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n /// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum\n bytes data;\n }\n\n struct NativeArbitrumBridgeData {\n uint256 value;\n /// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 maxGas;\n /// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 gasPriceBid;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of Gateway which handles the token bridging for the token\n /// @notice gatewayAddress is unique for each token\n address gatewayAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n /// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum\n bytes data;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for NativeArbitrumBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n NativeArbitrumBridgeData memory nativeArbitrumBridgeData = abi.decode(\n bridgeData,\n (NativeArbitrumBridgeData)\n );\n ERC20(nativeArbitrumBridgeData.token).safeApprove(\n nativeArbitrumBridgeData.gatewayAddress,\n amount\n );\n\n L1GatewayRouter(router).outboundTransfer{\n value: nativeArbitrumBridgeData.value\n }(\n nativeArbitrumBridgeData.token,\n nativeArbitrumBridgeData.receiverAddress,\n amount,\n nativeArbitrumBridgeData.maxGas,\n nativeArbitrumBridgeData.gasPriceBid,\n nativeArbitrumBridgeData.data\n );\n\n emit SocketBridge(\n amount,\n nativeArbitrumBridgeData.token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n nativeArbitrumBridgeData.receiverAddress,\n nativeArbitrumBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param nativeArbitrumBridgeData encoded data for NativeArbitrumBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n NativeArbitrumBridgeDataNoToken calldata nativeArbitrumBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n ERC20(token).safeApprove(\n nativeArbitrumBridgeData.gatewayAddress,\n bridgeAmount\n );\n\n L1GatewayRouter(router).outboundTransfer{\n value: nativeArbitrumBridgeData.value\n }(\n token,\n nativeArbitrumBridgeData.receiverAddress,\n bridgeAmount,\n nativeArbitrumBridgeData.maxGas,\n nativeArbitrumBridgeData.gasPriceBid,\n nativeArbitrumBridgeData.data\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n nativeArbitrumBridgeData.receiverAddress,\n nativeArbitrumBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativeArbitrum-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param value value\n * @param maxGas maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n * @param gasPriceBid gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param gatewayAddress address of Gateway which handles the token bridging for the token, gatewayAddress is unique for each token\n * @param data data is a depositParameter derived from erc20Bridger of nativeArbitrum\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 value,\n uint256 maxGas,\n uint256 gasPriceBid,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address gatewayAddress,\n bytes memory data\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(gatewayAddress, amount);\n\n L1GatewayRouter(router).outboundTransfer{value: value}(\n token,\n receiverAddress,\n amount,\n maxGas,\n gasPriceBid,\n data\n );\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/across/Across.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/across.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ACROSS} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Across-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Across-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AcrossImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract AcrossImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AcrossIdentifier = ACROSS;\n\n /// @notice Function-selector for ERC20-token bridging on Across-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ACROSS_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,uint32,uint64)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Across-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable ACROSS_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(uint256,uint256,bytes32,address,uint32,uint64)\"\n )\n );\n\n bytes4 public immutable ACROSS_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,uint32,uint64,bytes32))\"\n )\n );\n\n /// @notice spokePool Contract instance used to deposit ERC20 and Native on to Across-Bridge\n /// @dev contract instance is to be initialized in the constructor using the spokePoolAddress passed as constructor argument\n SpokePool public immutable spokePool;\n address public immutable spokePoolAddress;\n\n /// @notice address of WETH token to be initialised in constructor\n address public immutable WETH;\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AcrossBridgeDataNoToken {\n uint256 toChainId;\n address receiverAddress;\n uint32 quoteTimestamp;\n uint64 relayerFeePct;\n bytes32 metadata;\n }\n\n struct AcrossBridgeData {\n uint256 toChainId;\n address receiverAddress;\n address token;\n uint32 quoteTimestamp;\n uint64 relayerFeePct;\n bytes32 metadata;\n }\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure spokepool, weth-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _spokePool,\n address _wethAddress,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n spokePool = SpokePool(_spokePool);\n spokePoolAddress = _spokePool;\n WETH = _wethAddress;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AcrossBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AcrossBridgeData memory acrossBridgeData = abi.decode(\n bridgeData,\n (AcrossBridgeData)\n );\n\n if (acrossBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n spokePool.deposit{value: amount}(\n acrossBridgeData.receiverAddress,\n WETH,\n amount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n } else {\n spokePool.deposit(\n acrossBridgeData.receiverAddress,\n acrossBridgeData.token,\n amount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n }\n\n emit SocketBridge(\n amount,\n acrossBridgeData.token,\n acrossBridgeData.toChainId,\n AcrossIdentifier,\n msg.sender,\n acrossBridgeData.receiverAddress,\n acrossBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param acrossBridgeData encoded data for AcrossBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AcrossBridgeDataNoToken calldata acrossBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n spokePool.deposit{value: bridgeAmount}(\n acrossBridgeData.receiverAddress,\n WETH,\n bridgeAmount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n } else {\n spokePool.deposit(\n acrossBridgeData.receiverAddress,\n token,\n bridgeAmount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n acrossBridgeData.toChainId,\n AcrossIdentifier,\n msg.sender,\n acrossBridgeData.receiverAddress,\n acrossBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Across-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract\n * @param relayerFeePct feePct that will be relayed by the Bridge to the relayer\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n uint32 quoteTimestamp,\n uint64 relayerFeePct\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n spokePool.deposit(\n receiverAddress,\n address(token),\n amount,\n toChainId,\n relayerFeePct,\n quoteTimestamp\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AcrossIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Across-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract\n * @param relayerFeePct feePct that will be relayed by the Bridge to the relayer\n */\n function bridgeNativeTo(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n uint32 quoteTimestamp,\n uint64 relayerFeePct\n ) external payable {\n spokePool.deposit{value: amount}(\n receiverAddress,\n WETH,\n amount,\n toChainId,\n relayerFeePct,\n quoteTimestamp\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n AcrossIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/cbridge/interfaces/ICelerStorageWrapper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/**\n * @title Celer-StorageWrapper interface\n * @notice Interface to handle storageMappings used while bridging ERC20 and native on CelerBridge\n * @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway\n * @author Socket dot tech.\n */\ninterface ICelerStorageWrapper {\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @param transferIdAddress message sender who is making the bridging on CelerBridge\n */\n function setAddressForTransferId(\n bytes32 transferId,\n address transferIdAddress\n ) external;\n\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n */\n function deleteTransferId(bytes32 transferId) external;\n\n /**\n * @notice function to lookup the address mapped to the transferId\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @return address of account mapped to transferId\n */\n function getAddressFromTransferId(\n bytes32 transferId\n ) external view returns (address);\n}\n"},"src/bridges/optimism/interfaces/optimism.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\ninterface L1StandardBridge {\n /**\n * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of\n * the deposit.\n * @param _to Account to give the deposit to on L2.\n * @param _l2Gas Gas limit required to complete the deposit on L2.\n * @param _data Optional data to forward to L2. This data is provided\n * solely as a convenience for external contracts. Aside from enforcing a maximum\n * length, these contracts provide no guarantees about its content.\n */\n function depositETHTo(\n address _to,\n uint32 _l2Gas,\n bytes calldata _data\n ) external payable;\n\n /**\n * @dev deposit an amount of ERC20 to a recipient's balance on L2.\n * @param _l1Token Address of the L1 ERC20 we are depositing\n * @param _l2Token Address of the L1 respective L2 ERC20\n * @param _to L2 address to credit the withdrawal to.\n * @param _amount Amount of the ERC20 to deposit.\n * @param _l2Gas Gas limit required to complete the deposit on L2.\n * @param _data Optional data to forward to L2. This data is provided\n * solely as a convenience for external contracts. Aside from enforcing a maximum\n * length, these contracts provide no guarantees about its content.\n */\n function depositERC20To(\n address _l1Token,\n address _l2Token,\n address _to,\n uint256 _amount,\n uint32 _l2Gas,\n bytes calldata _data\n ) external;\n}\n\ninterface OldL1TokenGateway {\n /**\n * @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow\n *\n * @param _to Account to give the deposit to on L2\n * @param _amount Amount of the ERC20 to deposit.\n */\n function depositTo(address _to, uint256 _amount) external;\n\n /**\n * @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow\n *\n * @param currencyKey currencyKey for the SynthToken\n * @param destination Account to give the deposit to on L2\n * @param amount Amount of the ERC20 to deposit.\n */\n function initiateSynthTransfer(\n bytes32 currencyKey,\n address destination,\n uint256 amount\n ) external;\n}\n"},"src/bridges/arbitrum/interfaces/arbitrum.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\n\n/*\n * Copyright 2021, Offchain Labs, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npragma solidity >=0.8.0;\n\n/**\n * @title L1gatewayRouter for native-arbitrum\n */\ninterface L1GatewayRouter {\n /**\n * @notice outbound function to bridge ERC20 via NativeArbitrum-Bridge\n * @param _token address of token being bridged via GatewayRouter\n * @param _to recipient of the token on arbitrum chain\n * @param _amount amount of ERC20 token being bridged\n * @param _maxGas a depositParameter for bridging the token\n * @param _gasPriceBid a depositParameter for bridging the token\n * @param _data a depositParameter for bridging the token\n * @return calldata returns the output of transactioncall made on gatewayRouter\n */\n function outboundTransfer(\n address _token,\n address _to,\n uint256 _amount,\n uint256 _maxGas,\n uint256 _gasPriceBid,\n bytes calldata _data\n ) external payable returns (bytes calldata);\n}\n"},"src/deployFactory/DisabledSocketRoute.sol":{"content":"//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {OnlySocketGatewayOwner} from \"../errors/SocketErrors.sol\";\n\ncontract DisabledSocketRoute {\n using SafeTransferLib for ERC20;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n error RouteDisabled();\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n */\n constructor(address _socketGateway) {\n socketGateway = _socketGateway;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /**\n * @notice function to rescue the ERC20 tokens in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n /**\n * @notice Handle route function calls gracefully.\n */\n fallback() external payable {\n revert RouteDisabled();\n }\n\n /**\n * @notice Support receiving ether to handle refunds etc.\n */\n receive() external payable {}\n}\n"},"src/bridges/polygon/NativePolygon.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"./interfaces/polygon.sol\";\nimport {BridgeImplBase} from \"../BridgeImplBase.sol\";\nimport {NATIVE_POLYGON} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title NativePolygon-Route Implementation\n * @notice This is the L1 implementation, so this is used when transferring from ethereum to polygon via their native bridge.\n * @author Socket dot tech.\n */\ncontract NativePolygonImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativePolyonIdentifier = NATIVE_POLYGON;\n\n /// @notice destination-chain-Id for this router is always arbitrum\n uint256 public constant DESTINATION_CHAIN_ID = 137;\n\n /// @notice Function-selector for ERC20-token bridging on NativePolygon-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_POLYGON_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeERC20To(uint256,bytes32,address,address)\"));\n\n /// @notice Function-selector for Native bridging on NativePolygon-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable NATIVE_POLYGON_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,bytes32,address)\"));\n\n bytes4 public immutable NATIVE_POLYGON_SWAP_BRIDGE_SELECTOR =\n bytes4(keccak256(\"swapAndBridge(uint32,address,bytes32,bytes)\"));\n\n /// @notice root chain manager proxy on the ethereum chain\n /// @dev to be initialised in the constructor\n IRootChainManager public immutable rootChainManagerProxy;\n\n /// @notice ERC20 Predicate proxy on the ethereum chain\n /// @dev to be initialised in the constructor\n address public immutable erc20PredicateProxy;\n\n /**\n * // @notice We set all the required addresses in the constructor while deploying the contract.\n * // These will be constant addresses.\n * // @dev Please use the Proxy addresses and not the implementation addresses while setting these\n * // @param _rootChainManagerProxy address of the root chain manager proxy on the ethereum chain\n * // @param _erc20PredicateProxy address of the ERC20 Predicate proxy on the ethereum chain.\n * // @param _socketGateway address of the socketGateway contract that calls this contract\n */\n constructor(\n address _rootChainManagerProxy,\n address _erc20PredicateProxy,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n rootChainManagerProxy = IRootChainManager(_rootChainManagerProxy);\n erc20PredicateProxy = _erc20PredicateProxy;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for NativePolygon-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n (address token, address receiverAddress, bytes32 metadata) = abi.decode(\n bridgeData,\n (address, address, bytes32)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IRootChainManager(rootChainManagerProxy).depositEtherFor{\n value: amount\n }(receiverAddress);\n } else {\n ERC20(token).safeApprove(erc20PredicateProxy, amount);\n\n // deposit into rootchain manager\n IRootChainManager(rootChainManagerProxy).depositFor(\n receiverAddress,\n token,\n abi.encodePacked(amount)\n );\n }\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param receiverAddress address of the receiver\n * @param swapData encoded data for swap\n */\n function swapAndBridge(\n uint32 swapId,\n address receiverAddress,\n bytes32 metadata,\n bytes calldata swapData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IRootChainManager(rootChainManagerProxy).depositEtherFor{\n value: bridgeAmount\n }(receiverAddress);\n } else {\n ERC20(token).safeApprove(erc20PredicateProxy, bridgeAmount);\n\n // deposit into rootchain manager\n IRootChainManager(rootChainManagerProxy).depositFor(\n receiverAddress,\n token,\n abi.encodePacked(bridgeAmount)\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativePolygon-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of tokens being bridged\n * @param receiverAddress recipient address\n * @param token address of token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n address token\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n\n // set allowance for erc20 predicate\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(erc20PredicateProxy, amount);\n\n // deposit into rootchain manager\n rootChainManagerProxy.depositFor(\n receiverAddress,\n token,\n abi.encodePacked(amount)\n );\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via NativePolygon-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of tokens being bridged\n * @param receiverAddress recipient address\n */\n function bridgeNativeTo(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress\n ) external payable {\n rootChainManagerProxy.depositEtherFor{value: amount}(receiverAddress);\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/stargate/l1/Stargate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/stargate.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {STARGATE} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Stargate-L1-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L1-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L1-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract StargateImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable StargateIdentifier = STARGATE;\n\n /// @notice Function-selector for ERC20-token bridging on Stargate-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable STARGATE_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Stargate-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable STARGATE_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))\"\n )\n );\n\n /// @notice Stargate Router to bridge ERC20 tokens\n IBridgeStargate public immutable router;\n\n /// @notice Stargate Router to bridge native tokens\n IBridgeStargate public immutable routerETH;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _routerEth,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = IBridgeStargate(_router);\n routerETH = IBridgeStargate(_routerEth);\n }\n\n struct StargateBridgeExtraData {\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 destinationGasLimit;\n uint256 minReceivedAmt;\n bytes32 metadata;\n bytes destinationPayload;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct StargateBridgeDataNoToken {\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n struct StargateBridgeData {\n address token;\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Stargate-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n StargateBridgeData memory stargateBridgeData = abi.decode(\n bridgeData,\n (StargateBridgeData)\n );\n\n if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n amount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(stargateBridgeData.token).safeApprove(\n address(router),\n amount\n );\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n amount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n stargateBridgeData.token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param stargateBridgeData encoded data for StargateBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n StargateBridgeDataNoToken calldata stargateBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{\n value: bridgeAmount + stargateBridgeData.optionalValue\n }(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n bridgeAmount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(token).safeApprove(address(router), bridgeAmount);\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n bridgeAmount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param senderAddress address of sender\n * @param receiverAddress address of recipient\n * @param amount amount of token being bridge\n * @param value value\n * @param stargateBridgeExtraData stargate bridge extradata\n */\n function bridgeERC20To(\n address token,\n address senderAddress,\n address receiverAddress,\n uint256 amount,\n uint256 value,\n StargateBridgeExtraData calldata stargateBridgeExtraData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n {\n router.swap{value: value}(\n stargateBridgeExtraData.stargateDstChainId,\n stargateBridgeExtraData.srcPoolId,\n stargateBridgeExtraData.dstPoolId,\n payable(senderAddress), // default to refund to main contract\n amount,\n stargateBridgeExtraData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeExtraData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(receiverAddress),\n stargateBridgeExtraData.destinationPayload\n );\n }\n\n emit SocketBridge(\n amount,\n token,\n stargateBridgeExtraData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n stargateBridgeExtraData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of receipient\n * @param senderAddress address of sender\n * @param stargateDstChainId stargate defines chain id in its way\n * @param amount amount of token being bridge\n * @param minReceivedAmt defines the slippage, the min qty you would accept on the destination\n * @param optionalValue optionalValue Native amount\n */\n function bridgeNativeTo(\n address receiverAddress,\n address senderAddress,\n uint16 stargateDstChainId,\n uint256 amount,\n uint256 minReceivedAmt,\n uint256 optionalValue,\n bytes32 metadata\n ) external payable {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n minReceivedAmt\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/hop/l2/HopImplL2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../interfaces/amm.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {HOP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hop-L2 Route Implementation\n * @notice This is the L2 implementation, so this is used when transferring from l2 to supported l2s\n * Called via SocketGateway if the routeId in the request maps to the routeId of HopL2-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HopImplL2 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HopIdentifier = HOP;\n\n /// @notice Function-selector for ERC20-token bridging on Hop-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HOP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Hop-L2-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable HOP_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable HOP_L2_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used as a input parameter for Bridging tokens via Hop-L2-route\n /// @dev while building transactionData,values should be set in this sequence of properties in this struct\n struct HopBridgeRequestData {\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HopBridgeDataNoToken {\n // The address receiving funds at the destination\n address receiverAddress;\n // AMM address of Hop on L2\n address hopAMM;\n // The chainId of the destination chain\n uint256 toChainId;\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopBridgeData {\n /// @notice address of token being bridged\n address token;\n // The address receiving funds at the destination\n address receiverAddress;\n // AMM address of Hop on L2\n address hopAMM;\n // The chainId of the destination chain\n uint256 toChainId;\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Hop-L2-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HopBridgeData memory hopData = abi.decode(bridgeData, (HopBridgeData));\n\n if (hopData.token == NATIVE_TOKEN_ADDRESS) {\n HopAMM(hopData.hopAMM).swapAndSend{value: amount}(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n } else {\n // perform bridging\n HopAMM(hopData.hopAMM).swapAndSend(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n }\n\n emit SocketBridge(\n amount,\n hopData.token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hopData encoded data for HopData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HopBridgeDataNoToken calldata hopData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n HopAMM(hopData.hopAMM).swapAndSend{value: bridgeAmount}(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n } else {\n // perform bridging\n HopAMM(hopData.hopAMM).swapAndSend(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hop-L2-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param token token being bridged\n * @param hopAMM AMM address of Hop on L2\n * @param amount The amount being bridged\n * @param toChainId The chainId of the destination chain\n * @param hopBridgeRequestData extraData for Bridging across Hop-L2\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n address hopAMM,\n uint256 amount,\n uint256 toChainId,\n HopBridgeRequestData calldata hopBridgeRequestData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n\n HopAMM(hopAMM).swapAndSend(\n toChainId,\n receiverAddress,\n amount,\n hopBridgeRequestData.bonderFee,\n hopBridgeRequestData.amountOutMin,\n hopBridgeRequestData.deadline,\n hopBridgeRequestData.amountOutMinDestination,\n hopBridgeRequestData.deadlineDestination\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n hopBridgeRequestData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hop-L2-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param hopAMM AMM address of Hop on L2\n * @param amount The amount being bridged\n * @param toChainId The chainId of the destination chain\n * @param bonderFee fees passed to relayer\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n * @param amountOutMinDestination Minimum amount expected to be received or bridged to destination\n * @param deadlineDestination deadline for bridging to destination\n */\n function bridgeNativeTo(\n address receiverAddress,\n address hopAMM,\n uint256 amount,\n uint256 toChainId,\n uint256 bonderFee,\n uint256 amountOutMin,\n uint256 deadline,\n uint256 amountOutMinDestination,\n uint256 deadlineDestination,\n bytes32 metadata\n ) external payable {\n // token address might not be indication thats why passed through extraData\n // perform bridging\n HopAMM(hopAMM).swapAndSend{value: amount}(\n toChainId,\n receiverAddress,\n amount,\n bonderFee,\n amountOutMin,\n deadline,\n amountOutMinDestination,\n deadlineDestination\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/anyswap-router-v4/l2/Anyswap.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {ANYSWAP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Anyswap-V4-Route L1 Implementation\n * @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation\n * This is the L2 implementation, so this is used when transferring from l2.\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ninterface AnyswapV4Router {\n function anySwapOutUnderlying(\n address token,\n address to,\n uint256 amount,\n uint256 toChainID\n ) external;\n}\n\ncontract AnyswapL2Impl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AnyswapIdentifier = ANYSWAP;\n\n /// @notice Function-selector for ERC20-token bridging on Anyswap-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ANYSWAP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,address)\"\n )\n );\n\n bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))\"\n )\n );\n\n // polygon router multichain router v4\n AnyswapV4Router public immutable router;\n\n /**\n * @notice Constructor sets the router address and socketGateway address.\n * @dev anyswap v4 router is immutable. so no setter function required.\n */\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = AnyswapV4Router(_router);\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeDataNoToken {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeData {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AnyswapBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AnyswapBridgeData memory anyswapBridgeData = abi.decode(\n bridgeData,\n (AnyswapBridgeData)\n );\n ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n amount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n amount,\n anyswapBridgeData.token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param anyswapBridgeData encoded data for AnyswapBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AnyswapBridgeDataNoToken calldata anyswapBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n ERC20(token).safeApprove(address(router), bridgeAmount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n bridgeAmount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address wrapperTokenAddress\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n wrapperTokenAddress,\n receiverAddress,\n amount,\n toChainId\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AnyswapIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/hyphen/Hyphen.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/hyphen.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {HYPHEN} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hyphen-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Hyphen-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of HyphenImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HyphenImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HyphenIdentifier = HYPHEN;\n\n /// @notice Function-selector for ERC20-token bridging on Hyphen-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HYPHEN_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"bridgeERC20To(uint256,bytes32,address,address,uint256)\")\n );\n\n /// @notice Function-selector for Native bridging on Hyphen-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable HYPHEN_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,bytes32,address,uint256)\"));\n\n bytes4 public immutable HYPHEN_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\"swapAndBridge(uint32,bytes,(address,uint256,bytes32))\")\n );\n\n /// @notice liquidityPoolManager - liquidityPool Manager of Hyphen used to bridge ERC20 and native\n /// @dev this is to be initialized in constructor with a valid deployed address of hyphen-liquidityPoolManager\n HyphenLiquidityPoolManager public immutable liquidityPoolManager;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure liquidityPoolManager-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _liquidityPoolManager,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n liquidityPoolManager = HyphenLiquidityPoolManager(\n _liquidityPoolManager\n );\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HyphenData {\n /// @notice address of token being bridged\n address token;\n /// @notice address of receiver\n address receiverAddress;\n /// @notice chainId of destination\n uint256 toChainId;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n struct HyphenDataNoToken {\n /// @notice address of receiver\n address receiverAddress;\n /// @notice chainId of destination\n uint256 toChainId;\n /// @notice chainId of destination\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for HyphenBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HyphenData memory hyphenData = abi.decode(bridgeData, (HyphenData));\n\n if (hyphenData.token == NATIVE_TOKEN_ADDRESS) {\n liquidityPoolManager.depositNative{value: amount}(\n hyphenData.receiverAddress,\n hyphenData.toChainId,\n \"SOCKET\"\n );\n } else {\n ERC20(hyphenData.token).safeApprove(\n address(liquidityPoolManager),\n amount\n );\n liquidityPoolManager.depositErc20(\n hyphenData.toChainId,\n hyphenData.token,\n hyphenData.receiverAddress,\n amount,\n \"SOCKET\"\n );\n }\n\n emit SocketBridge(\n amount,\n hyphenData.token,\n hyphenData.toChainId,\n HyphenIdentifier,\n msg.sender,\n hyphenData.receiverAddress,\n hyphenData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hyphenData encoded data for hyphenData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HyphenDataNoToken calldata hyphenData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n liquidityPoolManager.depositNative{value: bridgeAmount}(\n hyphenData.receiverAddress,\n hyphenData.toChainId,\n \"SOCKET\"\n );\n } else {\n ERC20(token).safeApprove(\n address(liquidityPoolManager),\n bridgeAmount\n );\n liquidityPoolManager.depositErc20(\n hyphenData.toChainId,\n token,\n hyphenData.receiverAddress,\n bridgeAmount,\n \"SOCKET\"\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hyphenData.toChainId,\n HyphenIdentifier,\n msg.sender,\n hyphenData.receiverAddress,\n hyphenData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hyphen-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount to be sent\n * @param receiverAddress address of the token to bridged to the destination chain.\n * @param token address of token being bridged\n * @param toChainId chainId of destination\n */\n function bridgeERC20To(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n address token,\n uint256 toChainId\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(liquidityPoolManager), amount);\n liquidityPoolManager.depositErc20(\n toChainId,\n token,\n receiverAddress,\n amount,\n \"SOCKET\"\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HyphenIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hyphen-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount to be sent\n * @param receiverAddress address of the token to bridged to the destination chain.\n * @param toChainId chainId of destination\n */\n function bridgeNativeTo(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n uint256 toChainId\n ) external payable {\n liquidityPoolManager.depositNative{value: amount}(\n receiverAddress,\n toChainId,\n \"SOCKET\"\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HyphenIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/optimism/l1/NativeOptimism.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/optimism.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {UnsupportedInterfaceId} from \"../../../errors/SocketErrors.sol\";\nimport {NATIVE_OPTIMISM} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title NativeOptimism-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via NativeOptimism-Bridge\n * Tokens are bridged from Ethereum to Optimism Chain.\n * Called via SocketGateway if the routeId in the request maps to the routeId of NativeOptimism-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract NativeOptimismImpl is BridgeImplBase {\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativeOptimismIdentifier = NATIVE_OPTIMISM;\n\n uint256 public constant DESTINATION_CHAIN_ID = 10;\n\n /// @notice Function-selector for ERC20-token bridging on Native-Optimism-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_OPTIMISM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint32,(bytes32,bytes32),uint256,uint256,address,bytes)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Native-Optimism-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native balance\n bytes4\n public immutable NATIVE_OPTIMISM_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint32,uint256,bytes32,bytes)\"\n )\n );\n\n bytes4 public immutable NATIVE_OPTIMISM_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,bytes32,bytes32,address,address,uint32,address,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct OptimismBridgeDataNoToken {\n // interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n uint256 interfaceId;\n // currencyKey of the token beingBridged\n bytes32 currencyKey;\n // socket offchain created hash\n bytes32 metadata;\n // address of receiver of bridged tokens\n address receiverAddress;\n /**\n * OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n */\n address customBridgeAddress;\n // Gas limit required to complete the deposit on L2.\n uint32 l2Gas;\n // Address of the L1 respective L2 ERC20\n address l2Token;\n // additional data , for ll contracts this will be 0x data or empty data\n bytes data;\n }\n\n struct OptimismBridgeData {\n // interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n uint256 interfaceId;\n // currencyKey of the token beingBridged\n bytes32 currencyKey;\n // socket offchain created hash\n bytes32 metadata;\n // address of receiver of bridged tokens\n address receiverAddress;\n /**\n * OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n */\n address customBridgeAddress;\n /// @notice address of token being bridged\n address token;\n // Gas limit required to complete the deposit on L2.\n uint32 l2Gas;\n // Address of the L1 respective L2 ERC20\n address l2Token;\n // additional data , for ll contracts this will be 0x data or empty data\n bytes data;\n }\n\n struct OptimismERC20Data {\n bytes32 currencyKey;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Optimism-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n OptimismBridgeData memory optimismBridgeData = abi.decode(\n bridgeData,\n (OptimismBridgeData)\n );\n\n emit SocketBridge(\n amount,\n optimismBridgeData.token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n optimismBridgeData.receiverAddress,\n optimismBridgeData.metadata\n );\n if (optimismBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositETHTo{value: amount}(\n optimismBridgeData.receiverAddress,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n } else {\n if (optimismBridgeData.interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20(optimismBridgeData.token).safeApprove(\n optimismBridgeData.customBridgeAddress,\n amount\n );\n\n if (optimismBridgeData.interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositERC20To(\n optimismBridgeData.token,\n optimismBridgeData.l2Token,\n optimismBridgeData.receiverAddress,\n amount,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (optimismBridgeData.interfaceId == 2) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .depositTo(optimismBridgeData.receiverAddress, amount);\n return;\n }\n\n if (optimismBridgeData.interfaceId == 3) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .initiateSynthTransfer(\n optimismBridgeData.currencyKey,\n optimismBridgeData.receiverAddress,\n amount\n );\n return;\n }\n }\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param optimismBridgeData encoded data for OptimismBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n OptimismBridgeDataNoToken calldata optimismBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n optimismBridgeData.receiverAddress,\n optimismBridgeData.metadata\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositETHTo{value: bridgeAmount}(\n optimismBridgeData.receiverAddress,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n } else {\n if (optimismBridgeData.interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20(token).safeApprove(\n optimismBridgeData.customBridgeAddress,\n bridgeAmount\n );\n\n if (optimismBridgeData.interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositERC20To(\n token,\n optimismBridgeData.l2Token,\n optimismBridgeData.receiverAddress,\n bridgeAmount,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (optimismBridgeData.interfaceId == 2) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .depositTo(\n optimismBridgeData.receiverAddress,\n bridgeAmount\n );\n return;\n }\n\n if (optimismBridgeData.interfaceId == 3) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .initiateSynthTransfer(\n optimismBridgeData.currencyKey,\n optimismBridgeData.receiverAddress,\n bridgeAmount\n );\n return;\n }\n }\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativeOptimism-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param receiverAddress address of receiver of bridged tokens\n * @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n * @param l2Gas Gas limit required to complete the deposit on L2.\n * @param optimismData extra data needed for optimism bridge\n * @param amount amount being bridged\n * @param interfaceId interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n * @param l2Token Address of the L1 respective L2 ERC20\n * @param data additional data , for ll contracts this will be 0x data or empty data\n */\n function bridgeERC20To(\n address token,\n address receiverAddress,\n address customBridgeAddress,\n uint32 l2Gas,\n OptimismERC20Data calldata optimismData,\n uint256 amount,\n uint256 interfaceId,\n address l2Token,\n bytes calldata data\n ) external payable {\n if (interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(customBridgeAddress, amount);\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n receiverAddress,\n optimismData.metadata\n );\n if (interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(customBridgeAddress).depositERC20To(\n token,\n l2Token,\n receiverAddress,\n amount,\n l2Gas,\n data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (interfaceId == 2) {\n OldL1TokenGateway(customBridgeAddress).depositTo(\n receiverAddress,\n amount\n );\n return;\n }\n\n if (interfaceId == 3) {\n OldL1TokenGateway(customBridgeAddress).initiateSynthTransfer(\n optimismData.currencyKey,\n receiverAddress,\n amount\n );\n return;\n }\n }\n\n /**\n * @notice function to handle native balance bridging to receipent via NativeOptimism-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of receiver of bridged tokens\n * @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n * @param l2Gas Gas limit required to complete the deposit on L2.\n * @param amount amount being bridged\n * @param data additional data , for ll contracts this will be 0x data or empty data\n */\n function bridgeNativeTo(\n address receiverAddress,\n address customBridgeAddress,\n uint32 l2Gas,\n uint256 amount,\n bytes32 metadata,\n bytes calldata data\n ) external payable {\n L1StandardBridge(customBridgeAddress).depositETHTo{value: amount}(\n receiverAddress,\n l2Gas,\n data\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/deployFactory/SocketDeployFactory.sol":{"content":"//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketBridgeBase} from \"../interfaces/ISocketBridgeBase.sol\";\n\n/**\n * @dev In the constructor, set up the initialization code for socket\n * contracts as well as the keccak256 hash of the given initialization code.\n * that will be used to deploy any transient contracts, which will deploy any\n * socket contracts that require the use of a constructor.\n *\n * Socket contract initialization code (29 bytes):\n *\n * 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\n *\n * Description:\n *\n * pc|op|name | [stack] | \n *\n * ** set the first stack item to zero - used later **\n * 00 58 getpc [0] <>\n *\n * ** set second stack item to 32, length of word returned from staticcall **\n * 01 60 push1\n * 02 20 outsize [0, 32] <>\n *\n * ** set third stack item to 0, position of word returned from staticcall **\n * 03 81 dup2 [0, 32, 0] <>\n *\n * ** set fourth stack item to 4, length of selector given to staticcall **\n * 04 58 getpc [0, 32, 0, 4] <>\n *\n * ** set fifth stack item to 28, position of selector given to staticcall **\n * 05 60 push1\n * 06 1c inpos [0, 32, 0, 4, 28] <>\n *\n * ** set the sixth stack item to msg.sender, target address for staticcall **\n * 07 33 caller [0, 32, 0, 4, 28, caller] <>\n *\n * ** set the seventh stack item to msg.gas, gas to forward for staticcall **\n * 08 5a gas [0, 32, 0, 4, 28, caller, gas] <>\n *\n * ** set the eighth stack item to selector, \"what\" to store via mstore **\n * 09 63 push4\n * 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <>\n *\n * ** set the ninth stack item to 0, \"where\" to store via mstore ***\n * 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <>\n *\n * ** call mstore, consume 8 and 9 from the stack, place selector in memory **\n * 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42>\n *\n * ** call staticcall, consume items 2 through 7, place address in memory **\n * 13 fa staticcall [0, 1 (if successful)]
\n *\n * ** flip success bit in second stack item to set to 0 **\n * 14 15 iszero [0, 0]
\n *\n * ** push a third 0 to the stack, position of address in memory **\n * 15 81 dup2 [0, 0, 0]
\n *\n * ** place address from position in memory onto third stack item **\n * 16 51 mload [0, 0, address] <>\n *\n * ** place address to fourth stack item for extcodesize to consume **\n * 17 80 dup1 [0, 0, address, address] <>\n *\n * ** get extcodesize on fourth stack item for extcodecopy **\n * 18 3b extcodesize [0, 0, address, size] <>\n *\n * ** dup and swap size for use by return at end of init code **\n * 19 80 dup1 [0, 0, address, size, size] <>\n * 20 93 swap4 [size, 0, address, size, 0] <>\n *\n * ** push code position 0 to stack and reorder stack items for extcodecopy **\n * 21 80 dup1 [size, 0, address, size, 0, 0] <>\n * 22 91 swap2 [size, 0, address, 0, 0, size] <>\n * 23 92 swap3 [size, 0, size, 0, 0, address] <>\n *\n * ** call extcodecopy, consume four items, clone runtime code to memory **\n * 24 3c extcodecopy [size, 0] \n *\n * ** return to deploy final code in memory **\n * 25 f3 return [] *deployed!*\n */\ncontract SocketDeployFactory is Ownable {\n using SafeTransferLib for ERC20;\n address public immutable disabledRouteAddress;\n\n mapping(address => address) _implementations;\n mapping(uint256 => bool) isDisabled;\n mapping(uint256 => bool) isRouteDeployed;\n mapping(address => bool) canDisableRoute;\n\n event Deployed(address _addr);\n event DisabledRoute(address _addr);\n event Destroyed(address _addr);\n error ContractAlreadyDeployed();\n error NothingToDestroy();\n error AlreadyDisabled();\n error CannotBeDisabled();\n error OnlyDisabler();\n\n constructor(address _owner, address disabledRoute) Ownable(_owner) {\n disabledRouteAddress = disabledRoute;\n canDisableRoute[_owner] = true;\n }\n\n modifier onlyDisabler() {\n if (!canDisableRoute[msg.sender]) {\n revert OnlyDisabler();\n }\n _;\n }\n\n function addDisablerAddress(address disabler) external onlyOwner {\n canDisableRoute[disabler] = true;\n }\n\n function removeDisablerAddress(address disabler) external onlyOwner {\n canDisableRoute[disabler] = false;\n }\n\n /**\n * @notice Deploys a route contract at predetermined location\n * @notice Caller must first deploy the route contract at another location and pass its address as implementation.\n * @param routeId route identifier\n * @param implementationContract address of deployed route contract. Its byte code will be copied to predetermined location.\n */\n function deploy(\n uint256 routeId,\n address implementationContract\n ) external onlyOwner returns (address) {\n // assign the initialization code for the socket contract.\n\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (isRouteDeployed[routeId]) {\n revert ContractAlreadyDeployed();\n }\n\n isRouteDeployed[routeId] = true;\n\n //first we deploy the code we want to deploy on a separate address\n // store the implementation to be retrieved by the socket contract.\n _implementations[routeContractAddress] = implementationContract;\n address addr;\n assembly {\n let encoded_data := add(0x20, initCode) // load initialization code.\n let encoded_size := mload(initCode) // load init code's length.\n addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt\n }\n require(\n addr == routeContractAddress,\n \"Failed to deploy the new socket contract.\"\n );\n emit Deployed(addr);\n return addr;\n }\n\n /**\n * @notice Destroy the route deployed at a location.\n * @param routeId route identifier to be destroyed.\n */\n function destroy(uint256 routeId) external onlyDisabler {\n // determine the address of the socket contract.\n _destroy(routeId);\n }\n\n /**\n * @notice Deploy a disabled contract at destroyed route to handle it gracefully.\n * @param routeId route identifier to be disabled.\n */\n function disableRoute(\n uint256 routeId\n ) external onlyDisabler returns (address) {\n return _disableRoute(routeId);\n }\n\n /**\n * @notice Destroy a list of routeIds\n * @param routeIds array of routeIds to be destroyed.\n */\n function multiDestroy(uint256[] calldata routeIds) external onlyDisabler {\n for (uint32 index = 0; index < routeIds.length; ) {\n _destroy(routeIds[index]);\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Deploy a disabled contract at list of routeIds.\n * @param routeIds array of routeIds to be disabled.\n */\n function multiDisableRoute(\n uint256[] calldata routeIds\n ) external onlyDisabler {\n for (uint32 index = 0; index < routeIds.length; ) {\n _disableRoute(routeIds[index]);\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @dev External view function for calculating a socket contract address\n * given a particular routeId.\n */\n function getContractAddress(\n uint256 routeId\n ) external view returns (address) {\n // determine the address of the socket contract.\n return _getContractAddress(routeId);\n }\n\n //those two functions are getting called by the socket Contract\n function getImplementation()\n external\n view\n returns (address implementation)\n {\n return _implementations[msg.sender];\n }\n\n function _disableRoute(uint256 routeId) internal returns (address) {\n // assign the initialization code for the socket contract.\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (!isRouteDeployed[routeId]) {\n revert CannotBeDisabled();\n }\n\n if (isDisabled[routeId]) {\n revert AlreadyDisabled();\n }\n\n isDisabled[routeId] = true;\n\n //first we deploy the code we want to deploy on a separate address\n // store the implementation to be retrieved by the socket contract.\n _implementations[routeContractAddress] = disabledRouteAddress;\n address addr;\n assembly {\n let encoded_data := add(0x20, initCode) // load initialization code.\n let encoded_size := mload(initCode) // load init code's length.\n addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt.\n }\n require(\n addr == routeContractAddress,\n \"Failed to deploy the new socket contract.\"\n );\n emit Deployed(addr);\n return addr;\n }\n\n function _destroy(uint256 routeId) internal {\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (!isRouteDeployed[routeId]) {\n revert NothingToDestroy();\n }\n ISocketBridgeBase(routeContractAddress).killme();\n emit Destroyed(routeContractAddress);\n }\n\n /**\n * @dev Internal view function for calculating a socket contract address\n * given a particular routeId.\n */\n function _getContractAddress(\n uint256 routeId\n ) internal view returns (address) {\n // determine the address of the socket contract.\n\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n return\n address(\n uint160( // downcast to match the address type.\n uint256( // convert to uint to truncate upper digits.\n keccak256( // compute the CREATE2 hash using 4 inputs.\n abi.encodePacked( // pack all inputs to the hash together.\n hex\"ff\", // start with 0xff to distinguish from RLP.\n address(this), // this contract will be the caller.\n routeId, // the routeId is used as salt.\n keccak256(abi.encodePacked(initCode)) // the init code hash.\n )\n )\n )\n )\n );\n }\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n}\n"},"src/interfaces/ISocketController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketController\n * @notice Interface for SocketController functions.\n * @dev functions can be added here for invocation from external contracts or off-chain\n * only restriction is that this should have functions to manage controllers\n * @author Socket dot tech.\n */\ninterface ISocketController {\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param _controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address _controllerAddress\n ) external returns (uint32);\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param _controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 _controllerId) external;\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param _controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 _controllerId) external returns (address);\n}\n"},"lib/solmate/src/utils/SafeTransferLib.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\nimport {ERC20} from \"../tokens/ERC20.sol\";\n\n/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)\n/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.\n/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.\nlibrary SafeTransferLib {\n /*//////////////////////////////////////////////////////////////\n ETH OPERATIONS\n //////////////////////////////////////////////////////////////*/\n\n function safeTransferETH(address to, uint256 amount) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Transfer the ETH and store if it succeeded or not.\n success := call(gas(), to, amount, 0, 0, 0, 0)\n }\n\n require(success, \"ETH_TRANSFER_FAILED\");\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 OPERATIONS\n //////////////////////////////////////////////////////////////*/\n\n function safeTransferFrom(\n ERC20 token,\n address from,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), from) // Append the \"from\" argument.\n mstore(add(freeMemoryPointer, 36), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 68), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)\n )\n }\n\n require(success, \"TRANSFER_FROM_FAILED\");\n }\n\n function safeTransfer(\n ERC20 token,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n )\n }\n\n require(success, \"TRANSFER_FAILED\");\n }\n\n function safeApprove(\n ERC20 token,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n )\n }\n\n require(success, \"APPROVE_FAILED\");\n }\n}\n"},"src/controllers/BaseController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\n\n/// @title BaseController Controller\n/// @notice Base contract for all controller contracts\nabstract contract BaseController {\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice Address used to identify if it is a Zero address\n address public immutable NULL_ADDRESS = address(0);\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGatewayAddress;\n\n /// @notice immutable variable with instance of SocketRoute to access route functions\n ISocketRoute public immutable socketRoute;\n\n /**\n * @notice Construct the base for all controllers.\n * @param _socketGatewayAddress Socketgateway address, an immutable variable to set.\n * @notice initialize the immutable variables of SocketRoute, SocketGateway\n */\n constructor(address _socketGatewayAddress) {\n socketGatewayAddress = _socketGatewayAddress;\n socketRoute = ISocketRoute(_socketGatewayAddress);\n }\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param routeId routeId mapped to the routrImplementation\n * @param data transactionData generated with arguments of bridgeRequest (offchain or by caller)\n * @return returns the bytes response of the route execution (bridging, refuel or swap executions)\n */\n function _executeRoute(\n uint32 routeId,\n bytes memory data\n ) internal returns (bytes memory) {\n (bool success, bytes memory result) = socketRoute\n .getRoute(routeId)\n .delegatecall(data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n}\n"},"src/interfaces/ISocketRoute.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketRoute\n * @notice Interface for routeManagement functions in SocketGateway.\n * @author Socket dot tech.\n */\ninterface ISocketRoute {\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(address routeAddress) external returns (uint256);\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external;\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) external view returns (address);\n}\n"},"src/SocketGatewayDeployment.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\npragma experimental ABIEncoderV2;\n\nimport \"./utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {LibUtil} from \"./libraries/LibUtil.sol\";\nimport \"./libraries/LibBytes.sol\";\nimport {ISocketRoute} from \"./interfaces/ISocketRoute.sol\";\nimport {ISocketRequest} from \"./interfaces/ISocketRequest.sol\";\nimport {ISocketGateway} from \"./interfaces/ISocketGateway.sol\";\nimport {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from \"./errors/SocketErrors.sol\";\n\n/// @title SocketGatewayContract\n/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer\n/// @author Socket Team\ncontract SocketGateway is Ownable {\n using LibBytes for bytes;\n using LibBytes for bytes4;\n using SafeTransferLib for ERC20;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice storage variable to keep track of total number of routes registered in socketgateway\n uint32 public routesCount = 385;\n\n /// @notice storage variable to keep track of total number of controllers registered in socketgateway\n uint32 public controllerCount;\n\n address public immutable disabledRouteAddress;\n\n uint256 public constant CENT_PERCENT = 100e18;\n\n /// @notice storage mapping for route implementation addresses\n mapping(uint32 => address) public routes;\n\n /// storage mapping for controller implemenation addresses\n mapping(uint32 => address) public controllers;\n\n // Events ------------------------------------------------------------------------------------------------------->\n\n /// @notice Event emitted when a router is added to socketgateway\n event NewRouteAdded(uint32 indexed routeId, address indexed route);\n\n /// @notice Event emitted when a route is disabled\n event RouteDisabled(uint32 indexed routeId);\n\n /// @notice Event emitted when ownership transfer is requested by socket-gateway-owner\n event OwnershipTransferRequested(\n address indexed _from,\n address indexed _to\n );\n\n /// @notice Event emitted when a controller is added to socketgateway\n event ControllerAdded(\n uint32 indexed controllerId,\n address indexed controllerAddress\n );\n\n /// @notice Event emitted when a controller is disabled\n event ControllerDisabled(uint32 indexed controllerId);\n\n constructor(address _owner, address _disabledRoute) Ownable(_owner) {\n disabledRouteAddress = _disabledRoute;\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n\n /*******************************************\n * EXTERNAL AND PUBLIC FUNCTIONS *\n *******************************************/\n\n /**\n * @notice executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in routeData to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeId route identifier\n * @param routeData functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoute(\n uint32 routeId,\n bytes calldata routeData\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = addressAt(routeId).delegatecall(\n routeData\n );\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice swaps a token on sourceChain and split it across multiple bridge-recipients\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped\n * @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address\n * @param swapMultiBridgeRequest request\n */\n function swapAndMultiBridge(\n ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest\n ) external payable {\n uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;\n\n if (\n requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length\n ) {\n revert ArrayLengthMismatch();\n }\n uint256 ratioAggregate;\n for (uint256 index = 0; index < requestLength; ) {\n ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];\n }\n\n if (ratioAggregate != CENT_PERCENT) {\n revert IncorrectBridgeRatios();\n }\n\n (bool swapSuccess, bytes memory swapResult) = addressAt(\n swapMultiBridgeRequest.swapRouteId\n ).delegatecall(swapMultiBridgeRequest.swapImplData);\n\n if (!swapSuccess) {\n assembly {\n revert(add(swapResult, 32), mload(swapResult))\n }\n }\n\n uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));\n\n uint256 bridgedAmount;\n\n for (uint256 index = 0; index < requestLength; ) {\n uint256 bridgingAmount;\n\n // if it is the last bridge request, bridge the remaining amount\n if (index == requestLength - 1) {\n bridgingAmount = amountReceivedFromSwap - bridgedAmount;\n } else {\n // bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap\n bridgingAmount =\n (amountReceivedFromSwap *\n swapMultiBridgeRequest.bridgeRatios[index]) /\n (CENT_PERCENT);\n }\n\n // update the bridged amount, this would be used for computation for last bridgeRequest\n bridgedAmount += bridgingAmount;\n\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n bridgingAmount,\n swapMultiBridgeRequest.bridgeImplDataItems[index]\n );\n\n (bool bridgeSuccess, bytes memory bridgeResult) = addressAt(\n swapMultiBridgeRequest.bridgeRouteIds[index]\n ).delegatecall(bridgeImpldata);\n\n if (!bridgeSuccess) {\n assembly {\n revert(add(bridgeResult, 32), mload(bridgeResult))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice sequentially executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each dataItem to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeIds a list of route identifiers\n * @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoutes(\n uint32[] calldata routeIds,\n bytes[] calldata dataItems\n ) external payable {\n uint256 routeIdslength = routeIds.length;\n if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();\n for (uint256 index = 0; index < routeIdslength; ) {\n (bool success, bytes memory result) = addressAt(routeIds[index])\n .delegatecall(dataItems[index]);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice execute a controller function identified using the controllerId in the request\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param socketControllerRequest socketControllerRequest with controllerId to identify the\n * controllerAddress and byteData constructed using functionSelector\n * of the function being invoked\n * @return bytes data received from the call delegated to controller\n */\n function executeController(\n ISocketGateway.SocketControllerRequest calldata socketControllerRequest\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = controllers[\n socketControllerRequest.controllerId\n ].delegatecall(socketControllerRequest.data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice sequentially executes all controller requests\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each controller-request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param controllerRequests a list of socketControllerRequest\n * Each controllerRequest contains controllerId to identify the controllerAddress and\n * byteData constructed using functionSelector of the function being invoked\n */\n function executeControllers(\n ISocketGateway.SocketControllerRequest[] calldata controllerRequests\n ) external payable {\n for (uint32 index = 0; index < controllerRequests.length; ) {\n (bool success, bytes memory result) = controllers[\n controllerRequests[index].controllerId\n ].delegatecall(controllerRequests[index].data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**************************************\n * ADMIN FUNCTIONS *\n **************************************/\n\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(\n address routeAddress\n ) external onlyOwner returns (uint32) {\n uint32 routeId = routesCount;\n routes[routeId] = routeAddress;\n\n routesCount += 1;\n\n emit NewRouteAdded(routeId, routeAddress);\n\n return routeId;\n }\n\n /**\n * @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress\n This is a restricted function to be called by only socketGatewayOwner\n */\n\n function setApprovalForRouters(\n address[] memory routeAddresses,\n address[] memory tokenAddresses,\n bool isMax\n ) external onlyOwner {\n for (uint32 index = 0; index < routeAddresses.length; ) {\n ERC20(tokenAddresses[index]).approve(\n routeAddresses[index],\n isMax ? type(uint256).max : 0\n );\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address controllerAddress\n ) external onlyOwner returns (uint32) {\n uint32 controllerId = controllerCount;\n\n controllers[controllerId] = controllerAddress;\n\n controllerCount += 1;\n\n emit ControllerAdded(controllerId, controllerAddress);\n\n return controllerId;\n }\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 controllerId) public onlyOwner {\n controllers[controllerId] = disabledRouteAddress;\n emit ControllerDisabled(controllerId);\n }\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external onlyOwner {\n routes[routeId] = disabledRouteAddress;\n emit RouteDisabled(routeId);\n }\n\n /*******************************************\n * RESTRICTED RESCUE FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n\n /*******************************************\n * VIEW FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) public view returns (address) {\n return addressAt(routeId);\n }\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 controllerId) public view returns (address) {\n return controllers[controllerId];\n }\n\n function addressAt(uint32 routeId) public view returns (address) {\n if (routeId < 385) {\n if (routeId < 257) {\n if (routeId < 129) {\n if (routeId < 65) {\n if (routeId < 33) {\n if (routeId < 17) {\n if (routeId < 9) {\n if (routeId < 5) {\n if (routeId < 3) {\n if (routeId == 1) {\n return\n 0x8cd6BaCDAe46B449E2e5B34e348A4eD459c84D50;\n } else {\n return\n 0x31524750Cd865fF6A3540f232754Fb974c18585C;\n }\n } else {\n if (routeId == 3) {\n return\n 0xEd9b37342BeC8f3a2D7b000732ec87498aA6EC6a;\n } else {\n return\n 0xE8704Ef6211F8988Ccbb11badC89841808d66890;\n }\n }\n } else {\n if (routeId < 7) {\n if (routeId == 5) {\n return\n 0x9aFF58C460a461578C433e11C4108D1c4cF77761;\n } else {\n return\n 0x2D1733886cFd465B0B99F1492F40847495f334C5;\n }\n } else {\n if (routeId == 7) {\n return\n 0x715497Be4D130F04B8442F0A1F7a9312D4e54FC4;\n } else {\n return\n 0x90C8a40c38E633B5B0e0d0585b9F7FA05462CaaF;\n }\n }\n }\n } else {\n if (routeId < 13) {\n if (routeId < 11) {\n if (routeId == 9) {\n return\n 0xa402b70FCfF3F4a8422B93Ef58E895021eAdE4F6;\n } else {\n return\n 0xc1B718522E15CD42C4Ac385a929fc2B51f5B892e;\n }\n } else {\n if (routeId == 11) {\n return\n 0xa97bf2f7c26C43c010c349F52f5eA5dC49B2DD38;\n } else {\n return\n 0x969423d71b62C81d2f28d707364c9Dc4a0764c53;\n }\n }\n } else {\n if (routeId < 15) {\n if (routeId == 13) {\n return\n 0xF86729934C083fbEc8C796068A1fC60701Ea1207;\n } else {\n return\n 0xD7cC2571F5823caCA26A42690D2BE7803DD5393f;\n }\n } else {\n if (routeId == 15) {\n return\n 0x7c8837a279bbbf7d8B93413763176de9F65d5bB9;\n } else {\n return\n 0x13b81C27B588C07D04458ed7dDbdbD26D1e39bcc;\n }\n }\n }\n }\n } else {\n if (routeId < 25) {\n if (routeId < 21) {\n if (routeId < 19) {\n if (routeId == 17) {\n return\n 0x52560Ac678aFA1345D15474287d16Dc1eA3F78aE;\n } else {\n return\n 0x1E31e376551459667cd7643440c1b21CE69065A0;\n }\n } else {\n if (routeId == 19) {\n return\n 0xc57D822CB3288e7b97EF8f8af0EcdcD1B783529B;\n } else {\n return\n 0x2197A1D9Af24b4d6a64Bff95B4c29Fcd3Ff28C30;\n }\n }\n } else {\n if (routeId < 23) {\n if (routeId == 21) {\n return\n 0xE3700feAa5100041Bf6b7AdBA1f72f647809Fd00;\n } else {\n return\n 0xc02E8a0Fdabf0EeFCEA025163d90B5621E2b9948;\n }\n } else {\n if (routeId == 23) {\n return\n 0xF5144235E2926cAb3c69b30113254Fa632f72d62;\n } else {\n return\n 0xBa3F92313B00A1f7Bc53b2c24EB195c8b2F57682;\n }\n }\n }\n } else {\n if (routeId < 29) {\n if (routeId < 27) {\n if (routeId == 25) {\n return\n 0x77a6856fe1fFA5bEB55A1d2ED86E27C7c482CB76;\n } else {\n return\n 0x4826Ff4e01E44b1FCEFBfb38cd96687Eb7786b44;\n }\n } else {\n if (routeId == 27) {\n return\n 0x55FF3f5493cf5e80E76DEA7E327b9Cd8440Af646;\n } else {\n return\n 0xF430Db544bE9770503BE4aa51997aA19bBd5BA4f;\n }\n }\n } else {\n if (routeId < 31) {\n if (routeId == 29) {\n return\n 0x0f166446ce1484EE3B0663E7E67DF10F5D240115;\n } else {\n return\n 0x6365095D92537f242Db5EdFDd572745E72aC33d9;\n }\n } else {\n if (routeId == 31) {\n return\n 0x5c7BC93f06ce3eAe75ADf55E10e23d2c1dE5Bc65;\n } else {\n return\n 0xe46383bAD90d7A08197ccF08972e9DCdccCE9BA4;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 49) {\n if (routeId < 41) {\n if (routeId < 37) {\n if (routeId < 35) {\n if (routeId == 33) {\n return\n 0xf0f21710c071E3B728bdc4654c3c0b873aAaa308;\n } else {\n return\n 0x63Bc9ed3AcAAeB0332531C9fB03b0a2352E9Ff25;\n }\n } else {\n if (routeId == 35) {\n return\n 0xd1CE808625CB4007a1708824AE82CdB0ece57De9;\n } else {\n return\n 0x57BbB148112f4ba224841c3FE018884171004661;\n }\n }\n } else {\n if (routeId < 39) {\n if (routeId == 37) {\n return\n 0x037f7d6933036F34DFabd40Ff8e4D789069f92e3;\n } else {\n return\n 0xeF978c280915CfF3Dca4EDfa8932469e40ADA1e1;\n }\n } else {\n if (routeId == 39) {\n return\n 0x92ee9e071B13f7ecFD62B7DED404A16CBc223CD3;\n } else {\n return\n 0x94Ae539c186e41ed762271338Edf140414D1E442;\n }\n }\n }\n } else {\n if (routeId < 45) {\n if (routeId < 43) {\n if (routeId == 41) {\n return\n 0x30A64BBe4DdBD43dA2368EFd1eB2d80C10d84DAb;\n } else {\n return\n 0x3aEABf81c1Dc4c1b73d5B2a95410f126426FB596;\n }\n } else {\n if (routeId == 43) {\n return\n 0x25b08aB3D0C8ea4cC9d967b79688C6D98f3f563a;\n } else {\n return\n 0xea40cB15C9A3BBd27af6474483886F7c0c9AE406;\n }\n }\n } else {\n if (routeId < 47) {\n if (routeId == 45) {\n return\n 0x9580113Cc04e5a0a03359686304EF3A80b936Dd3;\n } else {\n return\n 0xD211c826d568957F3b66a3F4d9c5f68cCc66E619;\n }\n } else {\n if (routeId == 47) {\n return\n 0xCEE24D0635c4C56315d133b031984d4A6f509476;\n } else {\n return\n 0x3922e6B987983229798e7A20095EC372744d4D4c;\n }\n }\n }\n }\n } else {\n if (routeId < 57) {\n if (routeId < 53) {\n if (routeId < 51) {\n if (routeId == 49) {\n return\n 0x2d92D03413d296e1F31450479349757187F2a2b7;\n } else {\n return\n 0x0fe5308eE90FC78F45c89dB6053eA859097860CA;\n }\n } else {\n if (routeId == 51) {\n return\n 0x08Ba68e067C0505bAF0C1311E0cFB2B1B59b969c;\n } else {\n return\n 0x9bee5DdDF75C24897374f92A534B7A6f24e97f4a;\n }\n }\n } else {\n if (routeId < 55) {\n if (routeId == 53) {\n return\n 0x1FC5A90B232208704B930c1edf82FFC6ACc02734;\n } else {\n return\n 0x5b1B0417cb44c761C2a23ee435d011F0214b3C85;\n }\n } else {\n if (routeId == 55) {\n return\n 0x9d70cDaCA12A738C283020760f449D7816D592ec;\n } else {\n return\n 0x95a23b9CB830EcCFDDD5dF56A4ec665e3381Fa12;\n }\n }\n }\n } else {\n if (routeId < 61) {\n if (routeId < 59) {\n if (routeId == 57) {\n return\n 0x483a957Cf1251c20e096C35c8399721D1200A3Fc;\n } else {\n return\n 0xb4AD39Cb293b0Ec7FEDa743442769A7FF04987CD;\n }\n } else {\n if (routeId == 59) {\n return\n 0x4C543AD78c1590D81BAe09Fc5B6Df4132A2461d0;\n } else {\n return\n 0x471d5E5195c563902781734cfe1FF3981F8B6c86;\n }\n }\n } else {\n if (routeId < 63) {\n if (routeId == 61) {\n return\n 0x1B12a54B5E606D95B8B8D123c9Cb09221Ee37584;\n } else {\n return\n 0xE4127cC550baC433646a7D998775a84daC16c7f3;\n }\n } else {\n if (routeId == 63) {\n return\n 0xecb1b55AB12E7dd788D585c6C5cD61B5F87be836;\n } else {\n return\n 0xf91ef487C5A1579f70601b6D347e19756092eEBf;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 97) {\n if (routeId < 81) {\n if (routeId < 73) {\n if (routeId < 69) {\n if (routeId < 67) {\n if (routeId == 65) {\n return\n 0x34a16a7e9BADEEFD4f056310cbE0b1423Fa1b760;\n } else {\n return\n 0x60E10E80c7680f429dBbC232830BEcd3D623c4CF;\n }\n } else {\n if (routeId == 67) {\n return\n 0x66465285B8D65362A1d86CE00fE2bE949Fd6debF;\n } else {\n return\n 0x5aB231B7e1A3A74a48f67Ab7bde5Cdd4267022E0;\n }\n }\n } else {\n if (routeId < 71) {\n if (routeId == 69) {\n return\n 0x3A1C3633eE79d43366F5c67802a746aFD6b162Ba;\n } else {\n return\n 0x0C4BfCbA8dC3C811437521a80E81e41DAF479039;\n }\n } else {\n if (routeId == 71) {\n return\n 0x6caf25d2e139C5431a1FA526EAf8d73ff2e6252C;\n } else {\n return\n 0x74ad21e09FDa68638CE14A3009A79B6D16574257;\n }\n }\n }\n } else {\n if (routeId < 77) {\n if (routeId < 75) {\n if (routeId == 73) {\n return\n 0xD4923A61008894b99cc1CD3407eF9524f02aA0Ca;\n } else {\n return\n 0x6F159b5EB823BD415886b9271aA2A723a00a1987;\n }\n } else {\n if (routeId == 75) {\n return\n 0x742a8aA42E7bfB4554dE30f4Fb07FFb6f2068863;\n } else {\n return\n 0x4AE9702d3360400E47B446e76DE063ACAb930101;\n }\n }\n } else {\n if (routeId < 79) {\n if (routeId == 77) {\n return\n 0x0E19a0a44ddA7dAD854ec5Cc867d16869c4E80F4;\n } else {\n return\n 0xE021A51968f25148F726E326C88d2556c5647557;\n }\n } else {\n if (routeId == 79) {\n return\n 0x64287BDDDaeF4d94E4599a3D882bed29E6Ada4B6;\n } else {\n return\n 0xcBB57Fd2e19cc7e9D444d5b4325A2F1047d0C73f;\n }\n }\n }\n }\n } else {\n if (routeId < 89) {\n if (routeId < 85) {\n if (routeId < 83) {\n if (routeId == 81) {\n return\n 0x373DE80DF7D82cFF6D76F29581b360C56331e957;\n } else {\n return\n 0x0466356E131AD61596a51F86BAd1C03A328960D8;\n }\n } else {\n if (routeId == 83) {\n return\n 0x01726B960992f1b74311b248E2a922fC707d43A6;\n } else {\n return\n 0x2E21bdf9A4509b89795BCE7E132f248a75814CEc;\n }\n }\n } else {\n if (routeId < 87) {\n if (routeId == 85) {\n return\n 0x769512b23aEfF842379091d3B6E4B5456F631D42;\n } else {\n return\n 0xe7eD9be946a74Ec19325D39C6EEb57887ccB2B0D;\n }\n } else {\n if (routeId == 87) {\n return\n 0xc4D01Ec357c2b511d10c15e6b6974380F0E62e67;\n } else {\n return\n 0x5bC49CC9dD77bECF2fd3A3C55611e84E69AFa3AE;\n }\n }\n }\n } else {\n if (routeId < 93) {\n if (routeId < 91) {\n if (routeId == 89) {\n return\n 0x48bcD879954fA14e7DbdAeb56F79C1e9DDcb69ec;\n } else {\n return\n 0xE929bDde21b462572FcAA4de6F49B9D3246688D0;\n }\n } else {\n if (routeId == 91) {\n return\n 0x85Aae300438222f0e3A9Bc870267a5633A9438bd;\n } else {\n return\n 0x51f72E1096a81C55cd142d66d39B688C657f9Be8;\n }\n }\n } else {\n if (routeId < 95) {\n if (routeId == 93) {\n return\n 0x3A8a05BF68ac54B01E6C0f492abF97465F3d15f9;\n } else {\n return\n 0x145aA67133F0c2C36b9771e92e0B7655f0D59040;\n }\n } else {\n if (routeId == 95) {\n return\n 0xa030315d7DB11F9892758C9e7092D841e0ADC618;\n } else {\n return\n 0xdF1f8d81a3734bdDdEfaC6Ca1596E081e57c3044;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 113) {\n if (routeId < 105) {\n if (routeId < 101) {\n if (routeId < 99) {\n if (routeId == 97) {\n return\n 0xFF2833123B58aa05d04D7fb99f5FB768B2b435F8;\n } else {\n return\n 0xc8f09c1fD751C570233765f71b0e280d74e6e743;\n }\n } else {\n if (routeId == 99) {\n return\n 0x3026DA6Ceca2E5A57A05153653D9212FFAaA49d8;\n } else {\n return\n 0xdE68Ee703dE0D11f67B0cE5891cB4a903de6D160;\n }\n }\n } else {\n if (routeId < 103) {\n if (routeId == 101) {\n return\n 0xE23a7730e81FB4E87A6D0bd9f63EE77ac86C3DA4;\n } else {\n return\n 0x8b1DBe04aD76a7d8bC079cACd3ED4D99B897F4a0;\n }\n } else {\n if (routeId == 103) {\n return\n 0xBB227240FA459b69C6889B2b8cb1BE76F118061f;\n } else {\n return\n 0xC062b9b3f0dB28BB8afAfcD4d075729344114ffe;\n }\n }\n }\n } else {\n if (routeId < 109) {\n if (routeId < 107) {\n if (routeId == 105) {\n return\n 0x553188Aa45f5FDB83EC4Ca485982F8fC082480D1;\n } else {\n return\n 0x0109d83D746EaCb6d4014953D9E12d6ca85e330b;\n }\n } else {\n if (routeId == 107) {\n return\n 0x45B1bEd29812F5bf6711074ACD180B2aeB783AD9;\n } else {\n return\n 0xdA06eC8c19aea31D77F60299678Cba40E743e1aD;\n }\n }\n } else {\n if (routeId < 111) {\n if (routeId == 109) {\n return\n 0x3cC5235c97d975a9b4FD4501B3446c981ea3D855;\n } else {\n return\n 0xa1827267d6Bd989Ff38580aE3d9deff6Acf19163;\n }\n } else {\n if (routeId == 111) {\n return\n 0x3663CAA0433A3D4171b3581Cf2410702840A735A;\n } else {\n return\n 0x7575D0a7614F655BA77C74a72a43bbd4fA6246a3;\n }\n }\n }\n }\n } else {\n if (routeId < 121) {\n if (routeId < 117) {\n if (routeId < 115) {\n if (routeId == 113) {\n return\n 0x2516Defc18bc07089c5dAFf5eafD7B0EF64611E2;\n } else {\n return\n 0xfec5FF08E20fbc107a97Af2D38BD0025b84ee233;\n }\n } else {\n if (routeId == 115) {\n return\n 0x0FB5763a87242B25243e23D73f55945fE787523A;\n } else {\n return\n 0xe4C00db89678dBf8391f430C578Ca857Dd98aDE1;\n }\n }\n } else {\n if (routeId < 119) {\n if (routeId == 117) {\n return\n 0x8F2A22061F9F35E64f14523dC1A5f8159e6a21B7;\n } else {\n return\n 0x18e4b838ae966917E20E9c9c5Ad359cDD38303bB;\n }\n } else {\n if (routeId == 119) {\n return\n 0x61ACb1d3Dcb3e3429832A164Cc0fC9849fb75A4a;\n } else {\n return\n 0x7681e3c8e7A41DCA55C257cc0d1Ae757f5530E65;\n }\n }\n }\n } else {\n if (routeId < 125) {\n if (routeId < 123) {\n if (routeId == 121) {\n return\n 0x806a2AB9748C3D1DB976550890E3f528B7E8Faec;\n } else {\n return\n 0xBDb8A5DD52C2c239fbC31E9d43B763B0197028FF;\n }\n } else {\n if (routeId == 123) {\n return\n 0x474EC9203706010B9978D6bD0b105D36755e4848;\n } else {\n return\n 0x8dfd0D829b303F2239212E591a0F92a32880f36E;\n }\n }\n } else {\n if (routeId < 127) {\n if (routeId == 125) {\n return\n 0xad4BcE9745860B1adD6F1Bd34a916f050E4c82C2;\n } else {\n return\n 0xBC701115b9fe14bC8CC5934cdC92517173e308C4;\n }\n } else {\n if (routeId == 127) {\n return\n 0x0D1918d786Db8546a11aDeD475C98370E06f255E;\n } else {\n return\n 0xee44f57cD6936DB55B99163f3Df367B01EdA785a;\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 193) {\n if (routeId < 161) {\n if (routeId < 145) {\n if (routeId < 137) {\n if (routeId < 133) {\n if (routeId < 131) {\n if (routeId == 129) {\n return\n 0x63044521fe5a1e488D7eD419cD0e35b7C24F2aa7;\n } else {\n return\n 0x410085E73BD85e90d97b84A68C125aDB9F91f85b;\n }\n } else {\n if (routeId == 131) {\n return\n 0x7913fe97E07C7A397Ec274Ab1d4E2622C88EC5D1;\n } else {\n return\n 0x977f9fE93c064DCf54157406DaABC3a722e8184C;\n }\n }\n } else {\n if (routeId < 135) {\n if (routeId == 133) {\n return\n 0xCD2236468722057cFbbABad2db3DEA9c20d5B01B;\n } else {\n return\n 0x17c7287A491cf5Ff81E2678cF2BfAE4333F6108c;\n }\n } else {\n if (routeId == 135) {\n return\n 0x354D9a5Dbf96c71B79a265F03B595C6Fdc04dadd;\n } else {\n return\n 0xb4e409EB8e775eeFEb0344f9eee884cc7ed21c69;\n }\n }\n }\n } else {\n if (routeId < 141) {\n if (routeId < 139) {\n if (routeId == 137) {\n return\n 0xa1a3c4670Ad69D9be4ab2D39D1231FEC2a63b519;\n } else {\n return\n 0x4589A22199870729C1be5CD62EE93BeD858113E6;\n }\n } else {\n if (routeId == 139) {\n return\n 0x8E7b864dB26Bd6C798C38d4Ba36EbA0d6602cF11;\n } else {\n return\n 0xA2D17C7260a4CB7b9854e89Fc367E80E87872a2d;\n }\n }\n } else {\n if (routeId < 143) {\n if (routeId == 141) {\n return\n 0xC7F0EDf0A1288627b0432304918A75e9084CBD46;\n } else {\n return\n 0xE4B4EF1f9A4aBFEdB371fA7a6143993B15d4df25;\n }\n } else {\n if (routeId == 143) {\n return\n 0xfe3D84A2Ef306FEBb5452441C9BDBb6521666F6A;\n } else {\n return\n 0x8A12B6C64121920110aE58F7cd67DfEc21c6a4C3;\n }\n }\n }\n }\n } else {\n if (routeId < 153) {\n if (routeId < 149) {\n if (routeId < 147) {\n if (routeId == 145) {\n return\n 0x76c4d9aFC4717a2BAac4e5f26CccF02351f7a3DA;\n } else {\n return\n 0xd4719BA550E397aeAcca1Ad2201c1ba69024FAAf;\n }\n } else {\n if (routeId == 147) {\n return\n 0x9646126Ce025224d1682C227d915a386efc0A1Fb;\n } else {\n return\n 0x4DD8Af2E3F2044842f0247920Bc4BABb636915ea;\n }\n }\n } else {\n if (routeId < 151) {\n if (routeId == 149) {\n return\n 0x8e8a327183Af0cf8C2ece9F0ed547C42A160D409;\n } else {\n return\n 0x9D49614CaE1C685C71678CA6d8CDF7584bfd0740;\n }\n } else {\n if (routeId == 151) {\n return\n 0x5a00ef257394cbc31828d48655E3d39e9c11c93d;\n } else {\n return\n 0xC9a2751b38d3dDD161A41Ca0135C5C6c09EC1d56;\n }\n }\n }\n } else {\n if (routeId < 157) {\n if (routeId < 155) {\n if (routeId == 153) {\n return\n 0x7e1c261640a525C94Ca4f8c25b48CF754DD83590;\n } else {\n return\n 0x409Fe24ba6F6BD5aF31C1aAf8059b986A3158233;\n }\n } else {\n if (routeId == 155) {\n return\n 0x704Cf5BFDADc0f55fDBb53B6ed8B582E018A72A2;\n } else {\n return\n 0x3982bF65d7d6E77E3b6661cd6F6468c247512737;\n }\n }\n } else {\n if (routeId < 159) {\n if (routeId == 157) {\n return\n 0x3982b9f26FFD67a13Ee371e2C0a9Da338BA70E7f;\n } else {\n return\n 0x6D834AB385900c1f49055D098e90264077FbC4f2;\n }\n } else {\n if (routeId == 159) {\n return\n 0x11FE5F70779A094B7166B391e1Fb73d422eF4e4d;\n } else {\n return\n 0xD347e4E47280d21F13B73D89c6d16f867D50DD13;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 177) {\n if (routeId < 169) {\n if (routeId < 165) {\n if (routeId < 163) {\n if (routeId == 161) {\n return\n 0xb6035eDD53DDA28d8B69b4ae9836E40C80306CD7;\n } else {\n return\n 0x54c884e6f5C7CcfeCA990396c520C858c922b6CA;\n }\n } else {\n if (routeId == 163) {\n return\n 0x5eA93E240b083d686558Ed607BC013d88057cE46;\n } else {\n return\n 0x4C7131eE812De685cBe4e2cCb033d46ecD46612E;\n }\n }\n } else {\n if (routeId < 167) {\n if (routeId == 165) {\n return\n 0xc1a5Be9F0c33D8483801D702111068669f81fF91;\n } else {\n return\n 0x9E5fAb91455Be5E5b2C05967E73F456c8118B1Fc;\n }\n } else {\n if (routeId == 167) {\n return\n 0x3d9A05927223E0DC2F382831770405885e22F0d8;\n } else {\n return\n 0x6303A011fB6063f5B1681cb5a9938EA278dc6128;\n }\n }\n }\n } else {\n if (routeId < 173) {\n if (routeId < 171) {\n if (routeId == 169) {\n return\n 0xe9c60795c90C66797e4c8E97511eA07CdAda32bE;\n } else {\n return\n 0xD56cC98e69A1e13815818b466a8aA6163d84234A;\n }\n } else {\n if (routeId == 171) {\n return\n 0x47EbB9D36a6e40895316cD894E4860D774E2c531;\n } else {\n return\n 0xA5EB293629410065d14a7B1663A67829b0618292;\n }\n }\n } else {\n if (routeId < 175) {\n if (routeId == 173) {\n return\n 0x1b3B4C8146F939cE00899db8B3ddeF0062b7E023;\n } else {\n return\n 0x257Bbc11653625EbfB6A8587eF4f4FBe49828EB3;\n }\n } else {\n if (routeId == 175) {\n return\n 0x44cc979C01b5bB1eAC21301E73C37200dFD06F59;\n } else {\n return\n 0x2972fDF43352225D82754C0174Ff853819D1ef2A;\n }\n }\n }\n }\n } else {\n if (routeId < 185) {\n if (routeId < 181) {\n if (routeId < 179) {\n if (routeId == 177) {\n return\n 0x3e54144f032648A04D62d79f7B4b93FF3aC2333b;\n } else {\n return\n 0x444016102dB8adbE73C3B6703a1ea7F2f75A510D;\n }\n } else {\n if (routeId == 179) {\n return\n 0xac079143f98a6eb744Fde34541ebF243DF5B5dED;\n } else {\n return\n 0xAe9010767Fb112d29d35CEdfba2b372Ad7A308d3;\n }\n }\n } else {\n if (routeId < 183) {\n if (routeId == 181) {\n return\n 0xfE0BCcF9cCC2265D5fB3450743f17DfE57aE1e56;\n } else {\n return\n 0x04ED8C0545716119437a45386B1d691C63234C7D;\n }\n } else {\n if (routeId == 183) {\n return\n 0x636c14013e531A286Bc4C848da34585f0bB73d59;\n } else {\n return\n 0x2Fa67fc7ECC5cAA01C653d3BFeA98ecc5db9C42A;\n }\n }\n }\n } else {\n if (routeId < 189) {\n if (routeId < 187) {\n if (routeId == 185) {\n return\n 0x23e9a0FC180818aA872D2079a985217017E97bd9;\n } else {\n return\n 0x79A95c3Ef81b3ae64ee03A9D5f73e570495F164E;\n }\n } else {\n if (routeId == 187) {\n return\n 0xa7EA0E88F04a84ba0ad1E396cb07Fa3fDAD7dF6D;\n } else {\n return\n 0xd23cA1278a2B01a3C0Ca1a00d104b11c1Ebe6f42;\n }\n }\n } else {\n if (routeId < 191) {\n if (routeId == 189) {\n return\n 0x707bc4a9FA2E349AED5df4e9f5440C15aA9D14Bd;\n } else {\n return\n 0x7E290F2dd539Ac6CE58d8B4C2B944931a1fD3612;\n }\n } else {\n if (routeId == 191) {\n return\n 0x707AA5503088Ce06Ba450B6470A506122eA5c8eF;\n } else {\n return\n 0xFbB3f7BF680deeb149f4E7BC30eA3DDfa68F3C3f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 225) {\n if (routeId < 209) {\n if (routeId < 201) {\n if (routeId < 197) {\n if (routeId < 195) {\n if (routeId == 193) {\n return\n 0xDE74aD8cCC3dbF14992f49Cf24f36855912f4934;\n } else {\n return\n 0x409BA83df7777F070b2B50a10a41DE2468d2a3B3;\n }\n } else {\n if (routeId == 195) {\n return\n 0x5CB7Be90A5DD7CfDa54e87626e254FE8C18255B4;\n } else {\n return\n 0x0A684fE12BC64fb72B59d0771a566F49BC090356;\n }\n }\n } else {\n if (routeId < 199) {\n if (routeId == 197) {\n return\n 0xDf30048d91F8FA2bCfC54952B92bFA8e161D3360;\n } else {\n return\n 0x050825Fff032a547C47061CF0696FDB0f65AEa5D;\n }\n } else {\n if (routeId == 199) {\n return\n 0xd55e671dAC1f03d366d8535073ada5DB2Aab1Ea2;\n } else {\n return\n 0x9470C704A9616c8Cd41c595Fcd2181B6fe2183C2;\n }\n }\n }\n } else {\n if (routeId < 205) {\n if (routeId < 203) {\n if (routeId == 201) {\n return\n 0x2D9ffD275181F5865d5e11CbB4ced1521C4dF9f1;\n } else {\n return\n 0x816d28Dec10ec95DF5334f884dE85cA6215918d8;\n }\n } else {\n if (routeId == 203) {\n return\n 0xd1f87267c4A43835E666dd69Df077e578A3b6299;\n } else {\n return\n 0x39E89Bde9DACbe5468C025dE371FbDa12bDeBAB1;\n }\n }\n } else {\n if (routeId < 207) {\n if (routeId == 205) {\n return\n 0x7b40A3207956ecad6686E61EfcaC48912FcD0658;\n } else {\n return\n 0x090cF10D793B1Efba9c7D76115878814B663859A;\n }\n } else {\n if (routeId == 207) {\n return\n 0x312A59c06E41327878F2063eD0e9c282C1DA3AfC;\n } else {\n return\n 0x4F1188f46236DD6B5de11Ebf2a9fF08716E7DeB6;\n }\n }\n }\n }\n } else {\n if (routeId < 217) {\n if (routeId < 213) {\n if (routeId < 211) {\n if (routeId == 209) {\n return\n 0x0A6F9a3f4fA49909bBfb4339cbE12B42F53BbBeD;\n } else {\n return\n 0x01d13d7aCaCbB955B81935c80ffF31e14BdFa71f;\n }\n } else {\n if (routeId == 211) {\n return\n 0x691a14Fa6C7360422EC56dF5876f84d4eDD7f00A;\n } else {\n return\n 0x97Aad18d886d181a9c726B3B6aE15a0A69F5aF73;\n }\n }\n } else {\n if (routeId < 215) {\n if (routeId == 213) {\n return\n 0x2917241371D2099049Fa29432DC46735baEC33b4;\n } else {\n return\n 0x5F20F20F7890c2e383E29D4147C9695A371165f5;\n }\n } else {\n if (routeId == 215) {\n return\n 0xeC0a60e639958335662C5219A320cCEbb56C6077;\n } else {\n return\n 0x96d63CF5062975C09845d17ec672E10255866053;\n }\n }\n }\n } else {\n if (routeId < 221) {\n if (routeId < 219) {\n if (routeId == 217) {\n return\n 0xFF57429e57D383939CAB50f09ABBfB63C0e6c9AD;\n } else {\n return\n 0x18E393A7c8578fb1e235C242076E50013cDdD0d7;\n }\n } else {\n if (routeId == 219) {\n return\n 0xE7E5238AF5d61f52E9B4ACC025F713d1C0216507;\n } else {\n return\n 0x428401D4d0F25A2EE1DA4d5366cB96Ded425D9bD;\n }\n }\n } else {\n if (routeId < 223) {\n if (routeId == 221) {\n return\n 0x42E5733551ff1Ee5B48Aa9fc2B61Af9b58C812E6;\n } else {\n return\n 0x64Df9c7A0551B056d860Bc2419Ca4c1EF75320bE;\n }\n } else {\n if (routeId == 223) {\n return\n 0x46006925506145611bBf0263243D8627dAf26B0F;\n } else {\n return\n 0x8D64BE884314662804eAaB884531f5C50F4d500c;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 241) {\n if (routeId < 233) {\n if (routeId < 229) {\n if (routeId < 227) {\n if (routeId == 225) {\n return\n 0x157a62D92D07B5ce221A5429645a03bBaCE85373;\n } else {\n return\n 0xaF037D33e1F1F2F87309B425fe8a9d895Ef3722B;\n }\n } else {\n if (routeId == 227) {\n return\n 0x921D1154E494A2f7218a37ad7B17701f94b4B40e;\n } else {\n return\n 0xF282b4555186d8Dea51B8b3F947E1E0568d09bc4;\n }\n }\n } else {\n if (routeId < 231) {\n if (routeId == 229) {\n return\n 0xa794E2E1869765a4600b3DFd8a4ebcF16350f6B6;\n } else {\n return\n 0xFEFb048e20c5652F7940A49B1980E0125Ec4D358;\n }\n } else {\n if (routeId == 231) {\n return\n 0x220104b641971e9b25612a8F001bf48AbB23f1cF;\n } else {\n return\n 0xcB9D373Bb54A501B35dd3be5bF4Ba43cA31F7035;\n }\n }\n }\n } else {\n if (routeId < 237) {\n if (routeId < 235) {\n if (routeId == 233) {\n return\n 0x37D627F56e3FF36aC316372109ea82E03ac97DAc;\n } else {\n return\n 0x4E81355FfB4A271B4EA59ff78da2b61c7833161f;\n }\n } else {\n if (routeId == 235) {\n return\n 0xADd8D65cAF6Cc9ad73127B49E16eA7ac29d91e87;\n } else {\n return\n 0x630F9b95626487dfEAe3C97A44DB6C59cF35d996;\n }\n }\n } else {\n if (routeId < 239) {\n if (routeId == 237) {\n return\n 0x78CE2BC8238B679680A67FCB98C5A60E4ec17b2D;\n } else {\n return\n 0xA38D776028eD1310b9A6b086f67F788201762E21;\n }\n } else {\n if (routeId == 239) {\n return\n 0x7Bb5178827B76B86753Ed62a0d662c72cEcb1bD3;\n } else {\n return\n 0x4faC26f61C76eC5c3D43b43eDfAFF0736Ae0e3da;\n }\n }\n }\n }\n } else {\n if (routeId < 249) {\n if (routeId < 245) {\n if (routeId < 243) {\n if (routeId == 241) {\n return\n 0x791Bb49bfFA7129D6889FDB27744422Ac4571A85;\n } else {\n return\n 0x26766fFEbb5fa564777913A6f101dF019AB32afa;\n }\n } else {\n if (routeId == 243) {\n return\n 0x05e98E5e95b4ECBbbAf3258c3999Cc81ed8048Be;\n } else {\n return\n 0xC5c4621e52f1D6A1825A5ed4F95855401a3D9C6b;\n }\n }\n } else {\n if (routeId < 247) {\n if (routeId == 245) {\n return\n 0xfcb15f909BA7FC7Ea083503Fb4c1020203c107EB;\n } else {\n return\n 0xbD27603279d969c74f2486ad14E71080829DFd38;\n }\n } else {\n if (routeId == 247) {\n return\n 0xff2f756BcEcC1A55BFc09a30cc5F64720458cFCB;\n } else {\n return\n 0x3bfB968FEbC12F4e8420B2d016EfcE1E615f7246;\n }\n }\n }\n } else {\n if (routeId < 253) {\n if (routeId < 251) {\n if (routeId == 249) {\n return\n 0x982EE9Ffe23051A2ec945ed676D864fa8345222b;\n } else {\n return\n 0xe101899100785E74767d454FFF0131277BaD48d9;\n }\n } else {\n if (routeId == 251) {\n return\n 0x4F730C0c6b3B5B7d06ca511379f4Aa5BfB2E9525;\n } else {\n return\n 0x5499c36b365795e4e0Ef671aF6C2ce26D7c78265;\n }\n }\n } else {\n if (routeId < 255) {\n if (routeId == 253) {\n return\n 0x8AF51F7237Fc8fB2fc3E700488a94a0aC6Ad8b5a;\n } else {\n return\n 0xda8716df61213c0b143F2849785FB85928084857;\n }\n } else {\n if (routeId == 255) {\n return\n 0xF040Cf9b1ebD11Bf28e04e80740DF3DDe717e4f5;\n } else {\n return\n 0xB87ba32f759D14023C7520366B844dF7f0F036C2;\n }\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 321) {\n if (routeId < 289) {\n if (routeId < 273) {\n if (routeId < 265) {\n if (routeId < 261) {\n if (routeId < 259) {\n if (routeId == 257) {\n return\n 0x0Edde681b8478F0c3194f468EdD2dB5e75c65CDD;\n } else {\n return\n 0x59C70900Fca06eE2aCE1BDd5A8D0Af0cc3BBA720;\n }\n } else {\n if (routeId == 259) {\n return\n 0x8041F0f180D17dD07087199632c45E17AeB0BAd5;\n } else {\n return\n 0x4fB4727064BA595995DD516b63b5921Df9B93aC6;\n }\n }\n } else {\n if (routeId < 263) {\n if (routeId == 261) {\n return\n 0x86e98b594565857eD098864F560915C0dAfd6Ea1;\n } else {\n return\n 0x70f8818E8B698EFfeCd86A513a4c87c0c380Bef6;\n }\n } else {\n if (routeId == 263) {\n return\n 0x78Ed227c8A897A21Da2875a752142dd80d865158;\n } else {\n return\n 0xd02A30BB5C3a8C51d2751A029a6fcfDE2Af9fbc6;\n }\n }\n }\n } else {\n if (routeId < 269) {\n if (routeId < 267) {\n if (routeId == 265) {\n return\n 0x0F00d5c5acb24e975e2a56730609f7F40aa763b8;\n } else {\n return\n 0xC3e2091edc2D3D9D98ba09269138b617B536834A;\n }\n } else {\n if (routeId == 267) {\n return\n 0xa6FbaF7F30867C9633908998ea8C3da28920E75C;\n } else {\n return\n 0xE6dDdcD41E2bBe8122AE32Ac29B8fbAB79CD21d9;\n }\n }\n } else {\n if (routeId < 271) {\n if (routeId == 269) {\n return\n 0x537aa8c1Ef6a8Eaf039dd6e1Eb67694a48195cE4;\n } else {\n return\n 0x96ABAC485fd2D0B03CF4a10df8BD58b8dED28300;\n }\n } else {\n if (routeId == 271) {\n return\n 0xda8e7D46d04Bd4F62705Cd80355BDB6d441DafFD;\n } else {\n return\n 0xbE50018E7a5c67E2e5f5414393e971CC96F293f2;\n }\n }\n }\n }\n } else {\n if (routeId < 281) {\n if (routeId < 277) {\n if (routeId < 275) {\n if (routeId == 273) {\n return\n 0xa1b3907D6CB542a4cbe2eE441EfFAA909FAb62C3;\n } else {\n return\n 0x6d08ee8511C0237a515013aC389e7B3968Cb1753;\n }\n } else {\n if (routeId == 275) {\n return\n 0x22faa5B5Fe43eAdbB52745e35a5cdA8bD5F96bbA;\n } else {\n return\n 0x7a673eB74D79e4868D689E7852abB5f93Ec2fD4b;\n }\n }\n } else {\n if (routeId < 279) {\n if (routeId == 277) {\n return\n 0x0b8531F8AFD4190b76F3e10deCaDb84c98b4d419;\n } else {\n return\n 0x78eABC743A93583DeE403D6b84795490e652216B;\n }\n } else {\n if (routeId == 279) {\n return\n 0x3A95D907b2a7a8604B59BccA08585F58Afe0Aa64;\n } else {\n return\n 0xf4271f0C8c9Af0F06A80b8832fa820ccE64FAda8;\n }\n }\n }\n } else {\n if (routeId < 285) {\n if (routeId < 283) {\n if (routeId == 281) {\n return\n 0x74b2DF841245C3748c0d31542e1335659a25C33b;\n } else {\n return\n 0xdFC99Fd0Ad7D16f30f295a5EEFcE029E04d0fa65;\n }\n } else {\n if (routeId == 283) {\n return\n 0xE992416b6aC1144eD8148a9632973257839027F6;\n } else {\n return\n 0x54ce55ba954E981BB1fd9399054B35Ce1f2C0816;\n }\n }\n } else {\n if (routeId < 287) {\n if (routeId == 285) {\n return\n 0xD4AB52f9e7E5B315Bd7471920baD04F405Ab1c38;\n } else {\n return\n 0x3670C990994d12837e95eE127fE2f06FD3E2104B;\n }\n } else {\n if (routeId == 287) {\n return\n 0xDcf190B09C47E4f551E30BBb79969c3FdEA1e992;\n } else {\n return\n 0xa65057B967B59677237e57Ab815B209744b9bc40;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 305) {\n if (routeId < 297) {\n if (routeId < 293) {\n if (routeId < 291) {\n if (routeId == 289) {\n return\n 0x6Efc86B40573e4C7F28659B13327D55ae955C483;\n } else {\n return\n 0x06BcC25CF8e0E72316F53631b3aA7134E9f73Ae0;\n }\n } else {\n if (routeId == 291) {\n return\n 0x710b6414E1D53882b1FCD3A168aD5Ccd435fc6D0;\n } else {\n return\n 0x5Ebb2C3d78c4e9818074559e7BaE7FCc99781DC1;\n }\n }\n } else {\n if (routeId < 295) {\n if (routeId == 293) {\n return\n 0xAf0a409c3AEe0bD08015cfb29D89E90b6e89A88F;\n } else {\n return\n 0x522559d8b99773C693B80cE06DF559036295Ce44;\n }\n } else {\n if (routeId == 295) {\n return\n 0xB65290A5Bae838aaa7825c9ECEC68041841a1B64;\n } else {\n return\n 0x801b8F2068edd5Bcb659E6BDa0c425909043C420;\n }\n }\n }\n } else {\n if (routeId < 301) {\n if (routeId < 299) {\n if (routeId == 297) {\n return\n 0x29b5F00515d093627E0B7bd0b5c8E84F6b4cDb87;\n } else {\n return\n 0x652839Ae74683cbF9f1293F1019D938F87464D3E;\n }\n } else {\n if (routeId == 299) {\n return\n 0x5Bc95dCebDDE9B79F2b6DC76121BC7936eF8D666;\n } else {\n return\n 0x90db359CEA62E53051158Ab5F99811C0a07Fe686;\n }\n }\n } else {\n if (routeId < 303) {\n if (routeId == 301) {\n return\n 0x2c3625EedadbDcDbB5330eb0d17b3C39ff269807;\n } else {\n return\n 0xC3f0324471b5c9d415acD625b8d8694a4e48e001;\n }\n } else {\n if (routeId == 303) {\n return\n 0x8C60e7E05fa0FfB6F720233736f245134685799d;\n } else {\n return\n 0x98fAF2c09aa4EBb995ad0B56152993E7291a500e;\n }\n }\n }\n }\n } else {\n if (routeId < 313) {\n if (routeId < 309) {\n if (routeId < 307) {\n if (routeId == 305) {\n return\n 0x802c1063a861414dFAEc16bacb81429FC0d40D6e;\n } else {\n return\n 0x11C4AeFCC0dC156f64195f6513CB1Fb3Be0Ae056;\n }\n } else {\n if (routeId == 307) {\n return\n 0xEff1F3258214E31B6B4F640b4389d55715C3Be2B;\n } else {\n return\n 0x47e379Abe8DDFEA4289aBa01235EFF7E93758fd7;\n }\n }\n } else {\n if (routeId < 311) {\n if (routeId == 309) {\n return\n 0x3CC26384c3eA31dDc8D9789e8872CeA6F20cD3ff;\n } else {\n return\n 0xEdd9EFa6c69108FAA4611097d643E20Ba0Ed1634;\n }\n } else {\n if (routeId == 311) {\n return\n 0xCb93525CA5f3D371F74F3D112bC19526740717B8;\n } else {\n return\n 0x7071E0124EB4438137e60dF1b8DD8Af1BfB362cF;\n }\n }\n }\n } else {\n if (routeId < 317) {\n if (routeId < 315) {\n if (routeId == 313) {\n return\n 0x4691096EB0b78C8F4b4A8091E5B66b18e1835c10;\n } else {\n return\n 0x8d953c9b2d1C2137CF95992079f3A77fCd793272;\n }\n } else {\n if (routeId == 315) {\n return\n 0xbdCc2A3Bf6e3Ba49ff86595e6b2b8D70d8368c92;\n } else {\n return\n 0x95E6948aB38c61b2D294E8Bd896BCc4cCC0713cf;\n }\n }\n } else {\n if (routeId < 319) {\n if (routeId == 317) {\n return\n 0x607b27C881fFEE4Cb95B1c5862FaE7224ccd0b4A;\n } else {\n return\n 0x09D28aFA166e566A2Ee1cB834ea8e78C7E627eD2;\n }\n } else {\n if (routeId == 319) {\n return\n 0x9c01449b38bDF0B263818401044Fb1401B29fDfA;\n } else {\n return\n 0x1F7723599bbB658c051F8A39bE2688388d22ceD6;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 353) {\n if (routeId < 337) {\n if (routeId < 329) {\n if (routeId < 325) {\n if (routeId < 323) {\n if (routeId == 321) {\n return\n 0x52B71603f7b8A5d15B4482e965a0619aa3210194;\n } else {\n return\n 0x01c0f072CB210406653752FecFA70B42dA9173a2;\n }\n } else {\n if (routeId == 323) {\n return\n 0x3021142f021E943e57fc1886cAF58D06147D09A6;\n } else {\n return\n 0xe6f2AF38e76AB09Db59225d97d3E770942D3D842;\n }\n }\n } else {\n if (routeId < 327) {\n if (routeId == 325) {\n return\n 0x06a25554e5135F08b9e2eD1DEC1fc3CEd52e0B48;\n } else {\n return\n 0x71d75e670EE3511C8290C705E0620126B710BF8D;\n }\n } else {\n if (routeId == 327) {\n return\n 0x8b9cE142b80FeA7c932952EC533694b1DF9B3c54;\n } else {\n return\n 0xd7Be24f32f39231116B3fDc483C2A12E1521f73B;\n }\n }\n }\n } else {\n if (routeId < 333) {\n if (routeId < 331) {\n if (routeId == 329) {\n return\n 0xb40cafBC4797d4Ff64087E087F6D2e661f954CbE;\n } else {\n return\n 0xBdDCe7771EfEe81893e838f62204A4c76D72757e;\n }\n } else {\n if (routeId == 331) {\n return\n 0x5d3D299EA7Fd4F39AcDb336E26631Dfee41F9287;\n } else {\n return\n 0x6BfEE09E1Fc0684e0826A9A0dC1352a14B136FAC;\n }\n }\n } else {\n if (routeId < 335) {\n if (routeId == 333) {\n return\n 0xd0001bB8E2Cb661436093f96458a4358B5156E3c;\n } else {\n return\n 0x1867c6485CfD1eD448988368A22bfB17a7747293;\n }\n } else {\n if (routeId == 335) {\n return\n 0x8997EF9F95dF24aB67703AB6C262aABfeEBE33bD;\n } else {\n return\n 0x1e39E9E601922deD91BCFc8F78836302133465e2;\n }\n }\n }\n }\n } else {\n if (routeId < 345) {\n if (routeId < 341) {\n if (routeId < 339) {\n if (routeId == 337) {\n return\n 0x8A8ec6CeacFf502a782216774E5AF3421562C6ff;\n } else {\n return\n 0x3B8FC561df5415c8DC01e97Ee6E38435A8F9C40A;\n }\n } else {\n if (routeId == 339) {\n return\n 0xD5d5f5B37E67c43ceA663aEDADFFc3a93a2065B0;\n } else {\n return\n 0xCC8F55EC43B4f25013CE1946FBB740c43Be5B96D;\n }\n }\n } else {\n if (routeId < 343) {\n if (routeId == 341) {\n return\n 0x18f586E816eEeDbb57B8011239150367561B58Fb;\n } else {\n return\n 0xd0CD802B19c1a52501cb2f07d656e3Cd7B0Ce124;\n }\n } else {\n if (routeId == 343) {\n return\n 0xe0AeD899b39C6e4f2d83e4913a1e9e0cf6368abE;\n } else {\n return\n 0x0606e1b6c0f1A398C38825DCcc4678a7Cbc2737c;\n }\n }\n }\n } else {\n if (routeId < 349) {\n if (routeId < 347) {\n if (routeId == 345) {\n return\n 0x2d188e85b27d18EF80f16686EA1593ABF7Ed2A63;\n } else {\n return\n 0x64412292fA4A135a3300E24366E99ff59Db2eAc1;\n }\n } else {\n if (routeId == 347) {\n return\n 0x38b74c173f3733E8b90aAEf0e98B89791266149F;\n } else {\n return\n 0x36DAA49A79aaEF4E7a217A11530D3cCD84414124;\n }\n }\n } else {\n if (routeId < 351) {\n if (routeId == 349) {\n return\n 0x10f088FE2C88F90270E4449c46c8B1b232511d58;\n } else {\n return\n 0x4FeDbd25B58586838ABD17D10272697dF1dC3087;\n }\n } else {\n if (routeId == 351) {\n return\n 0x685278209248CB058E5cEe93e37f274A80Faf6eb;\n } else {\n return\n 0xDd9F8F1eeC3955f78168e2Fb2d1e808fa8A8f15b;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 369) {\n if (routeId < 361) {\n if (routeId < 357) {\n if (routeId < 355) {\n if (routeId == 353) {\n return\n 0x7392aEeFD5825aaC28817031dEEBbFaAA20983D9;\n } else {\n return\n 0x0Cc182555E00767D6FB8AD161A10d0C04C476d91;\n }\n } else {\n if (routeId == 355) {\n return\n 0x90E52837d56715c79FD592E8D58bFD20365798b2;\n } else {\n return\n 0x6F4451DE14049B6770ad5BF4013118529e68A40C;\n }\n }\n } else {\n if (routeId < 359) {\n if (routeId == 357) {\n return\n 0x89B97ef2aFAb9ed9c7f0FDb095d02E6840b52d9c;\n } else {\n return\n 0x92A5cC5C42d94d3e23aeB1214fFf43Db2B97759E;\n }\n } else {\n if (routeId == 359) {\n return\n 0x63ddc52F135A1dcBA831EAaC11C63849F018b739;\n } else {\n return\n 0x692A691533B571C2c54C1D7F8043A204b3d8120E;\n }\n }\n }\n } else {\n if (routeId < 365) {\n if (routeId < 363) {\n if (routeId == 361) {\n return\n 0x97c7492CF083969F61C6f302d45c8270391b921c;\n } else {\n return\n 0xDeFD2B8643553dAd19548eB14fd94A57F4B9e543;\n }\n } else {\n if (routeId == 363) {\n return\n 0x30645C04205cA3f670B67b02F971B088930ACB8C;\n } else {\n return\n 0xA6f80ed2d607Cd67aEB4109B64A0BEcc4D7d03CF;\n }\n }\n } else {\n if (routeId < 367) {\n if (routeId == 365) {\n return\n 0xBbbbC6c276eB3F7E674f2D39301509236001c42f;\n } else {\n return\n 0xC20E77d349FB40CE88eB01824e2873ad9f681f3C;\n }\n } else {\n if (routeId == 367) {\n return\n 0x5fCfD9a962De19294467C358C1FA55082285960b;\n } else {\n return\n 0x4D87BD6a0E4E5cc6332923cb3E85fC71b287F58A;\n }\n }\n }\n }\n } else {\n if (routeId < 377) {\n if (routeId < 373) {\n if (routeId < 371) {\n if (routeId == 369) {\n return\n 0x3AA5B757cd6Dde98214E56D57Dde7fcF0F7aB04E;\n } else {\n return\n 0xe28eFCE7192e11a2297f44059113C1fD6967b2d4;\n }\n } else {\n if (routeId == 371) {\n return\n 0x3251cAE10a1Cf246e0808D76ACC26F7B5edA0eE5;\n } else {\n return\n 0xbA2091cc9357Cf4c4F25D64F30d1b4Ba3A5a174B;\n }\n }\n } else {\n if (routeId < 375) {\n if (routeId == 373) {\n return\n 0x49c8e1Da9693692096F63C82D11b52d738566d55;\n } else {\n return\n 0xA0731615aB5FFF451031E9551367A4F7dB27b39c;\n }\n } else {\n if (routeId == 375) {\n return\n 0xFb214541888671AE1403CecC1D59763a12fc1609;\n } else {\n return\n 0x1D6bCB17642E2336405df73dF22F07688cAec020;\n }\n }\n }\n } else {\n if (routeId < 381) {\n if (routeId < 379) {\n if (routeId == 377) {\n return\n 0xfC9c0C7bfe187120fF7f4E21446161794A617a9e;\n } else {\n return\n 0xBa5bF37678EeE2dAB17AEf9D898153258252250E;\n }\n } else {\n if (routeId == 379) {\n return\n 0x7c55690bd2C9961576A32c02f8EB29ed36415Ec7;\n } else {\n return\n 0xcA40073E868E8Bc611aEc8Fe741D17E68Fe422f6;\n }\n }\n } else {\n if (routeId < 383) {\n if (routeId == 381) {\n return\n 0x31641bAFb87E9A58f78835050a7BE56921986339;\n } else {\n return\n 0xA54766424f6dA74b45EbCc5Bf0Bd1D74D2CCcaAB;\n }\n } else {\n if (routeId == 383) {\n return\n 0xc7bBa57F8C179EDDBaa62117ddA360e28f3F8252;\n } else {\n return\n 0x5e663ED97ea77d393B8858C90d0683bF180E0ffd;\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();\n return routes[routeId];\n }\n\n /// @notice fallback function to handle swap, bridge execution\n /// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction\n fallback() external payable {\n address routeAddress = addressAt(uint32(msg.sig));\n\n bytes memory result;\n\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 4, sub(calldatasize(), 4))\n // execute function call using the facet\n result := delegatecall(\n gas(),\n routeAddress,\n 0,\n sub(calldatasize(), 4),\n 0,\n 0\n )\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n}\n"},"src/bridges/hop/interfaces/IHopL1Bridge.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title L1Bridge Hop Interface\n * @notice L1 Hop Bridge, Used to transfer from L1 to L2s.\n */\ninterface IHopL1Bridge {\n /**\n * @notice `amountOutMin` and `deadline` should be 0 when no swap is intended at the destination.\n * @notice `amount` is the total amount the user wants to send including the relayer fee\n * @dev Send tokens to a supported layer-2 to mint hToken and optionally swap the hToken in the\n * AMM at the destination.\n * @param chainId The chainId of the destination chain\n * @param recipient The address receiving funds at the destination\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination\n * AMM market. 0 if no swap is intended.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no\n * swap is intended.\n * @param relayer The address of the relayer at the destination.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n */\n function sendToL2(\n uint256 chainId,\n address recipient,\n uint256 amount,\n uint256 amountOutMin,\n uint256 deadline,\n address relayer,\n uint256 relayerFee\n ) external payable;\n}\n"},"src/bridges/refuel/interfaces/refuel.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/// @notice interface with functions to interact with Refuel contract\ninterface IRefuel {\n /**\n * @notice function to deposit nativeToken to Destination-address on destinationChain\n * @param destinationChainId chainId of the Destination chain\n * @param _to recipient address\n */\n function depositNativeToken(\n uint256 destinationChainId,\n address _to\n ) external payable;\n}\n"},"src/bridges/stargate/interfaces/stargate.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.8.0;\n\n/**\n * @title IBridgeStargate Interface Contract.\n * @notice Interface used by Stargate-L1 and L2 Router implementations\n * @dev router and routerETH addresses will be distinct for L1 and L2\n */\ninterface IBridgeStargate {\n // @notice Struct to hold the additional-data for bridging ERC20 token\n struct lzTxObj {\n // gas limit to bridge the token in Stargate to destinationChain\n uint256 dstGasForCall;\n // destination nativeAmount, this is always set as 0\n uint256 dstNativeAmount;\n // destination nativeAddress, this is always set as 0x\n bytes dstNativeAddr;\n }\n\n /// @notice function in stargate bridge which is used to bridge ERC20 tokens to recipient on destinationChain\n function swap(\n uint16 _dstChainId,\n uint256 _srcPoolId,\n uint256 _dstPoolId,\n address payable _refundAddress,\n uint256 _amountLD,\n uint256 _minAmountLD,\n lzTxObj memory _lzTxParams,\n bytes calldata _to,\n bytes calldata _payload\n ) external payable;\n\n /// @notice function in stargate bridge which is used to bridge native tokens to recipient on destinationChain\n function swapETH(\n uint16 _dstChainId, // destination Stargate chainId\n address payable _refundAddress, // refund additional messageFee to this address\n bytes calldata _toAddress, // the receiver of the destination ETH\n uint256 _amountLD, // the amount, in Local Decimals, to be swapped\n uint256 _minAmountLD // the minimum amount accepted out on destination\n ) external payable;\n}\n"},"src/controllers/FeesTakerController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BaseController} from \"./BaseController.sol\";\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\n\n/**\n * @title FeesTaker-Controller Implementation\n * @notice Controller with composed actions to deduct-fees followed by Refuel, Swap and Bridge\n * to be executed Sequentially and this is atomic\n * @author Socket dot tech.\n */\ncontract FeesTakerController is BaseController {\n using SafeTransferLib for ERC20;\n\n /// @notice event emitted upon fee-deduction to fees-taker address\n event SocketFeesDeducted(\n uint256 fees,\n address feesToken,\n address feesTaker\n );\n\n /// @notice Function-selector to invoke deduct-fees and swap token\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_SWAP_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"takeFeesAndSwap((address,address,uint256,uint32,bytes))\")\n );\n\n /// @notice Function-selector to invoke deduct-fees and bridge token\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeesAndBridge((address,address,uint256,uint32,bytes))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees and bridge multiple tokens\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeesAndMultiBridge((address,address,uint256,uint32[],bytes[]))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees followed by swapping of a token and bridging the swapped bridge\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeeAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees refuel\n /// @notice followed by swapping of a token and bridging the swapped bridge\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeeAndRefuelAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BaseController\n constructor(\n address _socketGatewayAddress\n ) BaseController(_socketGatewayAddress) {}\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and swap token\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftsRequest feesTakerSwapRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_SWAP_FUNCTION_SELECTOR\n * @return output bytes from the swap operation (last operation in the composed actions)\n */\n function takeFeesAndSwap(\n ISocketRequest.FeesTakerSwapRequest calldata ftsRequest\n ) external payable returns (bytes memory) {\n if (ftsRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftsRequest.feesTakerAddress).transfer(\n ftsRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftsRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftsRequest.feesTakerAddress,\n ftsRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftsRequest.feesAmount,\n ftsRequest.feesTakerAddress,\n ftsRequest.feesToken\n );\n\n //call bridge function (executeRoute for the swapRequestData)\n return _executeRoute(ftsRequest.routeId, ftsRequest.swapRequestData);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftbRequest feesTakerBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_BRIDGE_FUNCTION_SELECTOR\n * @return output bytes from the bridge operation (last operation in the composed actions)\n */\n function takeFeesAndBridge(\n ISocketRequest.FeesTakerBridgeRequest calldata ftbRequest\n ) external payable returns (bytes memory) {\n if (ftbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftbRequest.feesTakerAddress).transfer(\n ftbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftbRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftbRequest.feesTakerAddress,\n ftbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftbRequest.feesAmount,\n ftbRequest.feesTakerAddress,\n ftbRequest.feesToken\n );\n\n //call bridge function (executeRoute for the bridgeData)\n return _executeRoute(ftbRequest.routeId, ftbRequest.bridgeRequestData);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain\n * @notice multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftmbRequest feesTakerMultiBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeesAndMultiBridge(\n ISocketRequest.FeesTakerMultiBridgeRequest calldata ftmbRequest\n ) external payable {\n if (ftmbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftmbRequest.feesTakerAddress).transfer(\n ftmbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftmbRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftmbRequest.feesTakerAddress,\n ftmbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftmbRequest.feesAmount,\n ftmbRequest.feesTakerAddress,\n ftmbRequest.feesToken\n );\n\n // multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array\n for (\n uint256 index = 0;\n index < ftmbRequest.bridgeRouteIds.length;\n ++index\n ) {\n //call bridge function (executeRoute for the bridgeData)\n _executeRoute(\n ftmbRequest.bridgeRouteIds[index],\n ftmbRequest.bridgeRequestDataItems[index]\n );\n }\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain followed by swap the amount on sourceChain followed by\n * bridging the swapped amount to destinationChain\n * @dev while generating implData for swap and bridgeRequests, ensure correct function selector is used\n * bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation\n * @param fsbRequest feesTakerSwapBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeeAndSwapAndBridge(\n ISocketRequest.FeesTakerSwapBridgeRequest calldata fsbRequest\n ) external payable returns (bytes memory) {\n if (fsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(fsbRequest.feesTakerAddress).transfer(\n fsbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(fsbRequest.feesToken).safeTransferFrom(\n msg.sender,\n fsbRequest.feesTakerAddress,\n fsbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n fsbRequest.feesAmount,\n fsbRequest.feesTakerAddress,\n fsbRequest.feesToken\n );\n\n // execute swap operation\n bytes memory swapResponseData = _executeRoute(\n fsbRequest.swapRouteId,\n fsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n // swapped amount is to be bridged to the recipient on destinationChain\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n fsbRequest.bridgeData\n );\n\n // execute bridge operation and return the byte-data from response of bridge operation\n return _executeRoute(fsbRequest.bridgeRouteId, bridgeImpldata);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain followed by refuel followed by\n * swap the amount on sourceChain followed by bridging the swapped amount to destinationChain\n * @dev while generating implData for refuel, swap and bridge Requests, ensure correct function selector is used\n * bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation\n * @param frsbRequest feesTakerRefuelSwapBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeeAndRefuelAndSwapAndBridge(\n ISocketRequest.FeesTakerRefuelSwapBridgeRequest calldata frsbRequest\n ) external payable returns (bytes memory) {\n if (frsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(frsbRequest.feesTakerAddress).transfer(\n frsbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(frsbRequest.feesToken).safeTransferFrom(\n msg.sender,\n frsbRequest.feesTakerAddress,\n frsbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n frsbRequest.feesAmount,\n frsbRequest.feesTakerAddress,\n frsbRequest.feesToken\n );\n\n // refuel is also done via bridge execution via refuelRouteImplementation identified by refuelRouteId\n _executeRoute(frsbRequest.refuelRouteId, frsbRequest.refuelData);\n\n // execute swap operation\n bytes memory swapResponseData = _executeRoute(\n frsbRequest.swapRouteId,\n frsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n // swapped amount is to be bridged to the recipient on destinationChain\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n frsbRequest.bridgeData\n );\n\n // execute bridge operation and return the byte-data from response of bridge operation\n return _executeRoute(frsbRequest.bridgeRouteId, bridgeImpldata);\n }\n}\n"},"src/errors/SocketErrors.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nerror CelerRefundNotReady();\nerror OnlySocketDeployer();\nerror OnlySocketGatewayOwner();\nerror OnlySocketGateway();\nerror OnlyOwner();\nerror OnlyNominee();\nerror TransferIdExists();\nerror TransferIdDoesnotExist();\nerror Address0Provided();\nerror SwapFailed();\nerror UnsupportedInterfaceId();\nerror InvalidCelerRefund();\nerror CelerAlreadyRefunded();\nerror IncorrectBridgeRatios();\nerror ZeroAddressNotAllowed();\nerror ArrayLengthMismatch();\n"},"src/bridges/hop/l1/HopImplL1.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport \"../interfaces/IHopL1Bridge.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {HOP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hop-L1 Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Hop-Bridge from L1 to Supported L2s\n * Called via SocketGateway if the routeId in the request maps to the routeId of HopImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HopImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HopIdentifier = HOP;\n\n /// @notice Function-selector for ERC20-token bridging on Hop-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HOP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,address,uint256,uint256,uint256,uint256,(uint256,bytes32))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Hop-L1-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable HOP_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable HOP_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,address,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HopDataNoToken {\n // The address receiving funds at the destination\n address receiverAddress;\n // address of the Hop-L1-Bridge to handle bridging the tokens\n address l1bridgeAddr;\n // relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n address relayer;\n // The chainId of the destination chain\n uint256 toChainId;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n uint256 relayerFee;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopData {\n /// @notice address of token being bridged\n address token;\n // The address receiving funds at the destination\n address receiverAddress;\n // address of the Hop-L1-Bridge to handle bridging the tokens\n address l1bridgeAddr;\n // relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n address relayer;\n // The chainId of the destination chain\n uint256 toChainId;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n uint256 relayerFee;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopERC20Data {\n uint256 deadline;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Hop-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HopData memory hopData = abi.decode(bridgeData, (HopData));\n\n if (hopData.token == NATIVE_TOKEN_ADDRESS) {\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: amount}(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n } else {\n ERC20(hopData.token).safeApprove(hopData.l1bridgeAddr, amount);\n\n // perform bridging\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n }\n\n emit SocketBridge(\n amount,\n hopData.token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hopData encoded data for HopData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HopDataNoToken calldata hopData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: bridgeAmount}(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n } else {\n ERC20(token).safeApprove(hopData.l1bridgeAddr, bridgeAmount);\n\n // perform bridging\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hop-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param token token being bridged\n * @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens\n * @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n * @param toChainId The chainId of the destination chain\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n * @param hopData extra data needed to build the tx\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n address l1bridgeAddr,\n address relayer,\n uint256 toChainId,\n uint256 amount,\n uint256 amountOutMin,\n uint256 relayerFee,\n HopERC20Data calldata hopData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(l1bridgeAddr, amount);\n\n // perform bridging\n IHopL1Bridge(l1bridgeAddr).sendToL2(\n toChainId,\n receiverAddress,\n amount,\n amountOutMin,\n hopData.deadline,\n relayer,\n relayerFee\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hop-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens\n * @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n * @param toChainId The chainId of the destination chain\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n */\n function bridgeNativeTo(\n address receiverAddress,\n address l1bridgeAddr,\n address relayer,\n uint256 toChainId,\n uint256 amount,\n uint256 amountOutMin,\n uint256 relayerFee,\n uint256 deadline,\n bytes32 metadata\n ) external payable {\n IHopL1Bridge(l1bridgeAddr).sendToL2{value: amount}(\n toChainId,\n receiverAddress,\n amount,\n amountOutMin,\n deadline,\n relayer,\n relayerFee\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/static/RouteIdentifiers.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\nbytes32 constant ACROSS = keccak256(\"Across\");\n\nbytes32 constant ANYSWAP = keccak256(\"Anyswap\");\n\nbytes32 constant CBRIDGE = keccak256(\"CBridge\");\n\nbytes32 constant HOP = keccak256(\"Hop\");\n\nbytes32 constant HYPHEN = keccak256(\"Hyphen\");\n\nbytes32 constant NATIVE_OPTIMISM = keccak256(\"NativeOptimism\");\n\nbytes32 constant NATIVE_ARBITRUM = keccak256(\"NativeArbitrum\");\n\nbytes32 constant NATIVE_POLYGON = keccak256(\"NativePolygon\");\n\nbytes32 constant REFUEL = keccak256(\"Refuel\");\n\nbytes32 constant STARGATE = keccak256(\"Stargate\");\n\nbytes32 constant ONEINCH = keccak256(\"OneInch\");\n\nbytes32 constant ZEROX = keccak256(\"Zerox\");\n\nbytes32 constant RAINBOW = keccak256(\"Rainbow\");\n"},"src/SocketGateway.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\npragma experimental ABIEncoderV2;\n\nimport \"./utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {LibUtil} from \"./libraries/LibUtil.sol\";\nimport \"./libraries/LibBytes.sol\";\nimport {ISocketRoute} from \"./interfaces/ISocketRoute.sol\";\nimport {ISocketRequest} from \"./interfaces/ISocketRequest.sol\";\nimport {ISocketGateway} from \"./interfaces/ISocketGateway.sol\";\nimport {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from \"./errors/SocketErrors.sol\";\n\n/// @title SocketGatewayContract\n/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer\n/// @author Socket Team\ncontract SocketGatewayTemplate is Ownable {\n using LibBytes for bytes;\n using LibBytes for bytes4;\n using SafeTransferLib for ERC20;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice storage variable to keep track of total number of routes registered in socketgateway\n uint32 public routesCount = 385;\n\n /// @notice storage variable to keep track of total number of controllers registered in socketgateway\n uint32 public controllerCount;\n\n address public immutable disabledRouteAddress;\n\n uint256 public constant CENT_PERCENT = 100e18;\n\n /// @notice storage mapping for route implementation addresses\n mapping(uint32 => address) public routes;\n\n /// storage mapping for controller implemenation addresses\n mapping(uint32 => address) public controllers;\n\n // Events ------------------------------------------------------------------------------------------------------->\n\n /// @notice Event emitted when a router is added to socketgateway\n event NewRouteAdded(uint32 indexed routeId, address indexed route);\n\n /// @notice Event emitted when a route is disabled\n event RouteDisabled(uint32 indexed routeId);\n\n /// @notice Event emitted when ownership transfer is requested by socket-gateway-owner\n event OwnershipTransferRequested(\n address indexed _from,\n address indexed _to\n );\n\n /// @notice Event emitted when a controller is added to socketgateway\n event ControllerAdded(\n uint32 indexed controllerId,\n address indexed controllerAddress\n );\n\n /// @notice Event emitted when a controller is disabled\n event ControllerDisabled(uint32 indexed controllerId);\n\n constructor(address _owner, address _disabledRoute) Ownable(_owner) {\n disabledRouteAddress = _disabledRoute;\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n\n /*******************************************\n * EXTERNAL AND PUBLIC FUNCTIONS *\n *******************************************/\n\n /**\n * @notice executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in routeData to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeId route identifier\n * @param routeData functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoute(\n uint32 routeId,\n bytes calldata routeData\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = addressAt(routeId).delegatecall(\n routeData\n );\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice swaps a token on sourceChain and split it across multiple bridge-recipients\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped\n * @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address\n * @param swapMultiBridgeRequest request\n */\n function swapAndMultiBridge(\n ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest\n ) external payable {\n uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;\n\n if (\n requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length\n ) {\n revert ArrayLengthMismatch();\n }\n uint256 ratioAggregate;\n for (uint256 index = 0; index < requestLength; ) {\n ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];\n }\n\n if (ratioAggregate != CENT_PERCENT) {\n revert IncorrectBridgeRatios();\n }\n\n (bool swapSuccess, bytes memory swapResult) = addressAt(\n swapMultiBridgeRequest.swapRouteId\n ).delegatecall(swapMultiBridgeRequest.swapImplData);\n\n if (!swapSuccess) {\n assembly {\n revert(add(swapResult, 32), mload(swapResult))\n }\n }\n\n uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));\n\n uint256 bridgedAmount;\n\n for (uint256 index = 0; index < requestLength; ) {\n uint256 bridgingAmount;\n\n // if it is the last bridge request, bridge the remaining amount\n if (index == requestLength - 1) {\n bridgingAmount = amountReceivedFromSwap - bridgedAmount;\n } else {\n // bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap\n bridgingAmount =\n (amountReceivedFromSwap *\n swapMultiBridgeRequest.bridgeRatios[index]) /\n (CENT_PERCENT);\n }\n\n // update the bridged amount, this would be used for computation for last bridgeRequest\n bridgedAmount += bridgingAmount;\n\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n bridgingAmount,\n swapMultiBridgeRequest.bridgeImplDataItems[index]\n );\n\n (bool bridgeSuccess, bytes memory bridgeResult) = addressAt(\n swapMultiBridgeRequest.bridgeRouteIds[index]\n ).delegatecall(bridgeImpldata);\n\n if (!bridgeSuccess) {\n assembly {\n revert(add(bridgeResult, 32), mload(bridgeResult))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice sequentially executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each dataItem to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeIds a list of route identifiers\n * @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoutes(\n uint32[] calldata routeIds,\n bytes[] calldata dataItems\n ) external payable {\n uint256 routeIdslength = routeIds.length;\n if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();\n for (uint256 index = 0; index < routeIdslength; ) {\n (bool success, bytes memory result) = addressAt(routeIds[index])\n .delegatecall(dataItems[index]);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice execute a controller function identified using the controllerId in the request\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param socketControllerRequest socketControllerRequest with controllerId to identify the\n * controllerAddress and byteData constructed using functionSelector\n * of the function being invoked\n * @return bytes data received from the call delegated to controller\n */\n function executeController(\n ISocketGateway.SocketControllerRequest calldata socketControllerRequest\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = controllers[\n socketControllerRequest.controllerId\n ].delegatecall(socketControllerRequest.data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice sequentially executes all controller requests\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each controller-request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param controllerRequests a list of socketControllerRequest\n * Each controllerRequest contains controllerId to identify the controllerAddress and\n * byteData constructed using functionSelector of the function being invoked\n */\n function executeControllers(\n ISocketGateway.SocketControllerRequest[] calldata controllerRequests\n ) external payable {\n for (uint32 index = 0; index < controllerRequests.length; ) {\n (bool success, bytes memory result) = controllers[\n controllerRequests[index].controllerId\n ].delegatecall(controllerRequests[index].data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**************************************\n * ADMIN FUNCTIONS *\n **************************************/\n\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(\n address routeAddress\n ) external onlyOwner returns (uint32) {\n uint32 routeId = routesCount;\n routes[routeId] = routeAddress;\n\n routesCount += 1;\n\n emit NewRouteAdded(routeId, routeAddress);\n\n return routeId;\n }\n\n /**\n * @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress\n This is a restricted function to be called by only socketGatewayOwner\n */\n\n function setApprovalForRouters(\n address[] memory routeAddresses,\n address[] memory tokenAddresses,\n bool isMax\n ) external onlyOwner {\n for (uint32 index = 0; index < routeAddresses.length; ) {\n ERC20(tokenAddresses[index]).approve(\n routeAddresses[index],\n isMax ? type(uint256).max : 0\n );\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address controllerAddress\n ) external onlyOwner returns (uint32) {\n uint32 controllerId = controllerCount;\n\n controllers[controllerId] = controllerAddress;\n\n controllerCount += 1;\n\n emit ControllerAdded(controllerId, controllerAddress);\n\n return controllerId;\n }\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 controllerId) public onlyOwner {\n controllers[controllerId] = disabledRouteAddress;\n emit ControllerDisabled(controllerId);\n }\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external onlyOwner {\n routes[routeId] = disabledRouteAddress;\n emit RouteDisabled(routeId);\n }\n\n /*******************************************\n * RESTRICTED RESCUE FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n\n /*******************************************\n * VIEW FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) public view returns (address) {\n return addressAt(routeId);\n }\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 controllerId) public view returns (address) {\n return controllers[controllerId];\n }\n\n function addressAt(uint32 routeId) public view returns (address) {\n if (routeId < 385) {\n if (routeId < 257) {\n if (routeId < 129) {\n if (routeId < 65) {\n if (routeId < 33) {\n if (routeId < 17) {\n if (routeId < 9) {\n if (routeId < 5) {\n if (routeId < 3) {\n if (routeId == 1) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 3) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 7) {\n if (routeId == 5) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 7) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 13) {\n if (routeId < 11) {\n if (routeId == 9) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 11) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 15) {\n if (routeId == 13) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 15) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 25) {\n if (routeId < 21) {\n if (routeId < 19) {\n if (routeId == 17) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 19) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 23) {\n if (routeId == 21) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 23) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 29) {\n if (routeId < 27) {\n if (routeId == 25) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 27) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 31) {\n if (routeId == 29) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 31) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 49) {\n if (routeId < 41) {\n if (routeId < 37) {\n if (routeId < 35) {\n if (routeId == 33) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 35) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 39) {\n if (routeId == 37) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 39) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 45) {\n if (routeId < 43) {\n if (routeId == 41) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 43) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 47) {\n if (routeId == 45) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 47) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 57) {\n if (routeId < 53) {\n if (routeId < 51) {\n if (routeId == 49) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 51) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 55) {\n if (routeId == 53) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 55) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 61) {\n if (routeId < 59) {\n if (routeId == 57) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 59) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 63) {\n if (routeId == 61) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 63) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 97) {\n if (routeId < 81) {\n if (routeId < 73) {\n if (routeId < 69) {\n if (routeId < 67) {\n if (routeId == 65) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 67) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 71) {\n if (routeId == 69) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 71) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 77) {\n if (routeId < 75) {\n if (routeId == 73) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 75) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 79) {\n if (routeId == 77) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 79) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 89) {\n if (routeId < 85) {\n if (routeId < 83) {\n if (routeId == 81) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 83) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 87) {\n if (routeId == 85) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 87) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 93) {\n if (routeId < 91) {\n if (routeId == 89) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 91) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 95) {\n if (routeId == 93) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 95) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 113) {\n if (routeId < 105) {\n if (routeId < 101) {\n if (routeId < 99) {\n if (routeId == 97) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 99) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 103) {\n if (routeId == 101) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 103) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 109) {\n if (routeId < 107) {\n if (routeId == 105) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 107) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 111) {\n if (routeId == 109) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 111) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 121) {\n if (routeId < 117) {\n if (routeId < 115) {\n if (routeId == 113) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 115) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 119) {\n if (routeId == 117) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 119) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 125) {\n if (routeId < 123) {\n if (routeId == 121) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 123) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 127) {\n if (routeId == 125) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 127) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 193) {\n if (routeId < 161) {\n if (routeId < 145) {\n if (routeId < 137) {\n if (routeId < 133) {\n if (routeId < 131) {\n if (routeId == 129) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 131) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 135) {\n if (routeId == 133) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 135) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 141) {\n if (routeId < 139) {\n if (routeId == 137) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 139) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 143) {\n if (routeId == 141) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 143) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 153) {\n if (routeId < 149) {\n if (routeId < 147) {\n if (routeId == 145) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 147) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 151) {\n if (routeId == 149) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 151) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 157) {\n if (routeId < 155) {\n if (routeId == 153) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 155) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 159) {\n if (routeId == 157) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 159) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 177) {\n if (routeId < 169) {\n if (routeId < 165) {\n if (routeId < 163) {\n if (routeId == 161) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 163) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 167) {\n if (routeId == 165) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 167) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 173) {\n if (routeId < 171) {\n if (routeId == 169) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 171) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 175) {\n if (routeId == 173) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 175) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 185) {\n if (routeId < 181) {\n if (routeId < 179) {\n if (routeId == 177) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 179) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 183) {\n if (routeId == 181) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 183) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 189) {\n if (routeId < 187) {\n if (routeId == 185) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 187) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 191) {\n if (routeId == 189) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 191) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 225) {\n if (routeId < 209) {\n if (routeId < 201) {\n if (routeId < 197) {\n if (routeId < 195) {\n if (routeId == 193) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 195) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 199) {\n if (routeId == 197) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 199) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 205) {\n if (routeId < 203) {\n if (routeId == 201) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 203) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 207) {\n if (routeId == 205) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 207) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 217) {\n if (routeId < 213) {\n if (routeId < 211) {\n if (routeId == 209) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 211) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 215) {\n if (routeId == 213) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 215) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 221) {\n if (routeId < 219) {\n if (routeId == 217) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 219) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 223) {\n if (routeId == 221) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 223) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 241) {\n if (routeId < 233) {\n if (routeId < 229) {\n if (routeId < 227) {\n if (routeId == 225) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 227) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 231) {\n if (routeId == 229) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 231) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 237) {\n if (routeId < 235) {\n if (routeId == 233) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 235) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 239) {\n if (routeId == 237) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 239) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 249) {\n if (routeId < 245) {\n if (routeId < 243) {\n if (routeId == 241) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 243) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 247) {\n if (routeId == 245) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 247) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 253) {\n if (routeId < 251) {\n if (routeId == 249) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 251) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 255) {\n if (routeId == 253) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 255) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 321) {\n if (routeId < 289) {\n if (routeId < 273) {\n if (routeId < 265) {\n if (routeId < 261) {\n if (routeId < 259) {\n if (routeId == 257) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 259) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 263) {\n if (routeId == 261) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 263) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 269) {\n if (routeId < 267) {\n if (routeId == 265) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 267) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 271) {\n if (routeId == 269) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 271) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 281) {\n if (routeId < 277) {\n if (routeId < 275) {\n if (routeId == 273) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 275) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 279) {\n if (routeId == 277) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 279) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 285) {\n if (routeId < 283) {\n if (routeId == 281) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 283) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 287) {\n if (routeId == 285) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 287) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 305) {\n if (routeId < 297) {\n if (routeId < 293) {\n if (routeId < 291) {\n if (routeId == 289) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 291) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 295) {\n if (routeId == 293) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 295) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 301) {\n if (routeId < 299) {\n if (routeId == 297) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 299) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 303) {\n if (routeId == 301) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 303) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 313) {\n if (routeId < 309) {\n if (routeId < 307) {\n if (routeId == 305) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 307) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 311) {\n if (routeId == 309) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 311) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 317) {\n if (routeId < 315) {\n if (routeId == 313) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 315) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 319) {\n if (routeId == 317) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 319) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 353) {\n if (routeId < 337) {\n if (routeId < 329) {\n if (routeId < 325) {\n if (routeId < 323) {\n if (routeId == 321) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 323) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 327) {\n if (routeId == 325) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 327) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 333) {\n if (routeId < 331) {\n if (routeId == 329) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 331) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 335) {\n if (routeId == 333) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 335) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 345) {\n if (routeId < 341) {\n if (routeId < 339) {\n if (routeId == 337) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 339) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 343) {\n if (routeId == 341) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 343) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 349) {\n if (routeId < 347) {\n if (routeId == 345) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 347) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 351) {\n if (routeId == 349) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 351) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 369) {\n if (routeId < 361) {\n if (routeId < 357) {\n if (routeId < 355) {\n if (routeId == 353) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 355) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 359) {\n if (routeId == 357) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 359) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 365) {\n if (routeId < 363) {\n if (routeId == 361) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 363) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 367) {\n if (routeId == 365) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 367) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 377) {\n if (routeId < 373) {\n if (routeId < 371) {\n if (routeId == 369) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 371) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 375) {\n if (routeId == 373) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 375) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 381) {\n if (routeId < 379) {\n if (routeId == 377) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 379) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 383) {\n if (routeId == 381) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 383) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();\n return routes[routeId];\n }\n\n /// @notice fallback function to handle swap, bridge execution\n /// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction\n fallback() external payable {\n address routeAddress = addressAt(uint32(msg.sig));\n\n bytes memory result;\n\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 4, sub(calldatasize(), 4))\n // execute function call using the facet\n result := delegatecall(\n gas(),\n routeAddress,\n 0,\n sub(calldatasize(), 4),\n 0,\n 0\n )\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n}\n"},"src/swap/rainbow/Rainbow.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {Address0Provided, SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {RAINBOW} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Rainbow-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via Rainbow-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of RainbowImplementation\n * @author Socket dot tech.\n */\ncontract RainbowSwapImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable RainbowIdentifier = RAINBOW;\n\n /// @notice unique name to identify the router, used to emit event upon successful bridging\n bytes32 public immutable NAME = keccak256(\"Rainbow-Router\");\n\n /// @notice address of rainbow-swap-aggregator to swap the tokens on Chain\n address payable public immutable rainbowSwapAggregator;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @notice rainbow swap aggregator contract is payable to allow ethereum swaps\n /// @dev ensure _rainbowSwapAggregator are set properly for the chainId in which the contract is being deployed\n constructor(\n address _rainbowSwapAggregator,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n rainbowSwapAggregator = payable(_rainbowSwapAggregator);\n }\n\n receive() external payable {}\n\n fallback() external payable {}\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * @notice This method is payable because the caller is doing token transfer and swap operation\n * @param fromToken address of token being Swapped\n * @param toToken address of token that recipient will receive after swap\n * @param amount amount of fromToken being swapped\n * @param receiverAddress recipient-address\n * @param swapExtraData additional Data to perform Swap via Rainbow-Aggregator\n * @return swapped amount (in toToken Address)\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 toTokenERC20 = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(rainbowSwapAggregator, amount);\n\n // solhint-disable-next-line\n (bool success, ) = rainbowSwapAggregator.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(rainbowSwapAggregator, 0);\n } else {\n (bool success, ) = rainbowSwapAggregator.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n if (toToken == NATIVE_TOKEN_ADDRESS) {\n payable(receiverAddress).transfer(returnAmount);\n } else {\n toTokenERC20.transfer(receiverAddress, returnAmount);\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n RainbowIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 toTokenERC20 = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(rainbowSwapAggregator, amount);\n\n // solhint-disable-next-line\n (bool success, ) = rainbowSwapAggregator.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(rainbowSwapAggregator, 0);\n } else {\n (bool success, ) = rainbowSwapAggregator.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n RainbowIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/interfaces/ISocketBridgeBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ninterface ISocketBridgeBase {\n function killme() external;\n}\n"},"src/interfaces/ISocketRequest.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketRoute\n * @notice Interface with Request DataStructures to invoke controller functions.\n * @author Socket dot tech.\n */\ninterface ISocketRequest {\n struct SwapMultiBridgeRequest {\n uint32 swapRouteId;\n bytes swapImplData;\n uint32[] bridgeRouteIds;\n bytes[] bridgeImplDataItems;\n uint256[] bridgeRatios;\n bytes[] eventDataItems;\n }\n\n // Datastructure for Refuel-Swap-Bridge function\n struct RefuelSwapBridgeRequest {\n uint32 refuelRouteId;\n bytes refuelData;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n\n // Datastructure for DeductFees-Swap function\n struct FeesTakerSwapRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 routeId;\n bytes swapRequestData;\n }\n\n // Datastructure for DeductFees-Bridge function\n struct FeesTakerBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 routeId;\n bytes bridgeRequestData;\n }\n\n // Datastructure for DeductFees-MultiBridge function\n struct FeesTakerMultiBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32[] bridgeRouteIds;\n bytes[] bridgeRequestDataItems;\n }\n\n // Datastructure for DeductFees-Swap-Bridge function\n struct FeesTakerSwapBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n\n // Datastructure for DeductFees-Refuel-Swap-Bridge function\n struct FeesTakerRefuelSwapBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 refuelRouteId;\n bytes refuelData;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n}\n"},"src/utils/Ownable.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\npragma solidity ^0.8.4;\n\nimport {OnlyOwner, OnlyNominee} from \"../errors/SocketErrors.sol\";\n\nabstract contract Ownable {\n address private _owner;\n address private _nominee;\n\n event OwnerNominated(address indexed nominee);\n event OwnerClaimed(address indexed claimer);\n\n constructor(address owner_) {\n _claimOwner(owner_);\n }\n\n modifier onlyOwner() {\n if (msg.sender != _owner) {\n revert OnlyOwner();\n }\n _;\n }\n\n function owner() public view returns (address) {\n return _owner;\n }\n\n function nominee() public view returns (address) {\n return _nominee;\n }\n\n function nominateOwner(address nominee_) external {\n if (msg.sender != _owner) {\n revert OnlyOwner();\n }\n _nominee = nominee_;\n emit OwnerNominated(_nominee);\n }\n\n function claimOwner() external {\n if (msg.sender != _nominee) {\n revert OnlyNominee();\n }\n _claimOwner(msg.sender);\n }\n\n function _claimOwner(address claimer_) internal {\n _owner = claimer_;\n _nominee = address(0);\n emit OwnerClaimed(claimer_);\n }\n}\n"},"lib/solmate/src/tokens/ERC20.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\n/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)\n/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)\n/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.\nabstract contract ERC20 {\n /*//////////////////////////////////////////////////////////////\n EVENTS\n //////////////////////////////////////////////////////////////*/\n\n event Transfer(address indexed from, address indexed to, uint256 amount);\n\n event Approval(address indexed owner, address indexed spender, uint256 amount);\n\n /*//////////////////////////////////////////////////////////////\n METADATA STORAGE\n //////////////////////////////////////////////////////////////*/\n\n string public name;\n\n string public symbol;\n\n uint8 public immutable decimals;\n\n /*//////////////////////////////////////////////////////////////\n ERC20 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 public totalSupply;\n\n mapping(address => uint256) public balanceOf;\n\n mapping(address => mapping(address => uint256)) public allowance;\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 internal immutable INITIAL_CHAIN_ID;\n\n bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;\n\n mapping(address => uint256) public nonces;\n\n /*//////////////////////////////////////////////////////////////\n CONSTRUCTOR\n //////////////////////////////////////////////////////////////*/\n\n constructor(\n string memory _name,\n string memory _symbol,\n uint8 _decimals\n ) {\n name = _name;\n symbol = _symbol;\n decimals = _decimals;\n\n INITIAL_CHAIN_ID = block.chainid;\n INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function approve(address spender, uint256 amount) public virtual returns (bool) {\n allowance[msg.sender][spender] = amount;\n\n emit Approval(msg.sender, spender, amount);\n\n return true;\n }\n\n function transfer(address to, uint256 amount) public virtual returns (bool) {\n balanceOf[msg.sender] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(msg.sender, to, amount);\n\n return true;\n }\n\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual returns (bool) {\n uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n balanceOf[from] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n return true;\n }\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n // Unchecked because the only math done is incrementing\n // the owner's nonce which cannot realistically overflow.\n unchecked {\n address recoveredAddress = ecrecover(\n keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n DOMAIN_SEPARATOR(),\n keccak256(\n abi.encode(\n keccak256(\n \"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\"\n ),\n owner,\n spender,\n value,\n nonces[owner]++,\n deadline\n )\n )\n )\n ),\n v,\n r,\n s\n );\n\n require(recoveredAddress != address(0) && recoveredAddress == owner, \"INVALID_SIGNER\");\n\n allowance[recoveredAddress][spender] = value;\n }\n\n emit Approval(owner, spender, value);\n }\n\n function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();\n }\n\n function computeDomainSeparator() internal view virtual returns (bytes32) {\n return\n keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(bytes(name)),\n keccak256(\"1\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /*//////////////////////////////////////////////////////////////\n INTERNAL MINT/BURN LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function _mint(address to, uint256 amount) internal virtual {\n totalSupply += amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(address(0), to, amount);\n }\n\n function _burn(address from, uint256 amount) internal virtual {\n balanceOf[from] -= amount;\n\n // Cannot underflow because a user's balance\n // will never be larger than the total supply.\n unchecked {\n totalSupply -= amount;\n }\n\n emit Transfer(from, address(0), amount);\n }\n}\n"},"src/controllers/RefuelSwapAndBridgeController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\nimport {BaseController} from \"./BaseController.sol\";\n\n/**\n * @title RefuelSwapAndBridge Controller Implementation\n * @notice Controller with composed actions for Refuel,Swap and Bridge to be executed Sequentially and this is atomic\n * @author Socket dot tech.\n */\ncontract RefuelSwapAndBridgeController is BaseController {\n /// @notice Function-selector to invoke refuel-swap-bridge function\n /// @dev This function selector is to be used while buidling transaction-data\n bytes4 public immutable REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"refuelAndSwapAndBridge((uint32,bytes,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BaseController\n constructor(\n address _socketGatewayAddress\n ) BaseController(_socketGatewayAddress) {}\n\n /**\n * @notice function to handle refuel followed by Swap and Bridge actions\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param rsbRequest Request with data to execute refuel followed by swap and bridge\n * @return output data from bridging operation\n */\n function refuelAndSwapAndBridge(\n ISocketRequest.RefuelSwapBridgeRequest calldata rsbRequest\n ) public payable returns (bytes memory) {\n _executeRoute(rsbRequest.refuelRouteId, rsbRequest.refuelData);\n\n // refuel is also a bridging activity via refuel-route-implementation\n bytes memory swapResponseData = _executeRoute(\n rsbRequest.swapRouteId,\n rsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n //sequence of arguments for implData: amount, token, data\n // Bridging the swapAmount received in the preceeding step\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n rsbRequest.bridgeData\n );\n\n return _executeRoute(rsbRequest.bridgeRouteId, bridgeImpldata);\n }\n}\n"},"src/interfaces/ISocketGateway.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketGateway\n * @notice Interface for SocketGateway functions.\n * @dev functions can be added here for invocation from external contracts or off-chain\n * @author Socket dot tech.\n */\ninterface ISocketGateway {\n /**\n * @notice Request-struct for controllerRequests\n * @dev ensure the value for data is generated using the function-selectors defined in the controllerImplementation contracts\n */\n struct SocketControllerRequest {\n // controllerId is the id mapped to the controllerAddress\n uint32 controllerId;\n // transactionImplData generated off-chain or by caller using function-selector of the controllerContract\n bytes data;\n }\n\n // @notice view to get owner-address\n function owner() external view returns (address);\n}\n"},"src/libraries/Pb.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.4;\n\n// runtime proto sol library\nlibrary Pb {\n enum WireType {\n Varint,\n Fixed64,\n LengthDelim,\n StartGroup,\n EndGroup,\n Fixed32\n }\n\n struct Buffer {\n uint256 idx; // the start index of next read. when idx=b.length, we're done\n bytes b; // hold serialized proto msg, readonly\n }\n\n // create a new in-memory Buffer object from raw msg bytes\n function fromBytes(\n bytes memory raw\n ) internal pure returns (Buffer memory buf) {\n buf.b = raw;\n buf.idx = 0;\n }\n\n // whether there are unread bytes\n function hasMore(Buffer memory buf) internal pure returns (bool) {\n return buf.idx < buf.b.length;\n }\n\n // decode current field number and wiretype\n function decKey(\n Buffer memory buf\n ) internal pure returns (uint256 tag, WireType wiretype) {\n uint256 v = decVarint(buf);\n tag = v / 8;\n wiretype = WireType(v & 7);\n }\n\n // read varint from current buf idx, move buf.idx to next read, return the int value\n function decVarint(Buffer memory buf) internal pure returns (uint256 v) {\n bytes10 tmp; // proto int is at most 10 bytes (7 bits can be used per byte)\n bytes memory bb = buf.b; // get buf.b mem addr to use in assembly\n v = buf.idx; // use v to save one additional uint variable\n assembly {\n tmp := mload(add(add(bb, 32), v)) // load 10 bytes from buf.b[buf.idx] to tmp\n }\n uint256 b; // store current byte content\n v = 0; // reset to 0 for return value\n for (uint256 i = 0; i < 10; i++) {\n assembly {\n b := byte(i, tmp) // don't use tmp[i] because it does bound check and costs extra\n }\n v |= (b & 0x7F) << (i * 7);\n if (b & 0x80 == 0) {\n buf.idx += i + 1;\n return v;\n }\n }\n revert(); // i=10, invalid varint stream\n }\n\n // read length delimited field and return bytes\n function decBytes(\n Buffer memory buf\n ) internal pure returns (bytes memory b) {\n uint256 len = decVarint(buf);\n uint256 end = buf.idx + len;\n require(end <= buf.b.length); // avoid overflow\n b = new bytes(len);\n bytes memory bufB = buf.b; // get buf.b mem addr to use in assembly\n uint256 bStart;\n uint256 bufBStart = buf.idx;\n assembly {\n bStart := add(b, 32)\n bufBStart := add(add(bufB, 32), bufBStart)\n }\n for (uint256 i = 0; i < len; i += 32) {\n assembly {\n mstore(add(bStart, i), mload(add(bufBStart, i)))\n }\n }\n buf.idx = end;\n }\n\n // move idx pass current value field, to beginning of next tag or msg end\n function skipValue(Buffer memory buf, WireType wire) internal pure {\n if (wire == WireType.Varint) {\n decVarint(buf);\n } else if (wire == WireType.LengthDelim) {\n uint256 len = decVarint(buf);\n buf.idx += len; // skip len bytes value data\n require(buf.idx <= buf.b.length); // avoid overflow\n } else {\n revert();\n } // unsupported wiretype\n }\n\n function _uint256(bytes memory b) internal pure returns (uint256 v) {\n require(b.length <= 32); // b's length must be smaller than or equal to 32\n assembly {\n v := mload(add(b, 32))\n } // load all 32bytes to v\n v = v >> (8 * (32 - b.length)); // only first b.length is valid\n }\n\n function _address(bytes memory b) internal pure returns (address v) {\n v = _addressPayable(b);\n }\n\n function _addressPayable(\n bytes memory b\n ) internal pure returns (address payable v) {\n require(b.length == 20);\n //load 32bytes then shift right 12 bytes\n assembly {\n v := div(mload(add(b, 32)), 0x1000000000000000000000000)\n }\n }\n\n function _bytes32(bytes memory b) internal pure returns (bytes32 v) {\n require(b.length == 32);\n assembly {\n v := mload(add(b, 32))\n }\n }\n}\n"},"src/bridges/BridgeImplBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\nimport {OnlySocketGatewayOwner, OnlySocketDeployer} from \"../errors/SocketErrors.sol\";\n\n/**\n * @title Abstract Implementation Contract.\n * @notice All Bridge Implementation will follow this interface.\n */\nabstract contract BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketDeployFactory;\n\n /// @notice immutable variable with instance of SocketRoute to access route functions\n ISocketRoute public immutable socketRoute;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /****************************************\n * EVENTS *\n ****************************************/\n\n event SocketBridge(\n uint256 amount,\n address token,\n uint256 toChainId,\n bytes32 bridgeName,\n address sender,\n address receiver,\n bytes32 metadata\n );\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n * @param _socketDeployFactory Socket Deploy Factory address, an immutable variable to set.\n */\n constructor(address _socketGateway, address _socketDeployFactory) {\n socketGateway = _socketGateway;\n socketDeployFactory = _socketDeployFactory;\n socketRoute = ISocketRoute(_socketGateway);\n }\n\n /****************************************\n * MODIFIERS *\n ****************************************/\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketDeployFactory() {\n if (msg.sender != socketDeployFactory) {\n revert OnlySocketDeployer();\n }\n _;\n }\n\n /****************************************\n * RESTRICTED FUNCTIONS *\n ****************************************/\n\n /**\n * @notice function to rescue the ERC20 tokens in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n function killme() external isSocketDeployFactory {\n selfdestruct(payable(msg.sender));\n }\n\n /******************************\n * VIRTUAL FUNCTIONS *\n *****************************/\n\n /**\n * @notice function to bridge which is succeeding the swap function\n * @notice this function is to be used only when bridging as a succeeding step\n * @notice All bridge implementation contracts must implement this function\n * @notice bridge-implementations will have a bridge specific struct with properties used in bridging\n * @param bridgeData encoded value of properties in the bridgeData Struct\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable virtual;\n}\n"},"src/bridges/cbridge/CelerImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../../libraries/Pb.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"./interfaces/cbridge.sol\";\nimport \"./interfaces/ICelerStorageWrapper.sol\";\nimport {TransferIdExists, InvalidCelerRefund, CelerAlreadyRefunded, CelerRefundNotReady} from \"../../errors/SocketErrors.sol\";\nimport {BridgeImplBase} from \"../BridgeImplBase.sol\";\nimport {CBRIDGE} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Celer-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Celer-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of CelerImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract CelerImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable CBridgeIdentifier = CBRIDGE;\n\n /// @notice Utility to perform operation on Buffer\n using Pb for Pb.Buffer;\n\n /// @notice Function-selector for ERC20-token bridging on Celer-Route\n /// @dev This function selector is to be used while building transaction-data to bridge ERC20 tokens\n bytes4 public immutable CELER_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,uint256,bytes32,uint64,uint64,uint32)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Celer-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable CELER_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,uint256,bytes32,uint64,uint64,uint32)\"\n )\n );\n\n bytes4 public immutable CELER_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,uint64,uint32,uint64,bytes32))\"\n )\n );\n\n /// @notice router Contract instance used to deposit ERC20 and Native on to Celer-Bridge\n /// @dev contract instance is to be initialized in the constructor using the routerAddress passed as constructor argument\n ICBridge public immutable router;\n\n /// @notice celerStorageWrapper Contract instance used to store the transferId generated during ERC20 and Native bridge on to Celer-Bridge\n /// @dev contract instance is to be initialized in the constructor using the celerStorageWrapperAddress passed as constructor argument\n ICelerStorageWrapper public immutable celerStorageWrapper;\n\n /// @notice WETH token address\n address public immutable weth;\n\n /// @notice chainId used during generation of transferId generated while bridging ERC20 and Native on to Celer-Bridge\n /// @dev this is to be initialised in the constructor\n uint64 public immutable chainId;\n\n struct WithdrawMsg {\n uint64 chainid; // tag: 1\n uint64 seqnum; // tag: 2\n address receiver; // tag: 3\n address token; // tag: 4\n uint256 amount; // tag: 5\n bytes32 refid; // tag: 6\n }\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure routerAddress, weth-address, celerStorageWrapperAddress are set properly for the chainId in which the contract is being deployed\n constructor(\n address _routerAddress,\n address _weth,\n address _celerStorageWrapperAddress,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = ICBridge(_routerAddress);\n celerStorageWrapper = ICelerStorageWrapper(_celerStorageWrapperAddress);\n weth = _weth;\n chainId = uint64(block.chainid);\n }\n\n // Function to receive Ether. msg.data must be empty\n receive() external payable {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct CelerBridgeDataNoToken {\n address receiverAddress;\n uint64 toChainId;\n uint32 maxSlippage;\n uint64 nonce;\n bytes32 metadata;\n }\n\n struct CelerBridgeData {\n address token;\n address receiverAddress;\n uint64 toChainId;\n uint32 maxSlippage;\n uint64 nonce;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for CelerBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n CelerBridgeData memory celerBridgeData = abi.decode(\n bridgeData,\n (CelerBridgeData)\n );\n\n if (celerBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n weth,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: amount}(\n celerBridgeData.receiverAddress,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n } else {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n celerBridgeData.token,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n router.send(\n celerBridgeData.receiverAddress,\n celerBridgeData.token,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n }\n\n emit SocketBridge(\n amount,\n celerBridgeData.token,\n celerBridgeData.toChainId,\n CBridgeIdentifier,\n msg.sender,\n celerBridgeData.receiverAddress,\n celerBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param celerBridgeData encoded data for CelerBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n CelerBridgeDataNoToken calldata celerBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n weth,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: bridgeAmount}(\n celerBridgeData.receiverAddress,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n } else {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n token,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n router.send(\n celerBridgeData.receiverAddress,\n token,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n celerBridgeData.toChainId,\n CBridgeIdentifier,\n msg.sender,\n celerBridgeData.receiverAddress,\n celerBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Celer-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of recipient\n * @param token address of token being bridged\n * @param amount amount of token for bridging\n * @param toChainId destination ChainId\n * @param nonce nonce of the sender-account address\n * @param maxSlippage maximum Slippage for the bridging\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n uint256 amount,\n bytes32 metadata,\n uint64 toChainId,\n uint64 nonce,\n uint32 maxSlippage\n ) external payable {\n /// @notice transferId is generated using the request-params and nonce of the account\n /// @notice transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n receiverAddress,\n token,\n amount,\n toChainId,\n nonce,\n chainId\n )\n );\n\n /// @notice stored in the CelerStorageWrapper contract\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n router.send(\n receiverAddress,\n token,\n amount,\n toChainId,\n nonce,\n maxSlippage\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n CBridgeIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Celer-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of recipient\n * @param amount amount of token for bridging\n * @param toChainId destination ChainId\n * @param nonce nonce of the sender-account address\n * @param maxSlippage maximum Slippage for the bridging\n */\n function bridgeNativeTo(\n address receiverAddress,\n uint256 amount,\n bytes32 metadata,\n uint64 toChainId,\n uint64 nonce,\n uint32 maxSlippage\n ) external payable {\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n receiverAddress,\n weth,\n amount,\n toChainId,\n nonce,\n chainId\n )\n );\n\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: amount}(\n receiverAddress,\n amount,\n toChainId,\n nonce,\n maxSlippage\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n CBridgeIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle refund from CelerBridge-Router\n * @param _request request data generated offchain using the celer-SDK\n * @param _sigs generated offchain using the celer-SDK\n * @param _signers generated offchain using the celer-SDK\n * @param _powers generated offchain using the celer-SDK\n */\n function refundCelerUser(\n bytes calldata _request,\n bytes[] calldata _sigs,\n address[] calldata _signers,\n uint256[] calldata _powers\n ) external payable {\n WithdrawMsg memory request = decWithdrawMsg(_request);\n bytes32 transferId = keccak256(\n abi.encodePacked(\n request.chainid,\n request.seqnum,\n request.receiver,\n request.token,\n request.amount\n )\n );\n uint256 _initialNativeBalance = address(this).balance;\n uint256 _initialTokenBalance = ERC20(request.token).balanceOf(\n address(this)\n );\n if (!router.withdraws(transferId)) {\n router.withdraw(_request, _sigs, _signers, _powers);\n }\n\n if (request.receiver != socketGateway) {\n revert InvalidCelerRefund();\n }\n\n address _receiver = celerStorageWrapper.getAddressFromTransferId(\n request.refid\n );\n celerStorageWrapper.deleteTransferId(request.refid);\n\n if (_receiver == address(0)) {\n revert CelerAlreadyRefunded();\n }\n\n uint256 _nativeBalanceAfter = address(this).balance;\n uint256 _tokenBalanceAfter = ERC20(request.token).balanceOf(\n address(this)\n );\n if (_nativeBalanceAfter > _initialNativeBalance) {\n if ((_nativeBalanceAfter - _initialNativeBalance) != request.amount)\n revert CelerRefundNotReady();\n payable(_receiver).transfer(request.amount);\n return;\n }\n\n if (_tokenBalanceAfter > _initialTokenBalance) {\n if ((_tokenBalanceAfter - _initialTokenBalance) != request.amount)\n revert CelerRefundNotReady();\n ERC20(request.token).safeTransfer(_receiver, request.amount);\n return;\n }\n\n revert CelerRefundNotReady();\n }\n\n function decWithdrawMsg(\n bytes memory raw\n ) internal pure returns (WithdrawMsg memory m) {\n Pb.Buffer memory buf = Pb.fromBytes(raw);\n\n uint256 tag;\n Pb.WireType wire;\n while (buf.hasMore()) {\n (tag, wire) = buf.decKey();\n if (false) {}\n // solidity has no switch/case\n else if (tag == 1) {\n m.chainid = uint64(buf.decVarint());\n } else if (tag == 2) {\n m.seqnum = uint64(buf.decVarint());\n } else if (tag == 3) {\n m.receiver = Pb._address(buf.decBytes());\n } else if (tag == 4) {\n m.token = Pb._address(buf.decBytes());\n } else if (tag == 5) {\n m.amount = Pb._uint256(buf.decBytes());\n } else if (tag == 6) {\n m.refid = Pb._bytes32(buf.decBytes());\n } else {\n buf.skipValue(wire);\n } // skip value of unknown tag\n }\n } // end decoder WithdrawMsg\n}\n"},"src/libraries/LibBytes.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n// Functions taken out from https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol\nlibrary LibBytes {\n // solhint-disable no-inline-assembly\n\n // LibBytes specific errors\n error SliceOverflow();\n error SliceOutOfBounds();\n error AddressOutOfBounds();\n error UintOutOfBounds();\n\n // -------------------------\n\n function concat(\n bytes memory _preBytes,\n bytes memory _postBytes\n ) internal pure returns (bytes memory) {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(\n 0x40,\n and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n )\n )\n }\n\n return tempBytes;\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n ) internal pure returns (bytes memory) {\n if (_length + 31 < _length) {\n revert SliceOverflow();\n }\n if (_bytes.length < _start + _length) {\n revert SliceOutOfBounds();\n }\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(\n add(tempBytes, lengthmod),\n mul(0x20, iszero(lengthmod))\n )\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(\n add(\n add(_bytes, lengthmod),\n mul(0x20, iszero(lengthmod))\n ),\n _start\n )\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n}\n"},"src/bridges/hyphen/interfaces/hyphen.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/**\n * @title HyphenLiquidityPoolManager\n * @notice interface with functions to bridge ERC20 and Native via Hyphen-Bridge\n * @author Socket dot tech.\n */\ninterface HyphenLiquidityPoolManager {\n /**\n * @dev Function used to deposit tokens into pool to initiate a cross chain token transfer.\n * @param toChainId Chain id where funds needs to be transfered\n * @param tokenAddress ERC20 Token address that needs to be transfered\n * @param receiver Address on toChainId where tokens needs to be transfered\n * @param amount Amount of token being transfered\n */\n function depositErc20(\n uint256 toChainId,\n address tokenAddress,\n address receiver,\n uint256 amount,\n string calldata tag\n ) external;\n\n /**\n * @dev Function used to deposit native token into pool to initiate a cross chain token transfer.\n * @param receiver Address on toChainId where tokens needs to be transfered\n * @param toChainId Chain id where funds needs to be transfered\n */\n function depositNative(\n address receiver,\n uint256 toChainId,\n string calldata tag\n ) external payable;\n}\n"},"src/swap/SwapImplBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {OnlySocketGatewayOwner, OnlySocketDeployer} from \"../errors/SocketErrors.sol\";\n\n/**\n * @title Abstract Implementation Contract.\n * @notice All Swap Implementation will follow this interface.\n * @author Socket dot tech.\n */\nabstract contract SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketDeployFactory;\n\n /// @notice FunctionSelector used to delegatecall to the performAction function of swap-router-implementation\n bytes4 public immutable SWAP_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"performAction(address,address,uint256,address,bytes)\")\n );\n\n /// @notice FunctionSelector used to delegatecall to the performActionWithIn function of swap-router-implementation\n bytes4 public immutable SWAP_WITHIN_FUNCTION_SELECTOR =\n bytes4(keccak256(\"performActionWithIn(address,address,uint256,bytes)\"));\n\n /****************************************\n * EVENTS *\n ****************************************/\n\n event SocketSwapTokens(\n address fromToken,\n address toToken,\n uint256 buyAmount,\n uint256 sellAmount,\n bytes32 routeName,\n address receiver\n );\n\n /**\n * @notice Construct the base for all SwapImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n */\n constructor(address _socketGateway, address _socketDeployFactory) {\n socketGateway = _socketGateway;\n socketDeployFactory = _socketDeployFactory;\n }\n\n /****************************************\n * MODIFIERS *\n ****************************************/\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketDeployFactory() {\n if (msg.sender != socketDeployFactory) {\n revert OnlySocketDeployer();\n }\n _;\n }\n\n /****************************************\n * RESTRICTED FUNCTIONS *\n ****************************************/\n\n /**\n * @notice function to rescue the ERC20 tokens in the Swap-Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the Swap-Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n function killme() external isSocketDeployFactory {\n selfdestruct(payable(msg.sender));\n }\n\n /******************************\n * VIRTUAL FUNCTIONS *\n *****************************/\n\n /**\n * @notice function to swap tokens on the chain\n * All swap implementation contracts must implement this function\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param receiverAddress recipient address of toToken\n * @param data encoded value of properties in the swapData Struct\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes memory data\n ) external payable virtual returns (uint256);\n\n /**\n * @notice function to swapWith - swaps tokens on the chain to socketGateway as recipient\n * All swap implementation contracts must implement this function\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes memory swapExtraData\n ) external payable virtual returns (uint256, address);\n}\n"},"src/swap/zerox/ZeroXSwapImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {Address0Provided, SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {ZEROX} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title ZeroX-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via ZeroX-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of ZeroX-Swap-Implementation\n * @author Socket dot tech.\n */\ncontract ZeroXSwapImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable ZeroXIdentifier = ZEROX;\n\n /// @notice unique name to identify the router, used to emit event upon successful bridging\n bytes32 public immutable NAME = keccak256(\"Zerox-Router\");\n\n /// @notice address of ZeroX-Exchange-Proxy to swap the tokens on Chain\n address payable public immutable zeroXExchangeProxy;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @notice ZeroXExchangeProxy contract is payable to allow ethereum swaps\n /// @dev ensure _zeroXExchangeProxy are set properly for the chainId in which the contract is being deployed\n constructor(\n address _zeroXExchangeProxy,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n zeroXExchangeProxy = payable(_zeroXExchangeProxy);\n }\n\n receive() external payable {}\n\n fallback() external payable {}\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * @dev This is called only when there is a request for a swap.\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken is to be swapped\n * @param amount amount to be swapped\n * @param receiverAddress address of toToken recipient\n * @param swapExtraData data required for zeroX Exchange to get the swap done\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 erc20ToToken = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, address(this), amount);\n token.safeApprove(zeroXExchangeProxy, amount);\n\n // solhint-disable-next-line\n (bool success, ) = zeroXExchangeProxy.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(zeroXExchangeProxy, 0);\n } else {\n (bool success, ) = zeroXExchangeProxy.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n if (toToken == NATIVE_TOKEN_ADDRESS) {\n payable(receiverAddress).transfer(returnAmount);\n } else {\n erc20ToToken.transfer(receiverAddress, returnAmount);\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n ZeroXIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 erc20ToToken = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, address(this), amount);\n token.safeApprove(zeroXExchangeProxy, amount);\n\n // solhint-disable-next-line\n (bool success, ) = zeroXExchangeProxy.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(zeroXExchangeProxy, 0);\n } else {\n (bool success, ) = zeroXExchangeProxy.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n ZeroXIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/bridges/cbridge/interfaces/cbridge.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\ninterface ICBridge {\n function send(\n address _receiver,\n address _token,\n uint256 _amount,\n uint64 _dstChinId,\n uint64 _nonce,\n uint32 _maxSlippage\n ) external;\n\n function sendNative(\n address _receiver,\n uint256 _amount,\n uint64 _dstChinId,\n uint64 _nonce,\n uint32 _maxSlippage\n ) external payable;\n\n function withdraws(bytes32 withdrawId) external view returns (bool);\n\n function withdraw(\n bytes calldata _wdmsg,\n bytes[] calldata _sigs,\n address[] calldata _signers,\n uint256[] calldata _powers\n ) external;\n}\n"},"src/bridges/stargate/l2/Stargate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/stargate.sol\";\nimport \"../../../errors/SocketErrors.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {STARGATE} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Stargate-L2-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L2-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L2-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract StargateImplL2 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable StargateIdentifier = STARGATE;\n\n /// @notice Function-selector for ERC20-token bridging on Stargate-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable STARGATE_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))\"\n )\n );\n\n bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Stargate-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable STARGATE_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n /// @notice Stargate Router to bridge ERC20 tokens\n IBridgeStargate public immutable router;\n\n /// @notice Stargate Router to bridge native tokens\n IBridgeStargate public immutable routerETH;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _routerEth,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = IBridgeStargate(_router);\n routerETH = IBridgeStargate(_routerEth);\n }\n\n /// @notice Struct to be used as a input parameter for Bridging tokens via Stargate-L2-route\n /// @dev while building transactionData,values should be set in this sequence of properties in this struct\n struct StargateBridgeExtraData {\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 destinationGasLimit;\n uint256 minReceivedAmt;\n bytes32 metadata;\n bytes destinationPayload;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct StargateBridgeDataNoToken {\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n struct StargateBridgeData {\n address token;\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Stargate-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n StargateBridgeData memory stargateBridgeData = abi.decode(\n bridgeData,\n (StargateBridgeData)\n );\n\n if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n amount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(stargateBridgeData.token).safeApprove(\n address(router),\n amount\n );\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n amount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n stargateBridgeData.token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swapping.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param stargateBridgeData encoded data for StargateBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n StargateBridgeDataNoToken calldata stargateBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n routerETH.swapETH{\n value: bridgeAmount + stargateBridgeData.optionalValue\n }(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n bridgeAmount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(token).safeApprove(address(router), bridgeAmount);\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n bridgeAmount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0,\n \"0x\"\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param senderAddress address of sender\n * @param receiverAddress address of recipient\n * @param amount amount of token being bridge\n * @param value value\n * @param optionalValue optionalValue\n * @param stargateBridgeExtraData stargate bridge extradata\n */\n function bridgeERC20To(\n address token,\n address senderAddress,\n address receiverAddress,\n uint256 amount,\n uint256 value,\n uint256 optionalValue,\n StargateBridgeExtraData calldata stargateBridgeExtraData\n ) external payable {\n // token address might not be indication thats why passed through extraData\n if (token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateBridgeExtraData.stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n stargateBridgeExtraData.minReceivedAmt\n );\n } else {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n {\n router.swap{value: value}(\n stargateBridgeExtraData.stargateDstChainId,\n stargateBridgeExtraData.srcPoolId,\n stargateBridgeExtraData.dstPoolId,\n payable(senderAddress), // default to refund to main contract\n amount,\n stargateBridgeExtraData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeExtraData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(receiverAddress),\n stargateBridgeExtraData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n token,\n stargateBridgeExtraData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n stargateBridgeExtraData.metadata\n );\n }\n\n function bridgeNativeTo(\n address receiverAddress,\n address senderAddress,\n uint16 stargateDstChainId,\n uint256 amount,\n uint256 minReceivedAmt,\n uint256 optionalValue,\n bytes32 metadata\n ) external payable {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n minReceivedAmt\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":1000000},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"metadata":{"useLiteralContent":true},"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_disabledRoute\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ArrayLengthMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IncorrectBridgeRatios\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyNominee\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"controllerAddress\",\"type\":\"address\"}],\"name\":\"ControllerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"ControllerDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"route\",\"type\":\"address\"}],\"name\":\"NewRouteAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"}],\"name\":\"OwnerClaimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"nominee\",\"type\":\"address\"}],\"name\":\"OwnerNominated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"RouteDisabled\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"BRIDGE_AFTER_SWAP_SELECTOR\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"\",\"type\":\"bytes4\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"CENT_PERCENT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"controllerAddress\",\"type\":\"address\"}],\"name\":\"addController\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"routeAddress\",\"type\":\"address\"}],\"name\":\"addRoute\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"addressAt\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"controllerCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"controllers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"disableController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"disableRoute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disabledRouteAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct ISocketGateway.SocketControllerRequest\",\"name\":\"socketControllerRequest\",\"type\":\"tuple\"}],\"name\":\"executeController\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct ISocketGateway.SocketControllerRequest[]\",\"name\":\"controllerRequests\",\"type\":\"tuple[]\"}],\"name\":\"executeControllers\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"routeData\",\"type\":\"bytes\"}],\"name\":\"executeRoute\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"routeIds\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"dataItems\",\"type\":\"bytes[]\"}],\"name\":\"executeRoutes\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"getController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"getRoute\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"nominee_\",\"type\":\"address\"}],\"name\":\"nominateOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nominee\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"userAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueEther\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"userAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"routes\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"routesCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"routeAddresses\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"tokenAddresses\",\"type\":\"address[]\"},{\"internalType\":\"bool\",\"name\":\"isMax\",\"type\":\"bool\"}],\"name\":\"setApprovalForRouters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"swapRouteId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"swapImplData\",\"type\":\"bytes\"},{\"internalType\":\"uint32[]\",\"name\":\"bridgeRouteIds\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"bridgeImplDataItems\",\"type\":\"bytes[]\"},{\"internalType\":\"uint256[]\",\"name\":\"bridgeRatios\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes[]\",\"name\":\"eventDataItems\",\"type\":\"bytes[]\"}],\"internalType\":\"struct ISocketRequest.SwapMultiBridgeRequest\",\"name\":\"swapMultiBridgeRequest\",\"type\":\"tuple\"}],\"name\":\"swapAndMultiBridge\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"SocketGateway","CompilerVersion":"v0.8.7+commit.e28d00a7","OptimizationUsed":1,"Runs":1000000,"ConstructorArguments":"0x000000000000000000000000e8dd38e673a93ccfc2e3d7053efccb5c93f493650000000000000000000000000f34a522ff82151c90679b73211955068fd854f1","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":1,"Implementation":"0xa3c4e32af0da5efaddb20cc9fb26159f55c8c42f","SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json new file mode 100644 index 0000000000000..08b838e2bc418 --- /dev/null +++ b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x71356e37e0368bd10bfdbf41dc052fe5fa24cd05","contractCreator":"0xaa1d342354d755ec515f40e7d5e83cb4184bb9ee","txHash":"0x1c800c2c2d5230823602cae6896a8db1ab7d1341ca697c6c64d9f0edf11dabe2"} \ No newline at end of file diff --git a/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json new file mode 100644 index 0000000000000..08318ba41edfe --- /dev/null +++ b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"@openzeppelin/contracts/access/IAccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n"},"@openzeppelin/contracts/token/ERC721/IERC721.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n}\n"},"@openzeppelin/contracts/access/IAccessControlEnumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n"},"@openzeppelin/contracts/utils/StorageSlot.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```\n * contract ERC1967 {\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n}\n"},"@openzeppelin/contracts/utils/cryptography/ECDSA.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Strings.sol\";\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n"},"contracts/v0.8/extensions/GatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/security/Pausable.sol\";\nimport \"../interfaces/IQuorum.sol\";\nimport \"../interfaces/IWeightedValidator.sol\";\nimport \"./HasProxyAdmin.sol\";\n\nabstract contract GatewayV2 is HasProxyAdmin, Pausable, IQuorum {\n /// @dev Emitted when the validator contract address is updated.\n event ValidatorContractUpdated(IWeightedValidator);\n\n uint256 internal _num;\n uint256 internal _denom;\n\n IWeightedValidator public validatorContract;\n uint256 public nonce;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n */\n uint256[50] private ______gap;\n\n /**\n * @dev See {IQuorum-getThreshold}.\n */\n function getThreshold() external view virtual returns (uint256, uint256) {\n return (_num, _denom);\n }\n\n /**\n * @dev See {IQuorum-checkThreshold}.\n */\n function checkThreshold(uint256 _voteWeight) external view virtual returns (bool) {\n return _voteWeight * _denom >= _num * validatorContract.totalWeights();\n }\n\n /**\n * @dev See {IQuorum-setThreshold}.\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n onlyAdmin\n returns (uint256, uint256)\n {\n return _setThreshold(_numerator, _denominator);\n }\n\n /**\n * @dev Triggers paused state.\n */\n function pause() external onlyAdmin {\n _pause();\n }\n\n /**\n * @dev Triggers unpaused state.\n */\n function unpause() external onlyAdmin {\n _unpause();\n }\n\n /**\n * @dev Sets validator contract address.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `ValidatorContractUpdated` event.\n *\n */\n function setValidatorContract(IWeightedValidator _validatorContract) external virtual onlyAdmin {\n _setValidatorContract(_validatorContract);\n }\n\n /**\n * @dev See {IQuorum-minimumVoteWeight}.\n */\n function minimumVoteWeight() public view virtual returns (uint256) {\n return _minimumVoteWeight(validatorContract.totalWeights());\n }\n\n /**\n * @dev Sets validator contract address.\n *\n * Emits the `ValidatorContractUpdated` event.\n *\n */\n function _setValidatorContract(IWeightedValidator _validatorContract) internal virtual {\n validatorContract = _validatorContract;\n emit ValidatorContractUpdated(_validatorContract);\n }\n\n /**\n * @dev Sets threshold and returns the old one.\n *\n * Emits the `ThresholdUpdated` event.\n *\n */\n function _setThreshold(uint256 _numerator, uint256 _denominator)\n internal\n virtual\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n require(_numerator <= _denominator, \"GatewayV2: invalid threshold\");\n _previousNum = _num;\n _previousDenom = _denom;\n _num = _numerator;\n _denom = _denominator;\n emit ThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom);\n }\n\n /**\n * @dev Returns minimum vote weight.\n */\n function _minimumVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) {\n return (_num * _totalWeight + _denom - 1) / _denom;\n }\n}\n"},"@openzeppelin/contracts/proxy/utils/Initializable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/Address.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the\n * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() initializer {}\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\n // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the\n // contract may have been reentered.\n require(_initializing ? _isConstructor() : !_initialized, \"Initializable: contract is already initialized\");\n\n bool isTopLevelCall = !_initializing;\n if (isTopLevelCall) {\n _initializing = true;\n _initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n _initializing = false;\n }\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} modifier, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n function _isConstructor() private view returns (bool) {\n return !Address.isContract(address(this));\n }\n}\n"},"contracts/v0.8/extensions/WithdrawalLimitation.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./GatewayV2.sol\";\n\nabstract contract WithdrawalLimitation is GatewayV2 {\n /// @dev Emitted when the high-tier vote weight threshold is updated\n event HighTierVoteWeightThresholdUpdated(\n uint256 indexed nonce,\n uint256 indexed numerator,\n uint256 indexed denominator,\n uint256 previousNumerator,\n uint256 previousDenominator\n );\n /// @dev Emitted when the thresholds for high-tier withdrawals that requires high-tier vote weights are updated\n event HighTierThresholdsUpdated(address[] tokens, uint256[] thresholds);\n /// @dev Emitted when the thresholds for locked withdrawals are updated\n event LockedThresholdsUpdated(address[] tokens, uint256[] thresholds);\n /// @dev Emitted when the fee percentages to unlock withdraw are updated\n event UnlockFeePercentagesUpdated(address[] tokens, uint256[] percentages);\n /// @dev Emitted when the daily limit thresholds are updated\n event DailyWithdrawalLimitsUpdated(address[] tokens, uint256[] limits);\n\n uint256 public constant _MAX_PERCENTAGE = 1_000_000;\n\n uint256 internal _highTierVWNum;\n uint256 internal _highTierVWDenom;\n\n /// @dev Mapping from mainchain token => the amount thresholds for high-tier withdrawals that requires high-tier vote weights\n mapping(address => uint256) public highTierThreshold;\n /// @dev Mapping from mainchain token => the amount thresholds to lock withdrawal\n mapping(address => uint256) public lockedThreshold;\n /// @dev Mapping from mainchain token => unlock fee percentages for unlocker\n /// @notice Values 0-1,000,000 map to 0%-100%\n mapping(address => uint256) public unlockFeePercentages;\n /// @dev Mapping from mainchain token => daily limit amount for withdrawal\n mapping(address => uint256) public dailyWithdrawalLimit;\n /// @dev Mapping from token address => today withdrawal amount\n mapping(address => uint256) public lastSyncedWithdrawal;\n /// @dev Mapping from token address => last date synced to record the `lastSyncedWithdrawal`\n mapping(address => uint256) public lastDateSynced;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n */\n uint256[50] private ______gap;\n\n /**\n * @dev Override {GatewayV2-setThreshold}.\n *\n * Requirements:\n * - The high-tier vote weight threshold must equal to or larger than the normal threshold.\n *\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n override\n onlyAdmin\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n (_previousNum, _previousDenom) = _setThreshold(_numerator, _denominator);\n _verifyThresholds();\n }\n\n /**\n * @dev Returns the high-tier vote weight threshold.\n */\n function getHighTierVoteWeightThreshold() external view virtual returns (uint256, uint256) {\n return (_highTierVWNum, _highTierVWDenom);\n }\n\n /**\n * @dev Checks whether the `_voteWeight` passes the high-tier vote weight threshold.\n */\n function checkHighTierVoteWeightThreshold(uint256 _voteWeight) external view virtual returns (bool) {\n return _voteWeight * _highTierVWDenom >= _highTierVWNum * validatorContract.totalWeights();\n }\n\n /**\n * @dev Sets high-tier vote weight threshold and returns the old one.\n *\n * Requirements:\n * - The method caller is admin.\n * - The high-tier vote weight threshold must equal to or larger than the normal threshold.\n *\n * Emits the `HighTierVoteWeightThresholdUpdated` event.\n *\n */\n function setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n onlyAdmin\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n (_previousNum, _previousDenom) = _setHighTierVoteWeightThreshold(_numerator, _denominator);\n _verifyThresholds();\n }\n\n /**\n * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `HighTierThresholdsUpdated` event.\n *\n */\n function setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds)\n external\n virtual\n onlyAdmin\n {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setHighTierThresholds(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets the amount thresholds to lock withdrawal.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `LockedThresholdsUpdated` event.\n *\n */\n function setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) external virtual onlyAdmin {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setLockedThresholds(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets fee percentages to unlock withdrawal.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `UnlockFeePercentagesUpdated` event.\n *\n */\n function setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages)\n external\n virtual\n onlyAdmin\n {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setUnlockFeePercentages(_tokens, _percentages);\n }\n\n /**\n * @dev Sets daily limit amounts for the withdrawals.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `DailyWithdrawalLimitsUpdated` event.\n *\n */\n function setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) external virtual onlyAdmin {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setDailyWithdrawalLimits(_tokens, _limits);\n }\n\n /**\n * @dev Checks whether the withdrawal reaches the limitation.\n */\n function reachedWithdrawalLimit(address _token, uint256 _quantity) external view virtual returns (bool) {\n return _reachedWithdrawalLimit(_token, _quantity);\n }\n\n /**\n * @dev Sets high-tier vote weight threshold and returns the old one.\n *\n * Emits the `HighTierVoteWeightThresholdUpdated` event.\n *\n */\n function _setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator)\n internal\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n require(_numerator <= _denominator, \"WithdrawalLimitation: invalid threshold\");\n _previousNum = _highTierVWNum;\n _previousDenom = _highTierVWDenom;\n _highTierVWNum = _numerator;\n _highTierVWDenom = _denominator;\n emit HighTierVoteWeightThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom);\n }\n\n /**\n * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `HighTierThresholdsUpdated` event.\n *\n */\n function _setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual {\n require(_tokens.length == _thresholds.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n highTierThreshold[_tokens[_i]] = _thresholds[_i];\n }\n emit HighTierThresholdsUpdated(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets the amount thresholds to lock withdrawal.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `LockedThresholdsUpdated` event.\n *\n */\n function _setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual {\n require(_tokens.length == _thresholds.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n lockedThreshold[_tokens[_i]] = _thresholds[_i];\n }\n emit LockedThresholdsUpdated(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets fee percentages to unlock withdrawal.\n *\n * Requirements:\n * - The array lengths are equal.\n * - The percentage is equal to or less than 100_000.\n *\n * Emits the `UnlockFeePercentagesUpdated` event.\n *\n */\n function _setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages) internal virtual {\n require(_tokens.length == _percentages.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n require(_percentages[_i] <= _MAX_PERCENTAGE, \"WithdrawalLimitation: invalid percentage\");\n unlockFeePercentages[_tokens[_i]] = _percentages[_i];\n }\n emit UnlockFeePercentagesUpdated(_tokens, _percentages);\n }\n\n /**\n * @dev Sets daily limit amounts for the withdrawals.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `DailyWithdrawalLimitsUpdated` event.\n *\n */\n function _setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) internal virtual {\n require(_tokens.length == _limits.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n dailyWithdrawalLimit[_tokens[_i]] = _limits[_i];\n }\n emit DailyWithdrawalLimitsUpdated(_tokens, _limits);\n }\n\n /**\n * @dev Checks whether the withdrawal reaches the daily limitation.\n *\n * Requirements:\n * - The daily withdrawal threshold should not apply for locked withdrawals.\n *\n */\n function _reachedWithdrawalLimit(address _token, uint256 _quantity) internal view virtual returns (bool) {\n if (_lockedWithdrawalRequest(_token, _quantity)) {\n return false;\n }\n\n uint256 _currentDate = block.timestamp / 1 days;\n if (_currentDate > lastDateSynced[_token]) {\n return dailyWithdrawalLimit[_token] <= _quantity;\n } else {\n return dailyWithdrawalLimit[_token] <= lastSyncedWithdrawal[_token] + _quantity;\n }\n }\n\n /**\n * @dev Record withdrawal token.\n */\n function _recordWithdrawal(address _token, uint256 _quantity) internal virtual {\n uint256 _currentDate = block.timestamp / 1 days;\n if (_currentDate > lastDateSynced[_token]) {\n lastDateSynced[_token] = _currentDate;\n lastSyncedWithdrawal[_token] = _quantity;\n } else {\n lastSyncedWithdrawal[_token] += _quantity;\n }\n }\n\n /**\n * @dev Returns whether the withdrawal request is locked or not.\n */\n function _lockedWithdrawalRequest(address _token, uint256 _quantity) internal view virtual returns (bool) {\n return lockedThreshold[_token] <= _quantity;\n }\n\n /**\n * @dev Computes fee percentage.\n */\n function _computeFeePercentage(uint256 _amount, uint256 _percentage) internal view virtual returns (uint256) {\n return (_amount * _percentage) / _MAX_PERCENTAGE;\n }\n\n /**\n * @dev Returns high-tier vote weight.\n */\n function _highTierVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) {\n return (_highTierVWNum * _totalWeight + _highTierVWDenom - 1) / _highTierVWDenom;\n }\n\n /**\n * @dev Validates whether the high-tier vote weight threshold is larger than the normal threshold.\n */\n function _verifyThresholds() internal view {\n require(_num * _highTierVWDenom <= _highTierVWNum * _denom, \"WithdrawalLimitation: invalid thresholds\");\n }\n}\n"},"contracts/v0.8/interfaces/MappedTokenConsumer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../library/Token.sol\";\n\ninterface MappedTokenConsumer {\n struct MappedToken {\n Token.Standard erc;\n address tokenAddr;\n }\n}\n"},"contracts/v0.8/library/Token.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"../interfaces/IWETH.sol\";\n\nlibrary Token {\n enum Standard {\n ERC20,\n ERC721\n }\n struct Info {\n Standard erc;\n // For ERC20: the id must be 0 and the quantity is larger than 0.\n // For ERC721: the quantity must be 0.\n uint256 id;\n uint256 quantity;\n }\n\n // keccak256(\"TokenInfo(uint8 erc,uint256 id,uint256 quantity)\");\n bytes32 public constant INFO_TYPE_HASH = 0x1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d;\n\n /**\n * @dev Returns token info struct hash.\n */\n function hash(Info memory _info) internal pure returns (bytes32) {\n return keccak256(abi.encode(INFO_TYPE_HASH, _info.erc, _info.id, _info.quantity));\n }\n\n /**\n * @dev Validates the token info.\n */\n function validate(Info memory _info) internal pure {\n require(\n (_info.erc == Standard.ERC20 && _info.quantity > 0 && _info.id == 0) ||\n (_info.erc == Standard.ERC721 && _info.quantity == 0),\n \"Token: invalid info\"\n );\n }\n\n /**\n * @dev Transfer asset from.\n *\n * Requirements:\n * - The `_from` address must approve for the contract using this library.\n *\n */\n function transferFrom(\n Info memory _info,\n address _from,\n address _to,\n address _token\n ) internal {\n bool _success;\n bytes memory _data;\n if (_info.erc == Standard.ERC20) {\n (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _info.quantity));\n _success = _success && (_data.length == 0 || abi.decode(_data, (bool)));\n } else if (_info.erc == Standard.ERC721) {\n // bytes4(keccak256(\"transferFrom(address,address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _info.id));\n } else {\n revert(\"Token: unsupported token standard\");\n }\n\n if (!_success) {\n revert(\n string(\n abi.encodePacked(\n \"Token: could not transfer \",\n toString(_info),\n \" from \",\n Strings.toHexString(uint160(_from), 20),\n \" to \",\n Strings.toHexString(uint160(_to), 20),\n \" token \",\n Strings.toHexString(uint160(_token), 20)\n )\n )\n );\n }\n }\n\n /**\n * @dev Transfers ERC721 token and returns the result.\n */\n function tryTransferERC721(\n address _token,\n address _to,\n uint256 _id\n ) internal returns (bool _success) {\n (_success, ) = _token.call(abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), _to, _id));\n }\n\n /**\n * @dev Transfers ERC20 token and returns the result.\n */\n function tryTransferERC20(\n address _token,\n address _to,\n uint256 _quantity\n ) internal returns (bool _success) {\n bytes memory _data;\n (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _quantity));\n _success = _success && (_data.length == 0 || abi.decode(_data, (bool)));\n }\n\n /**\n * @dev Transfer assets from current address to `_to` address.\n */\n function transfer(\n Info memory _info,\n address _to,\n address _token\n ) internal {\n bool _success;\n if (_info.erc == Standard.ERC20) {\n _success = tryTransferERC20(_token, _to, _info.quantity);\n } else if (_info.erc == Standard.ERC721) {\n _success = tryTransferERC721(_token, _to, _info.id);\n } else {\n revert(\"Token: unsupported token standard\");\n }\n\n if (!_success) {\n revert(\n string(\n abi.encodePacked(\n \"Token: could not transfer \",\n toString(_info),\n \" to \",\n Strings.toHexString(uint160(_to), 20),\n \" token \",\n Strings.toHexString(uint160(_token), 20)\n )\n )\n );\n }\n }\n\n /**\n * @dev Tries minting and transfering assets.\n *\n * @notice Prioritizes transfer native token if the token is wrapped.\n *\n */\n function handleAssetTransfer(\n Info memory _info,\n address payable _to,\n address _token,\n IWETH _wrappedNativeToken\n ) internal {\n bool _success;\n if (_token == address(_wrappedNativeToken)) {\n // Try sending the native token before transferring the wrapped token\n if (!_to.send(_info.quantity)) {\n _wrappedNativeToken.deposit{ value: _info.quantity }();\n transfer(_info, _to, _token);\n }\n } else if (_info.erc == Token.Standard.ERC20) {\n uint256 _balance = IERC20(_token).balanceOf(address(this));\n\n if (_balance < _info.quantity) {\n // bytes4(keccak256(\"mint(address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, address(this), _info.quantity - _balance));\n require(_success, \"Token: ERC20 minting failed\");\n }\n\n transfer(_info, _to, _token);\n } else if (_info.erc == Token.Standard.ERC721) {\n if (!tryTransferERC721(_token, _to, _info.id)) {\n // bytes4(keccak256(\"mint(address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, _to, _info.id));\n require(_success, \"Token: ERC721 minting failed\");\n }\n } else {\n revert(\"Token: unsupported token standard\");\n }\n }\n\n /**\n * @dev Returns readable string.\n */\n function toString(Info memory _info) internal pure returns (string memory) {\n return\n string(\n abi.encodePacked(\n \"TokenInfo(\",\n Strings.toHexString(uint160(_info.erc), 1),\n \",\",\n Strings.toHexString(_info.id),\n \",\",\n Strings.toHexString(_info.quantity),\n \")\"\n )\n );\n }\n\n struct Owner {\n address addr;\n address tokenAddr;\n uint256 chainId;\n }\n\n // keccak256(\"TokenOwner(address addr,address tokenAddr,uint256 chainId)\");\n bytes32 public constant OWNER_TYPE_HASH = 0x353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e8764;\n\n /**\n * @dev Returns ownership struct hash.\n */\n function hash(Owner memory _owner) internal pure returns (bytes32) {\n return keccak256(abi.encode(OWNER_TYPE_HASH, _owner.addr, _owner.tokenAddr, _owner.chainId));\n }\n}\n"},"contracts/v0.8/mainchain/IMainchainGatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../interfaces/IWETH.sol\";\nimport \"../library/Transfer.sol\";\nimport \"../interfaces/SignatureConsumer.sol\";\nimport \"../interfaces/MappedTokenConsumer.sol\";\n\ninterface IMainchainGatewayV2 is SignatureConsumer, MappedTokenConsumer {\n /// @dev Emitted when the deposit is requested\n event DepositRequested(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the assets are withdrawn\n event Withdrew(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the tokens are mapped\n event TokenMapped(address[] mainchainTokens, address[] roninTokens, Token.Standard[] standards);\n /// @dev Emitted when the wrapped native token contract is updated\n event WrappedNativeTokenContractUpdated(IWETH weth);\n /// @dev Emitted when the withdrawal is locked\n event WithdrawalLocked(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the withdrawal is unlocked\n event WithdrawalUnlocked(bytes32 receiptHash, Transfer.Receipt receipt);\n\n /**\n * @dev Returns the domain seperator.\n */\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /**\n * @dev Returns deposit count.\n */\n function depositCount() external view returns (uint256);\n\n /**\n * @dev Sets the wrapped native token contract.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `WrappedNativeTokenContractUpdated` event.\n *\n */\n function setWrappedNativeTokenContract(IWETH _wrappedToken) external;\n\n /**\n * @dev Returns whether the withdrawal is locked.\n */\n function withdrawalLocked(uint256 withdrawalId) external view returns (bool);\n\n /**\n * @dev Returns the withdrawal hash.\n */\n function withdrawalHash(uint256 withdrawalId) external view returns (bytes32);\n\n /**\n * @dev Locks the assets and request deposit.\n */\n function requestDepositFor(Transfer.Request calldata _request) external payable;\n\n /**\n * @dev Withdraws based on the receipt and the validator signatures.\n * Returns whether the withdrawal is locked.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function submitWithdrawal(Transfer.Receipt memory _receipt, Signature[] memory _signatures)\n external\n returns (bool _locked);\n\n /**\n * @dev Approves a specific withdrawal.\n *\n * Requirements:\n * - The method caller is a validator.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function unlockWithdrawal(Transfer.Receipt calldata _receipt) external;\n\n /**\n * @dev Maps mainchain tokens to Ronin network.\n *\n * Requirement:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) external;\n\n /**\n * @dev Maps mainchain tokens to Ronin network and sets thresholds.\n *\n * Requirement:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function mapTokensAndThresholds(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards,\n uint256[][4] calldata _thresholds\n ) external;\n\n /**\n * @dev Returns token address on Ronin network.\n * @notice Reverts for unsupported token.\n */\n function getRoninToken(address _mainchainToken) external view returns (MappedToken memory _token);\n}\n"},"contracts/v0.8/mainchain/MainchainGatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport \"@openzeppelin/contracts/proxy/utils/Initializable.sol\";\nimport \"../extensions/GatewayV2.sol\";\nimport \"../extensions/WithdrawalLimitation.sol\";\nimport \"../library/Transfer.sol\";\nimport \"./IMainchainGatewayV2.sol\";\n\ncontract MainchainGatewayV2 is WithdrawalLimitation, Initializable, AccessControlEnumerable, IMainchainGatewayV2 {\n using Token for Token.Info;\n using Transfer for Transfer.Request;\n using Transfer for Transfer.Receipt;\n\n /// @dev Withdrawal unlocker role hash\n bytes32 public constant WITHDRAWAL_UNLOCKER_ROLE = keccak256(\"WITHDRAWAL_UNLOCKER_ROLE\");\n\n /// @dev Wrapped native token address\n IWETH public wrappedNativeToken;\n /// @dev Ronin network id\n uint256 public roninChainId;\n /// @dev Total deposit\n uint256 public depositCount;\n /// @dev Domain seperator\n bytes32 internal _domainSeparator;\n /// @dev Mapping from mainchain token => token address on Ronin network\n mapping(address => MappedToken) internal _roninToken;\n /// @dev Mapping from withdrawal id => withdrawal hash\n mapping(uint256 => bytes32) public withdrawalHash;\n /// @dev Mapping from withdrawal id => locked\n mapping(uint256 => bool) public withdrawalLocked;\n\n fallback() external payable {\n _fallback();\n }\n\n receive() external payable {\n _fallback();\n }\n\n /**\n * @dev Initializes contract storage.\n */\n function initialize(\n address _roleSetter,\n IWETH _wrappedToken,\n IWeightedValidator _validatorContract,\n uint256 _roninChainId,\n uint256 _numerator,\n uint256 _highTierVWNumerator,\n uint256 _denominator,\n // _addresses[0]: mainchainTokens\n // _addresses[1]: roninTokens\n // _addresses[2]: withdrawalUnlockers\n address[][3] calldata _addresses,\n // _thresholds[0]: highTierThreshold\n // _thresholds[1]: lockedThreshold\n // _thresholds[2]: unlockFeePercentages\n // _thresholds[3]: dailyWithdrawalLimit\n uint256[][4] calldata _thresholds,\n Token.Standard[] calldata _standards\n ) external payable virtual initializer {\n _setupRole(DEFAULT_ADMIN_ROLE, _roleSetter);\n roninChainId = _roninChainId;\n\n _setWrappedNativeTokenContract(_wrappedToken);\n _setValidatorContract(_validatorContract);\n _updateDomainSeparator();\n _setThreshold(_numerator, _denominator);\n _setHighTierVoteWeightThreshold(_highTierVWNumerator, _denominator);\n _verifyThresholds();\n\n if (_addresses[0].length > 0) {\n // Map mainchain tokens to ronin tokens\n _mapTokens(_addresses[0], _addresses[1], _standards);\n // Sets thresholds based on the mainchain tokens\n _setHighTierThresholds(_addresses[0], _thresholds[0]);\n _setLockedThresholds(_addresses[0], _thresholds[1]);\n _setUnlockFeePercentages(_addresses[0], _thresholds[2]);\n _setDailyWithdrawalLimits(_addresses[0], _thresholds[3]);\n }\n\n // Grant role for withdrawal unlocker\n for (uint256 _i; _i < _addresses[2].length; _i++) {\n _grantRole(WITHDRAWAL_UNLOCKER_ROLE, _addresses[2][_i]);\n }\n }\n\n /**\n * @dev Receives ether without doing anything. Use this function to topup native token.\n */\n function receiveEther() external payable {}\n\n /**\n * @dev See {IMainchainGatewayV2-DOMAIN_SEPARATOR}.\n */\n function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {\n return _domainSeparator;\n }\n\n /**\n * @dev See {IMainchainGatewayV2-setWrappedNativeTokenContract}.\n */\n function setWrappedNativeTokenContract(IWETH _wrappedToken) external virtual onlyAdmin {\n _setWrappedNativeTokenContract(_wrappedToken);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-requestDepositFor}.\n */\n function requestDepositFor(Transfer.Request calldata _request) external payable virtual whenNotPaused {\n _requestDepositFor(_request, msg.sender);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-submitWithdrawal}.\n */\n function submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] calldata _signatures)\n external\n virtual\n whenNotPaused\n returns (bool _locked)\n {\n return _submitWithdrawal(_receipt, _signatures);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-unlockWithdrawal}.\n */\n function unlockWithdrawal(Transfer.Receipt calldata _receipt) external onlyRole(WITHDRAWAL_UNLOCKER_ROLE) {\n bytes32 _receiptHash = _receipt.hash();\n require(withdrawalHash[_receipt.id] == _receipt.hash(), \"MainchainGatewayV2: invalid receipt\");\n require(withdrawalLocked[_receipt.id], \"MainchainGatewayV2: query for approved withdrawal\");\n delete withdrawalLocked[_receipt.id];\n emit WithdrawalUnlocked(_receiptHash, _receipt);\n\n address _token = _receipt.mainchain.tokenAddr;\n if (_receipt.info.erc == Token.Standard.ERC20) {\n Token.Info memory _feeInfo = _receipt.info;\n _feeInfo.quantity = _computeFeePercentage(_receipt.info.quantity, unlockFeePercentages[_token]);\n Token.Info memory _withdrawInfo = _receipt.info;\n _withdrawInfo.quantity = _receipt.info.quantity - _feeInfo.quantity;\n\n _feeInfo.handleAssetTransfer(payable(msg.sender), _token, wrappedNativeToken);\n _withdrawInfo.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken);\n } else {\n _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken);\n }\n\n emit Withdrew(_receiptHash, _receipt);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-mapTokens}.\n */\n function mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) external virtual onlyAdmin {\n require(_mainchainTokens.length > 0, \"MainchainGatewayV2: query for empty array\");\n _mapTokens(_mainchainTokens, _roninTokens, _standards);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-mapTokensAndThresholds}.\n */\n function mapTokensAndThresholds(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards,\n // _thresholds[0]: highTierThreshold\n // _thresholds[1]: lockedThreshold\n // _thresholds[2]: unlockFeePercentages\n // _thresholds[3]: dailyWithdrawalLimit\n uint256[][4] calldata _thresholds\n ) external virtual onlyAdmin {\n require(_mainchainTokens.length > 0, \"MainchainGatewayV2: query for empty array\");\n _mapTokens(_mainchainTokens, _roninTokens, _standards);\n _setHighTierThresholds(_mainchainTokens, _thresholds[0]);\n _setLockedThresholds(_mainchainTokens, _thresholds[1]);\n _setUnlockFeePercentages(_mainchainTokens, _thresholds[2]);\n _setDailyWithdrawalLimits(_mainchainTokens, _thresholds[3]);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-getRoninToken}.\n */\n function getRoninToken(address _mainchainToken) public view returns (MappedToken memory _token) {\n _token = _roninToken[_mainchainToken];\n require(_token.tokenAddr != address(0), \"MainchainGatewayV2: unsupported token\");\n }\n\n /**\n * @dev Maps mainchain tokens to Ronin network.\n *\n * Requirement:\n * - The arrays have the same length.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function _mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) internal virtual {\n require(\n _mainchainTokens.length == _roninTokens.length && _mainchainTokens.length == _standards.length,\n \"MainchainGatewayV2: invalid array length\"\n );\n\n for (uint256 _i; _i < _mainchainTokens.length; _i++) {\n _roninToken[_mainchainTokens[_i]].tokenAddr = _roninTokens[_i];\n _roninToken[_mainchainTokens[_i]].erc = _standards[_i];\n }\n\n emit TokenMapped(_mainchainTokens, _roninTokens, _standards);\n }\n\n /**\n * @dev Submits withdrawal receipt.\n *\n * Requirements:\n * - The receipt kind is withdrawal.\n * - The receipt is to withdraw on this chain.\n * - The receipt is not used to withdraw before.\n * - The withdrawal is not reached the limit threshold.\n * - The signer weight total is larger than or equal to the minimum threshold.\n * - The signature signers are in order.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function _submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] memory _signatures)\n internal\n virtual\n returns (bool _locked)\n {\n uint256 _id = _receipt.id;\n uint256 _quantity = _receipt.info.quantity;\n address _tokenAddr = _receipt.mainchain.tokenAddr;\n\n _receipt.info.validate();\n require(_receipt.kind == Transfer.Kind.Withdrawal, \"MainchainGatewayV2: invalid receipt kind\");\n require(_receipt.mainchain.chainId == block.chainid, \"MainchainGatewayV2: invalid chain id\");\n MappedToken memory _token = getRoninToken(_receipt.mainchain.tokenAddr);\n require(\n _token.erc == _receipt.info.erc && _token.tokenAddr == _receipt.ronin.tokenAddr,\n \"MainchainGatewayV2: invalid receipt\"\n );\n require(withdrawalHash[_id] == bytes32(0), \"MainchainGatewayV2: query for processed withdrawal\");\n require(\n _receipt.info.erc == Token.Standard.ERC721 || !_reachedWithdrawalLimit(_tokenAddr, _quantity),\n \"MainchainGatewayV2: reached daily withdrawal limit\"\n );\n\n bytes32 _receiptHash = _receipt.hash();\n bytes32 _receiptDigest = Transfer.receiptDigest(_domainSeparator, _receiptHash);\n IWeightedValidator _validatorContract = validatorContract;\n\n uint256 _minimumVoteWeight;\n (_minimumVoteWeight, _locked) = _computeMinVoteWeight(_receipt.info.erc, _tokenAddr, _quantity, _validatorContract);\n\n {\n bool _passed;\n address _signer;\n address _lastSigner;\n Signature memory _sig;\n uint256 _weight;\n for (uint256 _i; _i < _signatures.length; _i++) {\n _sig = _signatures[_i];\n _signer = ecrecover(_receiptDigest, _sig.v, _sig.r, _sig.s);\n require(_lastSigner < _signer, \"MainchainGatewayV2: invalid order\");\n _lastSigner = _signer;\n\n _weight += _validatorContract.getValidatorWeight(_signer);\n if (_weight >= _minimumVoteWeight) {\n _passed = true;\n break;\n }\n }\n require(_passed, \"MainchainGatewayV2: query for insufficient vote weight\");\n withdrawalHash[_id] = _receiptHash;\n }\n\n if (_locked) {\n withdrawalLocked[_id] = true;\n emit WithdrawalLocked(_receiptHash, _receipt);\n return _locked;\n }\n\n _recordWithdrawal(_tokenAddr, _quantity);\n _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _tokenAddr, wrappedNativeToken);\n emit Withdrew(_receiptHash, _receipt);\n }\n\n /**\n * @dev Requests deposit made by `_requester` address.\n *\n * Requirements:\n * - The token info is valid.\n * - The `msg.value` is 0 while depositing ERC20 token.\n * - The `msg.value` is equal to deposit quantity while depositing native token.\n *\n * Emits the `DepositRequested` event.\n *\n */\n function _requestDepositFor(Transfer.Request memory _request, address _requester) internal virtual {\n MappedToken memory _token;\n address _weth = address(wrappedNativeToken);\n\n _request.info.validate();\n if (_request.tokenAddr == address(0)) {\n require(_request.info.quantity == msg.value, \"MainchainGatewayV2: invalid request\");\n _token = getRoninToken(_weth);\n require(_token.erc == _request.info.erc, \"MainchainGatewayV2: invalid token standard\");\n _request.tokenAddr = _weth;\n } else {\n require(msg.value == 0, \"MainchainGatewayV2: invalid request\");\n _token = getRoninToken(_request.tokenAddr);\n require(_token.erc == _request.info.erc, \"MainchainGatewayV2: invalid token standard\");\n _request.info.transferFrom(_requester, address(this), _request.tokenAddr);\n // Withdraw if token is WETH\n if (_weth == _request.tokenAddr) {\n IWETH(_weth).withdraw(_request.info.quantity);\n }\n }\n\n uint256 _depositId = depositCount++;\n Transfer.Receipt memory _receipt = _request.into_deposit_receipt(\n _requester,\n _depositId,\n _token.tokenAddr,\n roninChainId\n );\n\n emit DepositRequested(_receipt.hash(), _receipt);\n }\n\n /**\n * @dev Returns the minimum vote weight for the token.\n */\n function _computeMinVoteWeight(\n Token.Standard _erc,\n address _token,\n uint256 _quantity,\n IWeightedValidator _validatorContract\n ) internal virtual returns (uint256 _weight, bool _locked) {\n uint256 _totalWeights = _validatorContract.totalWeights();\n _weight = _minimumVoteWeight(_totalWeights);\n if (_erc == Token.Standard.ERC20) {\n if (highTierThreshold[_token] <= _quantity) {\n _weight = _highTierVoteWeight(_totalWeights);\n }\n _locked = _lockedWithdrawalRequest(_token, _quantity);\n }\n }\n\n /**\n * @dev Update domain seperator.\n */\n function _updateDomainSeparator() internal {\n _domainSeparator = keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(\"MainchainGatewayV2\"),\n keccak256(\"2\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /**\n * @dev Sets the WETH contract.\n *\n * Emits the `WrappedNativeTokenContractUpdated` event.\n *\n */\n function _setWrappedNativeTokenContract(IWETH _wrapedToken) internal {\n wrappedNativeToken = _wrapedToken;\n emit WrappedNativeTokenContractUpdated(_wrapedToken);\n }\n\n /**\n * @dev Receives ETH from WETH or creates deposit request.\n */\n function _fallback() internal virtual whenNotPaused {\n if (msg.sender != address(wrappedNativeToken)) {\n Transfer.Request memory _request;\n _request.recipientAddr = msg.sender;\n _request.info.quantity = msg.value;\n _requestDepositFor(_request, _request.recipientAddr);\n }\n }\n}\n"},"contracts/v0.8/interfaces/IWeightedValidator.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./IQuorum.sol\";\n\ninterface IWeightedValidator is IQuorum {\n struct WeightedValidator {\n address validator;\n address governor;\n uint256 weight;\n }\n\n /// @dev Emitted when the validators are added\n event ValidatorsAdded(uint256 indexed nonce, WeightedValidator[] validators);\n /// @dev Emitted when the validators are updated\n event ValidatorsUpdated(uint256 indexed nonce, WeightedValidator[] validators);\n /// @dev Emitted when the validators are removed\n event ValidatorsRemoved(uint256 indexed nonce, address[] validators);\n\n /**\n * @dev Returns validator weight of the validator.\n */\n function getValidatorWeight(address _addr) external view returns (uint256);\n\n /**\n * @dev Returns governor weight of the governor.\n */\n function getGovernorWeight(address _addr) external view returns (uint256);\n\n /**\n * @dev Returns total validator weights of the address list.\n */\n function sumValidatorWeights(address[] calldata _addrList) external view returns (uint256 _weight);\n\n /**\n * @dev Returns total governor weights of the address list.\n */\n function sumGovernorWeights(address[] calldata _addrList) external view returns (uint256 _weight);\n\n /**\n * @dev Returns the validator list attached with governor address and weight.\n */\n function getValidatorInfo() external view returns (WeightedValidator[] memory _list);\n\n /**\n * @dev Returns the validator list.\n */\n function getValidators() external view returns (address[] memory _validators);\n\n /**\n * @dev Returns the validator at `_index` position.\n */\n function validators(uint256 _index) external view returns (WeightedValidator memory);\n\n /**\n * @dev Returns total of validators.\n */\n function totalValidators() external view returns (uint256);\n\n /**\n * @dev Returns total weights.\n */\n function totalWeights() external view returns (uint256);\n\n /**\n * @dev Adds validators.\n *\n * Requirements:\n * - The weights are larger than 0.\n * - The validators are not added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsAdded` event.\n *\n */\n function addValidators(WeightedValidator[] calldata _validators) external;\n\n /**\n * @dev Updates validators.\n *\n * Requirements:\n * - The weights are larger than 0.\n * - The validators are added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsUpdated` event.\n *\n */\n function updateValidators(WeightedValidator[] calldata _validators) external;\n\n /**\n * @dev Removes validators.\n *\n * Requirements:\n * - The validators are added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsRemoved` event.\n *\n */\n function removeValidators(address[] calldata _validators) external;\n}\n"},"contracts/v0.8/extensions/HasProxyAdmin.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/utils/StorageSlot.sol\";\n\nabstract contract HasProxyAdmin {\n // bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n modifier onlyAdmin() {\n require(msg.sender == _getAdmin(), \"HasProxyAdmin: unauthorized sender\");\n _;\n }\n\n /**\n * @dev Returns proxy admin.\n */\n function _getAdmin() internal view returns (address) {\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"contracts/v0.8/interfaces/SignatureConsumer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface SignatureConsumer {\n struct Signature {\n uint8 v;\n bytes32 r;\n bytes32 s;\n }\n}\n"},"@openzeppelin/contracts/utils/Strings.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/ERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n"},"@openzeppelin/contracts/access/AccessControlEnumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n"},"contracts/v0.8/library/Transfer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"./Token.sol\";\n\nlibrary Transfer {\n using ECDSA for bytes32;\n\n enum Kind {\n Deposit,\n Withdrawal\n }\n\n struct Request {\n // For deposit request: Recipient address on Ronin network\n // For withdrawal request: Recipient address on mainchain network\n address recipientAddr;\n // Token address to deposit/withdraw\n // Value 0: native token\n address tokenAddr;\n Token.Info info;\n }\n\n /**\n * @dev Converts the transfer request into the deposit receipt.\n */\n function into_deposit_receipt(\n Request memory _request,\n address _requester,\n uint256 _id,\n address _roninTokenAddr,\n uint256 _roninChainId\n ) internal view returns (Receipt memory _receipt) {\n _receipt.id = _id;\n _receipt.kind = Kind.Deposit;\n _receipt.mainchain.addr = _requester;\n _receipt.mainchain.tokenAddr = _request.tokenAddr;\n _receipt.mainchain.chainId = block.chainid;\n _receipt.ronin.addr = _request.recipientAddr;\n _receipt.ronin.tokenAddr = _roninTokenAddr;\n _receipt.ronin.chainId = _roninChainId;\n _receipt.info = _request.info;\n }\n\n /**\n * @dev Converts the transfer request into the withdrawal receipt.\n */\n function into_withdrawal_receipt(\n Request memory _request,\n address _requester,\n uint256 _id,\n address _mainchainTokenAddr,\n uint256 _mainchainId\n ) internal view returns (Receipt memory _receipt) {\n _receipt.id = _id;\n _receipt.kind = Kind.Withdrawal;\n _receipt.ronin.addr = _requester;\n _receipt.ronin.tokenAddr = _request.tokenAddr;\n _receipt.ronin.chainId = block.chainid;\n _receipt.mainchain.addr = _request.recipientAddr;\n _receipt.mainchain.tokenAddr = _mainchainTokenAddr;\n _receipt.mainchain.chainId = _mainchainId;\n _receipt.info = _request.info;\n }\n\n struct Receipt {\n uint256 id;\n Kind kind;\n Token.Owner mainchain;\n Token.Owner ronin;\n Token.Info info;\n }\n\n // keccak256(\"Receipt(uint256 id,uint8 kind,TokenOwner mainchain,TokenOwner ronin,TokenInfo info)TokenInfo(uint8 erc,uint256 id,uint256 quantity)TokenOwner(address addr,address tokenAddr,uint256 chainId)\");\n bytes32 public constant TYPE_HASH = 0xb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea;\n\n /**\n * @dev Returns token info struct hash.\n */\n function hash(Receipt memory _receipt) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n TYPE_HASH,\n _receipt.id,\n _receipt.kind,\n Token.hash(_receipt.mainchain),\n Token.hash(_receipt.ronin),\n Token.hash(_receipt.info)\n )\n );\n }\n\n /**\n * @dev Returns the receipt digest.\n */\n function receiptDigest(bytes32 _domainSeparator, bytes32 _receiptHash) internal pure returns (bytes32) {\n return _domainSeparator.toTypedDataHash(_receiptHash);\n }\n}\n"},"@openzeppelin/contracts/utils/Context.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n"},"@openzeppelin/contracts/security/Pausable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n"},"@openzeppelin/contracts/utils/structs/EnumerableSet.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n"},"contracts/v0.8/interfaces/IQuorum.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IQuorum {\n /// @dev Emitted when the threshold is updated\n event ThresholdUpdated(\n uint256 indexed nonce,\n uint256 indexed numerator,\n uint256 indexed denominator,\n uint256 previousNumerator,\n uint256 previousDenominator\n );\n\n /**\n * @dev Returns the threshold.\n */\n function getThreshold() external view returns (uint256 _num, uint256 _denom);\n\n /**\n * @dev Checks whether the `_voteWeight` passes the threshold.\n */\n function checkThreshold(uint256 _voteWeight) external view returns (bool);\n\n /**\n * @dev Returns the minimum vote weight to pass the threshold.\n */\n function minimumVoteWeight() external view returns (uint256);\n\n /**\n * @dev Sets the threshold.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `ThresholdUpdated` event.\n *\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n returns (uint256 _previousNum, uint256 _previousDenom);\n}\n"},"contracts/v0.8/interfaces/IWETH.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH {\n function deposit() external payable;\n\n function withdraw(uint256 _wad) external;\n\n function balanceOf(address) external view returns (uint256);\n}\n"},"@openzeppelin/contracts/access/AccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view virtual override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view virtual {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"}},"settings":{"evmVersion":"london","libraries":{},"metadata":{"bytecodeHash":"ipfs","useLiteralContent":true},"optimizer":{"enabled":true,"runs":1000},"remappings":[],"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}}}},"ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"limits\",\"type\":\"uint256[]\"}],\"name\":\"DailyWithdrawalLimitsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"DepositRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"thresholds\",\"type\":\"uint256[]\"}],\"name\":\"HighTierThresholdsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"numerator\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"denominator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousNumerator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousDenominator\",\"type\":\"uint256\"}],\"name\":\"HighTierVoteWeightThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"thresholds\",\"type\":\"uint256[]\"}],\"name\":\"LockedThresholdsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"numerator\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"denominator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousNumerator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousDenominator\",\"type\":\"uint256\"}],\"name\":\"ThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"mainchainTokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"roninTokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"enum Token.Standard[]\",\"name\":\"standards\",\"type\":\"uint8[]\"}],\"name\":\"TokenMapped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"percentages\",\"type\":\"uint256[]\"}],\"name\":\"UnlockFeePercentagesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IWeightedValidator\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"ValidatorContractUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"WithdrawalLocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"WithdrawalUnlocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"Withdrew\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IWETH\",\"name\":\"weth\",\"type\":\"address\"}],\"name\":\"WrappedNativeTokenContractUpdated\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"WITHDRAWAL_UNLOCKER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_MAX_PERCENTAGE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_voteWeight\",\"type\":\"uint256\"}],\"name\":\"checkHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_voteWeight\",\"type\":\"uint256\"}],\"name\":\"checkThreshold\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"dailyWithdrawalLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"depositCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_mainchainToken\",\"type\":\"address\"}],\"name\":\"getRoninToken\",\"outputs\":[{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"}],\"internalType\":\"struct MappedTokenConsumer.MappedToken\",\"name\":\"_token\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"highTierThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_roleSetter\",\"type\":\"address\"},{\"internalType\":\"contract IWETH\",\"name\":\"_wrappedToken\",\"type\":\"address\"},{\"internalType\":\"contract IWeightedValidator\",\"name\":\"_validatorContract\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_roninChainId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_highTierVWNumerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"},{\"internalType\":\"address[][3]\",\"name\":\"_addresses\",\"type\":\"address[][3]\"},{\"internalType\":\"uint256[][4]\",\"name\":\"_thresholds\",\"type\":\"uint256[][4]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lastDateSynced\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lastSyncedWithdrawal\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lockedThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_mainchainTokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_roninTokens\",\"type\":\"address[]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"}],\"name\":\"mapTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_mainchainTokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_roninTokens\",\"type\":\"address[]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"},{\"internalType\":\"uint256[][4]\",\"name\":\"_thresholds\",\"type\":\"uint256[][4]\"}],\"name\":\"mapTokensAndThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumVoteWeight\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_quantity\",\"type\":\"uint256\"}],\"name\":\"reachedWithdrawalLimit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveEther\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"recipientAddr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Request\",\"name\":\"_request\",\"type\":\"tuple\"}],\"name\":\"requestDepositFor\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"roninChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_limits\",\"type\":\"uint256[]\"}],\"name\":\"setDailyWithdrawalLimits\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_thresholds\",\"type\":\"uint256[]\"}],\"name\":\"setHighTierThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"}],\"name\":\"setHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_previousNum\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_previousDenom\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_thresholds\",\"type\":\"uint256[]\"}],\"name\":\"setLockedThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"}],\"name\":\"setThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_previousNum\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_previousDenom\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_percentages\",\"type\":\"uint256[]\"}],\"name\":\"setUnlockFeePercentages\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IWeightedValidator\",\"name\":\"_validatorContract\",\"type\":\"address\"}],\"name\":\"setValidatorContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IWETH\",\"name\":\"_wrappedToken\",\"type\":\"address\"}],\"name\":\"setWrappedNativeTokenContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Receipt\",\"name\":\"_receipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"struct SignatureConsumer.Signature[]\",\"name\":\"_signatures\",\"type\":\"tuple[]\"}],\"name\":\"submitWithdrawal\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"_locked\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"unlockFeePercentages\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Receipt\",\"name\":\"_receipt\",\"type\":\"tuple\"}],\"name\":\"unlockWithdrawal\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"validatorContract\",\"outputs\":[{\"internalType\":\"contract IWeightedValidator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalLocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wrappedNativeToken\",\"outputs\":[{\"internalType\":\"contract IWETH\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"MainchainGatewayV2","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":1000,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"MIT","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json new file mode 100644 index 0000000000000..f4dfea9e7de46 --- /dev/null +++ b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x8b3d32cf2bb4d0d16656f4c0b04fa546274f1545","contractCreator":"0x958892b4a0512b28aaac890fc938868bbd42f064","txHash":"0x79820495643caf5a1e7e96578361c9ddba0e0735cd684ada7450254f6fd58f51"} \ No newline at end of file diff --git a/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json new file mode 100644 index 0000000000000..1fd95fa4fe5dd --- /dev/null +++ b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/governance/governor/GovernorStorage.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./IIpt.sol\";\nimport \"./Structs.sol\";\n\ncontract GovernorCharlieDelegatorStorage {\n /// @notice Active brains of Governor\n address public implementation;\n}\n\n/**\n * @title Storage for Governor Charlie Delegate\n * @notice For future upgrades, do not change GovernorCharlieDelegateStorage. Create a new\n * contract which implements GovernorCharlieDelegateStorage and following the naming convention\n * GovernorCharlieDelegateStorageVX.\n */\n//solhint-disable-next-line max-states-count\ncontract GovernorCharlieDelegateStorage is GovernorCharlieDelegatorStorage {\n /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed\n uint256 public quorumVotes;\n\n /// @notice The number of votes in support of a proposal required in order for an emergency quorum to be reached and for a vote to succeed\n uint256 public emergencyQuorumVotes;\n\n /// @notice The delay before voting on a proposal may take place, once proposed, in blocks\n uint256 public votingDelay;\n\n /// @notice The duration of voting on a proposal, in blocks\n uint256 public votingPeriod;\n\n /// @notice The number of votes required in order for a voter to become a proposer\n uint256 public proposalThreshold;\n\n /// @notice Initial proposal id set at become\n uint256 public initialProposalId;\n\n /// @notice The total number of proposals\n uint256 public proposalCount;\n\n /// @notice The address of the Interest Protocol governance token\n IIpt public ipt;\n\n /// @notice The official record of all proposals ever proposed\n mapping(uint256 => Proposal) public proposals;\n\n /// @notice The latest proposal for each proposer\n mapping(address => uint256) public latestProposalIds;\n\n /// @notice The latest proposal for each proposer\n mapping(bytes32 => bool) public queuedTransactions;\n\n /// @notice The proposal holding period\n uint256 public proposalTimelockDelay;\n\n /// @notice Stores the expiration of account whitelist status as a timestamp\n mapping(address => uint256) public whitelistAccountExpirations;\n\n /// @notice Address which manages whitelisted proposals and whitelist accounts\n address public whitelistGuardian;\n\n /// @notice The duration of the voting on a emergency proposal, in blocks\n uint256 public emergencyVotingPeriod;\n\n /// @notice The emergency proposal holding period\n uint256 public emergencyTimelockDelay;\n\n /// all receipts for proposal\n mapping(uint256 => mapping(address => Receipt)) public proposalReceipts;\n\n /// @notice The emergency proposal holding period\n bool public initialized;\n\n /// @notice The number of votes to reject an optimistic proposal\n uint256 public optimisticQuorumVotes; \n\n /// @notice The delay period before voting begins\n uint256 public optimisticVotingDelay; \n\n /// @notice The maximum number of seconds an address can be whitelisted for\n uint256 public maxWhitelistPeriod; \n}\n"},"hardhat/console.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >= 0.4.22 <0.9.0;\n\nlibrary console {\n\taddress constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);\n\n\tfunction _sendLogPayload(bytes memory payload) private view {\n\t\tuint256 payloadLength = payload.length;\n\t\taddress consoleAddress = CONSOLE_ADDRESS;\n\t\tassembly {\n\t\t\tlet payloadStart := add(payload, 32)\n\t\t\tlet r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)\n\t\t}\n\t}\n\n\tfunction log() internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log()\"));\n\t}\n\n\tfunction logInt(int p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(int)\", p0));\n\t}\n\n\tfunction logUint(uint p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint)\", p0));\n\t}\n\n\tfunction logString(string memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n\t}\n\n\tfunction logBool(bool p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n\t}\n\n\tfunction logAddress(address p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n\t}\n\n\tfunction logBytes(bytes memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes)\", p0));\n\t}\n\n\tfunction logBytes1(bytes1 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes1)\", p0));\n\t}\n\n\tfunction logBytes2(bytes2 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes2)\", p0));\n\t}\n\n\tfunction logBytes3(bytes3 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes3)\", p0));\n\t}\n\n\tfunction logBytes4(bytes4 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes4)\", p0));\n\t}\n\n\tfunction logBytes5(bytes5 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes5)\", p0));\n\t}\n\n\tfunction logBytes6(bytes6 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes6)\", p0));\n\t}\n\n\tfunction logBytes7(bytes7 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes7)\", p0));\n\t}\n\n\tfunction logBytes8(bytes8 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes8)\", p0));\n\t}\n\n\tfunction logBytes9(bytes9 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes9)\", p0));\n\t}\n\n\tfunction logBytes10(bytes10 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes10)\", p0));\n\t}\n\n\tfunction logBytes11(bytes11 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes11)\", p0));\n\t}\n\n\tfunction logBytes12(bytes12 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes12)\", p0));\n\t}\n\n\tfunction logBytes13(bytes13 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes13)\", p0));\n\t}\n\n\tfunction logBytes14(bytes14 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes14)\", p0));\n\t}\n\n\tfunction logBytes15(bytes15 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes15)\", p0));\n\t}\n\n\tfunction logBytes16(bytes16 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes16)\", p0));\n\t}\n\n\tfunction logBytes17(bytes17 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes17)\", p0));\n\t}\n\n\tfunction logBytes18(bytes18 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes18)\", p0));\n\t}\n\n\tfunction logBytes19(bytes19 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes19)\", p0));\n\t}\n\n\tfunction logBytes20(bytes20 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes20)\", p0));\n\t}\n\n\tfunction logBytes21(bytes21 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes21)\", p0));\n\t}\n\n\tfunction logBytes22(bytes22 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes22)\", p0));\n\t}\n\n\tfunction logBytes23(bytes23 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes23)\", p0));\n\t}\n\n\tfunction logBytes24(bytes24 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes24)\", p0));\n\t}\n\n\tfunction logBytes25(bytes25 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes25)\", p0));\n\t}\n\n\tfunction logBytes26(bytes26 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes26)\", p0));\n\t}\n\n\tfunction logBytes27(bytes27 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes27)\", p0));\n\t}\n\n\tfunction logBytes28(bytes28 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes28)\", p0));\n\t}\n\n\tfunction logBytes29(bytes29 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes29)\", p0));\n\t}\n\n\tfunction logBytes30(bytes30 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes30)\", p0));\n\t}\n\n\tfunction logBytes31(bytes31 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes31)\", p0));\n\t}\n\n\tfunction logBytes32(bytes32 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes32)\", p0));\n\t}\n\n\tfunction log(uint p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint)\", p0));\n\t}\n\n\tfunction log(string memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n\t}\n\n\tfunction log(bool p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n\t}\n\n\tfunction log(address p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n\t}\n\n\tfunction log(uint p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address)\", p0, p1));\n\t}\n\n\tfunction log(address p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint)\", p0, p1));\n\t}\n\n\tfunction log(address p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string)\", p0, p1));\n\t}\n\n\tfunction log(address p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool)\", p0, p1));\n\t}\n\n\tfunction log(address p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n}\n"},"contracts/governance/governor/IGovernor.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./Structs.sol\";\n\n/// @title interface to interact with TokenDelgator\ninterface IGovernorCharlieDelegator {\n function _setImplementation(address implementation_) external;\n\n fallback() external payable;\n\n receive() external payable;\n}\n\n/// @title interface to interact with TokenDelgate\ninterface IGovernorCharlieDelegate {\n function initialize(\n address ipt_\n ) external;\n\n function propose(\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas,\n string memory description,\n bool emergency\n ) external returns (uint256);\n\n function queue(uint256 proposalId) external;\n\n function execute(uint256 proposalId) external payable;\n\n function executeTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) external payable;\n\n function cancel(uint256 proposalId) external;\n\n function getActions(uint256 proposalId)\n external\n view\n returns (\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas\n );\n\n function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory);\n\n function state(uint256 proposalId) external view returns (ProposalState);\n\n function castVote(uint256 proposalId, uint8 support) external;\n\n function castVoteWithReason(\n uint256 proposalId,\n uint8 support,\n string calldata reason\n ) external;\n\n function castVoteBySig(\n uint256 proposalId,\n uint8 support,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function isWhitelisted(address account) external view returns (bool);\n\n function _setDelay(uint256 proposalTimelockDelay_) external;\n\n function _setEmergencyDelay(uint256 emergencyTimelockDelay_) external;\n\n function _setVotingDelay(uint256 newVotingDelay) external;\n\n function _setVotingPeriod(uint256 newVotingPeriod) external;\n\n function _setEmergencyVotingPeriod(uint256 newEmergencyVotingPeriod) external;\n\n function _setProposalThreshold(uint256 newProposalThreshold) external;\n\n function _setQuorumVotes(uint256 newQuorumVotes) external;\n\n function _setEmergencyQuorumVotes(uint256 newEmergencyQuorumVotes) external;\n\n function _setWhitelistAccountExpiration(address account, uint256 expiration) external;\n\n function _setWhitelistGuardian(address account) external;\n\n function _setOptimisticDelay(uint256 newOptimisticVotingDelay) external;\n\n function _setOptimisticQuorumVotes(uint256 newOptimisticQuorumVotes) external;\n}\n\n/// @title interface which contains all events emitted by delegator & delegate\ninterface GovernorCharlieEvents {\n /// @notice An event emitted when a new proposal is created\n event ProposalCreated(\n uint256 indexed id,\n address indexed proposer,\n address[] targets,\n uint256[] values,\n string[] signatures,\n bytes[] calldatas,\n uint256 indexed startBlock,\n uint256 endBlock,\n string description\n );\n\n /// @notice An event emitted when a vote has been cast on a proposal\n /// @param voter The address which casted a vote\n /// @param proposalId The proposal id which was voted on\n /// @param support Support value for the vote. 0=against, 1=for, 2=abstain\n /// @param votes Number of votes which were cast by the voter\n /// @param reason The reason given for the vote by the voter\n event VoteCast(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 votes, string reason);\n\n /// @notice An event emitted when a proposal has been canceled\n event ProposalCanceled(uint256 indexed id);\n\n /// @notice An event emitted when a proposal has been queued in the Timelock\n event ProposalQueued(uint256 indexed id, uint256 eta);\n\n /// @notice An event emitted when a proposal has been executed in the Timelock\n event ProposalExecuted(uint256 indexed id);\n\n /// @notice An event emitted when the voting delay is set\n event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);\n\n /// @notice An event emitted when the voting period is set\n event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);\n\n /// @notice An event emitted when the emergency voting period is set\n event EmergencyVotingPeriodSet(uint256 oldEmergencyVotingPeriod, uint256 emergencyVotingPeriod);\n\n /// @notice Emitted when implementation is changed\n event NewImplementation(address oldImplementation, address newImplementation);\n\n /// @notice Emitted when proposal threshold is set\n event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);\n\n /// @notice Emitted when pendingAdmin is changed\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\n\n /// @notice Emitted when pendingAdmin is accepted, which means admin is updated\n event NewAdmin(address oldAdmin, address newAdmin);\n\n /// @notice Emitted when whitelist account expiration is set\n event WhitelistAccountExpirationSet(address account, uint256 expiration);\n\n /// @notice Emitted when the whitelistGuardian is set\n event WhitelistGuardianSet(address oldGuardian, address newGuardian);\n\n /// @notice Emitted when the a new delay is set\n event NewDelay(uint256 oldTimelockDelay, uint256 proposalTimelockDelay);\n\n /// @notice Emitted when the a new emergency delay is set\n event NewEmergencyDelay(uint256 oldEmergencyTimelockDelay, uint256 emergencyTimelockDelay);\n\n /// @notice Emitted when the quorum is updated\n event NewQuorum(uint256 oldQuorumVotes, uint256 quorumVotes);\n\n /// @notice Emitted when the emergency quorum is updated\n event NewEmergencyQuorum(uint256 oldEmergencyQuorumVotes, uint256 emergencyQuorumVotes);\n\n /// @notice An event emitted when the optimistic voting delay is set\n event OptimisticVotingDelaySet(uint256 oldOptimisticVotingDelay, uint256 optimisticVotingDelay);\n\n /// @notice Emitted when the optimistic quorum is updated\n event OptimisticQuorumVotesSet(uint256 oldOptimisticQuorumVotes, uint256 optimisticQuorumVotes);\n\n /// @notice Emitted when a transaction is canceled\n event CancelTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n\n /// @notice Emitted when a transaction is executed\n event ExecuteTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n\n /// @notice Emitted when a transaction is queued\n event QueueTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n}\n"},"contracts/governance/governor/Structs.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nstruct Proposal {\n /// @notice Unique id for looking up a proposal\n uint256 id;\n /// @notice Creator of the proposal\n address proposer;\n /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds\n uint256 eta;\n /// @notice the ordered list of target addresses for calls to be made\n address[] targets;\n /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made\n uint256[] values;\n /// @notice The ordered list of function signatures to be called\n string[] signatures;\n /// @notice The ordered list of calldata to be passed to each call\n bytes[] calldatas;\n /// @notice The block at which voting begins: holders must delegate their votes prior to this block\n uint256 startBlock;\n /// @notice The block at which voting ends: votes must be cast prior to this block\n uint256 endBlock;\n /// @notice Current number of votes in favor of this proposal\n uint256 forVotes;\n /// @notice Current number of votes in opposition to this proposal\n uint256 againstVotes;\n /// @notice Current number of votes for abstaining for this proposal\n uint256 abstainVotes;\n /// @notice Flag marking whether the proposal has been canceled\n bool canceled;\n /// @notice Flag marking whether the proposal has been executed\n bool executed;\n /// @notice Whether the proposal is an emergency proposal\n bool emergency;\n /// @notice quorum votes requires\n uint256 quorumVotes;\n /// @notice time delay\n uint256 delay;\n}\n\n/// @notice Ballot receipt record for a voter\nstruct Receipt {\n /// @notice Whether or not a vote has been cast\n bool hasVoted;\n /// @notice Whether or not the voter supports the proposal or abstains\n uint8 support;\n /// @notice The number of votes the voter had, which were cast\n uint96 votes;\n}\n\n/// @notice Possible states that a proposal may be in\nenum ProposalState {\n Pending,\n Active,\n Canceled,\n Defeated,\n Succeeded,\n Queued,\n Expired,\n Executed\n}\n"},"contracts/governance/governor/IIpt.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface IIpt {\n function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96);\n}\n"},"contracts/governance/governor/GovernorDelegate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\npragma experimental ABIEncoderV2;\nimport \"hardhat/console.sol\";\n\nimport \"./IGovernor.sol\";\nimport \"./GovernorStorage.sol\";\n\ncontract GovernorCharlieDelegate is GovernorCharlieDelegateStorage, GovernorCharlieEvents, IGovernorCharlieDelegate {\n /// @notice The name of this contract\n string public constant name = \"Interest Protocol Governor\";\n\n /// @notice The maximum number of actions that can be included in a proposal\n uint256 public constant proposalMaxOperations = 10;\n\n /// @notice The EIP-712 typehash for the contract's domain\n bytes32 public constant DOMAIN_TYPEHASH =\n keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n /// @notice The EIP-712 typehash for the ballot struct used by the contract\n bytes32 public constant BALLOT_TYPEHASH = keccak256(\"Ballot(uint256 proposalId,uint8 support)\");\n\n /// @notice The time for a proposal to be executed after passing\n uint256 public constant GRACE_PERIOD = 14 days;\n\n /**\n * @notice Used to initialize the contract during delegator contructor\n * @param ipt_ The address of the IPT token\n */\n function initialize(\n address ipt_\n ) external override {\n require(!initialized, \"already been initialized\");\n ipt = IIpt(ipt_);\n votingPeriod = 40320;\n votingDelay = 13140;\n proposalThreshold = 1000000000000000000000000;\n proposalTimelockDelay = 172800;\n proposalCount = 0;\n quorumVotes = 10000000000000000000000000;\n emergencyQuorumVotes = 40000000000000000000000000;\n emergencyVotingPeriod = 6570;\n emergencyTimelockDelay = 43200;\n optimisticQuorumVotes = 2000000000000000000000000;\n optimisticVotingDelay = 18000;\n maxWhitelistPeriod = 31536000;\n\n initialized = true;\n }\n\n /// @notice any function with this modifier will call the pay_interest() function before\n modifier onlyGov() {\n require(_msgSender() == address(this), \"must come from the gov.\");\n _;\n }\n\n /**\n * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold\n * @param targets Target addresses for proposal calls\n * @param values Eth values for proposal calls\n * @param signatures Function signatures for proposal calls\n * @param calldatas Calldatas for proposal calls\n * @param description String description of the proposal\n * @return Proposal id of new proposal\n */\n function propose(\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas,\n string memory description,\n bool emergency\n ) public override returns (uint256) {\n // Reject proposals before initiating as Governor\n require(quorumVotes != 0, \"Charlie not active\");\n // Allow addresses above proposal threshold and whitelisted addresses to propose\n require(\n ipt.getPriorVotes(_msgSender(), (block.number - 1)) >= proposalThreshold || isWhitelisted(_msgSender()),\n \"votes below proposal threshold\"\n );\n require(\n targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,\n \"information arity mismatch\"\n );\n require(targets.length != 0, \"must provide actions\");\n require(targets.length <= proposalMaxOperations, \"too many actions\");\n\n uint256 latestProposalId = latestProposalIds[_msgSender()];\n if (latestProposalId != 0) {\n ProposalState proposersLatestProposalState = state(latestProposalId);\n require(proposersLatestProposalState != ProposalState.Active, \"one live proposal per proposer\");\n require(proposersLatestProposalState != ProposalState.Pending, \"one live proposal per proposer\");\n }\n\n proposalCount++;\n Proposal memory newProposal = Proposal({\n id: proposalCount,\n proposer: _msgSender(),\n eta: 0,\n targets: targets,\n values: values,\n signatures: signatures,\n calldatas: calldatas,\n startBlock: block.number + votingDelay,\n endBlock: block.number + votingDelay + votingPeriod,\n forVotes: 0,\n againstVotes: 0,\n abstainVotes: 0,\n canceled: false,\n executed: false,\n emergency: emergency,\n quorumVotes: quorumVotes,\n delay: proposalTimelockDelay\n });\n\n //whitelist can't make emergency\n if (emergency && !isWhitelisted(_msgSender())) {\n newProposal.startBlock = block.number;\n newProposal.endBlock = block.number + emergencyVotingPeriod;\n newProposal.quorumVotes = emergencyQuorumVotes;\n newProposal.delay = emergencyTimelockDelay;\n }\n\n //whitelist can only make optimistic proposals\n if (isWhitelisted(_msgSender())) {\n newProposal.quorumVotes = optimisticQuorumVotes;\n newProposal.startBlock = block.number + optimisticVotingDelay;\n newProposal.endBlock = block.number + optimisticVotingDelay + votingPeriod;\n }\n\n proposals[newProposal.id] = newProposal;\n latestProposalIds[newProposal.proposer] = newProposal.id;\n\n emit ProposalCreated(\n newProposal.id,\n _msgSender(),\n targets,\n values,\n signatures,\n calldatas,\n newProposal.startBlock,\n newProposal.endBlock,\n description\n );\n return newProposal.id;\n }\n\n /**\n * @notice Queues a proposal of state succeeded\n * @param proposalId The id of the proposal to queue\n */\n function queue(uint256 proposalId) external override {\n require(state(proposalId) == ProposalState.Succeeded, \"can only be queued if succeeded\");\n Proposal storage proposal = proposals[proposalId];\n uint256 eta = block.timestamp + proposal.delay;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n require(\n !queuedTransactions[\n keccak256(\n abi.encode(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta)\n )\n ],\n \"proposal already queued\"\n );\n queueTransaction(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n eta,\n proposal.delay\n );\n }\n proposal.eta = eta;\n emit ProposalQueued(proposalId, eta);\n }\n\n function queueTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta,\n uint256 delay\n ) internal returns (bytes32) {\n require(eta >= (getBlockTimestamp() + delay), \"must satisfy delay.\");\n\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = true;\n\n emit QueueTransaction(txHash, target, value, signature, data, eta);\n return txHash;\n }\n\n /**\n * @notice Executes a queued proposal if eta has passed\n * @param proposalId The id of the proposal to execute\n */\n function execute(uint256 proposalId) external payable override {\n require(state(proposalId) == ProposalState.Queued, \"can only be exec'd if queued\");\n Proposal storage proposal = proposals[proposalId];\n proposal.executed = true;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n this.executeTransaction{value: proposal.values[i]}(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n proposal.eta\n );\n }\n emit ProposalExecuted(proposalId);\n }\n\n function executeTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) external payable override {\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n require(queuedTransactions[txHash], \"tx hasn't been queued.\");\n require(getBlockTimestamp() >= eta, \"tx hasn't surpassed timelock.\");\n require(getBlockTimestamp() <= eta + GRACE_PERIOD, \"tx is stale.\");\n\n queuedTransactions[txHash] = false;\n\n bytes memory callData;\n\n if (bytes(signature).length == 0) {\n callData = data;\n } else {\n callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);\n }\n\n // solhint-disable-next-line avoid-low-level-calls\n (\n bool success, /*bytes memory returnData*/\n\n ) = target.call{value: value}(callData);\n require(success, \"tx execution reverted.\");\n\n emit ExecuteTransaction(txHash, target, value, signature, data, eta);\n }\n\n /**\n * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold\n * @param proposalId The id of the proposal to cancel\n */\n function cancel(uint256 proposalId) external override {\n require(state(proposalId) != ProposalState.Executed, \"cant cancel executed proposal\");\n\n Proposal storage proposal = proposals[proposalId];\n\n // Proposer can cancel\n if (_msgSender() != proposal.proposer) {\n // Whitelisted proposers can't be canceled for falling below proposal threshold\n if (isWhitelisted(proposal.proposer)) {\n require(\n (ipt.getPriorVotes(proposal.proposer, (block.number - 1)) < proposalThreshold) &&\n _msgSender() == whitelistGuardian,\n \"cancel: whitelisted proposer\"\n );\n } else {\n require(\n (ipt.getPriorVotes(proposal.proposer, (block.number - 1)) < proposalThreshold),\n \"cancel: proposer above threshold\"\n );\n }\n }\n\n proposal.canceled = true;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n cancelTransaction(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n proposal.eta\n );\n }\n\n emit ProposalCanceled(proposalId);\n }\n\n function cancelTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) internal {\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = false;\n\n emit CancelTransaction(txHash, target, value, signature, data, eta);\n }\n\n /**\n * @notice Gets actions of a proposal\n * @param proposalId the id of the proposal\n * @return targets proposal targets\n * @return values proposal values\n * @return signatures proposal signatures\n * @return calldatas proposal calldatae\n */\n function getActions(uint256 proposalId)\n external\n view\n override\n returns (\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas\n )\n {\n Proposal storage p = proposals[proposalId];\n return (p.targets, p.values, p.signatures, p.calldatas);\n }\n\n /**\n * @notice Gets the receipt for a voter on a given proposal\n * @param proposalId the id of proposal\n * @param voter The address of the voter\n * @return The voting receipt\n */\n function getReceipt(uint256 proposalId, address voter) external view override returns (Receipt memory) {\n return proposalReceipts[proposalId][voter];\n }\n\n /**\n * @notice Gets the state of a proposal\n * @param proposalId The id of the proposal\n * @return Proposal state\n */\n // solhint-disable-next-line code-complexity\n function state(uint256 proposalId) public view override returns (ProposalState) {\n require(proposalCount >= proposalId && proposalId > initialProposalId, \"state: invalid proposal id\");\n Proposal storage proposal = proposals[proposalId];\n bool whitelisted = isWhitelisted(proposal.proposer);\n if (proposal.canceled) {\n return ProposalState.Canceled;\n } else if (block.number <= proposal.startBlock) {\n return ProposalState.Pending;\n } else if (block.number <= proposal.endBlock) {\n return ProposalState.Active;\n } else if (\n (whitelisted && proposal.againstVotes > proposal.quorumVotes) ||\n (!whitelisted && proposal.forVotes <= proposal.againstVotes) ||\n (!whitelisted && proposal.forVotes < proposal.quorumVotes)\n ) {\n return ProposalState.Defeated;\n } else if (proposal.eta == 0) {\n return ProposalState.Succeeded;\n } else if (proposal.executed) {\n return ProposalState.Executed;\n } else if (block.timestamp >= (proposal.eta + GRACE_PERIOD)) {\n return ProposalState.Expired;\n }\n return ProposalState.Queued;\n }\n\n /**\n * @notice Cast a vote for a proposal\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n */\n function castVote(uint256 proposalId, uint8 support) external override {\n emit VoteCast(_msgSender(), proposalId, support, castVoteInternal(_msgSender(), proposalId, support), \"\");\n }\n\n /**\n * @notice Cast a vote for a proposal with a reason\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n * @param reason The reason given for the vote by the voter\n */\n function castVoteWithReason(\n uint256 proposalId,\n uint8 support,\n string calldata reason\n ) external override {\n emit VoteCast(_msgSender(), proposalId, support, castVoteInternal(_msgSender(), proposalId, support), reason);\n }\n\n /**\n * @notice Cast a vote for a proposal by signature\n * @dev external override function that accepts EIP-712 signatures for voting on proposals.\n */\n function castVoteBySig(\n uint256 proposalId,\n uint8 support,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external override {\n bytes32 domainSeparator = keccak256(\n abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))\n );\n bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));\n bytes32 digest = keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"castVoteBySig: invalid signature\");\n emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), \"\");\n }\n\n /**\n * @notice Internal function that caries out voting logic\n * @param voter The voter that is casting their vote\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n * @return The number of votes cast\n */\n function castVoteInternal(\n address voter,\n uint256 proposalId,\n uint8 support\n ) internal returns (uint96) {\n require(state(proposalId) == ProposalState.Active, \"voting is closed\");\n require(support <= 2, \"invalid vote type\");\n Proposal storage proposal = proposals[proposalId];\n Receipt storage receipt = proposalReceipts[proposalId][voter];\n require(receipt.hasVoted == false, \"voter already voted\");\n uint96 votes = ipt.getPriorVotes(voter, proposal.startBlock);\n\n if (support == 0) {\n proposal.againstVotes = proposal.againstVotes + votes;\n } else if (support == 1) {\n proposal.forVotes = proposal.forVotes + votes;\n } else if (support == 2) {\n proposal.abstainVotes = proposal.abstainVotes + votes;\n }\n\n receipt.hasVoted = true;\n receipt.support = support;\n receipt.votes = votes;\n\n return votes;\n }\n\n /**\n * @notice View function which returns if an account is whitelisted\n * @param account Account to check white list status of\n * @return If the account is whitelisted\n */\n function isWhitelisted(address account) public view override returns (bool) {\n return (whitelistAccountExpirations[account] > block.timestamp);\n }\n\n /**\n * @notice Governance function for setting the governance token\n * @param token_ new token addr\n */\n function _setNewToken(address token_) external onlyGov {\n ipt = IIpt(token_);\n }\n\n /**\n * @notice Used to update the timelock period\n * @param proposalTimelockDelay_ The proposal holding period\n */\n function _setDelay(uint256 proposalTimelockDelay_) public override onlyGov {\n uint256 oldTimelockDelay = proposalTimelockDelay;\n proposalTimelockDelay = proposalTimelockDelay_;\n\n emit NewDelay(oldTimelockDelay, proposalTimelockDelay);\n }\n\n /**\n * @notice Used to update the emergency timelock period\n * @param emergencyTimelockDelay_ The proposal holding period\n */\n function _setEmergencyDelay(uint256 emergencyTimelockDelay_) public override onlyGov {\n uint256 oldEmergencyTimelockDelay = emergencyTimelockDelay;\n emergencyTimelockDelay = emergencyTimelockDelay_;\n\n emit NewEmergencyDelay(oldEmergencyTimelockDelay, emergencyTimelockDelay);\n }\n\n /**\n * @notice Governance function for setting the voting delay\n * @param newVotingDelay new voting delay, in blocks\n */\n function _setVotingDelay(uint256 newVotingDelay) external override onlyGov {\n uint256 oldVotingDelay = votingDelay;\n votingDelay = newVotingDelay;\n\n emit VotingDelaySet(oldVotingDelay, votingDelay);\n }\n\n /**\n * @notice Governance function for setting the voting period\n * @param newVotingPeriod new voting period, in blocks\n */\n function _setVotingPeriod(uint256 newVotingPeriod) external override onlyGov {\n uint256 oldVotingPeriod = votingPeriod;\n votingPeriod = newVotingPeriod;\n\n emit VotingPeriodSet(oldVotingPeriod, votingPeriod);\n }\n\n /**\n * @notice Governance function for setting the emergency voting period\n * @param newEmergencyVotingPeriod new voting period, in blocks\n */\n function _setEmergencyVotingPeriod(uint256 newEmergencyVotingPeriod) external override onlyGov {\n uint256 oldEmergencyVotingPeriod = emergencyVotingPeriod;\n emergencyVotingPeriod = newEmergencyVotingPeriod;\n\n emit EmergencyVotingPeriodSet(oldEmergencyVotingPeriod, emergencyVotingPeriod);\n }\n\n /**\n * @notice Governance function for setting the proposal threshold\n * @param newProposalThreshold new proposal threshold\n */\n function _setProposalThreshold(uint256 newProposalThreshold) external override onlyGov {\n uint256 oldProposalThreshold = proposalThreshold;\n proposalThreshold = newProposalThreshold;\n\n emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);\n }\n\n /**\n * @notice Governance function for setting the quorum\n * @param newQuorumVotes new proposal quorum\n */\n function _setQuorumVotes(uint256 newQuorumVotes) external override onlyGov {\n uint256 oldQuorumVotes = quorumVotes;\n quorumVotes = newQuorumVotes;\n\n emit NewQuorum(oldQuorumVotes, quorumVotes);\n }\n\n /**\n * @notice Governance function for setting the emergency quorum\n * @param newEmergencyQuorumVotes new proposal quorum\n */\n function _setEmergencyQuorumVotes(uint256 newEmergencyQuorumVotes) external override onlyGov {\n uint256 oldEmergencyQuorumVotes = emergencyQuorumVotes;\n emergencyQuorumVotes = newEmergencyQuorumVotes;\n\n emit NewEmergencyQuorum(oldEmergencyQuorumVotes, emergencyQuorumVotes);\n }\n\n /**\n * @notice Governance function for setting the whitelist expiration as a timestamp\n * for an account. Whitelist status allows accounts to propose without meeting threshold\n * @param account Account address to set whitelist expiration for\n * @param expiration Expiration for account whitelist status as timestamp (if now < expiration, whitelisted)\n */\n function _setWhitelistAccountExpiration(address account, uint256 expiration) external override onlyGov {\n require (expiration < (maxWhitelistPeriod + block.timestamp), \"expiration exceeds max\");\n whitelistAccountExpirations[account] = expiration;\n\n emit WhitelistAccountExpirationSet(account, expiration);\n }\n\n /**\n * @notice Governance function for setting the whitelistGuardian. WhitelistGuardian can cancel proposals from whitelisted addresses\n * @param account Account to set whitelistGuardian to (0x0 to remove whitelistGuardian)\n */\n function _setWhitelistGuardian(address account) external override onlyGov {\n address oldGuardian = whitelistGuardian;\n whitelistGuardian = account;\n\n emit WhitelistGuardianSet(oldGuardian, whitelistGuardian);\n }\n\n /**\n * @notice Governance function for setting the optimistic voting delay\n * @param newOptimisticVotingDelay new optimistic voting delay, in blocks\n */\n function _setOptimisticDelay(uint256 newOptimisticVotingDelay) external override onlyGov {\n uint256 oldOptimisticVotingDelay = optimisticVotingDelay;\n optimisticVotingDelay = newOptimisticVotingDelay;\n\n emit OptimisticVotingDelaySet(oldOptimisticVotingDelay, optimisticVotingDelay);\n }\n\n /**\n * @notice Governance function for setting the optimistic quorum\n * @param newOptimisticQuorumVotes new optimistic quorum votes, in blocks\n */\n function _setOptimisticQuorumVotes(uint256 newOptimisticQuorumVotes) external override onlyGov {\n uint256 oldOptimisticQuorumVotes = optimisticQuorumVotes;\n optimisticQuorumVotes = newOptimisticQuorumVotes;\n\n emit OptimisticQuorumVotesSet(oldOptimisticQuorumVotes, optimisticQuorumVotes);\n }\n\n function getChainIdInternal() internal view returns (uint256) {\n return block.chainid;\n }\n\n function getBlockTimestamp() internal view returns (uint256) {\n // solium-disable-next-line security/no-block-members\n return block.timestamp;\n }\n\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":200,"details":{"orderLiterals":true,"deduplicate":true,"cse":true,"yul":true}},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"libraries":{}}},"ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"CancelTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyVotingPeriod\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"EmergencyVotingPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"ExecuteTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"NewAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldTimelockDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"proposalTimelockDelay\",\"type\":\"uint256\"}],\"name\":\"NewDelay\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyTimelockDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyTimelockDelay\",\"type\":\"uint256\"}],\"name\":\"NewEmergencyDelay\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"NewEmergencyQuorum\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldImplementation\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"NewImplementation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldPendingAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newPendingAdmin\",\"type\":\"address\"}],\"name\":\"NewPendingAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"quorumVotes\",\"type\":\"uint256\"}],\"name\":\"NewQuorum\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldOptimisticQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"OptimisticQuorumVotesSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldOptimisticVotingDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticVotingDelay\",\"type\":\"uint256\"}],\"name\":\"OptimisticVotingDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ProposalCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"indexed\":false,\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"startBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"endBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"name\":\"ProposalCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ProposalExecuted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"ProposalQueued\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldProposalThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newProposalThreshold\",\"type\":\"uint256\"}],\"name\":\"ProposalThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"QueueTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"votes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"VoteCast\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldVotingDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newVotingDelay\",\"type\":\"uint256\"}],\"name\":\"VotingDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldVotingPeriod\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"VotingPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"WhitelistAccountExpirationSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldGuardian\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"WhitelistGuardianSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BALLOT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GRACE_PERIOD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalTimelockDelay_\",\"type\":\"uint256\"}],\"name\":\"_setDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"emergencyTimelockDelay_\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newEmergencyQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newEmergencyVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyVotingPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token_\",\"type\":\"address\"}],\"name\":\"_setNewToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newOptimisticVotingDelay\",\"type\":\"uint256\"}],\"name\":\"_setOptimisticDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newOptimisticQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setOptimisticQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newProposalThreshold\",\"type\":\"uint256\"}],\"name\":\"_setProposalThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newVotingDelay\",\"type\":\"uint256\"}],\"name\":\"_setVotingDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"_setVotingPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"_setWhitelistAccountExpiration\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"_setWhitelistGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"cancel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"}],\"name\":\"castVote\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"castVoteBySig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"castVoteWithReason\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyQuorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyTimelockDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyVotingPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"execute\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"executeTransaction\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"getActions\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"getReceipt\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"hasVoted\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"internalType\":\"struct Receipt\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialProposalId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ipt_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ipt\",\"outputs\":[{\"internalType\":\"contract IIpt\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isWhitelisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"latestProposalIds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxWhitelistPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticQuorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticVotingDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalMaxOperations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"proposalReceipts\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"hasVoted\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalTimelockDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"proposals\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"startBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"endBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"forVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"againstVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"abstainVotes\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"canceled\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"executed\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"emergency\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"quorumVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"delay\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"emergency\",\"type\":\"bool\"}],\"name\":\"propose\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"queue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"queuedTransactions\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"quorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"state\",\"outputs\":[{\"internalType\":\"enum ProposalState\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"votingDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"votingPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"whitelistAccountExpirations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"whitelistGuardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]","ContractName":"GovernorCharlieDelegate","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json new file mode 100644 index 0000000000000..2378ed6aa2a8d --- /dev/null +++ b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x9ab6b21cdf116f611110b048987e58894786c244","contractCreator":"0x603d50bad151da8becf405e51a8c4abc8ba1c95e","txHash":"0x72be611ae1ade09242d9fc9c950a73d076f6c23514564a7b9ac730400dbaf2c0"} \ No newline at end of file diff --git a/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json new file mode 100644 index 0000000000000..b0f893895eb0f --- /dev/null +++ b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/InterestRates/InterestRatePositionManager.f.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n\n/// Parameters for ERC20Permit.permit call\nstruct ERC20PermitSignature {\n IERC20Permit token;\n uint256 value;\n uint256 deadline;\n uint8 v;\n bytes32 r;\n bytes32 s;\n}\n\nlibrary PermitHelper {\n function applyPermit(\n ERC20PermitSignature calldata p,\n address owner,\n address spender\n ) internal {\n p.token.permit(owner, spender, p.value, p.deadline, p.v, p.r, p.s);\n }\n\n function applyPermits(\n ERC20PermitSignature[] calldata permits,\n address owner,\n address spender\n ) internal {\n for (uint256 i = 0; i < permits.length; i++) {\n applyPermit(permits[i], owner, spender);\n }\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashLender.sol)\n\n// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC3156FlashBorrower.sol)\n\n/**\n * @dev Interface of the ERC3156 FlashBorrower, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * _Available since v4.1._\n */\ninterface IERC3156FlashBorrower {\n /**\n * @dev Receive a flash loan.\n * @param initiator The initiator of the loan.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @param fee The additional amount of tokens to repay.\n * @param data Arbitrary data structure, intended to contain user-defined parameters.\n * @return The keccak256 hash of \"IERC3156FlashBorrower.onFlashLoan\"\n */\n function onFlashLoan(\n address initiator,\n address token,\n uint256 amount,\n uint256 fee,\n bytes calldata data\n ) external returns (bytes32);\n}\n\n/**\n * @dev Interface of the ERC3156 FlashLender, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * _Available since v4.1._\n */\ninterface IERC3156FlashLender {\n /**\n * @dev The amount of currency available to be lended.\n * @param token The loan currency.\n * @return The amount of `token` that can be borrowed.\n */\n function maxFlashLoan(address token) external view returns (uint256);\n\n /**\n * @dev The fee to be charged for a given loan.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @return The amount of `token` to be charged for the loan, on top of the returned principal.\n */\n function flashFee(address token, uint256 amount) external view returns (uint256);\n\n /**\n * @dev Initiate a flash loan.\n * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @param data Arbitrary data structure, intended to contain user-defined parameters.\n */\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n ) external returns (bool);\n}\n\n/// @dev Interface to be used by contracts that collect fees. Contains fee recipient that can be changed by owner.\ninterface IFeeCollector {\n // --- Events ---\n\n /// @dev Fee Recipient is changed to @param feeRecipient address.\n /// @param feeRecipient New fee recipient address.\n event FeeRecipientChanged(address feeRecipient);\n\n // --- Errors ---\n\n /// @dev Invalid fee recipient.\n error InvalidFeeRecipient();\n\n // --- Functions ---\n\n /// @return Address of the current fee recipient.\n function feeRecipient() external view returns (address);\n\n /// @dev Sets new fee recipient address\n /// @param newFeeRecipient Address of the new fee recipient.\n function setFeeRecipient(address newFeeRecipient) external;\n}\n\ninterface IPositionManagerDependent {\n // --- Errors ---\n\n /// @dev Position Manager cannot be zero.\n error PositionManagerCannotBeZero();\n\n /// @dev Caller is not Position Manager.\n error CallerIsNotPositionManager(address caller);\n\n // --- Functions ---\n\n /// @dev Returns address of the PositionManager contract.\n function positionManager() external view returns (address);\n}\n\n/// @dev Interface of R stablecoin token. Implements some standards like IERC20, IERC20Permit, and IERC3156FlashLender.\n/// Raft's specific implementation contains IFeeCollector and IPositionManagerDependent.\n/// PositionManager can mint and burn R when particular actions happen with user's position.\ninterface IRToken is IERC20, IERC20Permit, IERC3156FlashLender, IFeeCollector, IPositionManagerDependent {\n // --- Events ---\n\n /// @dev New R token is deployed\n /// @param positionManager Address of the PositionManager contract that is authorized to mint and burn new tokens.\n /// @param flashMintFeeRecipient Address of flash mint fee recipient.\n event RDeployed(address positionManager, address flashMintFeeRecipient);\n\n /// @dev The Flash Mint Fee Percentage has been changed.\n /// @param flashMintFeePercentage The new Flash Mint Fee Percentage value.\n event FlashMintFeePercentageChanged(uint256 flashMintFeePercentage);\n\n /// --- Errors ---\n\n /// @dev Proposed flash mint fee percentage is too big.\n /// @param feePercentage Proposed flash mint fee percentage.\n error FlashFeePercentageTooBig(uint256 feePercentage);\n\n // --- Functions ---\n\n /// @return Number representing 100 percentage.\n function PERCENTAGE_BASE() external view returns (uint256);\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param to Address that will receive newly minted tokens.\n /// @param amount Amount of tokens to mint.\n function mint(address to, uint256 amount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param from Address of user whose tokens are burnt.\n /// @param amount Amount of tokens to burn.\n function burn(address from, uint256 amount) external;\n\n /// @return Maximum flash mint fee percentage that can be set by owner.\n function MAX_FLASH_MINT_FEE_PERCENTAGE() external view returns (uint256);\n\n /// @return Current flash mint fee percentage.\n function flashMintFeePercentage() external view returns (uint256);\n\n /// @dev Sets new flash mint fee percentage. Callable only by owner.\n /// @notice The proposed flash mint fee percentage cannot exceed `MAX_FLASH_MINT_FEE_PERCENTAGE`.\n /// @param feePercentage New flash fee percentage.\n function setFlashMintFeePercentage(uint256 feePercentage) external;\n}\n\ninterface IPriceOracle {\n // --- Errors ---\n\n /// @dev Contract initialized with an invalid deviation parameter.\n error InvalidDeviation();\n\n // --- Types ---\n\n struct PriceOracleResponse {\n bool isBrokenOrFrozen;\n bool priceChangeAboveMax;\n uint256 price;\n }\n\n // --- Functions ---\n\n /// @dev Return price oracle response which consists the following information: oracle is broken or frozen, the\n /// price change between two rounds is more than max, and the price.\n function getPriceOracleResponse() external returns (PriceOracleResponse memory);\n\n /// @dev Maximum time period allowed since oracle latest round data timestamp, beyond which oracle is considered\n /// frozen.\n function timeout() external view returns (uint256);\n\n /// @dev Used to convert a price answer to an 18-digit precision uint.\n function TARGET_DIGITS() external view returns (uint256);\n\n /// @dev price deviation for the oracle in percentage.\n function DEVIATION() external view returns (uint256);\n}\n\ninterface IPriceFeed {\n // --- Events ---\n\n /// @dev Last good price has been updated.\n event LastGoodPriceUpdated(uint256 lastGoodPrice);\n\n /// @dev Price difference between oracles has been updated.\n /// @param priceDifferenceBetweenOracles New price difference between oracles.\n event PriceDifferenceBetweenOraclesUpdated(uint256 priceDifferenceBetweenOracles);\n\n /// @dev Primary oracle has been updated.\n /// @param primaryOracle New primary oracle.\n event PrimaryOracleUpdated(IPriceOracle primaryOracle);\n\n /// @dev Secondary oracle has been updated.\n /// @param secondaryOracle New secondary oracle.\n event SecondaryOracleUpdated(IPriceOracle secondaryOracle);\n\n // --- Errors ---\n\n /// @dev Invalid primary oracle.\n error InvalidPrimaryOracle();\n\n /// @dev Invalid secondary oracle.\n error InvalidSecondaryOracle();\n\n /// @dev Primary oracle is broken or frozen or has bad result.\n error PrimaryOracleBrokenOrFrozenOrBadResult();\n\n /// @dev Invalid price difference between oracles.\n error InvalidPriceDifferenceBetweenOracles();\n\n // --- Functions ---\n\n /// @dev Return primary oracle address.\n function primaryOracle() external returns (IPriceOracle);\n\n /// @dev Return secondary oracle address\n function secondaryOracle() external returns (IPriceOracle);\n\n /// @dev The last good price seen from an oracle by Raft.\n function lastGoodPrice() external returns (uint256);\n\n /// @dev The maximum relative price difference between two oracle responses.\n function priceDifferenceBetweenOracles() external returns (uint256);\n\n /// @dev Set primary oracle address.\n /// @param newPrimaryOracle Primary oracle address.\n function setPrimaryOracle(IPriceOracle newPrimaryOracle) external;\n\n /// @dev Set secondary oracle address.\n /// @param newSecondaryOracle Secondary oracle address.\n function setSecondaryOracle(IPriceOracle newSecondaryOracle) external;\n\n /// @dev Set the maximum relative price difference between two oracle responses.\n /// @param newPriceDifferenceBetweenOracles The maximum relative price difference between two oracle responses.\n function setPriceDifferenceBetweenOracles(uint256 newPriceDifferenceBetweenOracles) external;\n\n /// @dev Returns the latest price obtained from the Oracle. Called by Raft functions that require a current price.\n ///\n /// Also callable by anyone externally.\n /// Non-view function - it stores the last good price seen by Raft.\n ///\n /// Uses a primary oracle and a fallback oracle in case primary fails. If both fail,\n /// it uses the last good price seen by Raft.\n ///\n /// @return currentPrice Returned price.\n /// @return deviation Deviation of the reported price in percentage.\n /// @notice Actual returned price is in range `currentPrice` +/- `currentPrice * deviation / ONE`\n function fetchPrice() external returns (uint256 currentPrice, uint256 deviation);\n}\n\ninterface IERC20Indexable is IERC20, IPositionManagerDependent {\n // --- Events ---\n\n /// @dev New token is deployed.\n /// @param positionManager Address of the PositionManager contract that is authorized to mint and burn new tokens.\n event ERC20IndexableDeployed(address positionManager);\n\n /// @dev New index has been set.\n /// @param newIndex Value of the new index.\n event IndexUpdated(uint256 newIndex);\n\n // --- Errors ---\n\n /// @dev Unsupported action for ERC20Indexable contract.\n error NotSupported();\n\n // --- Functions ---\n\n /// @return Precision for token index. Represents index that is equal to 1.\n function INDEX_PRECISION() external view returns (uint256);\n\n /// @return Current index value.\n function currentIndex() external view returns (uint256);\n\n /// @dev Sets new token index. Callable only by PositionManager contract.\n /// @param backingAmount Amount of backing token that is covered by total supply.\n function setIndex(uint256 backingAmount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param to Address that will receive newly minted tokens.\n /// @param amount Amount of tokens to mint.\n function mint(address to, uint256 amount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param from Address of user whose tokens are burnt.\n /// @param amount Amount of tokens to burn.\n function burn(address from, uint256 amount) external;\n}\n\ninterface ISplitLiquidationCollateral {\n // --- Functions ---\n\n /// @dev Returns lowest total debt that will be split.\n function LOW_TOTAL_DEBT() external view returns (uint256);\n\n /// @dev Minimum collateralization ratio for position\n function MCR() external view returns (uint256);\n\n /// @dev Splits collateral between protocol and liquidator.\n /// @param totalCollateral Amount of collateral to split.\n /// @param totalDebt Amount of debt to split.\n /// @param price Price of collateral.\n /// @param isRedistribution True if this is a redistribution.\n /// @return collateralToSendToProtocol Amount of collateral to send to protocol.\n /// @return collateralToSentToLiquidator Amount of collateral to send to liquidator.\n function split(\n uint256 totalCollateral,\n uint256 totalDebt,\n uint256 price,\n bool isRedistribution\n )\n external\n view\n returns (uint256 collateralToSendToProtocol, uint256 collateralToSentToLiquidator);\n}\n\n/// @dev Common interface for the Position Manager.\ninterface IPositionManager is IFeeCollector {\n // --- Types ---\n\n /// @dev Information for a Raft indexable collateral token.\n /// @param collateralToken The Raft indexable collateral token.\n /// @param debtToken Corresponding Raft indexable debt token.\n /// @param priceFeed The contract that provides a price for the collateral token.\n /// @param splitLiquidation The contract that calculates collateral split in case of liquidation.\n /// @param isEnabled Whether the token can be used as collateral or not.\n /// @param lastFeeOperationTime Timestamp of the last operation for the collateral token.\n /// @param borrowingSpread The current borrowing spread.\n /// @param baseRate The current base rate.\n /// @param redemptionSpread The current redemption spread.\n /// @param redemptionRebate Percentage of the redemption fee returned to redeemed positions.\n struct CollateralTokenInfo {\n IERC20Indexable collateralToken;\n IERC20Indexable debtToken;\n IPriceFeed priceFeed;\n ISplitLiquidationCollateral splitLiquidation;\n bool isEnabled;\n uint256 lastFeeOperationTime;\n uint256 borrowingSpread;\n uint256 baseRate;\n uint256 redemptionSpread;\n uint256 redemptionRebate;\n }\n\n // --- Events ---\n\n /// @dev New position manager has been token deployed.\n /// @param rToken The R token used by the position manager.\n /// @param feeRecipient The address of fee recipient.\n event PositionManagerDeployed(IRToken rToken, address feeRecipient);\n\n /// @dev New collateral token has been added added to the system.\n /// @param collateralToken The token used as collateral.\n /// @param raftCollateralToken The Raft indexable collateral token for the given collateral token.\n /// @param raftDebtToken The Raft indexable debt token for given collateral token.\n /// @param priceFeed The contract that provides price for the collateral token.\n event CollateralTokenAdded(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n IPriceFeed priceFeed\n );\n\n /// @dev Collateral token has been enabled or disabled.\n /// @param collateralToken The token used as collateral.\n /// @param isEnabled True if the token is enabled, false otherwise.\n event CollateralTokenModified(IERC20 collateralToken, bool isEnabled);\n\n /// @dev A delegate has been whitelisted for a certain position.\n /// @param position The position for which the delegate was whitelisted.\n /// @param delegate The delegate which was whitelisted.\n /// @param whitelisted Specifies whether the delegate whitelisting has been enabled (true) or disabled (false).\n event DelegateWhitelisted(address indexed position, address indexed delegate, bool whitelisted);\n\n /// @dev New position has been created.\n /// @param position The address of the user opening new position.\n /// @param collateralToken The token used as collateral for the created position.\n event PositionCreated(address indexed position, IERC20 indexed collateralToken);\n\n /// @dev The position has been closed by either repayment, liquidation, or redemption.\n /// @param position The address of the user whose position is closed.\n event PositionClosed(address indexed position);\n\n /// @dev Collateral amount for the position has been changed.\n /// @param position The address of the user that has opened the position.\n /// @param collateralToken The address of the collateral token being added to position.\n /// @param collateralAmount The amount of collateral added or removed.\n /// @param isCollateralIncrease Whether the collateral is added to the position or removed from it.\n event CollateralChanged(\n address indexed position, IERC20 indexed collateralToken, uint256 collateralAmount, bool isCollateralIncrease\n );\n\n /// @dev Debt amount for position has been changed.\n /// @param position The address of the user that has opened the position.\n /// @param collateralToken The address of the collateral token backing the debt.\n /// @param debtAmount The amount of debt added or removed.\n /// @param isDebtIncrease Whether the debt is added to the position or removed from it.\n event DebtChanged(\n address indexed position, IERC20 indexed collateralToken, uint256 debtAmount, bool isDebtIncrease\n );\n\n /// @dev Borrowing fee has been paid. Emitted only if the actual fee was paid - doesn't happen with no fees are\n /// paid.\n /// @param collateralToken Collateral token used to mint R.\n /// @param position The address of position's owner that triggered the fee payment.\n /// @param feeAmount The amount of tokens paid as the borrowing fee.\n event RBorrowingFeePaid(IERC20 collateralToken, address indexed position, uint256 feeAmount);\n\n /// @dev Liquidation has been executed.\n /// @param liquidator The liquidator that executed the liquidation.\n /// @param position The address of position's owner whose position was liquidated.\n /// @param collateralToken The collateral token used for the liquidation.\n /// @param debtLiquidated The total debt that was liquidated or redistributed.\n /// @param collateralLiquidated The total collateral liquidated.\n /// @param collateralSentToLiquidator The collateral amount sent to the liquidator.\n /// @param collateralLiquidationFeePaid The total collateral paid as the liquidation fee to the fee recipient.\n /// @param isRedistribution Whether the executed liquidation was redistribution or not.\n event Liquidation(\n address indexed liquidator,\n address indexed position,\n IERC20 indexed collateralToken,\n uint256 debtLiquidated,\n uint256 collateralLiquidated,\n uint256 collateralSentToLiquidator,\n uint256 collateralLiquidationFeePaid,\n bool isRedistribution\n );\n\n /// @dev Redemption has been executed.\n /// @param redeemer User that redeemed R.\n /// @param amount Amount of R that was redeemed.\n /// @param collateralSent The amount of collateral sent to the redeemer.\n /// @param fee The amount of fee paid to the fee recipient.\n /// @param rebate Redemption rebate amount.\n event Redemption(address indexed redeemer, uint256 amount, uint256 collateralSent, uint256 fee, uint256 rebate);\n\n /// @dev Borrowing spread has been updated.\n /// @param borrowingSpread The new borrowing spread.\n event BorrowingSpreadUpdated(uint256 borrowingSpread);\n\n /// @dev Redemption rebate has been updated.\n /// @param redemptionRebate The new redemption rebate.\n event RedemptionRebateUpdated(uint256 redemptionRebate);\n\n /// @dev Redemption spread has been updated.\n /// @param collateralToken Collateral token that the spread was set for.\n /// @param redemptionSpread The new redemption spread.\n event RedemptionSpreadUpdated(IERC20 collateralToken, uint256 redemptionSpread);\n\n /// @dev Base rate has been updated.\n /// @param collateralToken Collateral token that the baser rate was updated for.\n /// @param baseRate The new base rate.\n event BaseRateUpdated(IERC20 collateralToken, uint256 baseRate);\n\n /// @dev Last fee operation time has been updated.\n /// @param collateralToken Collateral token that the baser rate was updated for.\n /// @param lastFeeOpTime The new operation time.\n event LastFeeOpTimeUpdated(IERC20 collateralToken, uint256 lastFeeOpTime);\n\n /// @dev Split liquidation collateral has been changed.\n /// @param collateralToken Collateral token whose split liquidation collateral contract is set.\n /// @param newSplitLiquidationCollateral New value that was set to be split liquidation collateral.\n event SplitLiquidationCollateralChanged(\n IERC20 collateralToken, ISplitLiquidationCollateral indexed newSplitLiquidationCollateral\n );\n\n // --- Errors ---\n\n /// @dev Max fee percentage must be between borrowing spread and 100%.\n error InvalidMaxFeePercentage();\n\n /// @dev Max fee percentage must be between 0.5% and 100%.\n error MaxFeePercentageOutOfRange();\n\n /// @dev Amount is zero.\n error AmountIsZero();\n\n /// @dev Nothing to liquidate.\n error NothingToLiquidate();\n\n /// @dev Cannot liquidate last position.\n error CannotLiquidateLastPosition();\n\n /// @dev Cannot redeem collateral below minimum debt threshold.\n /// @param collateralToken Collateral token used to redeem.\n /// @param newTotalDebt New total debt backed by collateral, which is lower than minimum debt.\n error TotalDebtCannotBeLowerThanMinDebt(IERC20 collateralToken, uint256 newTotalDebt);\n\n /// @dev Cannot redeem collateral\n /// @param collateralToken Collateral token used to redeem.\n /// @param newTotalCollateral New total collateral, which is lower than minimum collateral.\n /// @param minimumCollateral Minimum collateral required to complete redeem\n error TotalCollateralCannotBeLowerThanMinCollateral(\n IERC20 collateralToken, uint256 newTotalCollateral, uint256 minimumCollateral\n );\n\n /// @dev Fee would eat up all returned collateral.\n error FeeEatsUpAllReturnedCollateral();\n\n /// @dev Borrowing spread exceeds maximum.\n error BorrowingSpreadExceedsMaximum();\n\n /// @dev Redemption rebate exceeds maximum.\n error RedemptionRebateExceedsMaximum();\n\n /// @dev Redemption spread is out of allowed range.\n error RedemptionSpreadOutOfRange();\n\n /// @dev There must be either a collateral change or a debt change.\n error NoCollateralOrDebtChange();\n\n /// @dev There is some collateral for position that doesn't have debt.\n error InvalidPosition();\n\n /// @dev An operation that would result in ICR < MCR is not permitted.\n /// @param newICR Resulting ICR that is below MCR.\n error NewICRLowerThanMCR(uint256 newICR);\n\n /// @dev Position's net debt must be greater than minimum.\n /// @param netDebt Net debt amount that is below minimum.\n error NetDebtBelowMinimum(uint256 netDebt);\n\n /// @dev The provided delegate address is invalid.\n error InvalidDelegateAddress();\n\n /// @dev A non-whitelisted delegate cannot adjust positions.\n error DelegateNotWhitelisted();\n\n /// @dev Fee exceeded provided maximum fee percentage.\n /// @param fee The fee amount.\n /// @param amount The amount of debt or collateral.\n /// @param maxFeePercentage The maximum fee percentage.\n error FeeExceedsMaxFee(uint256 fee, uint256 amount, uint256 maxFeePercentage);\n\n /// @dev Borrower uses a different collateral token already.\n error PositionCollateralTokenMismatch();\n\n /// @dev Collateral token address cannot be zero.\n error CollateralTokenAddressCannotBeZero();\n\n /// @dev Price feed address cannot be zero.\n error PriceFeedAddressCannotBeZero();\n\n /// @dev Collateral token already added.\n error CollateralTokenAlreadyAdded();\n\n /// @dev Collateral token is not added.\n error CollateralTokenNotAdded();\n\n /// @dev Collateral token is not enabled.\n error CollateralTokenDisabled();\n\n /// @dev Split liquidation collateral cannot be zero.\n error SplitLiquidationCollateralCannotBeZero();\n\n /// @dev Cannot change collateral in case of repaying the whole debt.\n error WrongCollateralParamsForFullRepayment();\n\n // --- Functions ---\n\n /// @return The R token used by position manager.\n function rToken() external view returns (IRToken);\n\n /// @dev Retrieves information about certain collateral type.\n /// @param collateralToken The token used as collateral.\n /// @return raftCollateralToken The Raft indexable collateral token.\n /// @return raftDebtToken The Raft indexable debt token.\n /// @return priceFeed The contract that provides a price for the collateral token.\n /// @return splitLiquidation The contract that calculates collateral split in case of liquidation.\n /// @return isEnabled Whether the collateral token can be used as collateral or not.\n /// @return lastFeeOperationTime Timestamp of the last operation for the collateral token.\n /// @return borrowingSpread The current borrowing spread.\n /// @return baseRate The current base rate.\n /// @return redemptionSpread The current redemption spread.\n /// @return redemptionRebate Percentage of the redemption fee returned to redeemed positions.\n function collateralInfo(IERC20 collateralToken)\n external\n view\n returns (\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral splitLiquidation,\n bool isEnabled,\n uint256 lastFeeOperationTime,\n uint256 borrowingSpread,\n uint256 baseRate,\n uint256 redemptionSpread,\n uint256 redemptionRebate\n );\n\n /// @param collateralToken Collateral token whose raft collateral indexable token is being queried.\n /// @return Raft collateral token address for given collateral token.\n function raftCollateralToken(IERC20 collateralToken) external view returns (IERC20Indexable);\n\n /// @param collateralToken Collateral token whose raft collateral indexable token is being queried.\n /// @return Raft debt token address for given collateral token.\n function raftDebtToken(IERC20 collateralToken) external view returns (IERC20Indexable);\n\n /// @param collateralToken Collateral token whose price feed contract is being queried.\n /// @return Price feed contract address for given collateral token.\n function priceFeed(IERC20 collateralToken) external view returns (IPriceFeed);\n\n /// @param collateralToken Collateral token whose split liquidation collateral is being queried.\n /// @return Returns address of the split liquidation collateral contract.\n function splitLiquidationCollateral(IERC20 collateralToken) external view returns (ISplitLiquidationCollateral);\n\n /// @param collateralToken Collateral token whose split liquidation collateral is being queried.\n /// @return Returns whether collateral is enabled or nor.\n function collateralEnabled(IERC20 collateralToken) external view returns (bool);\n\n /// @param collateralToken Collateral token we query last operation time fee for.\n /// @return The timestamp of the latest fee operation (redemption or new R issuance).\n function lastFeeOperationTime(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query borrowing spread for.\n /// @return The current borrowing spread.\n function borrowingSpread(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query base rate for.\n /// @return rate The base rate.\n function baseRate(IERC20 collateralToken) external view returns (uint256 rate);\n\n /// @param collateralToken Collateral token we query redemption spread for.\n /// @return The current redemption spread for collateral token.\n function redemptionSpread(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query redemption rebate for.\n /// @return rebate Percentage of the redemption fee returned to redeemed positions.\n function redemptionRebate(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query redemption rate for.\n /// @return rate The current redemption rate for collateral token.\n function getRedemptionRate(IERC20 collateralToken) external view returns (uint256 rate);\n\n /// @dev Returns the collateral token that a given position used for their position.\n /// @param position The address of the borrower.\n /// @return collateralToken The collateral token of the borrower's position.\n function collateralTokenForPosition(address position) external view returns (IERC20 collateralToken);\n\n /// @dev Adds a new collateral token to the protocol.\n /// @param collateralToken The new collateral token.\n /// @param priceFeed The price feed for the collateral token.\n /// @param newSplitLiquidationCollateral split liquidation collateral contract address.\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n external;\n\n /// @dev Adds a new collateral token to the protocol.\n /// @param collateralToken The new collateral token.\n /// @param priceFeed The price feed for the collateral token.\n /// @param newSplitLiquidationCollateral split liquidation collateral contract address.\n /// @param raftCollateralToken_ Address of raft collateral token.\n /// @param raftDebtToken_ Address of raft debt token.\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral,\n IERC20Indexable raftCollateralToken_,\n IERC20Indexable raftDebtToken_\n )\n external;\n\n /// @dev Enables or disables a collateral token. Reverts if the collateral token has not been added.\n /// @param collateralToken The collateral token.\n /// @param isEnabled Whether the collateral token can be used as collateral or not.\n function setCollateralEnabled(IERC20 collateralToken, bool isEnabled) external;\n\n /// @dev Sets the new split liquidation collateral contract.\n /// @param collateralToken Collateral token whose split liquidation collateral is being set.\n /// @param newSplitLiquidationCollateral New split liquidation collateral contract address.\n function setSplitLiquidationCollateral(\n IERC20 collateralToken,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n external;\n\n /// @dev Liquidates the borrower if its position's ICR is lower than the minimum collateral ratio.\n /// @param position The address of the borrower.\n function liquidate(address position) external;\n\n /// @dev Redeems the collateral token for a given debt amount. It sends @param debtAmount R to the system and\n /// redeems the corresponding amount of collateral from as many positions as are needed to fill the redemption\n /// request.\n /// @param collateralToken The token used as collateral.\n /// @param debtAmount The amount of debt to be redeemed. Must be greater than zero.\n /// @param maxFeePercentage The maximum fee percentage to pay for the redemption.\n function redeemCollateral(IERC20 collateralToken, uint256 debtAmount, uint256 maxFeePercentage) external;\n\n /// @dev Manages the position on behalf of a given borrower.\n /// @param collateralToken The token the borrower used as collateral.\n /// @param position The address of the borrower.\n /// @param collateralChange The amount of collateral to add or remove.\n /// @param isCollateralIncrease True if the collateral is being increased, false otherwise.\n /// @param debtChange The amount of R to add or remove. In case of repayment (isDebtIncrease = false)\n /// `type(uint256).max` value can be used to repay the whole outstanding loan.\n /// @param isDebtIncrease True if the debt is being increased, false otherwise.\n /// @param maxFeePercentage The maximum fee percentage to pay for the position management.\n /// @param permitSignature Optional permit signature for tokens that support IERC20Permit interface.\n /// @notice `permitSignature` it is ignored if permit signature is not for `collateralToken`.\n /// @notice In case of full debt repayment, `isCollateralIncrease` is ignored and `collateralChange` must be 0.\n /// These values are set to `false`(collateral decrease), and the whole collateral balance of the user.\n /// @return actualCollateralChange Actual amount of collateral added/removed.\n /// Can be different to `collateralChange` in case of full repayment.\n /// @return actualDebtChange Actual amount of debt added/removed.\n /// Can be different to `debtChange` in case of passing type(uint256).max as `debtChange`.\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n external\n returns (uint256 actualCollateralChange, uint256 actualDebtChange);\n\n /// @return The max borrowing spread.\n function MAX_BORROWING_SPREAD() external view returns (uint256);\n\n /// @return The max borrowing rate.\n function MAX_BORROWING_RATE() external view returns (uint256);\n\n /// @dev Sets the new borrowing spread.\n /// @param collateralToken Collateral token we set borrowing spread for.\n /// @param newBorrowingSpread New borrowing spread to be used.\n function setBorrowingSpread(IERC20 collateralToken, uint256 newBorrowingSpread) external;\n\n /// @param collateralToken Collateral token we query borrowing rate for.\n /// @return The current borrowing rate.\n function getBorrowingRate(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query borrowing rate with decay for.\n /// @return The current borrowing rate with decay.\n function getBorrowingRateWithDecay(IERC20 collateralToken) external view returns (uint256);\n\n /// @dev Returns the borrowing fee for a given debt amount.\n /// @param collateralToken Collateral token we query borrowing fee for.\n /// @param debtAmount The amount of debt.\n /// @return The borrowing fee.\n function getBorrowingFee(IERC20 collateralToken, uint256 debtAmount) external view returns (uint256);\n\n /// @dev Sets the new redemption spread.\n /// @param newRedemptionSpread New redemption spread to be used.\n function setRedemptionSpread(IERC20 collateralToken, uint256 newRedemptionSpread) external;\n\n /// @dev Sets new redemption rebate percentage.\n /// @param newRedemptionRebate Value that is being set as a redemption rebate percentage.\n function setRedemptionRebate(IERC20 collateralToken, uint256 newRedemptionRebate) external;\n\n /// @param collateralToken Collateral token we query redemption rate with decay for.\n /// @return The current redemption rate with decay.\n function getRedemptionRateWithDecay(IERC20 collateralToken) external view returns (uint256);\n\n /// @dev Returns the redemption fee for a given collateral amount.\n /// @param collateralToken Collateral token we query redemption fee for.\n /// @param collateralAmount The amount of collateral.\n /// @param priceDeviation Deviation for the reported price by oracle in percentage.\n /// @return The redemption fee.\n function getRedemptionFee(\n IERC20 collateralToken,\n uint256 collateralAmount,\n uint256 priceDeviation\n )\n external\n view\n returns (uint256);\n\n /// @dev Returns the redemption fee with decay for a given collateral amount.\n /// @param collateralToken Collateral token we query redemption fee with decay for.\n /// @param collateralAmount The amount of collateral.\n /// @return The redemption fee with decay.\n function getRedemptionFeeWithDecay(\n IERC20 collateralToken,\n uint256 collateralAmount\n )\n external\n view\n returns (uint256);\n\n /// @return Half-life of 12h (720 min).\n /// @dev (1/2) = d^720 => d = (1/2)^(1/720)\n function MINUTE_DECAY_FACTOR() external view returns (uint256);\n\n /// @dev Returns if a given delegate is whitelisted for a given borrower.\n /// @param position The address of the borrower.\n /// @param delegate The address of the delegate.\n /// @return isWhitelisted True if the delegate is whitelisted for a given borrower, false otherwise.\n function isDelegateWhitelisted(address position, address delegate) external view returns (bool isWhitelisted);\n\n /// @dev Whitelists a delegate.\n /// @param delegate The address of the delegate.\n /// @param whitelisted True if delegate is being whitelisted, false otherwise.\n function whitelistDelegate(address delegate, bool whitelisted) external;\n\n /// @return Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a\n /// redemption. Corresponds to (1 / ALPHA) in the white paper.\n function BETA() external view returns (uint256);\n}\n\n/// @dev Common interface for the Position Manager.\ninterface IInterestRatePositionManager is IPositionManager {\n // --- Events ---\n\n /// @dev Fees coming from accrued interest are minted.\n /// @param collateralToken Collateral token that fees are paid for.\n /// @param amount Amount of R minted.\n event MintedFees(IERC20 collateralToken, uint256 amount);\n\n // --- Errors ---\n\n /// @dev Only registered debt token can be caller.\n /// @param sender Actual caller.\n error InvalidDebtToken(address sender);\n\n // --- Functions ---\n\n /// @dev Mints fees coming from accrued interest. Can be called only from matching debt token.\n /// @param collateralToken Collateral token to mint fees for.\n /// @param amount Amount of R to mint.\n function mintFees(IERC20 collateralToken, uint256 amount) external;\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Down, // Toward negative infinity\n Up, // Toward infinity\n Zero // Toward zero\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a > b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a == 0 ? 0 : (a - 1) / b + 1;\n }\n\n /**\n * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0\n * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)\n * with further edits by Uniswap Labs also under MIT license.\n */\n function mulDiv(\n uint256 x,\n uint256 y,\n uint256 denominator\n ) internal pure returns (uint256 result) {\n unchecked {\n // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\n // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = prod1 * 2^256 + prod0.\n uint256 prod0; // Least significant 256 bits of the product\n uint256 prod1; // Most significant 256 bits of the product\n assembly {\n let mm := mulmod(x, y, not(0))\n prod0 := mul(x, y)\n prod1 := sub(sub(mm, prod0), lt(mm, prod0))\n }\n\n // Handle non-overflow cases, 256 by 256 division.\n if (prod1 == 0) {\n return prod0 / denominator;\n }\n\n // Make sure the result is less than 2^256. Also prevents denominator == 0.\n require(denominator > prod1);\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [prod1 prod0].\n uint256 remainder;\n assembly {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n prod1 := sub(prod1, gt(remainder, prod0))\n prod0 := sub(prod0, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.\n // See https://cs.stackexchange.com/q/138556/92363.\n\n // Does not overflow because the denominator cannot be zero at this stage in the function.\n uint256 twos = denominator & (~denominator + 1);\n assembly {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [prod1 prod0] by twos.\n prod0 := div(prod0, twos)\n\n // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from prod1 into prod0.\n prod0 |= prod1 * twos;\n\n // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\n // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv = 1 mod 2^4.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works\n // in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2^8\n inverse *= 2 - denominator * inverse; // inverse mod 2^16\n inverse *= 2 - denominator * inverse; // inverse mod 2^32\n inverse *= 2 - denominator * inverse; // inverse mod 2^64\n inverse *= 2 - denominator * inverse; // inverse mod 2^128\n inverse *= 2 - denominator * inverse; // inverse mod 2^256\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\n // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\n // is no longer required.\n result = prod0 * inverse;\n return result;\n }\n }\n\n /**\n * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(\n uint256 x,\n uint256 y,\n uint256 denominator,\n Rounding rounding\n ) internal pure returns (uint256) {\n uint256 result = mulDiv(x, y, denominator);\n if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {\n result += 1;\n }\n return result;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.\n *\n * Inspired by Henry S. Warren, Jr.'s \"Hacker's Delight\" (Chapter 11).\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n }\n\n // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\n //\n // We know that the \"msb\" (most significant bit) of our target number `a` is a power of 2 such that we have\n // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\n //\n // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\n // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\n // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\n //\n // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\n uint256 result = 1 << (log2(a) >> 1);\n\n // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\n // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\n // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\n // into the expected uint128 result.\n unchecked {\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n return min(result, a / result);\n }\n }\n\n /**\n * @notice Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 2, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 128;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 64;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 32;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 16;\n }\n if (value >> 8 > 0) {\n value >>= 8;\n result += 8;\n }\n if (value >> 4 > 0) {\n value >>= 4;\n result += 4;\n }\n if (value >> 2 > 0) {\n value >>= 2;\n result += 2;\n }\n if (value >> 1 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 10, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10**64) {\n value /= 10**64;\n result += 64;\n }\n if (value >= 10**32) {\n value /= 10**32;\n result += 32;\n }\n if (value >= 10**16) {\n value /= 10**16;\n result += 16;\n }\n if (value >= 10**8) {\n value /= 10**8;\n result += 8;\n }\n if (value >= 10**4) {\n value /= 10**4;\n result += 4;\n }\n if (value >= 10**2) {\n value /= 10**2;\n result += 2;\n }\n if (value >= 10**1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 256, rounded down, of a positive value.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 16;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 8;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 4;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 2;\n }\n if (value >> 8 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);\n }\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n\nlibrary Fixed256x18 {\n uint256 internal constant ONE = 1e18; // 18 decimal places\n\n function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {\n return (a * b) / ONE;\n }\n\n function mulUp(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 product = a * b;\n\n if (product == 0) {\n return 0;\n } else {\n return ((product - 1) / ONE) + 1;\n }\n }\n\n function divDown(uint256 a, uint256 b) internal pure returns (uint256) {\n return (a * ONE) / b;\n }\n\n function divUp(uint256 a, uint256 b) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n } else {\n return (((a * ONE) - 1) / b) + 1;\n }\n }\n\n function complement(uint256 x) internal pure returns (uint256) {\n return (x < ONE) ? (ONE - x) : 0;\n }\n}\n\nlibrary MathUtils {\n // --- Constants ---\n\n /// @notice Represents 100%.\n /// @dev 1e18 is the scaling factor (100% == 1e18).\n uint256 public constant _100_PERCENT = Fixed256x18.ONE;\n\n /// @notice Precision for Nominal ICR (independent of price).\n /// @dev Rationale for the value:\n /// - Making it “too high” could lead to overflows.\n /// - Making it “too low” could lead to an ICR equal to zero, due to truncation from floor division.\n ///\n /// This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 collateralToken,\n /// and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.\n uint256 internal constant _NICR_PRECISION = 1e20;\n\n /// @notice Number of minutes in 1000 years.\n uint256 internal constant _MINUTES_IN_1000_YEARS = 1000 * 365 days / 1 minutes;\n\n // --- Functions ---\n\n /// @notice Multiplies two decimal numbers and use normal rounding rules:\n /// - round product up if 19'th mantissa digit >= 5\n /// - round product down if 19'th mantissa digit < 5.\n /// @param x First number.\n /// @param y Second number.\n function _decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {\n decProd = (x * y + Fixed256x18.ONE / 2) / Fixed256x18.ONE;\n }\n\n /// @notice Exponentiation function for 18-digit decimal base, and integer exponent n.\n ///\n /// @dev Uses the efficient \"exponentiation by squaring\" algorithm. O(log(n)) complexity. The exponent is capped to\n /// avoid reverting due to overflow.\n ///\n /// If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be\n /// negligibly different from just passing the cap, since the decayed base rate will be 0 for 1000 years or > 1000\n /// years.\n /// @param base The decimal base.\n /// @param exponent The exponent.\n /// @return The result of the exponentiation.\n function _decPow(uint256 base, uint256 exponent) internal pure returns (uint256) {\n if (exponent == 0) {\n return Fixed256x18.ONE;\n }\n\n uint256 y = Fixed256x18.ONE;\n uint256 x = base;\n uint256 n = Math.min(exponent, _MINUTES_IN_1000_YEARS); // cap to avoid overflow\n\n // Exponentiation-by-squaring\n while (n > 1) {\n if (n % 2 != 0) {\n y = _decMul(x, y);\n }\n x = _decMul(x, x);\n n /= 2;\n }\n\n return _decMul(x, y);\n }\n\n /// @notice Computes the Nominal Individual Collateral Ratio (NICR) for given collateral and debt. If debt is zero,\n /// it returns the maximal value for uint256 (represents \"infinite\" CR).\n /// @param collateral Collateral amount.\n /// @param debt Debt amount.\n /// @return NICR.\n function _computeNominalCR(uint256 collateral, uint256 debt) internal pure returns (uint256) {\n return debt > 0 ? collateral * _NICR_PRECISION / debt : type(uint256).max;\n }\n\n /// @notice Computes the Collateral Ratio for given collateral, debt and price. If debt is zero, it returns the\n /// maximal value for uint256 (represents \"infinite\" CR).\n /// @param collateral Collateral amount.\n /// @param debt Debt amount.\n /// @param price Collateral price.\n /// @return Collateral ratio.\n function _computeCR(uint256 collateral, uint256 debt, uint256 price) internal pure returns (uint256) {\n return debt > 0 ? collateral * price / debt : type(uint256).max;\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)\n\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)\n\n// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() external {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n}\n\n/**\n * @dev Extension of {ERC20} that adds a cap to the supply of tokens.\n */\nabstract contract ERC20Capped is ERC20, Ownable2Step {\n uint256 public cap;\n\n /**\n * @dev Total supply cap has been exceeded.\n */\n error ERC20ExceededCap();\n\n /**\n * @dev The supplied cap is not a valid cap.\n */\n error ERC20InvalidCap(uint256 cap);\n\n constructor(uint256 cap_) {\n setCap(cap_);\n }\n\n /**\n * @dev Sets the value of the `cap`.\n */\n function setCap(uint256 cap_) public onlyOwner {\n if (cap_ == 0) {\n revert ERC20InvalidCap(0);\n }\n cap = cap_;\n }\n\n /**\n * @dev See {ERC20-_mint}.\n */\n function _mint(address account, uint256 amount) internal virtual override {\n if (totalSupply() + amount > cap) {\n revert ERC20ExceededCap();\n }\n super._mint(account, amount);\n }\n}\n\nabstract contract PositionManagerDependent is IPositionManagerDependent {\n // --- Immutable variables ---\n\n address public immutable override positionManager;\n\n // --- Modifiers ---\n\n modifier onlyPositionManager() {\n if (msg.sender != positionManager) {\n revert CallerIsNotPositionManager(msg.sender);\n }\n _;\n }\n\n // --- Constructor ---\n\n constructor(address positionManager_) {\n if (positionManager_ == address(0)) {\n revert PositionManagerCannotBeZero();\n }\n positionManager = positionManager_;\n }\n}\n\ncontract ERC20Indexable is IERC20Indexable, ERC20Capped, PositionManagerDependent {\n // --- Types ---\n\n using Fixed256x18 for uint256;\n\n // --- Constants ---\n\n uint256 public constant override INDEX_PRECISION = Fixed256x18.ONE;\n\n // --- Variables ---\n\n uint256 internal storedIndex;\n\n // --- Constructor ---\n\n constructor(\n address positionManager_,\n string memory name_,\n string memory symbol_,\n uint256 cap_\n )\n ERC20(name_, symbol_)\n ERC20Capped(cap_)\n PositionManagerDependent(positionManager_)\n {\n storedIndex = INDEX_PRECISION;\n emit ERC20IndexableDeployed(positionManager_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) public virtual override onlyPositionManager {\n _mint(to, amount.divUp(storedIndex));\n }\n\n function burn(address from, uint256 amount) public virtual override onlyPositionManager {\n _burn(from, amount == type(uint256).max ? ERC20.balanceOf(from) : amount.divUp(storedIndex));\n }\n\n function setIndex(uint256 backingAmount) external override onlyPositionManager {\n uint256 supply = ERC20.totalSupply();\n uint256 newIndex = (backingAmount == 0 && supply == 0) ? INDEX_PRECISION : backingAmount.divUp(supply);\n storedIndex = newIndex;\n emit IndexUpdated(newIndex);\n }\n\n function currentIndex() public view virtual override returns (uint256) {\n return storedIndex;\n }\n\n function totalSupply() public view virtual override(IERC20, ERC20) returns (uint256) {\n return ERC20.totalSupply().mulDown(currentIndex());\n }\n\n function balanceOf(address account) public view virtual override(IERC20, ERC20) returns (uint256) {\n return ERC20.balanceOf(account).mulDown(currentIndex());\n }\n\n function transfer(address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function allowance(address, address) public view virtual override(IERC20, ERC20) returns (uint256) {\n revert NotSupported();\n }\n\n function approve(address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function transferFrom(address, address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function increaseAllowance(address, uint256) public virtual override returns (bool) {\n revert NotSupported();\n }\n\n function decreaseAllowance(address, uint256) public virtual override returns (bool) {\n revert NotSupported();\n }\n}\n\nabstract contract FeeCollector is Ownable2Step, IFeeCollector {\n // --- Variables ---\n\n address public override feeRecipient;\n\n // --- Constructor ---\n\n /// @param feeRecipient_ Address of the fee recipient to initialize contract with.\n constructor(address feeRecipient_) {\n if (feeRecipient_ == address(0)) {\n revert InvalidFeeRecipient();\n }\n\n feeRecipient = feeRecipient_;\n }\n\n // --- Functions ---\n\n function setFeeRecipient(address newFeeRecipient) external onlyOwner {\n if (newFeeRecipient == address(0)) {\n revert InvalidFeeRecipient();\n }\n\n feeRecipient = newFeeRecipient;\n emit FeeRecipientChanged(newFeeRecipient);\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n /// @solidity memory-safe-assembly\n assembly {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n /// @solidity memory-safe-assembly\n assembly {\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV // Deprecated in v4.8\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n /// @solidity memory-safe-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol)\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,\n * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding\n * they need in their contracts using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * _Available since v3.4._\n */\nabstract contract EIP712 {\n /* solhint-disable var-name-mixedcase */\n // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to\n // invalidate the cached domain separator if the chain id changes.\n bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;\n uint256 private immutable _CACHED_CHAIN_ID;\n address private immutable _CACHED_THIS;\n\n bytes32 private immutable _HASHED_NAME;\n bytes32 private immutable _HASHED_VERSION;\n bytes32 private immutable _TYPE_HASH;\n\n /* solhint-enable var-name-mixedcase */\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n bytes32 hashedName = keccak256(bytes(name));\n bytes32 hashedVersion = keccak256(bytes(version));\n bytes32 typeHash = keccak256(\n \"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"\n );\n _HASHED_NAME = hashedName;\n _HASHED_VERSION = hashedVersion;\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);\n _CACHED_THIS = address(this);\n _TYPE_HASH = typeHash;\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view returns (bytes32) {\n if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {\n return _CACHED_DOMAIN_SEPARATOR;\n } else {\n return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);\n }\n }\n\n function _buildDomainSeparator(\n bytes32 typeHash,\n bytes32 nameHash,\n bytes32 versionHash\n ) private view returns (bytes32) {\n return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)\n\n/**\n * @title Counters\n * @author Matt Condon (@shrugs)\n * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number\n * of elements in a mapping, issuing ERC721 ids, or counting request ids.\n *\n * Include with `using Counters for Counters.Counter;`\n */\nlibrary Counters {\n struct Counter {\n // This variable should never be directly accessed by users of the library: interactions must be restricted to\n // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add\n // this feature: see https://github.com/ethereum/solidity/issues/4637\n uint256 _value; // default: 0\n }\n\n function current(Counter storage counter) internal view returns (uint256) {\n return counter._value;\n }\n\n function increment(Counter storage counter) internal {\n unchecked {\n counter._value += 1;\n }\n }\n\n function decrement(Counter storage counter) internal {\n uint256 value = counter._value;\n require(value > 0, \"Counter: decrement overflow\");\n unchecked {\n counter._value = value - 1;\n }\n }\n\n function reset(Counter storage counter) internal {\n counter._value = 0;\n }\n}\n\n/**\n * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * _Available since v3.4._\n */\nabstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {\n using Counters for Counters.Counter;\n\n mapping(address => Counters.Counter) private _nonces;\n\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private constant _PERMIT_TYPEHASH =\n keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\n /**\n * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.\n * However, to ensure consistency with the upgradeable transpiler, we will continue\n * to reserve a slot.\n * @custom:oz-renamed-from _PERMIT_TYPEHASH\n */\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;\n\n /**\n * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `\"1\"`.\n *\n * It's a good idea to use the same `name` that is defined as the ERC20 token name.\n */\n constructor(string memory name) EIP712(name, \"1\") {}\n\n /**\n * @dev See {IERC20Permit-permit}.\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual override {\n require(block.timestamp <= deadline, \"ERC20Permit: expired deadline\");\n\n bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));\n\n bytes32 hash = _hashTypedDataV4(structHash);\n\n address signer = ECDSA.recover(hash, v, r, s);\n require(signer == owner, \"ERC20Permit: invalid signature\");\n\n _approve(owner, spender, value);\n }\n\n /**\n * @dev See {IERC20Permit-nonces}.\n */\n function nonces(address owner) public view virtual override returns (uint256) {\n return _nonces[owner].current();\n }\n\n /**\n * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view override returns (bytes32) {\n return _domainSeparatorV4();\n }\n\n /**\n * @dev \"Consume a nonce\": return the current value and increment.\n *\n * _Available since v4.1._\n */\n function _useNonce(address owner) internal virtual returns (uint256 current) {\n Counters.Counter storage nonce = _nonces[owner];\n current = nonce.current();\n nonce.increment();\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20FlashMint.sol)\n\n/**\n * @dev Implementation of the ERC3156 Flash loans extension, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * Adds the {flashLoan} method, which provides flash loan support at the token\n * level. By default there is no fee, but this can be changed by overriding {flashFee}.\n *\n * _Available since v4.1._\n */\nabstract contract ERC20FlashMint is ERC20, IERC3156FlashLender {\n bytes32 private constant _RETURN_VALUE = keccak256(\"ERC3156FlashBorrower.onFlashLoan\");\n\n /**\n * @dev Returns the maximum amount of tokens available for loan.\n * @param token The address of the token that is requested.\n * @return The amount of token that can be loaned.\n */\n function maxFlashLoan(address token) public view virtual override returns (uint256) {\n return token == address(this) ? type(uint256).max - ERC20.totalSupply() : 0;\n }\n\n /**\n * @dev Returns the fee applied when doing flash loans. This function calls\n * the {_flashFee} function which returns the fee applied when doing flash\n * loans.\n * @param token The token to be flash loaned.\n * @param amount The amount of tokens to be loaned.\n * @return The fees applied to the corresponding flash loan.\n */\n function flashFee(address token, uint256 amount) public view virtual override returns (uint256) {\n require(token == address(this), \"ERC20FlashMint: wrong token\");\n return _flashFee(token, amount);\n }\n\n /**\n * @dev Returns the fee applied when doing flash loans. By default this\n * implementation has 0 fees. This function can be overloaded to make\n * the flash loan mechanism deflationary.\n * @param token The token to be flash loaned.\n * @param amount The amount of tokens to be loaned.\n * @return The fees applied to the corresponding flash loan.\n */\n function _flashFee(address token, uint256 amount) internal view virtual returns (uint256) {\n // silence warning about unused variable without the addition of bytecode.\n token;\n amount;\n return 0;\n }\n\n /**\n * @dev Returns the receiver address of the flash fee. By default this\n * implementation returns the address(0) which means the fee amount will be burnt.\n * This function can be overloaded to change the fee receiver.\n * @return The address for which the flash fee will be sent to.\n */\n function _flashFeeReceiver() internal view virtual returns (address) {\n return address(0);\n }\n\n /**\n * @dev Performs a flash loan. New tokens are minted and sent to the\n * `receiver`, who is required to implement the {IERC3156FlashBorrower}\n * interface. By the end of the flash loan, the receiver is expected to own\n * amount + fee tokens and have them approved back to the token contract itself so\n * they can be burned.\n * @param receiver The receiver of the flash loan. Should implement the\n * {IERC3156FlashBorrower-onFlashLoan} interface.\n * @param token The token to be flash loaned. Only `address(this)` is\n * supported.\n * @param amount The amount of tokens to be loaned.\n * @param data An arbitrary datafield that is passed to the receiver.\n * @return `true` if the flash loan was successful.\n */\n // This function can reenter, but it doesn't pose a risk because it always preserves the property that the amount\n // minted at the beginning is always recovered and burned at the end, or else the entire function will revert.\n // slither-disable-next-line reentrancy-no-eth\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n ) public virtual override returns (bool) {\n require(amount <= maxFlashLoan(token), \"ERC20FlashMint: amount exceeds maxFlashLoan\");\n uint256 fee = flashFee(token, amount);\n _mint(address(receiver), amount);\n require(\n receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,\n \"ERC20FlashMint: invalid return value\"\n );\n address flashFeeReceiver = _flashFeeReceiver();\n _spendAllowance(address(receiver), address(this), amount + fee);\n if (fee == 0 || flashFeeReceiver == address(0)) {\n _burn(address(receiver), amount + fee);\n } else {\n _burn(address(receiver), amount);\n _transfer(address(receiver), flashFeeReceiver, fee);\n }\n return true;\n }\n}\n\ncontract RToken is ReentrancyGuard, ERC20Permit, ERC20FlashMint, PositionManagerDependent, FeeCollector, IRToken {\n // --- Constants ---\n\n uint256 public constant override PERCENTAGE_BASE = 10_000;\n uint256 public constant override MAX_FLASH_MINT_FEE_PERCENTAGE = 500;\n\n // --- Variables ---\n\n uint256 public override flashMintFeePercentage;\n\n // --- Constructor ---\n\n /// @dev Deploys new R token. Sets flash mint fee percentage to 0. Transfers ownership to @param feeRecipient_.\n /// @param positionManager_ Address of the PositionManager contract that is authorized to mint and burn new tokens.\n /// @param feeRecipient_ Address of flash mint fee recipient.\n constructor(\n address positionManager_,\n address feeRecipient_\n )\n ERC20Permit(\"R Stablecoin\")\n ERC20(\"R Stablecoin\", \"R\")\n PositionManagerDependent(positionManager_)\n FeeCollector(feeRecipient_)\n {\n setFlashMintFeePercentage(PERCENTAGE_BASE / 200); // 0.5%\n\n transferOwnership(feeRecipient_);\n\n emit RDeployed(positionManager_, feeRecipient_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) external override onlyPositionManager {\n _mint(to, amount);\n }\n\n function burn(address from, uint256 amount) external override onlyPositionManager {\n _burn(from, amount);\n }\n\n function setFlashMintFeePercentage(uint256 feePercentage) public override onlyOwner {\n if (feePercentage > MAX_FLASH_MINT_FEE_PERCENTAGE) {\n revert FlashFeePercentageTooBig(feePercentage);\n }\n\n flashMintFeePercentage = feePercentage;\n emit FlashMintFeePercentageChanged(flashMintFeePercentage);\n }\n\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n )\n public\n override(ERC20FlashMint, IERC3156FlashLender)\n nonReentrant\n returns (bool)\n {\n return super.flashLoan(receiver, token, amount, data);\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines maximum size of the flash mint.\n /// @param token Token to be flash minted. Returns 0 amount in case of token != address(this).\n function maxFlashLoan(address token)\n public\n view\n virtual\n override(ERC20FlashMint, IERC3156FlashLender)\n returns (uint256)\n {\n return token == address(this) ? Math.min(totalSupply() / 10, ERC20FlashMint.maxFlashLoan(address(this))) : 0;\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines flash mint fee for the flash mint of @param amount tokens.\n /// @param token Token to be flash minted. Returns 0 fee in case of token != address(this).\n /// @param amount Size of the flash mint.\n function _flashFee(address token, uint256 amount) internal view virtual override returns (uint256) {\n return token == address(this) ? amount * flashMintFeePercentage / PERCENTAGE_BASE : 0;\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines flash mint fee receiver.\n /// @return Address that will receive flash mint fees.\n function _flashFeeReceiver() internal view virtual override returns (address) {\n return feeRecipient;\n }\n}\n\n/// @dev Implementation of Position Manager. Current implementation does not support rebasing tokens as collateral.\ncontract PositionManager is FeeCollector, IPositionManager {\n // --- Types ---\n\n using SafeERC20 for IERC20;\n using Fixed256x18 for uint256;\n\n // --- Constants ---\n\n uint256 public constant override MINUTE_DECAY_FACTOR = 999_037_758_833_783_000;\n\n uint256 public constant override MAX_BORROWING_SPREAD = MathUtils._100_PERCENT / 100; // 1%\n uint256 public constant override MAX_BORROWING_RATE = MathUtils._100_PERCENT / 100 * 5; // 5%\n\n uint256 public constant override BETA = 2;\n\n // --- Immutables ---\n\n IRToken public immutable override rToken;\n\n // --- Variables ---\n\n mapping(address position => IERC20 collateralToken) public override collateralTokenForPosition;\n\n mapping(address position => mapping(address delegate => bool isWhitelisted)) public override isDelegateWhitelisted;\n\n mapping(IERC20 collateralToken => CollateralTokenInfo collateralTokenInfo) public override collateralInfo;\n\n // --- Modifiers ---\n\n /// @dev Checks if the collateral token has been added to the position manager, or reverts otherwise.\n /// @param collateralToken The collateral token to check.\n modifier collateralTokenExists(IERC20 collateralToken) {\n if (address(collateralInfo[collateralToken].collateralToken) == address(0)) {\n revert CollateralTokenNotAdded();\n }\n _;\n }\n\n /// @dev Checks if the collateral token has enabled, or reverts otherwise. When the condition is false, the check\n /// is skipped.\n /// @param collateralToken The collateral token to check.\n /// @param condition If true, the check will be performed.\n modifier onlyEnabledCollateralTokenWhen(IERC20 collateralToken, bool condition) {\n if (condition && !collateralInfo[collateralToken].isEnabled) {\n revert CollateralTokenDisabled();\n }\n _;\n }\n\n /// @dev Checks if the borrower has a position with the collateral token or doesn't have a position at all, or\n /// reverts otherwise.\n /// @param position The borrower to check.\n /// @param collateralToken The collateral token to check.\n modifier onlyDepositedCollateralTokenOrNew(address position, IERC20 collateralToken) {\n if (\n collateralTokenForPosition[position] != IERC20(address(0))\n && collateralTokenForPosition[position] != collateralToken\n ) {\n revert PositionCollateralTokenMismatch();\n }\n _;\n }\n\n /// @dev Checks if the max fee percentage is between the borrowing spread and 100%, or reverts otherwise. When the\n /// condition is false, the check is skipped.\n /// @param maxFeePercentage The max fee percentage to check.\n /// @param condition If true, the check will be performed.\n modifier validMaxFeePercentageWhen(uint256 maxFeePercentage, bool condition) {\n if (condition && maxFeePercentage > MathUtils._100_PERCENT) {\n revert InvalidMaxFeePercentage();\n }\n _;\n }\n\n // --- Constructor ---\n\n /// @dev Initializes the position manager.\n constructor(address rToken_) FeeCollector(msg.sender) {\n rToken = rToken_ == address(0) ? new RToken(address(this), msg.sender) : IRToken(rToken_);\n emit PositionManagerDeployed(rToken, msg.sender);\n }\n\n // --- External functions ---\n\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n public\n virtual\n override\n collateralTokenExists(collateralToken)\n validMaxFeePercentageWhen(maxFeePercentage, isDebtIncrease)\n onlyDepositedCollateralTokenOrNew(position, collateralToken)\n onlyEnabledCollateralTokenWhen(collateralToken, isDebtIncrease && debtChange > 0)\n returns (uint256 actualCollateralChange, uint256 actualDebtChange)\n {\n if (position != msg.sender && !isDelegateWhitelisted[position][msg.sender]) {\n revert DelegateNotWhitelisted();\n }\n if (collateralChange == 0 && debtChange == 0) {\n revert NoCollateralOrDebtChange();\n }\n if (address(permitSignature.token) == address(collateralToken)) {\n PermitHelper.applyPermit(permitSignature, msg.sender, address(this));\n }\n\n CollateralTokenInfo storage collateralTokenInfo = collateralInfo[collateralToken];\n IERC20Indexable raftCollateralToken = collateralTokenInfo.collateralToken;\n IERC20Indexable raftDebtToken = collateralTokenInfo.debtToken;\n\n uint256 debtBefore = raftDebtToken.balanceOf(position);\n if (!isDebtIncrease && (debtChange == type(uint256).max || (debtBefore != 0 && debtChange == debtBefore))) {\n if (collateralChange != 0 || isCollateralIncrease) {\n revert WrongCollateralParamsForFullRepayment();\n }\n collateralChange = raftCollateralToken.balanceOf(position);\n debtChange = debtBefore;\n }\n\n _adjustDebt(position, collateralToken, raftDebtToken, debtChange, isDebtIncrease, maxFeePercentage);\n _adjustCollateral(collateralToken, raftCollateralToken, position, collateralChange, isCollateralIncrease);\n\n uint256 positionDebt = raftDebtToken.balanceOf(position);\n uint256 positionCollateral = raftCollateralToken.balanceOf(position);\n\n if (positionDebt == 0) {\n if (positionCollateral != 0) {\n revert InvalidPosition();\n }\n // position was closed, remove it\n _closePosition(raftCollateralToken, raftDebtToken, position, false);\n } else {\n _checkValidPosition(collateralToken, positionDebt, positionCollateral);\n\n if (debtBefore == 0) {\n collateralTokenForPosition[position] = collateralToken;\n emit PositionCreated(position, collateralToken);\n }\n }\n return (collateralChange, debtChange);\n }\n\n function liquidate(address position) external override {\n IERC20 collateralToken = collateralTokenForPosition[position];\n CollateralTokenInfo storage collateralTokenInfo = collateralInfo[collateralToken];\n IERC20Indexable raftCollateralToken = collateralTokenInfo.collateralToken;\n IERC20Indexable raftDebtToken = collateralTokenInfo.debtToken;\n ISplitLiquidationCollateral splitLiquidation = collateralTokenInfo.splitLiquidation;\n\n if (address(collateralToken) == address(0)) {\n revert NothingToLiquidate();\n }\n (uint256 price,) = collateralTokenInfo.priceFeed.fetchPrice();\n uint256 entireCollateral = raftCollateralToken.balanceOf(position);\n uint256 entireDebt = raftDebtToken.balanceOf(position);\n uint256 icr = MathUtils._computeCR(entireCollateral, entireDebt, price);\n\n if (icr >= splitLiquidation.MCR()) {\n revert NothingToLiquidate();\n }\n\n uint256 totalDebt = raftDebtToken.totalSupply();\n if (entireDebt == totalDebt) {\n revert CannotLiquidateLastPosition();\n }\n bool isRedistribution = icr <= MathUtils._100_PERCENT;\n\n // prettier: ignore\n (uint256 collateralLiquidationFee, uint256 collateralToSendToLiquidator) =\n splitLiquidation.split(entireCollateral, entireDebt, price, isRedistribution);\n\n if (!isRedistribution) {\n _burnRTokens(msg.sender, entireDebt);\n totalDebt -= entireDebt;\n\n // Collateral is sent to protocol as a fee only in case of liquidation\n collateralToken.safeTransfer(feeRecipient, collateralLiquidationFee);\n }\n\n collateralToken.safeTransfer(msg.sender, collateralToSendToLiquidator);\n\n _closePosition(raftCollateralToken, raftDebtToken, position, true);\n\n _updateDebtAndCollateralIndex(collateralToken, raftCollateralToken, raftDebtToken, totalDebt);\n\n emit Liquidation(\n msg.sender,\n position,\n collateralToken,\n entireDebt,\n entireCollateral,\n collateralToSendToLiquidator,\n collateralLiquidationFee,\n isRedistribution\n );\n }\n\n function redeemCollateral(\n IERC20 collateralToken,\n uint256 debtAmount,\n uint256 maxFeePercentage\n )\n public\n virtual\n override\n {\n if (maxFeePercentage > MathUtils._100_PERCENT) {\n revert MaxFeePercentageOutOfRange();\n }\n if (debtAmount == 0) {\n revert AmountIsZero();\n }\n IERC20Indexable raftDebtToken = collateralInfo[collateralToken].debtToken;\n\n uint256 newTotalDebt = raftDebtToken.totalSupply() - debtAmount;\n uint256 lowTotalDebt = collateralInfo[collateralToken].splitLiquidation.LOW_TOTAL_DEBT();\n if (newTotalDebt < lowTotalDebt) {\n revert TotalDebtCannotBeLowerThanMinDebt(collateralToken, newTotalDebt);\n }\n\n (uint256 price, uint256 deviation) = collateralInfo[collateralToken].priceFeed.fetchPrice();\n uint256 collateralToRedeem = debtAmount.divDown(price);\n uint256 totalCollateral = collateralToken.balanceOf(address(this));\n if (\n totalCollateral - collateralToRedeem == 0\n || totalCollateral - collateralToRedeem < lowTotalDebt.divDown(price)\n ) {\n revert TotalCollateralCannotBeLowerThanMinCollateral(\n collateralToken, totalCollateral - collateralToRedeem, lowTotalDebt.divDown(price)\n );\n }\n\n // Decay the baseRate due to time passed, and then increase it according to the size of this redemption.\n // Use the saved total R supply value, from before it was reduced by the redemption.\n _updateBaseRateFromRedemption(collateralToken, collateralToRedeem, price, rToken.totalSupply());\n\n // Calculate the redemption fee\n uint256 redemptionFee = getRedemptionFee(collateralToken, collateralToRedeem, deviation);\n uint256 rebate = redemptionFee.mulDown(collateralInfo[collateralToken].redemptionRebate);\n\n _checkValidFee(redemptionFee, collateralToRedeem, maxFeePercentage);\n\n // Send the redemption fee to the recipient\n collateralToken.safeTransfer(feeRecipient, redemptionFee - rebate);\n\n // Burn the total R that is cancelled with debt, and send the redeemed collateral to msg.sender\n _burnRTokens(msg.sender, debtAmount);\n\n // Send collateral to account\n collateralToken.safeTransfer(msg.sender, collateralToRedeem - redemptionFee);\n\n _updateDebtAndCollateralIndex(\n collateralToken, collateralInfo[collateralToken].collateralToken, raftDebtToken, newTotalDebt\n );\n\n emit Redemption(msg.sender, debtAmount, collateralToRedeem, redemptionFee, rebate);\n }\n\n function whitelistDelegate(address delegate, bool whitelisted) external override {\n if (delegate == address(0)) {\n revert InvalidDelegateAddress();\n }\n isDelegateWhitelisted[msg.sender][delegate] = whitelisted;\n\n emit DelegateWhitelisted(msg.sender, delegate, whitelisted);\n }\n\n function setBorrowingSpread(IERC20 collateralToken, uint256 newBorrowingSpread) external override onlyOwner {\n if (newBorrowingSpread > MAX_BORROWING_SPREAD) {\n revert BorrowingSpreadExceedsMaximum();\n }\n collateralInfo[collateralToken].borrowingSpread = newBorrowingSpread;\n emit BorrowingSpreadUpdated(newBorrowingSpread);\n }\n\n function setRedemptionRebate(IERC20 collateralToken, uint256 newRedemptionRebate) public override onlyOwner {\n if (newRedemptionRebate > MathUtils._100_PERCENT) {\n revert RedemptionRebateExceedsMaximum();\n }\n collateralInfo[collateralToken].redemptionRebate = newRedemptionRebate;\n emit RedemptionRebateUpdated(newRedemptionRebate);\n }\n\n function getRedemptionFeeWithDecay(\n IERC20 collateralToken,\n uint256 collateralAmount\n )\n external\n view\n override\n returns (uint256 redemptionFee)\n {\n redemptionFee = getRedemptionRateWithDecay(collateralToken).mulDown(collateralAmount);\n if (redemptionFee >= collateralAmount) {\n revert FeeEatsUpAllReturnedCollateral();\n }\n }\n\n // --- Public functions ---\n\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n public\n virtual\n override\n {\n addCollateralToken(\n collateralToken,\n priceFeed,\n newSplitLiquidationCollateral,\n new ERC20Indexable(\n address(this),\n string(bytes.concat(\"Raft \", bytes(IERC20Metadata(address(collateralToken)).name()), \" collateral\")),\n string(bytes.concat(\"r\", bytes(IERC20Metadata(address(collateralToken)).symbol()), \"-c\")),\n type(uint256).max\n ),\n new ERC20Indexable(\n address(this),\n string(bytes.concat(\"Raft \", bytes(IERC20Metadata(address(collateralToken)).name()), \" debt\")),\n string(bytes.concat(\"r\", bytes(IERC20Metadata(address(collateralToken)).symbol()), \"-d\")),\n type(uint256).max\n )\n );\n }\n\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral,\n IERC20Indexable raftCollateralToken_,\n IERC20Indexable raftDebtToken_\n )\n public\n override\n onlyOwner\n {\n if (address(collateralToken) == address(0)) {\n revert CollateralTokenAddressCannotBeZero();\n }\n if (address(priceFeed) == address(0)) {\n revert PriceFeedAddressCannotBeZero();\n }\n if (address(collateralInfo[collateralToken].collateralToken) != address(0)) {\n revert CollateralTokenAlreadyAdded();\n }\n\n CollateralTokenInfo memory raftCollateralTokenInfo;\n raftCollateralTokenInfo.collateralToken = raftCollateralToken_;\n raftCollateralTokenInfo.debtToken = raftDebtToken_;\n raftCollateralTokenInfo.isEnabled = true;\n raftCollateralTokenInfo.priceFeed = priceFeed;\n\n collateralInfo[collateralToken] = raftCollateralTokenInfo;\n\n setRedemptionSpread(collateralToken, MathUtils._100_PERCENT);\n setRedemptionRebate(collateralToken, MathUtils._100_PERCENT);\n\n setSplitLiquidationCollateral(collateralToken, newSplitLiquidationCollateral);\n\n emit CollateralTokenAdded(\n collateralToken, raftCollateralTokenInfo.collateralToken, raftCollateralTokenInfo.debtToken, priceFeed\n );\n }\n\n function setCollateralEnabled(\n IERC20 collateralToken,\n bool isEnabled\n )\n public\n override\n onlyOwner\n collateralTokenExists(collateralToken)\n {\n bool previousIsEnabled = collateralInfo[collateralToken].isEnabled;\n collateralInfo[collateralToken].isEnabled = isEnabled;\n\n if (previousIsEnabled != isEnabled) {\n emit CollateralTokenModified(collateralToken, isEnabled);\n }\n }\n\n function setSplitLiquidationCollateral(\n IERC20 collateralToken,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n public\n override\n onlyOwner\n {\n if (address(newSplitLiquidationCollateral) == address(0)) {\n revert SplitLiquidationCollateralCannotBeZero();\n }\n collateralInfo[collateralToken].splitLiquidation = newSplitLiquidationCollateral;\n emit SplitLiquidationCollateralChanged(collateralToken, newSplitLiquidationCollateral);\n }\n\n function setRedemptionSpread(IERC20 collateralToken, uint256 newRedemptionSpread) public override onlyOwner {\n if (newRedemptionSpread > MathUtils._100_PERCENT) {\n revert RedemptionSpreadOutOfRange();\n }\n collateralInfo[collateralToken].redemptionSpread = newRedemptionSpread;\n emit RedemptionSpreadUpdated(collateralToken, newRedemptionSpread);\n }\n\n function getRedemptionRateWithDecay(IERC20 collateralToken) public view override returns (uint256) {\n return _calcRedemptionRate(collateralToken, _calcDecayedBaseRate(collateralToken));\n }\n\n function raftCollateralToken(IERC20 collateralToken) external view override returns (IERC20Indexable) {\n return collateralInfo[collateralToken].collateralToken;\n }\n\n function raftDebtToken(IERC20 collateralToken) external view override returns (IERC20Indexable) {\n return collateralInfo[collateralToken].debtToken;\n }\n\n function priceFeed(IERC20 collateralToken) external view override returns (IPriceFeed) {\n return collateralInfo[collateralToken].priceFeed;\n }\n\n function splitLiquidationCollateral(IERC20 collateralToken) external view returns (ISplitLiquidationCollateral) {\n return collateralInfo[collateralToken].splitLiquidation;\n }\n\n function collateralEnabled(IERC20 collateralToken) external view override returns (bool) {\n return collateralInfo[collateralToken].isEnabled;\n }\n\n function lastFeeOperationTime(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].lastFeeOperationTime;\n }\n\n function borrowingSpread(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].borrowingSpread;\n }\n\n function baseRate(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].baseRate;\n }\n\n function redemptionSpread(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].redemptionSpread;\n }\n\n function redemptionRebate(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].redemptionRebate;\n }\n\n function getRedemptionRate(IERC20 collateralToken) public view override returns (uint256) {\n return _calcRedemptionRate(collateralToken, collateralInfo[collateralToken].baseRate);\n }\n\n function getRedemptionFee(\n IERC20 collateralToken,\n uint256 collateralAmount,\n uint256 priceDeviation\n )\n public\n view\n override\n returns (uint256)\n {\n return Math.min(getRedemptionRate(collateralToken) + priceDeviation, MathUtils._100_PERCENT).mulDown(\n collateralAmount\n );\n }\n\n function getBorrowingRate(IERC20 collateralToken) public view override returns (uint256) {\n return _calcBorrowingRate(collateralToken, collateralInfo[collateralToken].baseRate);\n }\n\n function getBorrowingRateWithDecay(IERC20 collateralToken) public view override returns (uint256) {\n return _calcBorrowingRate(collateralToken, _calcDecayedBaseRate(collateralToken));\n }\n\n function getBorrowingFee(IERC20 collateralToken, uint256 debtAmount) public view override returns (uint256) {\n return getBorrowingRate(collateralToken).mulDown(debtAmount);\n }\n\n // --- Helper functions ---\n\n /// @dev Adjusts the debt of a given borrower by burning or minting the corresponding amount of R and the Raft\n /// debt token. If the debt is being increased, the borrowing fee is also triggered.\n /// @param position The address of the borrower.\n /// @param debtChange The amount of R to add or remove. Must be positive.\n /// @param isDebtIncrease True if the debt is being increased, false otherwise.\n /// @param maxFeePercentage The maximum fee percentage.\n function _adjustDebt(\n address position,\n IERC20 collateralToken,\n IERC20Indexable raftDebtToken,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage\n )\n internal\n {\n if (debtChange == 0) {\n return;\n }\n\n if (isDebtIncrease) {\n uint256 totalDebtChange =\n debtChange + _triggerBorrowingFee(collateralToken, position, debtChange, maxFeePercentage);\n raftDebtToken.mint(position, totalDebtChange);\n _mintRTokens(msg.sender, debtChange);\n } else {\n raftDebtToken.burn(position, debtChange);\n _burnRTokens(msg.sender, debtChange);\n }\n\n emit DebtChanged(position, collateralToken, debtChange, isDebtIncrease);\n }\n\n /// @dev Mints R tokens\n function _mintRTokens(address to, uint256 amount) internal virtual {\n rToken.mint(to, amount);\n }\n\n /// @dev Burns R tokens\n function _burnRTokens(address from, uint256 amount) internal virtual {\n rToken.burn(from, amount);\n }\n\n /// @dev Adjusts the collateral of a given borrower by burning or minting the corresponding amount of Raft\n /// collateral token and transferring the corresponding amount of collateral token.\n /// @param collateralToken The token the borrower used as collateral.\n /// @param position The address of the borrower.\n /// @param collateralChange The amount of collateral to add or remove. Must be positive.\n /// @param isCollateralIncrease True if the collateral is being increased, false otherwise.\n function _adjustCollateral(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease\n )\n internal\n {\n if (collateralChange == 0) {\n return;\n }\n\n if (isCollateralIncrease) {\n raftCollateralToken.mint(position, collateralChange);\n collateralToken.safeTransferFrom(msg.sender, address(this), collateralChange);\n } else {\n raftCollateralToken.burn(position, collateralChange);\n collateralToken.safeTransfer(msg.sender, collateralChange);\n }\n\n emit CollateralChanged(position, collateralToken, collateralChange, isCollateralIncrease);\n }\n\n /// @dev Updates debt and collateral indexes for a given collateral token.\n /// @param collateralToken The collateral token for which to update the indexes.\n /// @param raftCollateralToken The raft collateral indexable token.\n /// @param raftDebtToken The raft debt indexable token.\n /// @param totalDebtForCollateral Totam amount of debt backed by collateral token.\n function _updateDebtAndCollateralIndex(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n uint256 totalDebtForCollateral\n )\n internal\n {\n raftDebtToken.setIndex(totalDebtForCollateral);\n raftCollateralToken.setIndex(collateralToken.balanceOf(address(this)));\n }\n\n function _closePosition(\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n address position,\n bool burnTokens\n )\n internal\n {\n collateralTokenForPosition[position] = IERC20(address(0));\n\n if (burnTokens) {\n raftDebtToken.burn(position, type(uint256).max);\n raftCollateralToken.burn(position, type(uint256).max);\n }\n emit PositionClosed(position);\n }\n\n // --- Borrowing & redemption fee helper functions ---\n\n /// @dev Updates the base rate from a redemption operation. Impacts on the base rate:\n /// 1. decays the base rate based on time passed since last redemption or R borrowing operation,\n /// 2. increases the base rate based on the amount redeemed, as a proportion of total supply.\n function _updateBaseRateFromRedemption(\n IERC20 collateralToken,\n uint256 collateralDrawn,\n uint256 price,\n uint256 totalDebtSupply\n )\n internal\n returns (uint256)\n {\n uint256 decayedBaseRate = _calcDecayedBaseRate(collateralToken);\n\n /* Convert the drawn collateral back to R at face value rate (1 R:1 USD), in order to get\n * the fraction of total supply that was redeemed at face value. */\n uint256 redeemedFraction = collateralDrawn * price / totalDebtSupply;\n\n uint256 newBaseRate = decayedBaseRate + redeemedFraction / BETA;\n newBaseRate = Math.min(newBaseRate, MathUtils._100_PERCENT); // cap baseRate at a maximum of 100%\n assert(newBaseRate > 0); // Base rate is always non-zero after redemption\n\n // Update the baseRate state variable\n collateralInfo[collateralToken].baseRate = newBaseRate;\n emit BaseRateUpdated(collateralToken, newBaseRate);\n\n _updateLastFeeOpTime(collateralToken);\n\n return newBaseRate;\n }\n\n function _calcRedemptionRate(IERC20 collateralToken, uint256 baseRate_) internal view returns (uint256) {\n return baseRate_ + collateralInfo[collateralToken].redemptionSpread;\n }\n\n function _calcBorrowingRate(IERC20 collateralToken, uint256 baseRate_) internal view returns (uint256) {\n return Math.min(collateralInfo[collateralToken].borrowingSpread + baseRate_, MAX_BORROWING_RATE);\n }\n\n /// @dev Updates the base rate based on time elapsed since the last redemption or R borrowing operation.\n function _decayBaseRateFromBorrowing(IERC20 collateralToken) internal {\n uint256 decayedBaseRate = _calcDecayedBaseRate(collateralToken);\n assert(decayedBaseRate <= MathUtils._100_PERCENT); // The baseRate can decay to 0\n\n collateralInfo[collateralToken].baseRate = decayedBaseRate;\n emit BaseRateUpdated(collateralToken, decayedBaseRate);\n\n _updateLastFeeOpTime(collateralToken);\n }\n\n /// @dev Update the last fee operation time only if time passed >= decay interval. This prevents base rate\n /// griefing.\n function _updateLastFeeOpTime(IERC20 collateralToken) internal {\n uint256 timePassed = block.timestamp - collateralInfo[collateralToken].lastFeeOperationTime;\n\n if (timePassed >= 1 minutes) {\n collateralInfo[collateralToken].lastFeeOperationTime = block.timestamp;\n emit LastFeeOpTimeUpdated(collateralToken, block.timestamp);\n }\n }\n\n function _calcDecayedBaseRate(IERC20 collateralToken) internal view returns (uint256) {\n uint256 minutesPassed = (block.timestamp - collateralInfo[collateralToken].lastFeeOperationTime) / 1 minutes;\n uint256 decayFactor = MathUtils._decPow(MINUTE_DECAY_FACTOR, minutesPassed);\n\n return collateralInfo[collateralToken].baseRate.mulDown(decayFactor);\n }\n\n function _triggerBorrowingFee(\n IERC20 collateralToken,\n address position,\n uint256 debtAmount,\n uint256 maxFeePercentage\n )\n internal\n virtual\n returns (uint256 borrowingFee)\n {\n _decayBaseRateFromBorrowing(collateralToken); // decay the baseRate state variable\n borrowingFee = getBorrowingFee(collateralToken, debtAmount);\n\n _checkValidFee(borrowingFee, debtAmount, maxFeePercentage);\n\n if (borrowingFee > 0) {\n _mintRTokens(feeRecipient, borrowingFee);\n emit RBorrowingFeePaid(collateralToken, position, borrowingFee);\n }\n }\n\n // --- Validation check helper functions ---\n\n function _checkValidPosition(IERC20 collateralToken, uint256 positionDebt, uint256 positionCollateral) internal {\n ISplitLiquidationCollateral splitCollateral = collateralInfo[collateralToken].splitLiquidation;\n if (positionDebt < splitCollateral.LOW_TOTAL_DEBT()) {\n revert NetDebtBelowMinimum(positionDebt);\n }\n\n (uint256 price,) = collateralInfo[collateralToken].priceFeed.fetchPrice();\n uint256 newICR = MathUtils._computeCR(positionCollateral, positionDebt, price);\n if (newICR < splitCollateral.MCR()) {\n revert NewICRLowerThanMCR(newICR);\n }\n }\n\n function _checkValidFee(uint256 fee, uint256 amount, uint256 maxFeePercentage) internal pure {\n uint256 feePercentage = fee.divDown(amount);\n\n if (feePercentage > maxFeePercentage) {\n revert FeeExceedsMaxFee(fee, amount, maxFeePercentage);\n }\n }\n}\n\ninterface IRMinter {\n /// @dev Emitted when tokens are recovered from the contract.\n /// @param token The address of the token being recovered.\n /// @param to The address receiving the recovered tokens.\n /// @param amount The amount of tokens recovered.\n event TokensRecovered(IERC20 token, address to, uint256 amount);\n\n /// @return Address of the R token.\n function r() external view returns (IRToken);\n\n /// @return Address of the Position manager contract responsible for minting R.\n function positionManager() external view returns (IPositionManager);\n\n /// @dev Recover accidentally sent tokens to the contract\n /// @param token Address of the token contract.\n /// @param to Address of the receiver of the tokens.\n /// @param amount Number of tokens to recover.\n function recoverTokens(IERC20 token, address to, uint256 amount) external;\n}\n\ninterface ILock {\n /// @dev Thrown when contract usage is locked.\n error ContractLocked();\n\n /// @dev Unauthorized call to lock/unlock.\n error Unauthorized();\n\n /// @dev Retrieves if contract is currently locked or not.\n function locked() external view returns (bool);\n\n /// @dev Retrieves address of the locker who can unlock contract.\n function locker() external view returns (address);\n\n /// @dev Unlcoks the usage of the contract.\n function unlock() external;\n\n /// @dev Locks the usage of the contract.\n function lock() external;\n}\n\nabstract contract ERC20RMinter is IRMinter, ERC20, Ownable2Step {\n using SafeERC20 for IERC20;\n\n IRToken public immutable override r;\n IPositionManager public immutable override positionManager;\n\n constructor(IRToken rToken_, string memory name_, string memory symbol_) ERC20(name_, symbol_) {\n r = rToken_;\n positionManager = IPositionManager(rToken_.positionManager());\n\n _approve(address(this), address(positionManager), type(uint256).max);\n }\n\n modifier unlockCall() {\n ILock lockContract = ILock(address(positionManager.priceFeed(IERC20(this))));\n lockContract.unlock();\n _;\n lockContract.lock();\n }\n\n function recoverTokens(IERC20 token, address to, uint256 amount) external override onlyOwner {\n token.safeTransfer(to, amount);\n emit TokensRecovered(token, to, amount);\n }\n\n function _mintR(address to, uint256 amount) internal unlockCall {\n _mint(address(this), amount);\n ERC20PermitSignature memory emptySignature;\n positionManager.managePosition(\n IERC20(address(this)),\n address(this),\n amount,\n true, // collateral increase\n amount,\n true, // debt increase\n 1e18, // 100%\n emptySignature\n );\n r.transfer(to, amount);\n }\n\n function _burnR(address from, uint256 amount) internal unlockCall {\n r.transferFrom(from, address(this), amount);\n ERC20PermitSignature memory emptySignature;\n positionManager.managePosition(\n IERC20(address(this)),\n address(this),\n amount,\n false, // collateral decrease\n amount,\n false, // debt decrease\n 1e18, // 100%\n emptySignature\n );\n _burn(address(this), amount);\n }\n}\n\ncontract InterestRateDebtToken is ERC20Indexable {\n // --- Types ---\n\n using Fixed256x18 for uint256;\n\n // --- Events ---\n\n event IndexIncreasePerSecondSet(uint256 indexIncreasePerSecond);\n\n // --- Immutables ---\n\n IERC20 immutable collateralToken;\n\n // --- Variables ---\n\n uint256 internal storedIndexUpdatedAt;\n\n uint256 public indexIncreasePerSecond;\n\n // --- Constructor ---\n\n constructor(\n address positionManager_,\n string memory name_,\n string memory symbol_,\n IERC20 collateralToken_,\n uint256 cap_,\n uint256 indexIncreasePerSecond_\n )\n ERC20Indexable(positionManager_, name_, symbol_, cap_)\n {\n storedIndexUpdatedAt = block.timestamp;\n collateralToken = collateralToken_;\n setIndexIncreasePerSecond(indexIncreasePerSecond_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) public virtual override {\n updateIndexAndPayFees();\n super.mint(to, amount);\n }\n\n function burn(address from, uint256 amount) public virtual override {\n updateIndexAndPayFees();\n super.burn(from, amount);\n }\n\n function currentIndex() public view virtual override returns (uint256) {\n return storedIndex.mulUp(INDEX_PRECISION + indexIncreasePerSecond * (block.timestamp - storedIndexUpdatedAt));\n }\n\n function updateIndexAndPayFees() public {\n uint256 currentIndex_ = currentIndex();\n _payFees(currentIndex_);\n storedIndexUpdatedAt = block.timestamp;\n storedIndex = currentIndex_;\n emit IndexUpdated(currentIndex_);\n }\n\n function setIndexIncreasePerSecond(uint256 indexIncreasePerSecond_) public onlyOwner {\n updateIndexAndPayFees();\n indexIncreasePerSecond = indexIncreasePerSecond_;\n emit IndexIncreasePerSecondSet(indexIncreasePerSecond_);\n }\n\n function unpaidFees() external view returns (uint256) {\n return _unpaidFees(currentIndex());\n }\n\n function _unpaidFees(uint256 currentIndex_) private view returns (uint256) {\n return totalSupply().mulDown(currentIndex_ - storedIndex);\n }\n\n function _payFees(uint256 currentIndex_) private {\n uint256 unpaidFees = _unpaidFees(currentIndex_);\n if (unpaidFees > 0) {\n IInterestRatePositionManager(positionManager).mintFees(collateralToken, unpaidFees);\n }\n }\n}\n\n/// @dev Implementation of Position Manager. Current implementation does not support rebasing tokens as collateral.\ncontract InterestRatePositionManager is ERC20RMinter, PositionManager, IInterestRatePositionManager {\n // --- Errors ---\n\n error Unsupported();\n\n // --- Constructor ---\n\n /// @dev Initializes the position manager.\n constructor(IRToken rToken_)\n PositionManager(address(rToken_))\n ERC20RMinter(rToken_, \"Interest Rate Posman\", \"IRPM\")\n { }\n\n // --- External functions ---\n\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n public\n virtual\n override(IPositionManager, PositionManager)\n returns (uint256 actualCollateralChange, uint256 actualDebtChange)\n {\n if (address(permitSignature.token) == address(r)) {\n PermitHelper.applyPermit(permitSignature, msg.sender, address(this));\n }\n return super.managePosition(\n collateralToken,\n position,\n collateralChange,\n isCollateralIncrease,\n debtChange,\n isDebtIncrease,\n maxFeePercentage,\n permitSignature\n );\n }\n\n function mintFees(IERC20 collateralToken, uint256 amount) external {\n if (msg.sender != address(collateralInfo[collateralToken].debtToken)) {\n revert InvalidDebtToken(msg.sender);\n }\n _mintR(feeRecipient, amount);\n\n emit MintedFees(collateralToken, amount);\n }\n\n function redeemCollateral(IERC20, uint256, uint256) public virtual override(IPositionManager, PositionManager) {\n revert Unsupported();\n }\n\n function addCollateralToken(\n IERC20,\n IPriceFeed,\n ISplitLiquidationCollateral\n )\n public\n override(IPositionManager, PositionManager)\n {\n revert Unsupported();\n }\n\n // --- Helper functions ---\n\n function _mintRTokens(address to, uint256 amount) internal virtual override {\n _mintR(to, amount);\n }\n\n function _burnRTokens(address from, uint256 amount) internal virtual override {\n _burnR(from, amount);\n }\n\n function _triggerBorrowingFee(\n IERC20,\n address,\n uint256,\n uint256\n )\n internal\n pure\n virtual\n override\n returns (uint256)\n {\n return 0;\n }\n}\n"}},"settings":{"remappings":["@balancer-labs/=node_modules/@balancer-labs/","@balancer-labs/v2-interfaces/contracts/=lib/balancer-v2-monorepo/pkg/interfaces/contracts/","@chainlink/=node_modules/@chainlink/","@eth-optimism/=node_modules/@eth-optimism/","@openzeppelin/=node_modules/@openzeppelin/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@redstone-finance/=node_modules/@redstone-finance/","@smartcontractkit/chainlink/=lib/chainlink/contracts/src/v0.8/","@tempusfinance/=node_modules/@tempusfinance/","@tempusfinance/tempus-utils/contracts/=lib/tempus-utils/contracts/","balancer-v2-monorepo/=lib/balancer-v2-monorepo/","chainlink/=lib/chainlink/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/chainlink/contracts/foundry-lib/openzeppelin-contracts/lib/erc4626-tests/","eth-gas-reporter/=node_modules/eth-gas-reporter/","forge-std/=lib/forge-std/src/","hardhat/=node_modules/hardhat/","openzeppelin-contracts/=lib/openzeppelin-contracts/","tempus-utils/=lib/tempus-utils/contracts/"],"optimizer":{"enabled":true,"runs":200000},"metadata":{"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"evmVersion":"london","viaIR":true,"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"rToken_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AmountIsZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BorrowingSpreadExceedsMaximum\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotLiquidateLastPosition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenAlreadyAdded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenDisabled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenNotAdded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DelegateNotWhitelisted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FeeEatsUpAllReturnedCollateral\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePercentage\",\"type\":\"uint256\"}],\"name\":\"FeeExceedsMaxFee\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"InvalidDebtToken\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegateAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidFeeRecipient\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidMaxFeePercentage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPosition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MaxFeePercentageOutOfRange\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"netDebt\",\"type\":\"uint256\"}],\"name\":\"NetDebtBelowMinimum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newICR\",\"type\":\"uint256\"}],\"name\":\"NewICRLowerThanMCR\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoCollateralOrDebtChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NothingToLiquidate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PositionCollateralTokenMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PriceFeedAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RedemptionRebateExceedsMaximum\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RedemptionSpreadOutOfRange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SplitLiquidationCollateralCannotBeZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTotalCollateral\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimumCollateral\",\"type\":\"uint256\"}],\"name\":\"TotalCollateralCannotBeLowerThanMinCollateral\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTotalDebt\",\"type\":\"uint256\"}],\"name\":\"TotalDebtCannotBeLowerThanMinDebt\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unsupported\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongCollateralParamsForFullRepayment\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseRate\",\"type\":\"uint256\"}],\"name\":\"BaseRateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"borrowingSpread\",\"type\":\"uint256\"}],\"name\":\"BorrowingSpreadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCollateralIncrease\",\"type\":\"bool\"}],\"name\":\"CollateralChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftCollateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftDebtToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"}],\"name\":\"CollateralTokenAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"CollateralTokenModified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isDebtIncrease\",\"type\":\"bool\"}],\"name\":\"DebtChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"DelegateWhitelisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeRecipient\",\"type\":\"address\"}],\"name\":\"FeeRecipientChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"lastFeeOpTime\",\"type\":\"uint256\"}],\"name\":\"LastFeeOpTimeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtLiquidated\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralLiquidated\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralSentToLiquidator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralLiquidationFeePaid\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isRedistribution\",\"type\":\"bool\"}],\"name\":\"Liquidation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MintedFees\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"PositionClosed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"PositionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IRToken\",\"name\":\"rToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeRecipient\",\"type\":\"address\"}],\"name\":\"PositionManagerDeployed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeAmount\",\"type\":\"uint256\"}],\"name\":\"RBorrowingFeePaid\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"redeemer\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralSent\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rebate\",\"type\":\"uint256\"}],\"name\":\"Redemption\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"redemptionRebate\",\"type\":\"uint256\"}],\"name\":\"RedemptionRebateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"redemptionSpread\",\"type\":\"uint256\"}],\"name\":\"RedemptionSpreadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"}],\"name\":\"SplitLiquidationCollateralChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TokensRecovered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BETA\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_BORROWING_RATE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_BORROWING_SPREAD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MINUTE_DECAY_FACTOR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftCollateralToken_\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftDebtToken_\",\"type\":\"address\"}],\"name\":\"addCollateralToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"addCollateralToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"baseRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"borrowingSpread\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"collateralEnabled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"collateralInfo\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"debtToken\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"splitLiquidation\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"lastFeeOperationTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"borrowingSpread\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"baseRate\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"redemptionSpread\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"redemptionRebate\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"collateralTokenForPosition\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeRecipient\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"debtAmount\",\"type\":\"uint256\"}],\"name\":\"getBorrowingFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getBorrowingRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getBorrowingRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"priceDeviation\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFeeWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"redemptionFee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getRedemptionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getRedemptionRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"}],\"name\":\"isDelegateWhitelisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isWhitelisted\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"lastFeeOperationTime\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"liquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralChange\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isCollateralIncrease\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"debtChange\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isDebtIncrease\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePercentage\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"contract IERC20Permit\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"struct ERC20PermitSignature\",\"name\":\"permitSignature\",\"type\":\"tuple\"}],\"name\":\"managePosition\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"actualCollateralChange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actualDebtChange\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mintFees\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"positionManager\",\"outputs\":[{\"internalType\":\"contract IPositionManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"priceFeed\",\"outputs\":[{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"r\",\"outputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rToken\",\"outputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"raftCollateralToken\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"raftDebtToken\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"redeemCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"redemptionRebate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"redemptionSpread\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newBorrowingSpread\",\"type\":\"uint256\"}],\"name\":\"setBorrowingSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"setCollateralEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newFeeRecipient\",\"type\":\"address\"}],\"name\":\"setFeeRecipient\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newRedemptionRebate\",\"type\":\"uint256\"}],\"name\":\"setRedemptionRebate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newRedemptionSpread\",\"type\":\"uint256\"}],\"name\":\"setRedemptionSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"}],\"name\":\"setSplitLiquidationCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"splitLiquidationCollateral\",\"outputs\":[{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"whitelistDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"InterestRatePositionManager","CompilerVersion":"v0.8.19+commit.7dd6d404","OptimizationUsed":1,"Runs":200000,"ConstructorArguments":"0x000000000000000000000000183015a9ba6ff60230fdeadc3f43b3d788b13e21","EVMVersion":"london","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json new file mode 100644 index 0000000000000..7896ef85659c8 --- /dev/null +++ b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json @@ -0,0 +1,5 @@ +{ + "contractAddress": "0x9d27527ada2cf29fbdab2973cfa243845a08bd3f", + "contractCreator": "0x7773ae67403d2e30102a84c48cc939919c4c881c", + "txHash": "0xc757719b7ae11ea651b1b23249978a3e8fca94d358610f5809a5dc19fbf850ce" +} \ No newline at end of file diff --git a/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json new file mode 100644 index 0000000000000..514b2571c4ecf --- /dev/null +++ b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json @@ -0,0 +1,96 @@ +[ + { + "SourceCode": { + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "src/base/ERC721Checkpointable.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\n/// @title Vote checkpointing for an ERC-721 token\n\n/*********************************\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░█████████░░█████████░░░ *\n * ░░░░░░██░░░████░░██░░░████░░░ *\n * ░░██████░░░████████░░░████░░░ *\n * ░░██░░██░░░████░░██░░░████░░░ *\n * ░░██░░██░░░████░░██░░░████░░░ *\n * ░░░░░░█████████░░█████████░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n *********************************/\n\n// LICENSE\n// ERC721Checkpointable.sol uses and modifies part of Compound Lab's Comp.sol:\n// https://github.com/compound-finance/compound-protocol/blob/ae4388e780a8d596d97619d9704a931a2752c2bc/contracts/Governance/Comp.sol\n//\n// Comp.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.\n// With modifications by Nounders DAO.\n//\n// Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause\n//\n// MODIFICATIONS\n// Checkpointing logic from Comp.sol has been used with the following modifications:\n// - `delegates` is renamed to `_delegates` and is set to private\n// - `delegates` is a public function that uses the `_delegates` mapping look-up, but unlike\n// Comp.sol, returns the delegator's own address if there is no delegate.\n// This avoids the delegator needing to \"delegate to self\" with an additional transaction\n// - `_transferTokens()` is renamed `_beforeTokenTransfer()` and adapted to hook into OpenZeppelin's ERC721 hooks.\n\npragma solidity 0.8.9;\n\nimport \"./ERC721BaseWithERC4494Permit.sol\";\n\nabstract contract ERC721Checkpointable is ERC721BaseWithERC4494Permit {\n bool internal _useCheckpoints = true; // can only be disabled and never re-enabled\n\n /// @notice Defines decimals as per ERC-20 convention to make integrations with 3rd party governance platforms easier\n uint8 public constant decimals = 0;\n\n /// @notice A record of each accounts delegate\n mapping(address => address) private _delegates;\n\n /// @notice A checkpoint for marking number of votes from a given block\n struct Checkpoint {\n uint32 fromBlock;\n uint96 votes;\n }\n\n /// @notice A record of votes checkpoints for each account, by index\n mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;\n\n /// @notice The number of checkpoints for each account\n mapping(address => uint32) public numCheckpoints;\n\n /// @notice The EIP-712 typehash for the delegation struct used by the contract\n bytes32 public constant DELEGATION_TYPEHASH =\n keccak256(\"Delegation(address delegatee,uint256 nonce,uint256 expiry)\");\n\n /// @notice An event thats emitted when an account changes its delegate\n event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);\n\n /// @notice An event thats emitted when a delegate account's vote balance changes\n event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);\n\n /**\n * @notice The votes a delegator can delegate, which is the current balance of the delegator.\n * @dev Used when calling `_delegate()`\n */\n function votesToDelegate(address delegator) public view returns (uint96) {\n return safe96(balanceOf(delegator), \"ERC721Checkpointable::votesToDelegate: amount exceeds 96 bits\");\n }\n\n /**\n * @notice Overrides the standard `Comp.sol` delegates mapping to return\n * the delegator's own address if they haven't delegated.\n * This avoids having to delegate to oneself.\n */\n function delegates(address delegator) public view returns (address) {\n address current = _delegates[delegator];\n return current == address(0) ? delegator : current;\n }\n\n /**\n * @notice Adapted from `_transferTokens()` in `Comp.sol` to update delegate votes.\n * @dev hooks into ERC721Base's `ERC721._transfer`\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal override {\n super._beforeTokenTransfer(from, to, tokenId);\n if (_useCheckpoints) {\n /// @notice Differs from `_transferTokens()` to use `delegates` override method to simulate auto-delegation\n _moveDelegates(delegates(from), delegates(to), 1);\n }\n }\n\n /**\n * @notice Delegate votes from `msg.sender` to `delegatee`\n * @param delegatee The address to delegate votes to\n */\n function delegate(address delegatee) public {\n if (delegatee == address(0)) delegatee = msg.sender;\n return _delegate(msg.sender, delegatee);\n }\n\n /**\n * @notice Delegates votes from signatory to `delegatee`\n * @param delegatee The address to delegate votes to\n * @param nonce The contract state required to match the signature\n * @param expiry The time at which to expire the signature\n * @param v The recovery byte of the signature\n * @param r Half of the ECDSA signature pair\n * @param s Half of the ECDSA signature pair\n */\n function delegateBySig(\n address delegatee,\n uint256 nonce,\n uint256 expiry,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))\n )\n );\n // TODO support smart contract wallet via IERC721, require change in function signature to know which signer to call first\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"ERC721Checkpointable::delegateBySig: invalid signature\");\n require(nonce == _userNonces[signatory]++, \"ERC721Checkpointable::delegateBySig: invalid nonce\");\n require(block.timestamp <= expiry, \"ERC721Checkpointable::delegateBySig: signature expired\");\n return _delegate(signatory, delegatee);\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getCurrentVotes(address account) public view returns (uint96) {\n uint32 nCheckpoints = numCheckpoints[account];\n return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getVotes(address account) external view returns (uint96) {\n return getCurrentVotes(account);\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {\n require(blockNumber < block.number, \"ERC721Checkpointable::getPriorVotes: not yet determined\");\n\n uint32 nCheckpoints = numCheckpoints[account];\n if (nCheckpoints == 0) {\n return 0;\n }\n\n // First check most recent balance\n if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {\n return checkpoints[account][nCheckpoints - 1].votes;\n }\n\n // Next check implicit zero balance\n if (checkpoints[account][0].fromBlock > blockNumber) {\n return 0;\n }\n\n uint32 lower = 0;\n uint32 upper = nCheckpoints - 1;\n while (upper > lower) {\n uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow\n Checkpoint memory cp = checkpoints[account][center];\n if (cp.fromBlock == blockNumber) {\n return cp.votes;\n } else if (cp.fromBlock < blockNumber) {\n lower = center;\n } else {\n upper = center - 1;\n }\n }\n return checkpoints[account][lower].votes;\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPastVotes(address account, uint256 blockNumber) external view returns (uint96) {\n return this.getPriorVotes(account, blockNumber);\n }\n\n function _delegate(address delegator, address delegatee) internal {\n /// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation\n address currentDelegate = delegates(delegator);\n\n _delegates[delegator] = delegatee;\n\n emit DelegateChanged(delegator, currentDelegate, delegatee);\n\n uint96 amount = votesToDelegate(delegator);\n\n _moveDelegates(currentDelegate, delegatee, amount);\n }\n\n function _moveDelegates(\n address srcRep,\n address dstRep,\n uint96 amount\n ) internal {\n if (srcRep != dstRep && amount > 0) {\n if (srcRep != address(0)) {\n uint32 srcRepNum = numCheckpoints[srcRep];\n uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;\n uint96 srcRepNew = sub96(srcRepOld, amount, \"ERC721Checkpointable::_moveDelegates: amount underflows\");\n _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);\n }\n\n if (dstRep != address(0)) {\n uint32 dstRepNum = numCheckpoints[dstRep];\n uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;\n uint96 dstRepNew = add96(dstRepOld, amount, \"ERC721Checkpointable::_moveDelegates: amount overflows\");\n _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);\n }\n }\n }\n\n function _writeCheckpoint(\n address delegatee,\n uint32 nCheckpoints,\n uint96 oldVotes,\n uint96 newVotes\n ) internal {\n uint32 blockNumber = safe32(\n block.number,\n \"ERC721Checkpointable::_writeCheckpoint: block number exceeds 32 bits\"\n );\n\n if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {\n checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;\n } else {\n checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);\n numCheckpoints[delegatee] = nCheckpoints + 1;\n }\n\n emit DelegateVotesChanged(delegatee, oldVotes, newVotes);\n }\n\n function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {\n require(n < 2**32, errorMessage);\n return uint32(n);\n }\n\n function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) {\n require(n < 2**96, errorMessage);\n return uint96(n);\n }\n\n function add96(\n uint96 a,\n uint96 b,\n string memory errorMessage\n ) internal pure returns (uint96) {\n uint96 c = a + b;\n require(c >= a, errorMessage);\n return c;\n }\n\n function sub96(\n uint96 a,\n uint96 b,\n string memory errorMessage\n ) internal pure returns (uint96) {\n require(b <= a, errorMessage);\n return a - b;\n }\n}\n" + }, + "src/base/ERC721Base.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport \"@openzeppelin/contracts/interfaces/IERC165.sol\";\n\nabstract contract ERC721Base is IERC165, IERC721 {\n using Address for address;\n\n bytes4 internal constant ERC721_RECEIVED = 0x150b7a02;\n bytes4 internal constant ERC165ID = 0x01ffc9a7;\n\n uint256 internal constant OPERATOR_FLAG = 0x8000000000000000000000000000000000000000000000000000000000000000;\n uint256 internal constant NOT_OPERATOR_FLAG = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\n\n mapping(uint256 => uint256) internal _owners;\n mapping(address => uint256) internal _balances;\n mapping(address => mapping(address => bool)) internal _operatorsForAll;\n mapping(uint256 => address) internal _operators;\n\n function name() public pure virtual returns (string memory) {\n revert(\"NOT_IMPLEMENTED\");\n }\n\n /// @notice Approve an operator to transfer a specific token on the senders behalf.\n /// @param operator The address receiving the approval.\n /// @param id The id of the token.\n function approve(address operator, uint256 id) external override {\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(msg.sender == owner || isApprovedForAll(owner, msg.sender), \"UNAUTHORIZED_APPROVAL\");\n _approveFor(owner, blockNumber, operator, id);\n }\n\n /// @notice Transfer a token between 2 addresses.\n /// @param from The sender of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n function transferFrom(\n address from,\n address to,\n uint256 id\n ) external override {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(owner == from, \"NOT_OWNER\");\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n if (msg.sender != from) {\n require(\n (operatorEnabled && _operators[id] == msg.sender) || isApprovedForAll(from, msg.sender),\n \"UNAUTHORIZED_TRANSFER\"\n );\n }\n _transferFrom(from, to, id);\n }\n\n /// @notice Transfer a token between 2 addresses letting the receiver know of the transfer.\n /// @param from The send of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n function safeTransferFrom(\n address from,\n address to,\n uint256 id\n ) external override {\n safeTransferFrom(from, to, id, \"\");\n }\n\n /// @notice Set the approval for an operator to manage all the tokens of the sender.\n /// @param operator The address receiving the approval.\n /// @param approved The determination of the approval.\n function setApprovalForAll(address operator, bool approved) external override {\n _setApprovalForAll(msg.sender, operator, approved);\n }\n\n /// @notice Get the number of tokens owned by an address.\n /// @param owner The address to look for.\n /// @return balance The number of tokens owned by the address.\n function balanceOf(address owner) public view override returns (uint256 balance) {\n require(owner != address(0), \"ZERO_ADDRESS_OWNER\");\n balance = _balances[owner];\n }\n\n /// @notice Get the owner of a token.\n /// @param id The id of the token.\n /// @return owner The address of the token owner.\n function ownerOf(uint256 id) external view override returns (address owner) {\n owner = _ownerOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n }\n\n /// @notice Get the owner of a token and the blockNumber of the last transfer, useful to voting mechanism.\n /// @param id The id of the token.\n /// @return owner The address of the token owner.\n /// @return blockNumber The blocknumber at which the last transfer of that id happened.\n function ownerAndLastTransferBlockNumberOf(uint256 id) internal view returns (address owner, uint256 blockNumber) {\n return _ownerAndBlockNumberOf(id);\n }\n\n struct OwnerData {\n address owner;\n uint256 lastTransferBlockNumber;\n }\n\n /// @notice Get the list of owner of a token and the blockNumber of its last transfer, useful to voting mechanism.\n /// @param ids The list of token ids to check.\n /// @return ownersData The list of (owner, lastTransferBlockNumber) for each ids given as input.\n function ownerAndLastTransferBlockNumberList(uint256[] calldata ids)\n external\n view\n returns (OwnerData[] memory ownersData)\n {\n ownersData = new OwnerData[](ids.length);\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 data = _owners[ids[i]];\n ownersData[i].owner = address(uint160(data));\n ownersData[i].lastTransferBlockNumber = (data >> 160) & 0xFFFFFFFFFFFFFFFFFFFFFF;\n }\n }\n\n /// @notice Get the approved operator for a specific token.\n /// @param id The id of the token.\n /// @return The address of the operator.\n function getApproved(uint256 id) external view override returns (address) {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n if (operatorEnabled) {\n return _operators[id];\n } else {\n return address(0);\n }\n }\n\n /// @notice Check if the sender approved the operator.\n /// @param owner The address of the owner.\n /// @param operator The address of the operator.\n /// @return isOperator The status of the approval.\n function isApprovedForAll(address owner, address operator) public view virtual override returns (bool isOperator) {\n return _operatorsForAll[owner][operator];\n }\n\n /// @notice Transfer a token between 2 addresses letting the receiver knows of the transfer.\n /// @param from The sender of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n /// @param data Additional data.\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes memory data\n ) public override {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(owner == from, \"NOT_OWNER\");\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n if (msg.sender != from) {\n require(\n (operatorEnabled && _operators[id] == msg.sender) || isApprovedForAll(from, msg.sender),\n \"UNAUTHORIZED_TRANSFER\"\n );\n }\n _safeTransferFrom(from, to, id, data);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id) public pure virtual override returns (bool) {\n /// 0x01ffc9a7 is ERC165.\n /// 0x80ac58cd is ERC721\n /// 0x5b5e139f is for ERC721 metadata\n return id == 0x01ffc9a7 || id == 0x80ac58cd || id == 0x5b5e139f;\n }\n\n function _safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes memory data\n ) internal {\n _transferFrom(from, to, id);\n if (to.isContract()) {\n require(_checkOnERC721Received(msg.sender, from, to, id, data), \"ERC721_TRANSFER_REJECTED\");\n }\n }\n\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 id\n ) internal virtual {}\n\n function _transferFrom(\n address from,\n address to,\n uint256 id\n ) internal {\n _beforeTokenTransfer(from, to, id);\n unchecked {\n _balances[to]++;\n if (from != address(0)) {\n _balances[from]--;\n }\n }\n _owners[id] = (block.number << 160) | uint256(uint160(to));\n emit Transfer(from, to, id);\n }\n\n /// @dev See approve.\n function _approveFor(\n address owner,\n uint256 blockNumber,\n address operator,\n uint256 id\n ) internal {\n if (operator == address(0)) {\n _owners[id] = (blockNumber << 160) | uint256(uint160(owner));\n } else {\n _owners[id] = OPERATOR_FLAG | (blockNumber << 160) | uint256(uint160(owner));\n _operators[id] = operator;\n }\n emit Approval(owner, operator, id);\n }\n\n /// @dev See setApprovalForAll.\n function _setApprovalForAll(\n address sender,\n address operator,\n bool approved\n ) internal {\n _operatorsForAll[sender][operator] = approved;\n\n emit ApprovalForAll(sender, operator, approved);\n }\n\n /// @dev Check if receiving contract accepts erc721 transfers.\n /// @param operator The address of the operator.\n /// @param from The from address, may be different from msg.sender.\n /// @param to The adddress we want to transfer to.\n /// @param id The id of the token we would like to transfer.\n /// @param _data Any additional data to send with the transfer.\n /// @return Whether the expected value of 0x150b7a02 is returned.\n function _checkOnERC721Received(\n address operator,\n address from,\n address to,\n uint256 id,\n bytes memory _data\n ) internal returns (bool) {\n bytes4 retval = IERC721Receiver(to).onERC721Received(operator, from, id, _data);\n return (retval == ERC721_RECEIVED);\n }\n\n /// @dev See ownerOf\n function _ownerOf(uint256 id) internal view returns (address owner) {\n return address(uint160(_owners[id]));\n }\n\n /// @dev Get the owner and operatorEnabled status of a token.\n /// @param id The token to query.\n /// @return owner The owner of the token.\n /// @return operatorEnabled Whether or not operators are enabled for this token.\n function _ownerAndOperatorEnabledOf(uint256 id) internal view returns (address owner, bool operatorEnabled) {\n uint256 data = _owners[id];\n owner = address(uint160(data));\n operatorEnabled = (data & OPERATOR_FLAG) == OPERATOR_FLAG;\n }\n\n // @dev Get the owner and operatorEnabled status of a token.\n /// @param id The token to query.\n /// @return owner The owner of the token.\n /// @return blockNumber the blockNumber at which the owner became the owner (last transfer).\n function _ownerAndBlockNumberOf(uint256 id) internal view returns (address owner, uint256 blockNumber) {\n uint256 data = _owners[id];\n owner = address(uint160(data));\n blockNumber = (data >> 160) & 0xFFFFFFFFFFFFFFFFFFFFFF;\n }\n\n // from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol\n /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed.\n /// @dev The `msg.value` should not be trusted for any method callable from multicall.\n /// @param data The encoded function data for each of the calls to make to this contract.\n /// @return results The results from each of the calls passed in via data.\n function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {\n results = new bytes[](data.length);\n for (uint256 i = 0; i < data.length; i++) {\n (bool success, bytes memory result) = address(this).delegatecall(data[i]);\n\n if (!success) {\n // Next 5 lines from https://ethereum.stackexchange.com/a/83577\n if (result.length < 68) revert();\n assembly {\n result := add(result, 0x04)\n }\n revert(abi.decode(result, (string)));\n }\n\n results[i] = result;\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "src/base/IERC4494.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface IERC4494 {\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /// @notice Allows to retrieve current nonce for token\n /// @param tokenId token id\n /// @return current token nonce\n function nonces(uint256 tokenId) external view returns (uint256);\n\n /// @notice function to be called by anyone to approve `spender` using a Permit signature\n /// @dev Anyone can call this to approve `spender`, even a third-party\n /// @param spender the actor to approve\n /// @param tokenId the token id\n /// @param deadline the deadline for the permit to be used\n /// @param signature permit\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory signature\n ) external;\n}\n\ninterface IERC4494Alternative {\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /// @notice Allows to retrieve current nonce for token\n /// @param tokenId token id\n /// @return current token nonce\n function tokenNonces(uint256 tokenId) external view returns (uint256);\n\n /// @notice function to be called by anyone to approve `spender` using a Permit signature\n /// @dev Anyone can call this to approve `spender`, even a third-party\n /// @param spender the actor to approve\n /// @param tokenId the token id\n /// @param deadline the deadline for the permit to be used\n /// @param signature permit\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory signature\n ) external;\n}\n" + }, + "src/bleeps/BleepsRoles.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface ReverseRegistrar {\n function setName(string memory name) external returns (bytes32);\n}\n\ninterface ENS {\n function owner(bytes32 node) external view returns (address);\n}\n\ncontract BleepsRoles {\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n event TokenURIAdminSet(address newTokenURIAdmin);\n event RoyaltyAdminSet(address newRoyaltyAdmin);\n event MinterAdminSet(address newMinterAdmin);\n event GuardianSet(address newGuardian);\n event MinterSet(address newMinter);\n\n bytes32 internal constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;\n ENS internal immutable _ens;\n\n ///@notice the address of the current owner, that is able to set ENS names and withdraw ERC20 owned by the contract.\n address public owner;\n\n /// @notice tokenURIAdmin can update the tokenURI contract, this is intended to be relinquished once the tokenURI has been heavily tested in the wild and that no modification are needed.\n address public tokenURIAdmin;\n\n /// @notice address allowed to set royalty parameters\n address public royaltyAdmin;\n\n /// @notice minterAdmin can update the minter. At the time being there is 576 Bleeps but there is space for extra instrument and the upper limit is 1024.\n /// could be given to the DAO later so instrument can be added, the sale of these new bleeps could benenfit the DAO too and add new members.\n address public minterAdmin;\n\n /// @notice address allowed to mint, allow the sale contract to be separated from the token contract that can focus on the core logic\n /// Once all 1024 potential bleeps (there could be less, at minimum there are 576 bleeps) are minted, no minter can mint anymore\n address public minter;\n\n /// @notice guardian has some special vetoing power to guide the direction of the DAO. It can only remove rights from the DAO. It could be used to immortalize rules.\n /// For example: the royalty setup could be frozen.\n address public guardian;\n\n constructor(\n address ens,\n address initialOwner,\n address initialTokenURIAdmin,\n address initialMinterAdmin,\n address initialRoyaltyAdmin,\n address initialGuardian\n ) {\n _ens = ENS(ens);\n owner = initialOwner;\n tokenURIAdmin = initialTokenURIAdmin;\n royaltyAdmin = initialRoyaltyAdmin;\n minterAdmin = initialMinterAdmin;\n guardian = initialGuardian;\n emit OwnershipTransferred(address(0), initialOwner);\n emit TokenURIAdminSet(initialTokenURIAdmin);\n emit RoyaltyAdminSet(initialRoyaltyAdmin);\n emit MinterAdminSet(initialMinterAdmin);\n emit GuardianSet(initialGuardian);\n }\n\n function setENSName(string memory name) external {\n require(msg.sender == owner, \"NOT_AUTHORIZED\");\n ReverseRegistrar reverseRegistrar = ReverseRegistrar(_ens.owner(ADDR_REVERSE_NODE));\n reverseRegistrar.setName(name);\n }\n\n function withdrawERC20(IERC20 token, address to) external {\n require(msg.sender == owner, \"NOT_AUTHORIZED\");\n token.transfer(to, token.balanceOf(address(this)));\n }\n\n /**\n * @notice Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) external {\n address oldOwner = owner;\n require(msg.sender == oldOwner);\n owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @notice set the new tokenURIAdmin that can change the tokenURI\n * Can only be called by the current tokenURI admin.\n */\n function setTokenURIAdmin(address newTokenURIAdmin) external {\n require(\n msg.sender == tokenURIAdmin || (msg.sender == guardian && newTokenURIAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n tokenURIAdmin = newTokenURIAdmin;\n emit TokenURIAdminSet(newTokenURIAdmin);\n }\n\n /**\n * @notice set the new royaltyAdmin that can change the royalties\n * Can only be called by the current royalty admin.\n */\n function setRoyaltyAdmin(address newRoyaltyAdmin) external {\n require(\n msg.sender == royaltyAdmin || (msg.sender == guardian && newRoyaltyAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n royaltyAdmin = newRoyaltyAdmin;\n emit RoyaltyAdminSet(newRoyaltyAdmin);\n }\n\n /**\n * @notice set the new minterAdmin that can set the minter for Bleeps\n * Can only be called by the current minter admin.\n */\n function setMinterAdmin(address newMinterAdmin) external {\n require(\n msg.sender == minterAdmin || (msg.sender == guardian && newMinterAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n minterAdmin = newMinterAdmin;\n emit MinterAdminSet(newMinterAdmin);\n }\n\n /**\n * @notice set the new guardian that can freeze the other admins (except owner).\n * Can only be called by the current guardian.\n */\n function setGuardian(address newGuardian) external {\n require(msg.sender == guardian, \"NOT_AUTHORIZED\");\n guardian = newGuardian;\n emit GuardianSet(newGuardian);\n }\n\n /**\n * @notice set the new minter that can mint Bleeps (up to 1024).\n * Can only be called by the minter admin.\n */\n function setMinter(address newMinter) external {\n require(msg.sender == minterAdmin, \"NOT_AUTHORIZED\");\n minter = newMinter;\n emit MinterSet(newMinter);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "src/interfaces/ITokenURI.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface ITokenURI {\n function tokenURI(uint256 id) external view returns (string memory);\n\n function contractURI(address receiver, uint96 per10Thousands) external view returns (string memory);\n}\n" + }, + "src/base/WithSupportForOpenSeaProxies.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ncontract OwnableDelegateProxy {}\n\ncontract ProxyRegistry {\n mapping(address => OwnableDelegateProxy) public proxies;\n}\n\nabstract contract WithSupportForOpenSeaProxies {\n address internal immutable _proxyRegistryAddress;\n\n constructor(address proxyRegistryAddress) {\n _proxyRegistryAddress = proxyRegistryAddress;\n }\n\n function _isOpenSeaProxy(address owner, address operator) internal view returns (bool) {\n if (_proxyRegistryAddress == address(0)) {\n return false;\n }\n // Whitelist OpenSea proxy contract for easy trading.\n ProxyRegistry proxyRegistry = ProxyRegistry(_proxyRegistryAddress);\n return address(proxyRegistry.proxies(owner)) == operator;\n }\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC1271.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC1271 standard signature validation method for\n * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].\n *\n * _Available since v4.1._\n */\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with _data\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"../utils/introspection/IERC165.sol\";\n" + }, + "src/bleeps/Bleeps.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./BleepsRoles.sol\";\nimport \"../base/ERC721Checkpointable.sol\";\n\nimport \"../interfaces/ITokenURI.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\n\nimport \"../base/WithSupportForOpenSeaProxies.sol\";\n\ncontract Bleeps is IERC721, WithSupportForOpenSeaProxies, ERC721Checkpointable, BleepsRoles {\n event RoyaltySet(address receiver, uint256 royaltyPer10Thousands);\n event TokenURIContractSet(ITokenURI newTokenURIContract);\n event CheckpointingDisablerSet(address newCheckpointingDisabler);\n event CheckpointingDisabled();\n\n /// @notice the contract that actually generate the sound (and all metadata via the a data: uri via tokenURI call).\n ITokenURI public tokenURIContract;\n\n struct Royalty {\n address receiver;\n uint96 per10Thousands;\n }\n\n Royalty internal _royalty;\n\n /// @dev address that is able to switch off the use of checkpointing, this will make token transfers much cheaper in term of gas, but require the design of a new governance system.\n address public checkpointingDisabler;\n\n /// @dev Create the Bleeps contract\n /// @param ens ENS address for the network the contract is deployed to\n /// @param initialOwner address that can set the ENS name of the contract and that can witthdraw ERC20 tokens sent by mistake here.\n /// @param initialTokenURIAdmin admin able to update the tokenURI contract.\n /// @param initialMinterAdmin admin able to set the minter contract.\n /// @param initialRoyaltyAdmin admin able to update the royalty receiver and rates.\n /// @param initialGuardian guardian able to immortalize rules\n /// @param openseaProxyRegistry allow Bleeps to be sold on opensea without prior approval tx as long as the user have already an opensea proxy.\n /// @param initialRoyaltyReceiver receiver of royalties\n /// @param imitialRoyaltyPer10Thousands amount of royalty in 10,000 basis point\n /// @param initialTokenURIContract initial tokenURI contract that generate the metadata including the wav file.\n /// @param initialCheckpointingDisabler admin able to cancel checkpointing\n constructor(\n address ens,\n address initialOwner,\n address initialTokenURIAdmin,\n address initialMinterAdmin,\n address initialRoyaltyAdmin,\n address initialGuardian,\n address openseaProxyRegistry,\n address initialRoyaltyReceiver,\n uint96 imitialRoyaltyPer10Thousands,\n ITokenURI initialTokenURIContract,\n address initialCheckpointingDisabler\n )\n WithSupportForOpenSeaProxies(openseaProxyRegistry)\n BleepsRoles(ens, initialOwner, initialTokenURIAdmin, initialMinterAdmin, initialRoyaltyAdmin, initialGuardian)\n {\n tokenURIContract = initialTokenURIContract;\n emit TokenURIContractSet(initialTokenURIContract);\n checkpointingDisabler = initialCheckpointingDisabler;\n emit CheckpointingDisablerSet(initialCheckpointingDisabler);\n\n _royalty.receiver = initialRoyaltyReceiver;\n _royalty.per10Thousands = imitialRoyaltyPer10Thousands;\n emit RoyaltySet(initialRoyaltyReceiver, imitialRoyaltyPer10Thousands);\n }\n\n /// @notice A descriptive name for a collection of NFTs in this contract.\n function name() public pure override returns (string memory) {\n return \"Bleeps\";\n }\n\n /// @notice An abbreviated name for NFTs in this contract.\n function symbol() external pure returns (string memory) {\n return \"BLEEP\";\n }\n\n /// @notice Returns the Uniform Resource Identifier (URI) for the token collection.\n function contractURI() external view returns (string memory) {\n return tokenURIContract.contractURI(_royalty.receiver, _royalty.per10Thousands);\n }\n\n /// @notice Returns the Uniform Resource Identifier (URI) for token `id`.\n function tokenURI(uint256 id) external view returns (string memory) {\n return tokenURIContract.tokenURI(id);\n }\n\n /// @notice set a new tokenURI contract, that generate the metadata including the wav file, Can only be set by the `tokenURIAdmin`.\n /// @param newTokenURIContract The address of the new tokenURI contract.\n function setTokenURIContract(ITokenURI newTokenURIContract) external {\n require(msg.sender == tokenURIAdmin, \"NOT_AUTHORIZED\");\n tokenURIContract = newTokenURIContract;\n emit TokenURIContractSet(newTokenURIContract);\n }\n\n /// @notice give the list of owners for the list of ids given.\n /// @param ids The list if token ids to check.\n /// @return addresses The list of addresses, corresponding to the list of ids.\n function owners(uint256[] calldata ids) external view returns (address[] memory addresses) {\n addresses = new address[](ids.length);\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 id = ids[i];\n addresses[i] = address(uint160(_owners[id]));\n }\n }\n\n /// @notice Check if the sender approved the operator.\n /// @param owner The address of the owner.\n /// @param operator The address of the operator.\n /// @return isOperator The status of the approval.\n function isApprovedForAll(address owner, address operator)\n public\n view\n virtual\n override(ERC721Base, IERC721)\n returns (bool isOperator)\n {\n return super.isApprovedForAll(owner, operator) || _isOpenSeaProxy(owner, operator);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id)\n public\n pure\n virtual\n override(ERC721BaseWithERC4494Permit, IERC165)\n returns (bool)\n {\n return super.supportsInterface(id) || id == 0x2a55205a; /// 0x2a55205a is ERC2981 (royalty standard)\n }\n\n /// @notice Called with the sale price to determine how much royalty is owed and to whom.\n /// @param id - the token queried for royalty information.\n /// @param salePrice - the sale price of the token specified by id.\n /// @return receiver - address of who should be sent the royalty payment.\n /// @return royaltyAmount - the royalty payment amount for salePrice.\n function royaltyInfo(uint256 id, uint256 salePrice)\n external\n view\n returns (address receiver, uint256 royaltyAmount)\n {\n receiver = _royalty.receiver;\n royaltyAmount = (salePrice * uint256(_royalty.per10Thousands)) / 10000;\n }\n\n /// @notice set a new royalty receiver and rate, Can only be set by the `royaltyAdmin`.\n /// @param newReceiver the address that should receive the royalty proceeds.\n /// @param royaltyPer10Thousands the share of the salePrice (in 1/10000) given to the receiver.\n function setRoyaltyParameters(address newReceiver, uint96 royaltyPer10Thousands) external {\n require(msg.sender == royaltyAdmin, \"NOT_AUTHORIZED\");\n // require(royaltyPer10Thousands <= 50, \"ROYALTY_TOO_HIGH\"); ?\n _royalty.receiver = newReceiver;\n _royalty.per10Thousands = royaltyPer10Thousands;\n emit RoyaltySet(newReceiver, royaltyPer10Thousands);\n }\n\n /// @notice disable checkpointing overhead\n /// This can be used if the governance system can switch to use ownerAndLastTransferBlockNumberOf instead of checkpoints\n function disableTheUseOfCheckpoints() external {\n require(msg.sender == checkpointingDisabler, \"NOT_AUTHORIZED\");\n _useCheckpoints = false;\n checkpointingDisabler = address(0);\n emit CheckpointingDisablerSet(address(0));\n emit CheckpointingDisabled();\n }\n\n /// @notice update the address that can disable the use of checkpinting, can be used to disable it entirely.\n /// @param newCheckpointingDisabler new address that can disable the use of checkpointing. can be the zero address to remove the ability to change.\n function setCheckpointingDisabler(address newCheckpointingDisabler) external {\n require(msg.sender == checkpointingDisabler, \"NOT_AUTHORIZED\");\n checkpointingDisabler = newCheckpointingDisabler;\n emit CheckpointingDisablerSet(newCheckpointingDisabler);\n }\n\n /// @notice mint one of bleep if not already minted. Can only be called by `minter`.\n /// @param id bleep id which represent a pair of (note, instrument).\n /// @param to address that will receive the Bleep.\n function mint(uint16 id, address to) external {\n require(msg.sender == minter, \"ONLY_MINTER_ALLOWED\");\n require(id < 1024, \"INVALID_BLEEP\");\n\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n address owner = _ownerOf(id);\n require(owner == address(0), \"ALREADY_CREATED\");\n _safeTransferFrom(address(0), to, id, \"\");\n }\n\n /// @notice mint multiple bleeps if not already minted. Can only be called by `minter`.\n /// @param ids list of bleep id which represent each a pair of (note, instrument).\n /// @param tos addresses that will receive the Bleeps. (if only one, use for all)\n function multiMint(uint16[] calldata ids, address[] calldata tos) external {\n require(msg.sender == minter, \"ONLY_MINTER_ALLOWED\");\n\n address to;\n if (tos.length == 1) {\n to = tos[0];\n }\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 id = ids[i];\n if (tos.length > 1) {\n to = tos[i];\n }\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n require(id < 1024, \"INVALID_BLEEP\");\n address owner = _ownerOf(id);\n require(owner == address(0), \"ALREADY_CREATED\");\n _safeTransferFrom(address(0), to, id, \"\");\n }\n }\n\n /// @notice gives the note and instrument for a particular Bleep id.\n /// @param id bleep id which represent a pair of (note, instrument).\n /// @return note the note index (0 to 63) starting from C2 to D#7\n /// @return instrument the instrument index (0 to 16). At launch there is only 9 instrument but the DAO could add more (up to 16 in total).\n function sound(uint256 id) external pure returns (uint8 note, uint8 instrument) {\n note = uint8(id & 0x3F);\n instrument = uint8(uint256(id >> 6) & 0x0F);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/ECDSA.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s;\n uint8 v;\n assembly {\n s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)\n v := add(shr(255, vs), 27)\n }\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"./ECDSA.sol\";\nimport \"../Address.sol\";\nimport \"../../interfaces/IERC1271.sol\";\n\n/**\n * @dev Signature verification helper: Provide a single mechanism to verify both private-key (EOA) ECDSA signature and\n * ERC1271 contract sigantures. Using this instead of ECDSA.recover in your contract will make them compatible with\n * smart contract wallets such as Argent and Gnosis.\n *\n * Note: unlike ECDSA signatures, contract signature's are revocable, and the outcome of this function can thus change\n * through time. It could return true at block N and false at block N+1 (or the opposite).\n *\n * _Available since v4.1._\n */\nlibrary SignatureChecker {\n function isValidSignatureNow(\n address signer,\n bytes32 hash,\n bytes memory signature\n ) internal view returns (bool) {\n (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);\n if (error == ECDSA.RecoverError.NoError && recovered == signer) {\n return true;\n }\n\n (bool success, bytes memory result) = signer.staticcall(\n abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)\n );\n return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);\n }\n}\n" + }, + "src/base/ERC721BaseWithERC4494Permit.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./ERC721Base.sol\";\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol\";\nimport \"./IERC4494.sol\";\n\nabstract contract ERC721BaseWithERC4494Permit is ERC721Base {\n using Address for address;\n\n bytes32 public constant PERMIT_TYPEHASH =\n keccak256(\"Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)\");\n bytes32 public constant PERMIT_FOR_ALL_TYPEHASH =\n keccak256(\"PermitForAll(address spender,uint256 nonce,uint256 deadline)\");\n bytes32 public constant DOMAIN_TYPEHASH =\n keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n uint256 private immutable _deploymentChainId;\n bytes32 private immutable _deploymentDomainSeparator;\n\n mapping(address => uint256) internal _userNonces;\n\n constructor() {\n uint256 chainId;\n //solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n _deploymentChainId = chainId;\n _deploymentDomainSeparator = _calculateDomainSeparator(chainId);\n }\n\n /// @dev Return the DOMAIN_SEPARATOR.\n function DOMAIN_SEPARATOR() external view returns (bytes32) {\n return _DOMAIN_SEPARATOR();\n }\n\n /// @notice return the account nonce, used for approvalForAll permit or other account related matter\n /// @param account the account to query\n /// @return nonce\n function nonces(address account) external view virtual returns (uint256 nonce) {\n return _userNonces[account];\n }\n\n /// @notice return the token nonce, used for individual approve permit or other token related matter\n /// @param id token id to query\n /// @return nonce\n function nonces(uint256 id) public view virtual returns (uint256 nonce) {\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n return blockNumber;\n }\n\n /// @notice return the token nonce, used for individual approve permit or other token related matter\n /// @param id token id to query\n /// @return nonce\n function tokenNonces(uint256 id) external view returns (uint256 nonce) {\n return nonces(id);\n }\n\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory sig\n ) external {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(tokenId);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n\n // We use blockNumber as nonce as we already store it per tokens. It can thus act as an increasing transfer counter.\n // while technically multiple transfer could happen in the same block, the signed message would be using a previous block.\n // And the transfer would use then a more recent blockNumber, invalidating that message when transfer is executed.\n _requireValidPermit(owner, spender, tokenId, deadline, blockNumber, sig);\n\n _approveFor(owner, blockNumber, spender, tokenId);\n }\n\n function permitForAll(\n address signer,\n address spender,\n uint256 deadline,\n bytes memory sig\n ) external {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n _requireValidPermitForAll(signer, spender, deadline, _userNonces[signer]++, sig);\n\n _setApprovalForAll(signer, spender, true);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id) public pure virtual override returns (bool) {\n return\n super.supportsInterface(id) ||\n id == type(IERC4494).interfaceId ||\n id == type(IERC4494Alternative).interfaceId;\n }\n\n // -------------------------------------------------------- INTERNAL --------------------------------------------------------------------\n\n function _requireValidPermit(\n address signer,\n address spender,\n uint256 id,\n uint256 deadline,\n uint256 nonce,\n bytes memory sig\n ) internal view {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(PERMIT_TYPEHASH, spender, id, nonce, deadline))\n )\n );\n require(SignatureChecker.isValidSignatureNow(signer, digest, sig), \"INVALID_SIGNATURE\");\n }\n\n function _requireValidPermitForAll(\n address signer,\n address spender,\n uint256 deadline,\n uint256 nonce,\n bytes memory sig\n ) internal view {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, spender, nonce, deadline))\n )\n );\n require(SignatureChecker.isValidSignatureNow(signer, digest, sig), \"INVALID_SIGNATURE\");\n }\n\n /// @dev Return the DOMAIN_SEPARATOR.\n function _DOMAIN_SEPARATOR() internal view returns (bytes32) {\n uint256 chainId;\n //solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n\n // in case a fork happen, to support the chain that had to change its chainId,, we compute the domain operator\n return chainId == _deploymentChainId ? _deploymentDomainSeparator : _calculateDomainSeparator(chainId);\n }\n\n /// @dev Calculate the DOMAIN_SEPARATOR.\n function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {\n return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), chainId, address(this)));\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721Receiver {\n /**\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n * by `operator` from `from`, this function is called.\n *\n * It must return its Solidity selector to confirm the token transfer.\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\n *\n * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.\n */\n function onERC721Received(\n address operator,\n address from,\n uint256 tokenId,\n bytes calldata data\n ) external returns (bytes4);\n}\n" + } + }, + "settings": { + "evmVersion": "london", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "optimizer": { + "enabled": true, + "runs": 999999 + }, + "remappings": [], + "outputSelection": { + "*": { + "*": [ + "evm.bytecode", + "evm.deployedBytecode", + "devdoc", + "userdoc", + "metadata", + "abi" + ] + } + } + } + }, + "ABI": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ens\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialTokenURIAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialMinterAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialRoyaltyAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialGuardian\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"openseaProxyRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialRoyaltyReceiver\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"imitialRoyaltyPer10Thousands\",\"type\":\"uint96\"},{\"internalType\":\"contract ITokenURI\",\"name\":\"initialTokenURIContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialCheckpointingDisabler\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"approved\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CheckpointingDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newCheckpointingDisabler\",\"type\":\"address\"}],\"name\":\"CheckpointingDisablerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromDelegate\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toDelegate\",\"type\":\"address\"}],\"name\":\"DelegateChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"DelegateVotesChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"GuardianSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newMinterAdmin\",\"type\":\"address\"}],\"name\":\"MinterAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newMinter\",\"type\":\"address\"}],\"name\":\"MinterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newRoyaltyAdmin\",\"type\":\"address\"}],\"name\":\"RoyaltyAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"royaltyPer10Thousands\",\"type\":\"uint256\"}],\"name\":\"RoyaltySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newTokenURIAdmin\",\"type\":\"address\"}],\"name\":\"TokenURIAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract ITokenURI\",\"name\":\"newTokenURIContract\",\"type\":\"address\"}],\"name\":\"TokenURIContractSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DELEGATION_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_FOR_ALL_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"checkpointingDisabler\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"checkpoints\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"fromBlock\",\"type\":\"uint32\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"contractURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegatee\",\"type\":\"address\"}],\"name\":\"delegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegatee\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiry\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"delegateBySig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"delegates\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableTheUseOfCheckpoints\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"getApproved\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getCurrentVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getPastVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getPriorVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"isApprovedForAll\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isOperator\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"id\",\"type\":\"uint16\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minterAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16[]\",\"name\":\"ids\",\"type\":\"uint16[]\"},{\"internalType\":\"address[]\",\"name\":\"tos\",\"type\":\"address[]\"}],\"name\":\"multiMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"data\",\"type\":\"bytes[]\"}],\"name\":\"multicall\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"results\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"numCheckpoints\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"name\":\"ownerAndLastTransferBlockNumberList\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"lastTransferBlockNumber\",\"type\":\"uint256\"}],\"internalType\":\"struct ERC721Base.OwnerData[]\",\"name\":\"ownersData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ownerOf\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"name\":\"owners\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"permitForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"royaltyAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"salePrice\",\"type\":\"uint256\"}],\"name\":\"royaltyInfo\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"royaltyAmount\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"setApprovalForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newCheckpointingDisabler\",\"type\":\"address\"}],\"name\":\"setCheckpointingDisabler\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"setENSName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"setGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newMinter\",\"type\":\"address\"}],\"name\":\"setMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newMinterAdmin\",\"type\":\"address\"}],\"name\":\"setMinterAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newRoyaltyAdmin\",\"type\":\"address\"}],\"name\":\"setRoyaltyAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newReceiver\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"royaltyPer10Thousands\",\"type\":\"uint96\"}],\"name\":\"setRoyaltyParameters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newTokenURIAdmin\",\"type\":\"address\"}],\"name\":\"setTokenURIAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract ITokenURI\",\"name\":\"newTokenURIContract\",\"type\":\"address\"}],\"name\":\"setTokenURIContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"sound\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"note\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"instrument\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"id\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tokenNonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tokenURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenURIAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenURIContract\",\"outputs\":[{\"internalType\":\"contract ITokenURI\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"votesToDelegate\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "ContractName": "Bleeps", + "CompilerVersion": "v0.8.9+commit.e5eed63a", + "OptimizationUsed": 1, + "Runs": 999999, + "ConstructorArguments": "0x00000000000000000000000000000000000c2e074ec69a0dfb2997ba6c7d2e1e0000000000000000000000007773ae67403d2e30102a84c48cc939919c4c881c000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b40000000000000000000000007773ae67403d2e30102a84c48cc939919c4c881c000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4000000000000000000000000a5409ec958c83c3f309868babaca7c86dcb077c10000000000000000000000008350c9989ef11325b36ce6f7549004d418dbcee700000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000e114dce59a333f8d351371f54188f92c287b73e6000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4", + "EVMVersion": "Default", + "Library": "", + "LicenseType": "MIT", + "Proxy": 0, + "SwarmSource": "" + } +] \ No newline at end of file diff --git a/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json new file mode 100644 index 0000000000000..646ea065a473e --- /dev/null +++ b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0xdb53f47ac61fe54f456a4eb3e09832d08dd7beec","contractCreator":"0xc7f8d87734ab2cbf70030ac8aa82abfe3e8126cb","txHash":"0x196898c69f6b1944f1011120b15c0903329d46259c8cdc0fbcad71da1fe58245"} \ No newline at end of file diff --git a/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json new file mode 100644 index 0000000000000..df45d9be39c9e --- /dev/null +++ b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC1155 } from '../IERC1155.sol';\nimport { IERC1155Receiver } from '../IERC1155Receiver.sol';\nimport { ERC1155BaseInternal, ERC1155BaseStorage } from './ERC1155BaseInternal.sol';\n\n/**\n * @title Base ERC1155 contract\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)\n */\nabstract contract ERC1155Base is IERC1155, ERC1155BaseInternal {\n /**\n * @inheritdoc IERC1155\n */\n function balanceOf(address account, uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return _balanceOf(account, id);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function balanceOfBatch(address[] memory accounts, uint256[] memory ids)\n public\n view\n virtual\n override\n returns (uint256[] memory)\n {\n require(\n accounts.length == ids.length,\n 'ERC1155: accounts and ids length mismatch'\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n uint256[] memory batchBalances = new uint256[](accounts.length);\n\n unchecked {\n for (uint256 i; i < accounts.length; i++) {\n require(\n accounts[i] != address(0),\n 'ERC1155: batch balance query for the zero address'\n );\n batchBalances[i] = balances[ids[i]][accounts[i]];\n }\n }\n\n return batchBalances;\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function isApprovedForAll(address account, address operator)\n public\n view\n virtual\n override\n returns (bool)\n {\n return ERC1155BaseStorage.layout().operatorApprovals[account][operator];\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function setApprovalForAll(address operator, bool status)\n public\n virtual\n override\n {\n require(\n msg.sender != operator,\n 'ERC1155: setting approval status for self'\n );\n ERC1155BaseStorage.layout().operatorApprovals[msg.sender][\n operator\n ] = status;\n emit ApprovalForAll(msg.sender, operator, status);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) public virtual override {\n require(\n from == msg.sender || isApprovedForAll(from, msg.sender),\n 'ERC1155: caller is not owner nor approved'\n );\n _safeTransfer(msg.sender, from, to, id, amount, data);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function safeBatchTransferFrom(\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) public virtual override {\n require(\n from == msg.sender || isApprovedForAll(from, msg.sender),\n 'ERC1155: caller is not owner nor approved'\n );\n _safeTransferBatch(msg.sender, from, to, ids, amounts, data);\n }\n}\n"},"@solidstate/contracts/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC165 interface registration interface\n * @dev see https://eips.ethereum.org/EIPS/eip-165\n */\ninterface IERC165 {\n /**\n * @notice query whether contract has registered support for given interface\n * @param interfaceId interface id\n * @return bool whether interface is supported\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"abdk-libraries-solidity/ABDKMath64x64.sol":{"content":"// SPDX-License-Identifier: BSD-4-Clause\n/*\n * ABDK Math 64.64 Smart Contract Library. Copyright © 2019 by ABDK Consulting.\n * Author: Mikhail Vladimirov \n */\npragma solidity ^0.8.0;\n\n/**\n * Smart contract library of mathematical functions operating with signed\n * 64.64-bit fixed point numbers. Signed 64.64-bit fixed point number is\n * basically a simple fraction whose numerator is signed 128-bit integer and\n * denominator is 2^64. As long as denominator is always the same, there is no\n * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are\n * represented by int128 type holding only the numerator.\n */\nlibrary ABDKMath64x64 {\n /*\n * Minimum value signed 64.64-bit fixed point number may have. \n */\n int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;\n\n /*\n * Maximum value signed 64.64-bit fixed point number may have. \n */\n int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\n\n /**\n * Convert signed 256-bit integer number into signed 64.64-bit fixed point\n * number. Revert on overflow.\n *\n * @param x signed 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function fromInt (int256 x) internal pure returns (int128) {\n unchecked {\n require (x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);\n return int128 (x << 64);\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into signed 64-bit integer number\n * rounding down.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64-bit integer number\n */\n function toInt (int128 x) internal pure returns (int64) {\n unchecked {\n return int64 (x >> 64);\n }\n }\n\n /**\n * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point\n * number. Revert on overflow.\n *\n * @param x unsigned 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function fromUInt (uint256 x) internal pure returns (int128) {\n unchecked {\n require (x <= 0x7FFFFFFFFFFFFFFF);\n return int128 (int256 (x << 64));\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into unsigned 64-bit integer\n * number rounding down. Revert on underflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return unsigned 64-bit integer number\n */\n function toUInt (int128 x) internal pure returns (uint64) {\n unchecked {\n require (x >= 0);\n return uint64 (uint128 (x >> 64));\n }\n }\n\n /**\n * Convert signed 128.128 fixed point number into signed 64.64-bit fixed point\n * number rounding down. Revert on overflow.\n *\n * @param x signed 128.128-bin fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function from128x128 (int256 x) internal pure returns (int128) {\n unchecked {\n int256 result = x >> 64;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into signed 128.128 fixed point\n * number.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 128.128 fixed point number\n */\n function to128x128 (int128 x) internal pure returns (int256) {\n unchecked {\n return int256 (x) << 64;\n }\n }\n\n /**\n * Calculate x + y. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function add (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) + y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x - y. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function sub (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) - y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x * y rounding down. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function mul (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) * y >> 64;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x * y rounding towards zero, where x is signed 64.64 fixed point\n * number and y is signed 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64 fixed point number\n * @param y signed 256-bit integer number\n * @return signed 256-bit integer number\n */\n function muli (int128 x, int256 y) internal pure returns (int256) {\n unchecked {\n if (x == MIN_64x64) {\n require (y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF &&\n y <= 0x1000000000000000000000000000000000000000000000000);\n return -y << 63;\n } else {\n bool negativeResult = false;\n if (x < 0) {\n x = -x;\n negativeResult = true;\n }\n if (y < 0) {\n y = -y; // We rely on overflow behavior here\n negativeResult = !negativeResult;\n }\n uint256 absoluteResult = mulu (x, uint256 (y));\n if (negativeResult) {\n require (absoluteResult <=\n 0x8000000000000000000000000000000000000000000000000000000000000000);\n return -int256 (absoluteResult); // We rely on overflow behavior here\n } else {\n require (absoluteResult <=\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return int256 (absoluteResult);\n }\n }\n }\n }\n\n /**\n * Calculate x * y rounding down, where x is signed 64.64 fixed point number\n * and y is unsigned 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64 fixed point number\n * @param y unsigned 256-bit integer number\n * @return unsigned 256-bit integer number\n */\n function mulu (int128 x, uint256 y) internal pure returns (uint256) {\n unchecked {\n if (y == 0) return 0;\n\n require (x >= 0);\n\n uint256 lo = (uint256 (int256 (x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;\n uint256 hi = uint256 (int256 (x)) * (y >> 128);\n\n require (hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n hi <<= 64;\n\n require (hi <=\n 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);\n return hi + lo;\n }\n }\n\n /**\n * Calculate x / y rounding towards zero. Revert on overflow or when y is\n * zero.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function div (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n int256 result = (int256 (x) << 64) / y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are signed 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x signed 256-bit integer number\n * @param y signed 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function divi (int256 x, int256 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n\n bool negativeResult = false;\n if (x < 0) {\n x = -x; // We rely on overflow behavior here\n negativeResult = true;\n }\n if (y < 0) {\n y = -y; // We rely on overflow behavior here\n negativeResult = !negativeResult;\n }\n uint128 absoluteResult = divuu (uint256 (x), uint256 (y));\n if (negativeResult) {\n require (absoluteResult <= 0x80000000000000000000000000000000);\n return -int128 (absoluteResult); // We rely on overflow behavior here\n } else {\n require (absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return int128 (absoluteResult); // We rely on overflow behavior here\n }\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x unsigned 256-bit integer number\n * @param y unsigned 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function divu (uint256 x, uint256 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n uint128 result = divuu (x, y);\n require (result <= uint128 (MAX_64x64));\n return int128 (result);\n }\n }\n\n /**\n * Calculate -x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function neg (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != MIN_64x64);\n return -x;\n }\n }\n\n /**\n * Calculate |x|. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function abs (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != MIN_64x64);\n return x < 0 ? -x : x;\n }\n }\n\n /**\n * Calculate 1 / x rounding towards zero. Revert on overflow or when x is\n * zero.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function inv (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != 0);\n int256 result = int256 (0x100000000000000000000000000000000) / x;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function avg (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n return int128 ((int256 (x) + int256 (y)) >> 1);\n }\n }\n\n /**\n * Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.\n * Revert on overflow or in case x * y is negative.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function gavg (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 m = int256 (x) * int256 (y);\n require (m >= 0);\n require (m <\n 0x4000000000000000000000000000000000000000000000000000000000000000);\n return int128 (sqrtu (uint256 (m)));\n }\n }\n\n /**\n * Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number\n * and y is unsigned 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y uint256 value\n * @return signed 64.64-bit fixed point number\n */\n function pow (int128 x, uint256 y) internal pure returns (int128) {\n unchecked {\n bool negative = x < 0 && y & 1 == 1;\n\n uint256 absX = uint128 (x < 0 ? -x : x);\n uint256 absResult;\n absResult = 0x100000000000000000000000000000000;\n\n if (absX <= 0x10000000000000000) {\n absX <<= 63;\n while (y != 0) {\n if (y & 0x1 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x2 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x4 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x8 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n y >>= 4;\n }\n\n absResult >>= 64;\n } else {\n uint256 absXShift = 63;\n if (absX < 0x1000000000000000000000000) { absX <<= 32; absXShift -= 32; }\n if (absX < 0x10000000000000000000000000000) { absX <<= 16; absXShift -= 16; }\n if (absX < 0x1000000000000000000000000000000) { absX <<= 8; absXShift -= 8; }\n if (absX < 0x10000000000000000000000000000000) { absX <<= 4; absXShift -= 4; }\n if (absX < 0x40000000000000000000000000000000) { absX <<= 2; absXShift -= 2; }\n if (absX < 0x80000000000000000000000000000000) { absX <<= 1; absXShift -= 1; }\n\n uint256 resultShift = 0;\n while (y != 0) {\n require (absXShift < 64);\n\n if (y & 0x1 != 0) {\n absResult = absResult * absX >> 127;\n resultShift += absXShift;\n if (absResult > 0x100000000000000000000000000000000) {\n absResult >>= 1;\n resultShift += 1;\n }\n }\n absX = absX * absX >> 127;\n absXShift <<= 1;\n if (absX >= 0x100000000000000000000000000000000) {\n absX >>= 1;\n absXShift += 1;\n }\n\n y >>= 1;\n }\n\n require (resultShift < 64);\n absResult >>= 64 - resultShift;\n }\n int256 result = negative ? -int256 (absResult) : int256 (absResult);\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate sqrt (x) rounding down. Revert if x < 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function sqrt (int128 x) internal pure returns (int128) {\n unchecked {\n require (x >= 0);\n return int128 (sqrtu (uint256 (int256 (x)) << 64));\n }\n }\n\n /**\n * Calculate binary logarithm of x. Revert if x <= 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function log_2 (int128 x) internal pure returns (int128) {\n unchecked {\n require (x > 0);\n\n int256 msb = 0;\n int256 xc = x;\n if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }\n if (xc >= 0x100000000) { xc >>= 32; msb += 32; }\n if (xc >= 0x10000) { xc >>= 16; msb += 16; }\n if (xc >= 0x100) { xc >>= 8; msb += 8; }\n if (xc >= 0x10) { xc >>= 4; msb += 4; }\n if (xc >= 0x4) { xc >>= 2; msb += 2; }\n if (xc >= 0x2) msb += 1; // No need to shift xc anymore\n\n int256 result = msb - 64 << 64;\n uint256 ux = uint256 (int256 (x)) << uint256 (127 - msb);\n for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {\n ux *= ux;\n uint256 b = ux >> 255;\n ux >>= 127 + b;\n result += bit * int256 (b);\n }\n\n return int128 (result);\n }\n }\n\n /**\n * Calculate natural logarithm of x. Revert if x <= 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function ln (int128 x) internal pure returns (int128) {\n unchecked {\n require (x > 0);\n\n return int128 (int256 (\n uint256 (int256 (log_2 (x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));\n }\n }\n\n /**\n * Calculate binary exponent of x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function exp_2 (int128 x) internal pure returns (int128) {\n unchecked {\n require (x < 0x400000000000000000); // Overflow\n\n if (x < -0x400000000000000000) return 0; // Underflow\n\n uint256 result = 0x80000000000000000000000000000000;\n\n if (x & 0x8000000000000000 > 0)\n result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;\n if (x & 0x4000000000000000 > 0)\n result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;\n if (x & 0x2000000000000000 > 0)\n result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;\n if (x & 0x1000000000000000 > 0)\n result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;\n if (x & 0x800000000000000 > 0)\n result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;\n if (x & 0x400000000000000 > 0)\n result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;\n if (x & 0x200000000000000 > 0)\n result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;\n if (x & 0x100000000000000 > 0)\n result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;\n if (x & 0x80000000000000 > 0)\n result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;\n if (x & 0x40000000000000 > 0)\n result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;\n if (x & 0x20000000000000 > 0)\n result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;\n if (x & 0x10000000000000 > 0)\n result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;\n if (x & 0x8000000000000 > 0)\n result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;\n if (x & 0x4000000000000 > 0)\n result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;\n if (x & 0x2000000000000 > 0)\n result = result * 0x1000162E525EE054754457D5995292026 >> 128;\n if (x & 0x1000000000000 > 0)\n result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;\n if (x & 0x800000000000 > 0)\n result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;\n if (x & 0x400000000000 > 0)\n result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;\n if (x & 0x200000000000 > 0)\n result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;\n if (x & 0x100000000000 > 0)\n result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;\n if (x & 0x80000000000 > 0)\n result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;\n if (x & 0x40000000000 > 0)\n result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;\n if (x & 0x20000000000 > 0)\n result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;\n if (x & 0x10000000000 > 0)\n result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;\n if (x & 0x8000000000 > 0)\n result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;\n if (x & 0x4000000000 > 0)\n result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;\n if (x & 0x2000000000 > 0)\n result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;\n if (x & 0x1000000000 > 0)\n result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;\n if (x & 0x800000000 > 0)\n result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;\n if (x & 0x400000000 > 0)\n result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;\n if (x & 0x200000000 > 0)\n result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;\n if (x & 0x100000000 > 0)\n result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;\n if (x & 0x80000000 > 0)\n result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;\n if (x & 0x40000000 > 0)\n result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;\n if (x & 0x20000000 > 0)\n result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;\n if (x & 0x10000000 > 0)\n result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;\n if (x & 0x8000000 > 0)\n result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;\n if (x & 0x4000000 > 0)\n result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;\n if (x & 0x2000000 > 0)\n result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;\n if (x & 0x1000000 > 0)\n result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;\n if (x & 0x800000 > 0)\n result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;\n if (x & 0x400000 > 0)\n result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;\n if (x & 0x200000 > 0)\n result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;\n if (x & 0x100000 > 0)\n result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;\n if (x & 0x80000 > 0)\n result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;\n if (x & 0x40000 > 0)\n result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;\n if (x & 0x20000 > 0)\n result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;\n if (x & 0x10000 > 0)\n result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;\n if (x & 0x8000 > 0)\n result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;\n if (x & 0x4000 > 0)\n result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;\n if (x & 0x2000 > 0)\n result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;\n if (x & 0x1000 > 0)\n result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;\n if (x & 0x800 > 0)\n result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;\n if (x & 0x400 > 0)\n result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;\n if (x & 0x200 > 0)\n result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;\n if (x & 0x100 > 0)\n result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;\n if (x & 0x80 > 0)\n result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;\n if (x & 0x40 > 0)\n result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;\n if (x & 0x20 > 0)\n result = result * 0x100000000000000162E42FEFA39EF366F >> 128;\n if (x & 0x10 > 0)\n result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;\n if (x & 0x8 > 0)\n result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;\n if (x & 0x4 > 0)\n result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;\n if (x & 0x2 > 0)\n result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;\n if (x & 0x1 > 0)\n result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;\n\n result >>= uint256 (int256 (63 - (x >> 64)));\n require (result <= uint256 (int256 (MAX_64x64)));\n\n return int128 (int256 (result));\n }\n }\n\n /**\n * Calculate natural exponent of x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function exp (int128 x) internal pure returns (int128) {\n unchecked {\n require (x < 0x400000000000000000); // Overflow\n\n if (x < -0x400000000000000000) return 0; // Underflow\n\n return exp_2 (\n int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x unsigned 256-bit integer number\n * @param y unsigned 256-bit integer number\n * @return unsigned 64.64-bit fixed point number\n */\n function divuu (uint256 x, uint256 y) private pure returns (uint128) {\n unchecked {\n require (y != 0);\n\n uint256 result;\n\n if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)\n result = (x << 64) / y;\n else {\n uint256 msb = 192;\n uint256 xc = x >> 192;\n if (xc >= 0x100000000) { xc >>= 32; msb += 32; }\n if (xc >= 0x10000) { xc >>= 16; msb += 16; }\n if (xc >= 0x100) { xc >>= 8; msb += 8; }\n if (xc >= 0x10) { xc >>= 4; msb += 4; }\n if (xc >= 0x4) { xc >>= 2; msb += 2; }\n if (xc >= 0x2) msb += 1; // No need to shift xc anymore\n\n result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);\n require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n\n uint256 hi = result * (y >> 128);\n uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n\n uint256 xh = x >> 192;\n uint256 xl = x << 64;\n\n if (xl < lo) xh -= 1;\n xl -= lo; // We rely on overflow behavior here\n lo = hi << 128;\n if (xl < lo) xh -= 1;\n xl -= lo; // We rely on overflow behavior here\n\n assert (xh == hi >> 128);\n\n result += xl / y;\n }\n\n require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return uint128 (result);\n }\n }\n\n /**\n * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer\n * number.\n *\n * @param x unsigned 256-bit integer number\n * @return unsigned 128-bit integer number\n */\n function sqrtu (uint256 x) private pure returns (uint128) {\n unchecked {\n if (x == 0) return 0;\n else {\n uint256 xx = x;\n uint256 r = 1;\n if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }\n if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }\n if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }\n if (xx >= 0x10000) { xx >>= 16; r <<= 8; }\n if (xx >= 0x100) { xx >>= 8; r <<= 4; }\n if (xx >= 0x10) { xx >>= 4; r <<= 2; }\n if (xx >= 0x8) { r <<= 1; }\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1; // Seven iterations should be enough\n uint256 r1 = x / r;\n return uint128 (r < r1 ? r : r1);\n }\n }\n }\n}\n"},"@solidstate/contracts/token/ERC20/metadata/IERC20Metadata.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC20 metadata interface\n */\ninterface IERC20Metadata {\n /**\n * @notice return token name\n * @return token name\n */\n function name() external view returns (string memory);\n\n /**\n * @notice return token symbol\n * @return token symbol\n */\n function symbol() external view returns (string memory);\n\n /**\n * @notice return token decimals, generally used only for display purposes\n * @return token decimals\n */\n function decimals() external view returns (uint8);\n}\n"},"contracts/staking/FeeDiscountStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nlibrary FeeDiscountStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.staking.PremiaFeeDiscount\");\r\n\r\n struct UserInfo {\r\n uint256 balance; // Balance staked by user\r\n uint64 stakePeriod; // Stake period selected by user\r\n uint64 lockedUntil; // Timestamp at which the lock ends\r\n }\r\n\r\n struct Layout {\r\n // User data with xPREMIA balance staked and date at which lock ends\r\n mapping(address => UserInfo) userInfo;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n}\r\n"},"contracts/libraries/OptionMath.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\n\r\nlibrary OptionMath {\r\n using ABDKMath64x64 for int128;\r\n\r\n struct QuoteArgs {\r\n int128 varianceAnnualized64x64; // 64x64 fixed point representation of annualized variance\r\n int128 strike64x64; // 64x64 fixed point representation of strike price\r\n int128 spot64x64; // 64x64 fixed point representation of spot price\r\n int128 timeToMaturity64x64; // 64x64 fixed point representation of duration of option contract (in years)\r\n int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level of Pool before purchase\r\n int128 oldPoolState; // 64x64 fixed point representation of current state of the pool\r\n int128 newPoolState; // 64x64 fixed point representation of state of the pool after trade\r\n int128 steepness64x64; // 64x64 fixed point representation of Pool state delta multiplier\r\n int128 minAPY64x64; // 64x64 fixed point representation of minimum APY for capital locked up to underwrite options\r\n bool isCall; // whether to price \"call\" or \"put\" option\r\n }\r\n\r\n struct CalculateCLevelDecayArgs {\r\n int128 timeIntervalsElapsed64x64; // 64x64 fixed point representation of quantity of discrete arbitrary intervals elapsed since last update\r\n int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level prior to accounting for decay\r\n int128 utilization64x64; // 64x64 fixed point representation of pool capital utilization rate\r\n int128 utilizationLowerBound64x64;\r\n int128 utilizationUpperBound64x64;\r\n int128 cLevelLowerBound64x64;\r\n int128 cLevelUpperBound64x64;\r\n int128 cConvergenceULowerBound64x64;\r\n int128 cConvergenceUUpperBound64x64;\r\n }\r\n\r\n // 64x64 fixed point integer constants\r\n int128 internal constant ONE_64x64 = 0x10000000000000000;\r\n int128 internal constant THREE_64x64 = 0x30000000000000000;\r\n\r\n // 64x64 fixed point constants used in Choudhury’s approximation of the Black-Scholes CDF\r\n int128 private constant CDF_CONST_0 = 0x09109f285df452394; // 2260 / 3989\r\n int128 private constant CDF_CONST_1 = 0x19abac0ea1da65036; // 6400 / 3989\r\n int128 private constant CDF_CONST_2 = 0x0d3c84b78b749bd6b; // 3300 / 3989\r\n\r\n /**\r\n * @notice recalculate C-Level based on change in liquidity\r\n * @param initialCLevel64x64 64x64 fixed point representation of C-Level of Pool before update\r\n * @param oldPoolState64x64 64x64 fixed point representation of liquidity in pool before update\r\n * @param newPoolState64x64 64x64 fixed point representation of liquidity in pool after update\r\n * @param steepness64x64 64x64 fixed point representation of steepness coefficient\r\n * @return 64x64 fixed point representation of new C-Level\r\n */\r\n function calculateCLevel(\r\n int128 initialCLevel64x64,\r\n int128 oldPoolState64x64,\r\n int128 newPoolState64x64,\r\n int128 steepness64x64\r\n ) external pure returns (int128) {\r\n return\r\n newPoolState64x64\r\n .sub(oldPoolState64x64)\r\n .div(\r\n oldPoolState64x64 > newPoolState64x64\r\n ? oldPoolState64x64\r\n : newPoolState64x64\r\n )\r\n .mul(steepness64x64)\r\n .neg()\r\n .exp()\r\n .mul(initialCLevel64x64);\r\n }\r\n\r\n /**\r\n * @notice calculate the price of an option using the Premia Finance model\r\n * @param args arguments of quotePrice\r\n * @return premiaPrice64x64 64x64 fixed point representation of Premia option price\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after purchase\r\n */\r\n function quotePrice(QuoteArgs memory args)\r\n external\r\n pure\r\n returns (\r\n int128 premiaPrice64x64,\r\n int128 cLevel64x64,\r\n int128 slippageCoefficient64x64\r\n )\r\n {\r\n int128 deltaPoolState64x64 = args\r\n .newPoolState\r\n .sub(args.oldPoolState)\r\n .div(args.oldPoolState)\r\n .mul(args.steepness64x64);\r\n int128 tradingDelta64x64 = deltaPoolState64x64.neg().exp();\r\n\r\n int128 blackScholesPrice64x64 = _blackScholesPrice(\r\n args.varianceAnnualized64x64,\r\n args.strike64x64,\r\n args.spot64x64,\r\n args.timeToMaturity64x64,\r\n args.isCall\r\n );\r\n\r\n cLevel64x64 = tradingDelta64x64.mul(args.oldCLevel64x64);\r\n slippageCoefficient64x64 = ONE_64x64.sub(tradingDelta64x64).div(\r\n deltaPoolState64x64\r\n );\r\n\r\n premiaPrice64x64 = blackScholesPrice64x64.mul(cLevel64x64).mul(\r\n slippageCoefficient64x64\r\n );\r\n\r\n int128 intrinsicValue64x64;\r\n\r\n if (args.isCall && args.strike64x64 < args.spot64x64) {\r\n intrinsicValue64x64 = args.spot64x64.sub(args.strike64x64);\r\n } else if (!args.isCall && args.strike64x64 > args.spot64x64) {\r\n intrinsicValue64x64 = args.strike64x64.sub(args.spot64x64);\r\n }\r\n\r\n int128 collateralValue64x64 = args.isCall\r\n ? args.spot64x64\r\n : args.strike64x64;\r\n\r\n int128 minPrice64x64 = intrinsicValue64x64.add(\r\n collateralValue64x64.mul(args.minAPY64x64).mul(\r\n args.timeToMaturity64x64\r\n )\r\n );\r\n\r\n if (minPrice64x64 > premiaPrice64x64) {\r\n premiaPrice64x64 = minPrice64x64;\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate the decay of C-Level based on heat diffusion function\r\n * @param args structured CalculateCLevelDecayArgs\r\n * @return cLevelDecayed64x64 C-Level after accounting for decay\r\n */\r\n function calculateCLevelDecay(CalculateCLevelDecayArgs memory args)\r\n external\r\n pure\r\n returns (int128 cLevelDecayed64x64)\r\n {\r\n int128 convFHighU64x64 = (args.utilization64x64 >=\r\n args.utilizationUpperBound64x64 &&\r\n args.oldCLevel64x64 <= args.cLevelLowerBound64x64)\r\n ? ONE_64x64\r\n : int128(0);\r\n\r\n int128 convFLowU64x64 = (args.utilization64x64 <=\r\n args.utilizationLowerBound64x64 &&\r\n args.oldCLevel64x64 >= args.cLevelUpperBound64x64)\r\n ? ONE_64x64\r\n : int128(0);\r\n\r\n cLevelDecayed64x64 = args\r\n .oldCLevel64x64\r\n .sub(args.cConvergenceULowerBound64x64.mul(convFLowU64x64))\r\n .sub(args.cConvergenceUUpperBound64x64.mul(convFHighU64x64))\r\n .mul(\r\n convFLowU64x64\r\n .mul(ONE_64x64.sub(args.utilization64x64))\r\n .add(convFHighU64x64.mul(args.utilization64x64))\r\n .mul(args.timeIntervalsElapsed64x64)\r\n .neg()\r\n .exp()\r\n )\r\n .add(\r\n args.cConvergenceULowerBound64x64.mul(convFLowU64x64).add(\r\n args.cConvergenceUUpperBound64x64.mul(convFHighU64x64)\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate the exponential decay coefficient for a given interval\r\n * @param oldTimestamp timestamp of previous update\r\n * @param newTimestamp current timestamp\r\n * @return 64x64 fixed point representation of exponential decay coefficient\r\n */\r\n function _decay(uint256 oldTimestamp, uint256 newTimestamp)\r\n internal\r\n pure\r\n returns (int128)\r\n {\r\n return\r\n ONE_64x64.sub(\r\n (-ABDKMath64x64.divu(newTimestamp - oldTimestamp, 7 days)).exp()\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate Choudhury’s approximation of the Black-Scholes CDF\r\n * @param input64x64 64x64 fixed point representation of random variable\r\n * @return 64x64 fixed point representation of the approximated CDF of x\r\n */\r\n function _N(int128 input64x64) internal pure returns (int128) {\r\n // squaring via mul is cheaper than via pow\r\n int128 inputSquared64x64 = input64x64.mul(input64x64);\r\n\r\n int128 value64x64 = (-inputSquared64x64 >> 1).exp().div(\r\n CDF_CONST_0.add(CDF_CONST_1.mul(input64x64.abs())).add(\r\n CDF_CONST_2.mul(inputSquared64x64.add(THREE_64x64).sqrt())\r\n )\r\n );\r\n\r\n return input64x64 > 0 ? ONE_64x64.sub(value64x64) : value64x64;\r\n }\r\n\r\n /**\r\n * @notice calculate the price of an option using the Black-Scholes model\r\n * @param varianceAnnualized64x64 64x64 fixed point representation of annualized variance\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param spot64x64 64x64 fixed point representation of spot price\r\n * @param timeToMaturity64x64 64x64 fixed point representation of duration of option contract (in years)\r\n * @param isCall whether to price \"call\" or \"put\" option\r\n * @return 64x64 fixed point representation of Black-Scholes option price\r\n */\r\n function _blackScholesPrice(\r\n int128 varianceAnnualized64x64,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) internal pure returns (int128) {\r\n int128 cumulativeVariance64x64 = timeToMaturity64x64.mul(\r\n varianceAnnualized64x64\r\n );\r\n int128 cumulativeVarianceSqrt64x64 = cumulativeVariance64x64.sqrt();\r\n\r\n int128 d1_64x64 = spot64x64\r\n .div(strike64x64)\r\n .ln()\r\n .add(cumulativeVariance64x64 >> 1)\r\n .div(cumulativeVarianceSqrt64x64);\r\n int128 d2_64x64 = d1_64x64.sub(cumulativeVarianceSqrt64x64);\r\n\r\n if (isCall) {\r\n return\r\n spot64x64.mul(_N(d1_64x64)).sub(strike64x64.mul(_N(d2_64x64)));\r\n } else {\r\n return\r\n -spot64x64.mul(_N(-d1_64x64)).sub(\r\n strike64x64.mul(_N(-d2_64x64))\r\n );\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/access/IERC173.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Contract ownership standard interface\n * @dev see https://eips.ethereum.org/EIPS/eip-173\n */\ninterface IERC173 {\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n\n /**\n * @notice get the ERC173 contract owner\n * @return conract owner\n */\n function owner() external view returns (address);\n\n /**\n * @notice transfer contract ownership to new account\n * @param account address of new owner\n */\n function transferOwnership(address account) external;\n}\n"},"contracts/libraries/ABDKMath64x64Token.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\n\r\nlibrary ABDKMath64x64Token {\r\n using ABDKMath64x64 for int128;\r\n\r\n /**\r\n * @notice convert 64x64 fixed point representation of token amount to decimal\r\n * @param value64x64 64x64 fixed point representation of token amount\r\n * @param decimals token display decimals\r\n * @return value decimal representation of token amount\r\n */\r\n function toDecimals(int128 value64x64, uint8 decimals)\r\n internal\r\n pure\r\n returns (uint256 value)\r\n {\r\n value = value64x64.mulu(10**decimals);\r\n }\r\n\r\n /**\r\n * @notice convert decimal representation of token amount to 64x64 fixed point\r\n * @param value decimal representation of token amount\r\n * @param decimals token display decimals\r\n * @return value64x64 64x64 fixed point representation of token amount\r\n */\r\n function fromDecimals(uint256 value, uint8 decimals)\r\n internal\r\n pure\r\n returns (int128 value64x64)\r\n {\r\n value64x64 = ABDKMath64x64.divu(value, 10**decimals);\r\n }\r\n\r\n /**\r\n * @notice convert 64x64 fixed point representation of token amount to wei (18 decimals)\r\n * @param value64x64 64x64 fixed point representation of token amount\r\n * @return value wei representation of token amount\r\n */\r\n function toWei(int128 value64x64) internal pure returns (uint256 value) {\r\n value = toDecimals(value64x64, 18);\r\n }\r\n\r\n /**\r\n * @notice convert wei representation (18 decimals) of token amount to 64x64 fixed point\r\n * @param value wei representation of token amount\r\n * @return value64x64 64x64 fixed point representation of token amount\r\n */\r\n function fromWei(uint256 value) internal pure returns (int128 value64x64) {\r\n value64x64 = fromDecimals(value, 18);\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/IERC1155.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC1155Internal } from './IERC1155Internal.sol';\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @notice ERC1155 interface\n * @dev see https://github.com/ethereum/EIPs/issues/1155\n */\ninterface IERC1155 is IERC1155Internal, IERC165 {\n /**\n * @notice query the balance of given token held by given address\n * @param account address to query\n * @param id token to query\n * @return token balance\n */\n function balanceOf(address account, uint256 id)\n external\n view\n returns (uint256);\n\n /**\n * @notice query the balances of given tokens held by given addresses\n * @param accounts addresss to query\n * @param ids tokens to query\n * @return token balances\n */\n function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)\n external\n view\n returns (uint256[] memory);\n\n /**\n * @notice query approval status of given operator with respect to given address\n * @param account address to query for approval granted\n * @param operator address to query for approval received\n * @return whether operator is approved to spend tokens held by account\n */\n function isApprovedForAll(address account, address operator)\n external\n view\n returns (bool);\n\n /**\n * @notice grant approval to or revoke approval from given operator to spend held tokens\n * @param operator address whose approval status to update\n * @param status whether operator should be considered approved\n */\n function setApprovalForAll(address operator, bool status) external;\n\n /**\n * @notice transfer tokens between given addresses, checking for ERC1155Receiver implementation if applicable\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes calldata data\n ) external;\n\n /**\n * @notice transfer batch of tokens between given addresses, checking for ERC1155Receiver implementation if applicable\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to transfer\n * @param data data payload\n */\n function safeBatchTransferFrom(\n address from,\n address to,\n uint256[] calldata ids,\n uint256[] calldata amounts,\n bytes calldata data\n ) external;\n}\n"},"contracts/staking/IFeeDiscount.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {FeeDiscountStorage} from \"./FeeDiscountStorage.sol\";\r\n\r\ninterface IFeeDiscount {\r\n event Staked(\r\n address indexed user,\r\n uint256 amount,\r\n uint256 stakePeriod,\r\n uint256 lockedUntil\r\n );\r\n event Unstaked(address indexed user, uint256 amount);\r\n\r\n struct StakeLevel {\r\n uint256 amount; // Amount to stake\r\n uint256 discount; // Discount when amount is reached\r\n }\r\n\r\n /**\r\n * @notice Stake using IERC2612 permit\r\n * @param amount The amount of xPremia to stake\r\n * @param period The lockup period (in seconds)\r\n * @param deadline Deadline after which permit will fail\r\n * @param v V\r\n * @param r R\r\n * @param s S\r\n */\r\n function stakeWithPermit(\r\n uint256 amount,\r\n uint256 period,\r\n uint256 deadline,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external;\r\n\r\n /**\r\n * @notice Lockup xPremia for protocol fee discounts\r\n * Longer period of locking will apply a multiplier on the amount staked, in the fee discount calculation\r\n * @param amount The amount of xPremia to stake\r\n * @param period The lockup period (in seconds)\r\n */\r\n function stake(uint256 amount, uint256 period) external;\r\n\r\n /**\r\n * @notice Unstake xPremia (If lockup period has ended)\r\n * @param amount The amount of xPremia to unstake\r\n */\r\n function unstake(uint256 amount) external;\r\n\r\n //////////\r\n // View //\r\n //////////\r\n\r\n /**\r\n * Calculate the stake amount of a user, after applying the bonus from the lockup period chosen\r\n * @param user The user from which to query the stake amount\r\n * @return The user stake amount after applying the bonus\r\n */\r\n function getStakeAmountWithBonus(address user)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n /**\r\n * @notice Calculate the % of fee discount for user, based on his stake\r\n * @param user The _user for which the discount is for\r\n * @return Percentage of protocol fee discount (in basis point)\r\n * Ex : 1000 = 10% fee discount\r\n */\r\n function getDiscount(address user) external view returns (uint256);\r\n\r\n /**\r\n * @notice Get stake levels\r\n * @return Stake levels\r\n * Ex : 2500 = -25%\r\n */\r\n function getStakeLevels() external returns (StakeLevel[] memory);\r\n\r\n /**\r\n * @notice Get stake period multiplier\r\n * @param period The duration (in seconds) for which tokens are locked\r\n * @return The multiplier for this staking period\r\n * Ex : 20000 = x2\r\n */\r\n function getStakePeriodMultiplier(uint256 period)\r\n external\r\n returns (uint256);\r\n\r\n /**\r\n * @notice Get staking infos of a user\r\n * @param user The user address for which to get staking infos\r\n * @return The staking infos of the user\r\n */\r\n function getUserInfo(address user)\r\n external\r\n view\r\n returns (FeeDiscountStorage.UserInfo memory);\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/base/ERC1155BaseInternal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { AddressUtils } from '../../../utils/AddressUtils.sol';\nimport { IERC1155Internal } from '../IERC1155Internal.sol';\nimport { IERC1155Receiver } from '../IERC1155Receiver.sol';\nimport { ERC1155BaseStorage } from './ERC1155BaseStorage.sol';\n\n/**\n * @title Base ERC1155 internal functions\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)\n */\nabstract contract ERC1155BaseInternal is IERC1155Internal {\n using AddressUtils for address;\n\n /**\n * @notice query the balance of given token held by given address\n * @param account address to query\n * @param id token to query\n * @return token balance\n */\n function _balanceOf(address account, uint256 id)\n internal\n view\n virtual\n returns (uint256)\n {\n require(\n account != address(0),\n 'ERC1155: balance query for the zero address'\n );\n return ERC1155BaseStorage.layout().balances[id][account];\n }\n\n /**\n * @notice mint given quantity of tokens for given address\n * @dev ERC1155Receiver implementation is not checked\n * @param account beneficiary of minting\n * @param id token ID\n * @param amount quantity of tokens to mint\n * @param data data payload\n */\n function _mint(\n address account,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n require(account != address(0), 'ERC1155: mint to the zero address');\n\n _beforeTokenTransfer(\n msg.sender,\n address(0),\n account,\n _asSingletonArray(id),\n _asSingletonArray(amount),\n data\n );\n\n mapping(address => uint256) storage balances = ERC1155BaseStorage\n .layout()\n .balances[id];\n balances[account] += amount;\n\n emit TransferSingle(msg.sender, address(0), account, id, amount);\n }\n\n /**\n * @notice mint given quantity of tokens for given address\n * @param account beneficiary of minting\n * @param id token ID\n * @param amount quantity of tokens to mint\n * @param data data payload\n */\n function _safeMint(\n address account,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n _mint(account, id, amount, data);\n\n _doSafeTransferAcceptanceCheck(\n msg.sender,\n address(0),\n account,\n id,\n amount,\n data\n );\n }\n\n /**\n * @notice mint batch of tokens for given address\n * @dev ERC1155Receiver implementation is not checked\n * @param account beneficiary of minting\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to mint\n * @param data data payload\n */\n function _mintBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n require(account != address(0), 'ERC1155: mint to the zero address');\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(\n msg.sender,\n address(0),\n account,\n ids,\n amounts,\n data\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n for (uint256 i; i < ids.length; i++) {\n balances[ids[i]][account] += amounts[i];\n }\n\n emit TransferBatch(msg.sender, address(0), account, ids, amounts);\n }\n\n /**\n * @notice mint batch of tokens for given address\n * @param account beneficiary of minting\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to mint\n * @param data data payload\n */\n function _safeMintBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n _mintBatch(account, ids, amounts, data);\n\n _doSafeBatchTransferAcceptanceCheck(\n msg.sender,\n address(0),\n account,\n ids,\n amounts,\n data\n );\n }\n\n /**\n * @notice burn given quantity of tokens held by given address\n * @param account holder of tokens to burn\n * @param id token ID\n * @param amount quantity of tokens to burn\n */\n function _burn(\n address account,\n uint256 id,\n uint256 amount\n ) internal virtual {\n require(account != address(0), 'ERC1155: burn from the zero address');\n\n _beforeTokenTransfer(\n msg.sender,\n account,\n address(0),\n _asSingletonArray(id),\n _asSingletonArray(amount),\n ''\n );\n\n mapping(address => uint256) storage balances = ERC1155BaseStorage\n .layout()\n .balances[id];\n\n unchecked {\n require(\n balances[account] >= amount,\n 'ERC1155: burn amount exceeds balances'\n );\n balances[account] -= amount;\n }\n\n emit TransferSingle(msg.sender, account, address(0), id, amount);\n }\n\n /**\n * @notice burn given batch of tokens held by given address\n * @param account holder of tokens to burn\n * @param ids token IDs\n * @param amounts quantities of tokens to burn\n */\n function _burnBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts\n ) internal virtual {\n require(account != address(0), 'ERC1155: burn from the zero address');\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(msg.sender, account, address(0), ids, amounts, '');\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n unchecked {\n for (uint256 i; i < ids.length; i++) {\n uint256 id = ids[i];\n require(\n balances[id][account] >= amounts[i],\n 'ERC1155: burn amount exceeds balance'\n );\n balances[id][account] -= amounts[i];\n }\n }\n\n emit TransferBatch(msg.sender, account, address(0), ids, amounts);\n }\n\n /**\n * @notice transfer tokens between given addresses\n * @dev ERC1155Receiver implementation is not checked\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _transfer(\n address operator,\n address sender,\n address recipient,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n require(\n recipient != address(0),\n 'ERC1155: transfer to the zero address'\n );\n\n _beforeTokenTransfer(\n operator,\n sender,\n recipient,\n _asSingletonArray(id),\n _asSingletonArray(amount),\n data\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n unchecked {\n uint256 senderBalance = balances[id][sender];\n require(\n senderBalance >= amount,\n 'ERC1155: insufficient balances for transfer'\n );\n balances[id][sender] = senderBalance - amount;\n }\n\n balances[id][recipient] += amount;\n\n emit TransferSingle(operator, sender, recipient, id, amount);\n }\n\n /**\n * @notice transfer tokens between given addresses\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _safeTransfer(\n address operator,\n address sender,\n address recipient,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n _transfer(operator, sender, recipient, id, amount, data);\n\n _doSafeTransferAcceptanceCheck(\n operator,\n sender,\n recipient,\n id,\n amount,\n data\n );\n }\n\n /**\n * @notice transfer batch of tokens between given addresses\n * @dev ERC1155Receiver implementation is not checked\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _transferBatch(\n address operator,\n address sender,\n address recipient,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n require(\n recipient != address(0),\n 'ERC1155: transfer to the zero address'\n );\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(operator, sender, recipient, ids, amounts, data);\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n for (uint256 i; i < ids.length; i++) {\n uint256 token = ids[i];\n uint256 amount = amounts[i];\n\n unchecked {\n uint256 senderBalance = balances[token][sender];\n require(\n senderBalance >= amount,\n 'ERC1155: insufficient balances for transfer'\n );\n balances[token][sender] = senderBalance - amount;\n }\n\n balances[token][recipient] += amount;\n }\n\n emit TransferBatch(operator, sender, recipient, ids, amounts);\n }\n\n /**\n * @notice transfer batch of tokens between given addresses\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _safeTransferBatch(\n address operator,\n address sender,\n address recipient,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n _transferBatch(operator, sender, recipient, ids, amounts, data);\n\n _doSafeBatchTransferAcceptanceCheck(\n operator,\n sender,\n recipient,\n ids,\n amounts,\n data\n );\n }\n\n /**\n * @notice wrap given element in array of length 1\n * @param element element to wrap\n * @return singleton array\n */\n function _asSingletonArray(uint256 element)\n private\n pure\n returns (uint256[] memory)\n {\n uint256[] memory array = new uint256[](1);\n array[0] = element;\n return array;\n }\n\n /**\n * @notice revert if applicable transfer recipient is not valid ERC1155Receiver\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _doSafeTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) private {\n if (to.isContract()) {\n try\n IERC1155Receiver(to).onERC1155Received(\n operator,\n from,\n id,\n amount,\n data\n )\n returns (bytes4 response) {\n require(\n response == IERC1155Receiver.onERC1155Received.selector,\n 'ERC1155: ERC1155Receiver rejected tokens'\n );\n } catch Error(string memory reason) {\n revert(reason);\n } catch {\n revert('ERC1155: transfer to non ERC1155Receiver implementer');\n }\n }\n }\n\n /**\n * @notice revert if applicable transfer recipient is not valid ERC1155Receiver\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _doSafeBatchTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) private {\n if (to.isContract()) {\n try\n IERC1155Receiver(to).onERC1155BatchReceived(\n operator,\n from,\n ids,\n amounts,\n data\n )\n returns (bytes4 response) {\n require(\n response ==\n IERC1155Receiver.onERC1155BatchReceived.selector,\n 'ERC1155: ERC1155Receiver rejected tokens'\n );\n } catch Error(string memory reason) {\n revert(reason);\n } catch {\n revert('ERC1155: transfer to non ERC1155Receiver implementer');\n }\n }\n }\n\n /**\n * @notice ERC1155 hook, called before all transfers including mint and burn\n * @dev function should be overridden and new implementation must call super\n * @dev called for both single and batch transfers\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {}\n}\n"},"@solidstate/contracts/token/ERC20/IERC20Internal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Partial ERC20 interface needed by internal functions\n */\ninterface IERC20Internal {\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n}\n"},"@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorInterface {\n function latestAnswer()\n external\n view\n returns (\n int256\n );\n \n function latestTimestamp()\n external\n view\n returns (\n uint256\n );\n\n function latestRound()\n external\n view\n returns (\n uint256\n );\n\n function getAnswer(\n uint256 roundId\n )\n external\n view\n returns (\n int256\n );\n\n function getTimestamp(\n uint256 roundId\n )\n external\n view\n returns (\n uint256\n );\n\n event AnswerUpdated(\n int256 indexed current,\n uint256 indexed roundId,\n uint256 updatedAt\n );\n\n event NewRound(\n uint256 indexed roundId,\n address indexed startedBy,\n uint256 startedAt\n );\n}\n"},"contracts/pool/PoolExercise.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ERC1155BaseStorage} from \"@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol\";\r\n\r\nimport {PoolInternal} from \"./PoolInternal.sol\";\r\nimport {IPoolExercise} from \"./IPoolExercise.sol\";\r\n\r\n/**\r\n * @title Premia option pool\r\n * @dev deployed standalone and referenced by PoolProxy\r\n */\r\ncontract PoolExercise is IPoolExercise, PoolInternal {\r\n constructor(\r\n address ivolOracle,\r\n address weth,\r\n address premiaMining,\r\n address feeReceiver,\r\n address feeDiscountAddress,\r\n int128 fee64x64\r\n )\r\n PoolInternal(\r\n ivolOracle,\r\n weth,\r\n premiaMining,\r\n feeReceiver,\r\n feeDiscountAddress,\r\n fee64x64\r\n )\r\n {}\r\n\r\n /**\r\n * @inheritdoc IPoolExercise\r\n */\r\n function exerciseFrom(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) external override {\r\n if (msg.sender != holder) {\r\n require(\r\n ERC1155BaseStorage.layout().operatorApprovals[holder][\r\n msg.sender\r\n ],\r\n \"not approved\"\r\n );\r\n }\r\n\r\n _exercise(holder, longTokenId, contractSize);\r\n }\r\n\r\n /**\r\n * @inheritdoc IPoolExercise\r\n */\r\n function processExpired(uint256 longTokenId, uint256 contractSize)\r\n external\r\n override\r\n {\r\n _exercise(address(0), longTokenId, contractSize);\r\n }\r\n}\r\n"},"contracts/pool/IPoolExercise.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\n/**\r\n * @notice Pool interface for exercising and processing of expired options\r\n */\r\ninterface IPoolExercise {\r\n /**\r\n * @notice exercise option on behalf of holder\r\n * @param holder owner of long option tokens to exercise\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to exercise\r\n */\r\n function exerciseFrom(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) external;\r\n\r\n /**\r\n * @notice process expired option, freeing liquidity and distributing profits\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to process\r\n */\r\n function processExpired(uint256 longTokenId, uint256 contractSize) external;\r\n}\r\n"},"contracts/pool/PoolStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {AggregatorInterface} from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol\";\r\nimport {AggregatorV3Interface} from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\r\nimport {EnumerableSet, ERC1155EnumerableStorage} from \"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol\";\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\nimport {ABDKMath64x64Token} from \"../libraries/ABDKMath64x64Token.sol\";\r\nimport {OptionMath} from \"../libraries/OptionMath.sol\";\r\n\r\nlibrary PoolStorage {\r\n using ABDKMath64x64 for int128;\r\n using PoolStorage for PoolStorage.Layout;\r\n\r\n enum TokenType {\r\n UNDERLYING_FREE_LIQ,\r\n BASE_FREE_LIQ,\r\n UNDERLYING_RESERVED_LIQ,\r\n BASE_RESERVED_LIQ,\r\n LONG_CALL,\r\n SHORT_CALL,\r\n LONG_PUT,\r\n SHORT_PUT\r\n }\r\n\r\n struct PoolSettings {\r\n address underlying;\r\n address base;\r\n address underlyingOracle;\r\n address baseOracle;\r\n }\r\n\r\n struct QuoteArgsInternal {\r\n address feePayer; // address of the fee payer\r\n uint64 maturity; // timestamp of option maturity\r\n int128 strike64x64; // 64x64 fixed point representation of strike price\r\n int128 spot64x64; // 64x64 fixed point representation of spot price\r\n uint256 contractSize; // size of option contract\r\n bool isCall; // true for call, false for put\r\n }\r\n\r\n struct QuoteResultInternal {\r\n int128 baseCost64x64; // 64x64 fixed point representation of option cost denominated in underlying currency (without fee)\r\n int128 feeCost64x64; // 64x64 fixed point representation of option fee cost denominated in underlying currency for call, or base currency for put\r\n int128 cLevel64x64; // 64x64 fixed point representation of C-Level of Pool after purchase\r\n int128 slippageCoefficient64x64; // 64x64 fixed point representation of slippage coefficient for given order size\r\n }\r\n\r\n struct BatchData {\r\n uint256 eta;\r\n uint256 totalPendingDeposits;\r\n }\r\n\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.Pool\");\r\n\r\n uint256 private constant C_DECAY_BUFFER = 12 hours;\r\n uint256 private constant C_DECAY_INTERVAL = 4 hours;\r\n\r\n struct Layout {\r\n // ERC20 token addresses\r\n address base;\r\n address underlying;\r\n // AggregatorV3Interface oracle addresses\r\n address baseOracle;\r\n address underlyingOracle;\r\n // token metadata\r\n uint8 underlyingDecimals;\r\n uint8 baseDecimals;\r\n // minimum amounts\r\n uint256 baseMinimum;\r\n uint256 underlyingMinimum;\r\n // deposit caps\r\n uint256 basePoolCap;\r\n uint256 underlyingPoolCap;\r\n // market state\r\n int128 _deprecated_steepness64x64;\r\n int128 cLevelBase64x64;\r\n int128 cLevelUnderlying64x64;\r\n uint256 cLevelBaseUpdatedAt;\r\n uint256 cLevelUnderlyingUpdatedAt;\r\n uint256 updatedAt;\r\n // User -> isCall -> depositedAt\r\n mapping(address => mapping(bool => uint256)) depositedAt;\r\n mapping(address => mapping(bool => uint256)) divestmentTimestamps;\r\n // doubly linked list of free liquidity intervals\r\n // isCall -> User -> User\r\n mapping(bool => mapping(address => address)) liquidityQueueAscending;\r\n mapping(bool => mapping(address => address)) liquidityQueueDescending;\r\n // minimum resolution price bucket => price\r\n mapping(uint256 => int128) bucketPrices64x64;\r\n // sequence id (minimum resolution price bucket / 256) => price update sequence\r\n mapping(uint256 => uint256) priceUpdateSequences;\r\n // isCall -> batch data\r\n mapping(bool => BatchData) nextDeposits;\r\n // user -> batch timestamp -> isCall -> pending amount\r\n mapping(address => mapping(uint256 => mapping(bool => uint256))) pendingDeposits;\r\n EnumerableSet.UintSet tokenIds;\r\n // user -> isCallPool -> total value locked of user (Used for liquidity mining)\r\n mapping(address => mapping(bool => uint256)) userTVL;\r\n // isCallPool -> total value locked\r\n mapping(bool => uint256) totalTVL;\r\n // steepness values\r\n int128 steepnessBase64x64;\r\n int128 steepnessUnderlying64x64;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate ERC1155 token id for given option parameters\r\n * @param tokenType TokenType enum\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @return tokenId token id\r\n */\r\n function formatTokenId(\r\n TokenType tokenType,\r\n uint64 maturity,\r\n int128 strike64x64\r\n ) internal pure returns (uint256 tokenId) {\r\n tokenId =\r\n (uint256(tokenType) << 248) +\r\n (uint256(maturity) << 128) +\r\n uint256(int256(strike64x64));\r\n }\r\n\r\n /**\r\n * @notice derive option maturity and strike price from ERC1155 token id\r\n * @param tokenId token id\r\n * @return tokenType TokenType enum\r\n * @return maturity timestamp of option maturity\r\n * @return strike64x64 option strike price\r\n */\r\n function parseTokenId(uint256 tokenId)\r\n internal\r\n pure\r\n returns (\r\n TokenType tokenType,\r\n uint64 maturity,\r\n int128 strike64x64\r\n )\r\n {\r\n assembly {\r\n tokenType := shr(248, tokenId)\r\n maturity := shr(128, tokenId)\r\n strike64x64 := tokenId\r\n }\r\n }\r\n\r\n function getTokenDecimals(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint8 decimals)\r\n {\r\n decimals = isCall ? l.underlyingDecimals : l.baseDecimals;\r\n }\r\n\r\n /**\r\n * @notice get the total supply of free liquidity tokens, minus pending deposits\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return 64x64 fixed point representation of total free liquidity\r\n */\r\n function totalFreeLiquiditySupply64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n uint256 tokenId = formatTokenId(\r\n isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n return\r\n ABDKMath64x64Token.fromDecimals(\r\n ERC1155EnumerableStorage.layout().totalSupply[tokenId] -\r\n l.nextDeposits[isCall].totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n );\r\n }\r\n\r\n function getReinvestmentStatus(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal view returns (bool) {\r\n uint256 timestamp = l.divestmentTimestamps[account][isCallPool];\r\n return timestamp == 0 || timestamp > block.timestamp;\r\n }\r\n\r\n function addUnderwriter(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal {\r\n require(account != address(0));\r\n\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n if (_isInQueue(account, asc, desc)) return;\r\n\r\n address last = desc[address(0)];\r\n\r\n asc[last] = account;\r\n desc[account] = last;\r\n desc[address(0)] = account;\r\n }\r\n\r\n function removeUnderwriter(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal {\r\n require(account != address(0));\r\n\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n if (!_isInQueue(account, asc, desc)) return;\r\n\r\n address prev = desc[account];\r\n address next = asc[account];\r\n asc[prev] = next;\r\n desc[next] = prev;\r\n delete asc[account];\r\n delete desc[account];\r\n }\r\n\r\n function isInQueue(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal view returns (bool) {\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n return _isInQueue(account, asc, desc);\r\n }\r\n\r\n function _isInQueue(\r\n address account,\r\n mapping(address => address) storage asc,\r\n mapping(address => address) storage desc\r\n ) private view returns (bool) {\r\n return asc[account] != address(0) || desc[address(0)] == account;\r\n }\r\n\r\n /**\r\n * @notice get current C-Level, without accounting for pending adjustments\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function getRawCLevel64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128 cLevel64x64)\r\n {\r\n cLevel64x64 = isCall ? l.cLevelUnderlying64x64 : l.cLevelBase64x64;\r\n }\r\n\r\n /**\r\n * @notice get current C-Level, accounting for unrealized decay\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function getDecayAdjustedCLevel64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128 cLevel64x64)\r\n {\r\n // get raw C-Level from storage\r\n cLevel64x64 = l.getRawCLevel64x64(isCall);\r\n\r\n // account for C-Level decay\r\n cLevel64x64 = l.applyCLevelDecayAdjustment(cLevel64x64, isCall);\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for unrealized decay\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for decay\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after accounting for decay\r\n */\r\n function applyCLevelDecayAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n bool isCall\r\n ) internal view returns (int128 cLevel64x64) {\r\n uint256 timeElapsed = block.timestamp -\r\n (isCall ? l.cLevelUnderlyingUpdatedAt : l.cLevelBaseUpdatedAt);\r\n\r\n // do not apply C decay if less than 24 hours have elapsed\r\n\r\n if (timeElapsed > C_DECAY_BUFFER) {\r\n timeElapsed -= C_DECAY_BUFFER;\r\n } else {\r\n return oldCLevel64x64;\r\n }\r\n\r\n int128 timeIntervalsElapsed64x64 = ABDKMath64x64.divu(\r\n timeElapsed,\r\n C_DECAY_INTERVAL\r\n );\r\n\r\n uint256 tokenId = formatTokenId(\r\n isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n uint256 tvl = l.totalTVL[isCall];\r\n\r\n int128 utilization = ABDKMath64x64.divu(\r\n tvl -\r\n (ERC1155EnumerableStorage.layout().totalSupply[tokenId] -\r\n l.nextDeposits[isCall].totalPendingDeposits),\r\n tvl\r\n );\r\n\r\n return\r\n OptionMath.calculateCLevelDecay(\r\n OptionMath.CalculateCLevelDecayArgs(\r\n timeIntervalsElapsed64x64,\r\n oldCLevel64x64,\r\n utilization,\r\n 0xb333333333333333, // 0.7\r\n 0xe666666666666666, // 0.9\r\n 0x10000000000000000, // 1.0\r\n 0x10000000000000000, // 1.0\r\n 0xe666666666666666, // 0.9\r\n 0x56fc2a2c515da32ea // 2e\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for pending deposits\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change\r\n * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity\r\n * @param isCall whether to update C-Level for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n * @return liquidity64x64 64x64 fixed point representation of new liquidity amount\r\n */\r\n function applyCLevelPendingDepositAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n int128 oldLiquidity64x64,\r\n bool isCall\r\n ) internal view returns (int128 cLevel64x64, int128 liquidity64x64) {\r\n PoolStorage.BatchData storage batchData = l.nextDeposits[isCall];\r\n int128 pendingDeposits64x64;\r\n\r\n if (\r\n batchData.totalPendingDeposits > 0 &&\r\n batchData.eta != 0 &&\r\n block.timestamp >= batchData.eta\r\n ) {\r\n pendingDeposits64x64 = ABDKMath64x64Token.fromDecimals(\r\n batchData.totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n liquidity64x64 = oldLiquidity64x64.add(pendingDeposits64x64);\r\n\r\n cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n liquidity64x64,\r\n isCall\r\n );\r\n } else {\r\n cLevel64x64 = oldCLevel64x64;\r\n liquidity64x64 = oldLiquidity64x64;\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for change in liquidity\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change\r\n * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity\r\n * @param newLiquidity64x64 64x64 fixed point representation of current liquidity\r\n * @param isCallPool whether to update C-Level for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function applyCLevelLiquidityChangeAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64,\r\n bool isCallPool\r\n ) internal view returns (int128 cLevel64x64) {\r\n int128 steepness64x64 = isCallPool\r\n ? l.steepnessUnderlying64x64\r\n : l.steepnessBase64x64;\r\n\r\n // fallback to deprecated storage value if side-specific value is not set\r\n if (steepness64x64 == 0) steepness64x64 = l._deprecated_steepness64x64;\r\n\r\n cLevel64x64 = OptionMath.calculateCLevel(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64,\r\n steepness64x64\r\n );\r\n\r\n if (cLevel64x64 < 0xb333333333333333) {\r\n cLevel64x64 = int128(0xb333333333333333); // 64x64 fixed point representation of 0.7\r\n }\r\n }\r\n\r\n /**\r\n * @notice set C-Level to arbitrary pre-calculated value\r\n * @param cLevel64x64 new C-Level of pool\r\n * @param isCallPool whether to update C-Level for call or put pool\r\n */\r\n function setCLevel(\r\n Layout storage l,\r\n int128 cLevel64x64,\r\n bool isCallPool\r\n ) internal {\r\n if (isCallPool) {\r\n l.cLevelUnderlying64x64 = cLevel64x64;\r\n l.cLevelUnderlyingUpdatedAt = block.timestamp;\r\n } else {\r\n l.cLevelBase64x64 = cLevel64x64;\r\n l.cLevelBaseUpdatedAt = block.timestamp;\r\n }\r\n }\r\n\r\n function setOracles(\r\n Layout storage l,\r\n address baseOracle,\r\n address underlyingOracle\r\n ) internal {\r\n require(\r\n AggregatorV3Interface(baseOracle).decimals() ==\r\n AggregatorV3Interface(underlyingOracle).decimals(),\r\n \"Pool: oracle decimals must match\"\r\n );\r\n\r\n l.baseOracle = baseOracle;\r\n l.underlyingOracle = underlyingOracle;\r\n }\r\n\r\n function fetchPriceUpdate(Layout storage l)\r\n internal\r\n view\r\n returns (int128 price64x64)\r\n {\r\n int256 priceUnderlying = AggregatorInterface(l.underlyingOracle)\r\n .latestAnswer();\r\n int256 priceBase = AggregatorInterface(l.baseOracle).latestAnswer();\r\n\r\n return ABDKMath64x64.divi(priceUnderlying, priceBase);\r\n }\r\n\r\n /**\r\n * @notice set price update for hourly bucket corresponding to given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to update\r\n * @param price64x64 64x64 fixed point representation of price\r\n */\r\n function setPriceUpdate(\r\n Layout storage l,\r\n uint256 timestamp,\r\n int128 price64x64\r\n ) internal {\r\n uint256 bucket = timestamp / (1 hours);\r\n l.bucketPrices64x64[bucket] = price64x64;\r\n l.priceUpdateSequences[bucket >> 8] += 1 << (255 - (bucket & 255));\r\n }\r\n\r\n /**\r\n * @notice get price update for hourly bucket corresponding to given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to query\r\n * @return 64x64 fixed point representation of price\r\n */\r\n function getPriceUpdate(Layout storage l, uint256 timestamp)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n return l.bucketPrices64x64[timestamp / (1 hours)];\r\n }\r\n\r\n /**\r\n * @notice get first price update available following given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to query\r\n * @return 64x64 fixed point representation of price\r\n */\r\n function getPriceUpdateAfter(Layout storage l, uint256 timestamp)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n // price updates are grouped into hourly buckets\r\n uint256 bucket = timestamp / (1 hours);\r\n // divide by 256 to get the index of the relevant price update sequence\r\n uint256 sequenceId = bucket >> 8;\r\n\r\n // get position within sequence relevant to current price update\r\n\r\n uint256 offset = bucket & 255;\r\n // shift to skip buckets from earlier in sequence\r\n uint256 sequence = (l.priceUpdateSequences[sequenceId] << offset) >>\r\n offset;\r\n\r\n // iterate through future sequences until a price update is found\r\n // sequence corresponding to current timestamp used as upper bound\r\n\r\n uint256 currentPriceUpdateSequenceId = block.timestamp / (256 hours);\r\n\r\n while (sequence == 0 && sequenceId <= currentPriceUpdateSequenceId) {\r\n sequence = l.priceUpdateSequences[++sequenceId];\r\n }\r\n\r\n // if no price update is found (sequence == 0) function will return 0\r\n // this should never occur, as each relevant external function triggers a price update\r\n\r\n // the most significant bit of the sequence corresponds to the offset of the relevant bucket\r\n\r\n uint256 msb;\r\n\r\n for (uint256 i = 128; i > 0; i >>= 1) {\r\n if (sequence >> i > 0) {\r\n msb += i;\r\n sequence >>= i;\r\n }\r\n }\r\n\r\n return l.bucketPrices64x64[((sequenceId + 1) << 8) - msb - 1];\r\n }\r\n\r\n function fromBaseToUnderlyingDecimals(Layout storage l, uint256 value)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(\r\n value,\r\n l.baseDecimals\r\n );\r\n return\r\n ABDKMath64x64Token.toDecimals(\r\n valueFixed64x64,\r\n l.underlyingDecimals\r\n );\r\n }\r\n\r\n function fromUnderlyingToBaseDecimals(Layout storage l, uint256 value)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(\r\n value,\r\n l.underlyingDecimals\r\n );\r\n return ABDKMath64x64Token.toDecimals(valueFixed64x64, l.baseDecimals);\r\n }\r\n}\r\n"},"@solidstate/contracts/utils/AddressUtils.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary AddressUtils {\n function toString(address account) internal pure returns (string memory) {\n bytes32 value = bytes32(uint256(uint160(account)));\n bytes memory alphabet = '0123456789abcdef';\n bytes memory chars = new bytes(42);\n\n chars[0] = '0';\n chars[1] = 'x';\n\n for (uint256 i = 0; i < 20; i++) {\n chars[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];\n chars[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];\n }\n\n return string(chars);\n }\n\n function isContract(address account) internal view returns (bool) {\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n function sendValue(address payable account, uint256 amount) internal {\n (bool success, ) = account.call{ value: amount }('');\n require(success, 'AddressUtils: failed to send value');\n }\n\n function functionCall(address target, bytes memory data)\n internal\n returns (bytes memory)\n {\n return\n functionCall(target, data, 'AddressUtils: failed low-level call');\n }\n\n function functionCall(\n address target,\n bytes memory data,\n string memory error\n ) internal returns (bytes memory) {\n return _functionCallWithValue(target, data, 0, error);\n }\n\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return\n functionCallWithValue(\n target,\n data,\n value,\n 'AddressUtils: failed low-level call with value'\n );\n }\n\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory error\n ) internal returns (bytes memory) {\n require(\n address(this).balance >= value,\n 'AddressUtils: insufficient balance for call'\n );\n return _functionCallWithValue(target, data, value, error);\n }\n\n function _functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory error\n ) private returns (bytes memory) {\n require(\n isContract(target),\n 'AddressUtils: function call to non-contract'\n );\n\n (bool success, bytes memory returnData) = target.call{ value: value }(\n data\n );\n\n if (success) {\n return returnData;\n } else if (returnData.length > 0) {\n assembly {\n let returnData_size := mload(returnData)\n revert(add(32, returnData), returnData_size)\n }\n } else {\n revert(error);\n }\n }\n}\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\nimport { ERC1155Base, ERC1155BaseInternal } from '../base/ERC1155Base.sol';\nimport { IERC1155Enumerable } from './IERC1155Enumerable.sol';\nimport { ERC1155EnumerableInternal, ERC1155EnumerableStorage } from './ERC1155EnumerableInternal.sol';\n\n/**\n * @title ERC1155 implementation including enumerable and aggregate functions\n */\nabstract contract ERC1155Enumerable is\n IERC1155Enumerable,\n ERC1155Base,\n ERC1155EnumerableInternal\n{\n using EnumerableSet for EnumerableSet.AddressSet;\n using EnumerableSet for EnumerableSet.UintSet;\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function totalSupply(uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return ERC1155EnumerableStorage.layout().totalSupply[id];\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function totalHolders(uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return ERC1155EnumerableStorage.layout().accountsByToken[id].length();\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function accountsByToken(uint256 id)\n public\n view\n virtual\n override\n returns (address[] memory)\n {\n EnumerableSet.AddressSet storage accounts = ERC1155EnumerableStorage\n .layout()\n .accountsByToken[id];\n\n address[] memory addresses = new address[](accounts.length());\n\n for (uint256 i; i < accounts.length(); i++) {\n addresses[i] = accounts.at(i);\n }\n\n return addresses;\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function tokensByAccount(address account)\n public\n view\n virtual\n override\n returns (uint256[] memory)\n {\n EnumerableSet.UintSet storage tokens = ERC1155EnumerableStorage\n .layout()\n .tokensByAccount[account];\n\n uint256[] memory ids = new uint256[](tokens.length());\n\n for (uint256 i; i < tokens.length(); i++) {\n ids[i] = tokens.at(i);\n }\n\n return ids;\n }\n\n /**\n * @notice ERC1155 hook: update aggregate values\n * @inheritdoc ERC1155EnumerableInternal\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n )\n internal\n virtual\n override(ERC1155BaseInternal, ERC1155EnumerableInternal)\n {\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\n }\n}\n"},"@solidstate/contracts/token/ERC1155/IERC1155Receiver.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @title ERC1155 transfer receiver interface\n */\ninterface IERC1155Receiver is IERC165 {\n /**\n * @notice validate receipt of ERC1155 transfer\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param id token ID received\n * @param value quantity of tokens received\n * @param data data payload\n * @return function's own selector if transfer is accepted\n */\n function onERC1155Received(\n address operator,\n address from,\n uint256 id,\n uint256 value,\n bytes calldata data\n ) external returns (bytes4);\n\n /**\n * @notice validate receipt of ERC1155 batch transfer\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param ids token IDs received\n * @param values quantities of tokens received\n * @param data data payload\n * @return function's own selector if transfer is accepted\n */\n function onERC1155BatchReceived(\n address operator,\n address from,\n uint256[] calldata ids,\n uint256[] calldata values,\n bytes calldata data\n ) external returns (bytes4);\n}\n"},"contracts/pool/IPoolEvents.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\ninterface IPoolEvents {\r\n event Purchase(\r\n address indexed user,\r\n uint256 longTokenId,\r\n uint256 contractSize,\r\n uint256 baseCost,\r\n uint256 feeCost,\r\n int128 spot64x64\r\n );\r\n\r\n event Exercise(\r\n address indexed user,\r\n uint256 longTokenId,\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 fee\r\n );\r\n\r\n event Underwrite(\r\n address indexed underwriter,\r\n address indexed longReceiver,\r\n uint256 shortTokenId,\r\n uint256 intervalContractSize,\r\n uint256 intervalPremium,\r\n bool isManualUnderwrite\r\n );\r\n\r\n event AssignExercise(\r\n address indexed underwriter,\r\n uint256 shortTokenId,\r\n uint256 freedAmount,\r\n uint256 intervalContractSize,\r\n uint256 fee\r\n );\r\n\r\n event Deposit(address indexed user, bool isCallPool, uint256 amount);\r\n\r\n event Withdrawal(\r\n address indexed user,\r\n bool isCallPool,\r\n uint256 depositedAt,\r\n uint256 amount\r\n );\r\n\r\n event FeeWithdrawal(bool indexed isCallPool, uint256 amount);\r\n\r\n event Annihilate(uint256 shortTokenId, uint256 amount);\r\n\r\n event UpdateCLevel(\r\n bool indexed isCall,\r\n int128 cLevel64x64,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64\r\n );\r\n\r\n event UpdateSteepness(int128 steepness64x64, bool isCallPool);\r\n}\r\n"},"contracts/oracle/VolatilitySurfaceOracleStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {EnumerableSet} from \"@solidstate/contracts/utils/EnumerableSet.sol\";\r\n\r\nlibrary VolatilitySurfaceOracleStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.VolatilitySurfaceOracle\");\r\n\r\n uint256 internal constant COEFF_BITS = 51;\r\n uint256 internal constant COEFF_BITS_MINUS_ONE = 50;\r\n uint256 internal constant COEFF_AMOUNT = 5;\r\n // START_BIT = COEFF_BITS * (COEFF_AMOUNT - 1)\r\n uint256 internal constant START_BIT = 204;\r\n\r\n struct Update {\r\n uint256 updatedAt;\r\n bytes32 callCoefficients;\r\n bytes32 putCoefficients;\r\n }\r\n\r\n struct Layout {\r\n // Base token -> Underlying token -> Update\r\n mapping(address => mapping(address => Update)) volatilitySurfaces;\r\n // Relayer addresses which can be trusted to provide accurate option trades\r\n EnumerableSet.AddressSet whitelistedRelayers;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n\r\n function getCoefficients(\r\n Layout storage l,\r\n address baseToken,\r\n address underlyingToken,\r\n bool isCall\r\n ) internal view returns (bytes32) {\r\n Update storage u = l.volatilitySurfaces[baseToken][underlyingToken];\r\n return isCall ? u.callCoefficients : u.putCoefficients;\r\n }\r\n\r\n function parseVolatilitySurfaceCoefficients(bytes32 input)\r\n internal\r\n pure\r\n returns (int256[] memory coefficients)\r\n {\r\n coefficients = new int256[](COEFF_AMOUNT);\r\n\r\n // Value to add to negative numbers to cast them to int256\r\n int256 toAdd = (int256(-1) >> COEFF_BITS) << COEFF_BITS;\r\n\r\n assembly {\r\n let i := 0\r\n // Value equal to -1\r\n let mid := shl(COEFF_BITS_MINUS_ONE, 1)\r\n\r\n for {\r\n\r\n } lt(i, COEFF_AMOUNT) {\r\n\r\n } {\r\n let offset := sub(START_BIT, mul(COEFF_BITS, i))\r\n let coeff := shr(\r\n offset,\r\n sub(\r\n input,\r\n shl(\r\n add(offset, COEFF_BITS),\r\n shr(add(offset, COEFF_BITS), input)\r\n )\r\n )\r\n )\r\n\r\n // Check if value is a negative number and needs casting\r\n if or(eq(coeff, mid), gt(coeff, mid)) {\r\n coeff := add(coeff, toAdd)\r\n }\r\n\r\n // Store result in the coefficients array\r\n mstore(add(coefficients, add(0x20, mul(0x20, i))), coeff)\r\n\r\n i := add(i, 1)\r\n }\r\n }\r\n }\r\n\r\n function formatVolatilitySurfaceCoefficients(int256[5] memory coefficients)\r\n internal\r\n pure\r\n returns (bytes32 result)\r\n {\r\n for (uint256 i = 0; i < COEFF_AMOUNT; i++) {\r\n int256 max = int256(1 << COEFF_BITS_MINUS_ONE);\r\n require(\r\n coefficients[i] < max && coefficients[i] > -max,\r\n \"Out of bounds\"\r\n );\r\n }\r\n\r\n assembly {\r\n let i := 0\r\n\r\n for {\r\n\r\n } lt(i, COEFF_AMOUNT) {\r\n\r\n } {\r\n let offset := sub(START_BIT, mul(COEFF_BITS, i))\r\n let coeff := mload(add(coefficients, mul(0x20, i)))\r\n\r\n result := add(\r\n result,\r\n shl(\r\n offset,\r\n sub(coeff, shl(COEFF_BITS, shr(COEFF_BITS, coeff)))\r\n )\r\n )\r\n\r\n i := add(i, 1)\r\n }\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/enumerable/IERC1155Enumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC1155 enumerable and aggregate function interface\n */\ninterface IERC1155Enumerable {\n /**\n * @notice query total minted supply of given token\n * @param id token id to query\n * @return token supply\n */\n function totalSupply(uint256 id) external view returns (uint256);\n\n /**\n * @notice query total number of holders for given token\n * @param id token id to query\n * @return quantity of holders\n */\n function totalHolders(uint256 id) external view returns (uint256);\n\n /**\n * @notice query holders of given token\n * @param id token id to query\n * @return list of holder addresses\n */\n function accountsByToken(uint256 id)\n external\n view\n returns (address[] memory);\n\n /**\n * @notice query tokens held by given address\n * @param account address to query\n * @return list of token ids\n */\n function tokensByAccount(address account)\n external\n view\n returns (uint256[] memory);\n}\n"},"@solidstate/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20Internal } from './IERC20Internal.sol';\n\n/**\n * @title ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/20\n */\ninterface IERC20 is IERC20Internal {\n /**\n * @notice query the total minted token supply\n * @return token supply\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @notice query the token balance of given account\n * @param account address to query\n * @return token balance\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @notice query the allowance granted from given holder to given spender\n * @param holder approver of allowance\n * @param spender recipient of allowance\n * @return token allowance\n */\n function allowance(address holder, address spender)\n external\n view\n returns (uint256);\n\n /**\n * @notice grant approval to spender to spend tokens\n * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)\n * @param spender recipient of allowance\n * @param amount quantity of tokens approved for spending\n * @return success status (always true; otherwise function should revert)\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @notice transfer tokens to given recipient\n * @param recipient beneficiary of token transfer\n * @param amount quantity of tokens to transfer\n * @return success status (always true; otherwise function should revert)\n */\n function transfer(address recipient, uint256 amount)\n external\n returns (bool);\n\n /**\n * @notice transfer tokens to given recipient on behalf of given holder\n * @param holder holder of tokens prior to transfer\n * @param recipient beneficiary of token transfer\n * @param amount quantity of tokens to transfer\n * @return success status (always true; otherwise function should revert)\n */\n function transferFrom(\n address holder,\n address recipient,\n uint256 amount\n ) external returns (bool);\n}\n"},"contracts/mining/IPremiaMining.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {PremiaMiningStorage} from \"./PremiaMiningStorage.sol\";\r\n\r\ninterface IPremiaMining {\r\n function addPremiaRewards(uint256 _amount) external;\r\n\r\n function premiaRewardsAvailable() external view returns (uint256);\r\n\r\n function getTotalAllocationPoints() external view returns (uint256);\r\n\r\n function getPoolInfo(address pool, bool isCallPool)\r\n external\r\n view\r\n returns (PremiaMiningStorage.PoolInfo memory);\r\n\r\n function getPremiaPerYear() external view returns (uint256);\r\n\r\n function addPool(address _pool, uint256 _allocPoints) external;\r\n\r\n function setPoolAllocPoints(\r\n address[] memory _pools,\r\n uint256[] memory _allocPoints\r\n ) external;\r\n\r\n function pendingPremia(\r\n address _pool,\r\n bool _isCallPool,\r\n address _user\r\n ) external view returns (uint256);\r\n\r\n function updatePool(\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _totalTVL\r\n ) external;\r\n\r\n function allocatePending(\r\n address _user,\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _userTVLOld,\r\n uint256 _userTVLNew,\r\n uint256 _totalTVL\r\n ) external;\r\n\r\n function claim(\r\n address _user,\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _userTVLOld,\r\n uint256 _userTVLNew,\r\n uint256 _totalTVL\r\n ) external;\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\n\nlibrary ERC1155EnumerableStorage {\n struct Layout {\n mapping(uint256 => uint256) totalSupply;\n mapping(uint256 => EnumerableSet.AddressSet) accountsByToken;\n mapping(address => EnumerableSet.UintSet) tokensByAccount;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.ERC1155Enumerable');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n}\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\nimport { ERC1155BaseInternal, ERC1155BaseStorage } from '../base/ERC1155BaseInternal.sol';\nimport { ERC1155EnumerableStorage } from './ERC1155EnumerableStorage.sol';\n\n/**\n * @title ERC1155Enumerable internal functions\n */\nabstract contract ERC1155EnumerableInternal is ERC1155BaseInternal {\n using EnumerableSet for EnumerableSet.AddressSet;\n using EnumerableSet for EnumerableSet.UintSet;\n\n /**\n * @notice ERC1155 hook: update aggregate values\n * @inheritdoc ERC1155BaseInternal\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual override {\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\n\n if (from != to) {\n ERC1155EnumerableStorage.Layout storage l = ERC1155EnumerableStorage\n .layout();\n mapping(uint256 => EnumerableSet.AddressSet)\n storage tokenAccounts = l.accountsByToken;\n EnumerableSet.UintSet storage fromTokens = l.tokensByAccount[from];\n EnumerableSet.UintSet storage toTokens = l.tokensByAccount[to];\n\n for (uint256 i; i < ids.length; i++) {\n uint256 amount = amounts[i];\n\n if (amount > 0) {\n uint256 id = ids[i];\n\n if (from == address(0)) {\n l.totalSupply[id] += amount;\n } else if (_balanceOf(from, id) == amount) {\n tokenAccounts[id].remove(from);\n fromTokens.remove(id);\n }\n\n if (to == address(0)) {\n l.totalSupply[id] -= amount;\n } else if (_balanceOf(to, id) == 0) {\n tokenAccounts[id].add(to);\n toTokens.add(id);\n }\n }\n }\n }\n }\n}\n"},"contracts/oracle/IVolatilitySurfaceOracle.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {VolatilitySurfaceOracleStorage} from \"./VolatilitySurfaceOracleStorage.sol\";\r\n\r\ninterface IVolatilitySurfaceOracle {\r\n function getWhitelistedRelayers() external view returns (address[] memory);\r\n\r\n function getVolatilitySurface(address baseToken, address underlyingToken)\r\n external\r\n view\r\n returns (VolatilitySurfaceOracleStorage.Update memory);\r\n\r\n function getVolatilitySurfaceCoefficientsUnpacked(\r\n address baseToken,\r\n address underlyingToken,\r\n bool isCall\r\n ) external view returns (int256[] memory);\r\n\r\n function getTimeToMaturity64x64(uint64 maturity)\r\n external\r\n view\r\n returns (int128);\r\n\r\n function getAnnualizedVolatility64x64(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 spot64x64,\r\n int128 strike64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (int128);\r\n\r\n function getBlackScholesPrice64x64(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (int128);\r\n\r\n function getBlackScholesPrice(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (uint256);\r\n}\r\n"},"contracts/pool/PoolInternal.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {IERC173} from \"@solidstate/contracts/access/IERC173.sol\";\r\nimport {OwnableStorage} from \"@solidstate/contracts/access/OwnableStorage.sol\";\r\nimport {IERC20} from \"@solidstate/contracts/token/ERC20/IERC20.sol\";\r\nimport {ERC1155EnumerableInternal, ERC1155EnumerableStorage, EnumerableSet} from \"@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol\";\r\nimport {IWETH} from \"@solidstate/contracts/utils/IWETH.sol\";\r\n\r\nimport {PoolStorage} from \"./PoolStorage.sol\";\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\nimport {ABDKMath64x64Token} from \"../libraries/ABDKMath64x64Token.sol\";\r\nimport {OptionMath} from \"../libraries/OptionMath.sol\";\r\nimport {IFeeDiscount} from \"../staking/IFeeDiscount.sol\";\r\nimport {IPoolEvents} from \"./IPoolEvents.sol\";\r\nimport {IPremiaMining} from \"../mining/IPremiaMining.sol\";\r\nimport {IVolatilitySurfaceOracle} from \"../oracle/IVolatilitySurfaceOracle.sol\";\r\n\r\n/**\r\n * @title Premia option pool\r\n * @dev deployed standalone and referenced by PoolProxy\r\n */\r\ncontract PoolInternal is IPoolEvents, ERC1155EnumerableInternal {\r\n using ABDKMath64x64 for int128;\r\n using EnumerableSet for EnumerableSet.AddressSet;\r\n using EnumerableSet for EnumerableSet.UintSet;\r\n using PoolStorage for PoolStorage.Layout;\r\n\r\n address internal immutable WETH_ADDRESS;\r\n address internal immutable PREMIA_MINING_ADDRESS;\r\n address internal immutable FEE_RECEIVER_ADDRESS;\r\n address internal immutable FEE_DISCOUNT_ADDRESS;\r\n address internal immutable IVOL_ORACLE_ADDRESS;\r\n\r\n int128 internal immutable FEE_64x64;\r\n\r\n uint256 internal immutable UNDERLYING_FREE_LIQ_TOKEN_ID;\r\n uint256 internal immutable BASE_FREE_LIQ_TOKEN_ID;\r\n\r\n uint256 internal immutable UNDERLYING_RESERVED_LIQ_TOKEN_ID;\r\n uint256 internal immutable BASE_RESERVED_LIQ_TOKEN_ID;\r\n\r\n uint256 internal constant INVERSE_BASIS_POINT = 1e4;\r\n uint256 internal constant BATCHING_PERIOD = 260;\r\n\r\n // Minimum APY for capital locked up to underwrite options.\r\n // The quote will return a minimum price corresponding to this APY\r\n int128 internal constant MIN_APY_64x64 = 0x4ccccccccccccccd; // 0.3\r\n\r\n constructor(\r\n address ivolOracle,\r\n address weth,\r\n address premiaMining,\r\n address feeReceiver,\r\n address feeDiscountAddress,\r\n int128 fee64x64\r\n ) {\r\n IVOL_ORACLE_ADDRESS = ivolOracle;\r\n WETH_ADDRESS = weth;\r\n PREMIA_MINING_ADDRESS = premiaMining;\r\n FEE_RECEIVER_ADDRESS = feeReceiver;\r\n // PremiaFeeDiscount contract address\r\n FEE_DISCOUNT_ADDRESS = feeDiscountAddress;\r\n FEE_64x64 = fee64x64;\r\n\r\n UNDERLYING_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.UNDERLYING_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n BASE_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n UNDERLYING_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.UNDERLYING_RESERVED_LIQ,\r\n 0,\r\n 0\r\n );\r\n BASE_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.BASE_RESERVED_LIQ,\r\n 0,\r\n 0\r\n );\r\n }\r\n\r\n modifier onlyProtocolOwner() {\r\n require(\r\n msg.sender == IERC173(OwnableStorage.layout().owner).owner(),\r\n \"Not protocol owner\"\r\n );\r\n _;\r\n }\r\n\r\n function _getFeeDiscount(address feePayer)\r\n internal\r\n view\r\n returns (uint256 discount)\r\n {\r\n if (FEE_DISCOUNT_ADDRESS != address(0)) {\r\n discount = IFeeDiscount(FEE_DISCOUNT_ADDRESS).getDiscount(feePayer);\r\n }\r\n }\r\n\r\n function _getFeeWithDiscount(address feePayer, uint256 fee)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n uint256 discount = _getFeeDiscount(feePayer);\r\n return fee - ((fee * discount) / INVERSE_BASIS_POINT);\r\n }\r\n\r\n function _withdrawFees(bool isCall) internal returns (uint256 amount) {\r\n uint256 tokenId = _getReservedLiquidityTokenId(isCall);\r\n amount = _balanceOf(FEE_RECEIVER_ADDRESS, tokenId);\r\n\r\n if (amount > 0) {\r\n _burn(FEE_RECEIVER_ADDRESS, tokenId, amount);\r\n emit FeeWithdrawal(isCall, amount);\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate price of option contract\r\n * @param args structured quote arguments\r\n * @return result quote result\r\n */\r\n function _quote(PoolStorage.QuoteArgsInternal memory args)\r\n internal\r\n view\r\n returns (PoolStorage.QuoteResultInternal memory result)\r\n {\r\n require(\r\n args.strike64x64 > 0 && args.spot64x64 > 0 && args.maturity > 0,\r\n \"invalid args\"\r\n );\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n int128 contractSize64x64 = ABDKMath64x64Token.fromDecimals(\r\n args.contractSize,\r\n l.underlyingDecimals\r\n );\r\n bool isCall = args.isCall;\r\n\r\n (int128 adjustedCLevel64x64, int128 oldLiquidity64x64) = l\r\n .applyCLevelPendingDepositAdjustment(\r\n l.getDecayAdjustedCLevel64x64(isCall),\r\n l.totalFreeLiquiditySupply64x64(isCall),\r\n isCall\r\n );\r\n\r\n require(oldLiquidity64x64 > 0, \"no liq\");\r\n\r\n int128 timeToMaturity64x64 = ABDKMath64x64.divu(\r\n args.maturity - block.timestamp,\r\n 365 days\r\n );\r\n\r\n int128 annualizedVolatility64x64 = IVolatilitySurfaceOracle(\r\n IVOL_ORACLE_ADDRESS\r\n ).getAnnualizedVolatility64x64(\r\n l.base,\r\n l.underlying,\r\n args.spot64x64,\r\n args.strike64x64,\r\n timeToMaturity64x64,\r\n isCall\r\n );\r\n\r\n require(annualizedVolatility64x64 > 0, \"vol = 0\");\r\n\r\n (\r\n int128 price64x64,\r\n int128 cLevel64x64,\r\n int128 slippageCoefficient64x64\r\n ) = OptionMath.quotePrice(\r\n OptionMath.QuoteArgs(\r\n annualizedVolatility64x64.mul(annualizedVolatility64x64),\r\n args.strike64x64,\r\n args.spot64x64,\r\n timeToMaturity64x64,\r\n adjustedCLevel64x64,\r\n oldLiquidity64x64,\r\n oldLiquidity64x64.sub(contractSize64x64),\r\n 0x10000000000000000, // 64x64 fixed point representation of 1\r\n MIN_APY_64x64,\r\n isCall\r\n )\r\n );\r\n\r\n result.baseCost64x64 = isCall\r\n ? price64x64.mul(contractSize64x64).div(args.spot64x64)\r\n : price64x64.mul(contractSize64x64);\r\n result.feeCost64x64 = result.baseCost64x64.mul(FEE_64x64);\r\n result.cLevel64x64 = cLevel64x64;\r\n result.slippageCoefficient64x64 = slippageCoefficient64x64;\r\n\r\n int128 discount = ABDKMath64x64.divu(\r\n _getFeeDiscount(args.feePayer),\r\n INVERSE_BASIS_POINT\r\n );\r\n result.feeCost64x64 -= result.feeCost64x64.mul(discount);\r\n }\r\n\r\n /**\r\n * @notice burn corresponding long and short option tokens\r\n * @param account holder of tokens to annihilate\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize quantity of option contract tokens to annihilate\r\n */\r\n function _annihilate(\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize\r\n ) internal {\r\n uint256 longTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, true),\r\n maturity,\r\n strike64x64\r\n );\r\n uint256 shortTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n _burn(account, longTokenId, contractSize);\r\n _burn(account, shortTokenId, contractSize);\r\n\r\n emit Annihilate(shortTokenId, contractSize);\r\n }\r\n\r\n /**\r\n * @notice purchase option\r\n * @param l storage layout struct\r\n * @param account recipient of purchased option\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize size of option contract\r\n * @param newPrice64x64 64x64 fixed point representation of current spot price\r\n * @return baseCost quantity of tokens required to purchase long position\r\n * @return feeCost quantity of tokens required to pay fees\r\n */\r\n function _purchase(\r\n PoolStorage.Layout storage l,\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize,\r\n int128 newPrice64x64\r\n ) internal returns (uint256 baseCost, uint256 feeCost) {\r\n require(maturity > block.timestamp, \"expired\");\r\n require(contractSize >= l.underlyingMinimum, \"too small\");\r\n\r\n {\r\n uint256 size = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(\r\n strike64x64.mulu(contractSize)\r\n );\r\n\r\n require(\r\n size <=\r\n ERC1155EnumerableStorage.layout().totalSupply[\r\n _getFreeLiquidityTokenId(isCall)\r\n ] -\r\n l.nextDeposits[isCall].totalPendingDeposits,\r\n \"insuf liq\"\r\n );\r\n }\r\n\r\n PoolStorage.QuoteResultInternal memory quote = _quote(\r\n PoolStorage.QuoteArgsInternal(\r\n account,\r\n maturity,\r\n strike64x64,\r\n newPrice64x64,\r\n contractSize,\r\n isCall\r\n )\r\n );\r\n\r\n baseCost = ABDKMath64x64Token.toDecimals(\r\n quote.baseCost64x64,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n feeCost = ABDKMath64x64Token.toDecimals(\r\n quote.feeCost64x64,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n uint256 longTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, true),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n uint256 shortTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n // mint long option token for buyer\r\n _mint(account, longTokenId, contractSize);\r\n\r\n int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n // burn free liquidity tokens from other underwriters\r\n _mintShortTokenLoop(\r\n l,\r\n account,\r\n contractSize,\r\n baseCost,\r\n shortTokenId,\r\n isCall\r\n );\r\n int128 newLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n\r\n _setCLevel(l, oldLiquidity64x64, newLiquidity64x64, isCall);\r\n\r\n // mint reserved liquidity tokens for fee receiver\r\n _mint(\r\n FEE_RECEIVER_ADDRESS,\r\n _getReservedLiquidityTokenId(isCall),\r\n feeCost\r\n );\r\n\r\n emit Purchase(\r\n account,\r\n longTokenId,\r\n contractSize,\r\n baseCost,\r\n feeCost,\r\n newPrice64x64\r\n );\r\n }\r\n\r\n /**\r\n * @notice reassign short position to new underwriter\r\n * @param l storage layout struct\r\n * @param account holder of positions to be reassigned\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize quantity of option contract tokens to reassign\r\n * @param newPrice64x64 64x64 fixed point representation of current spot price\r\n * @return baseCost quantity of tokens required to reassign short position\r\n * @return feeCost quantity of tokens required to pay fees\r\n * @return amountOut quantity of liquidity freed\r\n */\r\n function _reassign(\r\n PoolStorage.Layout storage l,\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize,\r\n int128 newPrice64x64\r\n )\r\n internal\r\n returns (\r\n uint256 baseCost,\r\n uint256 feeCost,\r\n uint256 amountOut\r\n )\r\n {\r\n (baseCost, feeCost) = _purchase(\r\n l,\r\n account,\r\n maturity,\r\n strike64x64,\r\n isCall,\r\n contractSize,\r\n newPrice64x64\r\n );\r\n\r\n _annihilate(account, maturity, strike64x64, isCall, contractSize);\r\n\r\n uint256 annihilateAmount = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));\r\n\r\n amountOut = annihilateAmount - baseCost - feeCost;\r\n }\r\n\r\n /**\r\n * @notice exercise option on behalf of holder\r\n * @dev used for processing of expired options if passed holder is zero address\r\n * @param holder owner of long option tokens to exercise\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to exercise\r\n */\r\n function _exercise(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) internal {\r\n uint64 maturity;\r\n int128 strike64x64;\r\n bool isCall;\r\n\r\n bool onlyExpired = holder == address(0);\r\n\r\n {\r\n PoolStorage.TokenType tokenType;\r\n (tokenType, maturity, strike64x64) = PoolStorage.parseTokenId(\r\n longTokenId\r\n );\r\n require(\r\n tokenType == PoolStorage.TokenType.LONG_CALL ||\r\n tokenType == PoolStorage.TokenType.LONG_PUT,\r\n \"invalid type\"\r\n );\r\n require(!onlyExpired || maturity < block.timestamp, \"not expired\");\r\n isCall = tokenType == PoolStorage.TokenType.LONG_CALL;\r\n }\r\n\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n int128 spot64x64 = _update(l);\r\n\r\n if (maturity < block.timestamp) {\r\n spot64x64 = l.getPriceUpdateAfter(maturity);\r\n }\r\n\r\n require(\r\n onlyExpired ||\r\n (\r\n isCall\r\n ? (spot64x64 > strike64x64)\r\n : (spot64x64 < strike64x64)\r\n ),\r\n \"not ITM\"\r\n );\r\n\r\n uint256 exerciseValue;\r\n // option has a non-zero exercise value\r\n if (isCall) {\r\n if (spot64x64 > strike64x64) {\r\n exerciseValue = spot64x64.sub(strike64x64).div(spot64x64).mulu(\r\n contractSize\r\n );\r\n }\r\n } else {\r\n if (spot64x64 < strike64x64) {\r\n exerciseValue = l.fromUnderlyingToBaseDecimals(\r\n strike64x64.sub(spot64x64).mulu(contractSize)\r\n );\r\n }\r\n }\r\n\r\n uint256 totalFee;\r\n\r\n if (onlyExpired) {\r\n totalFee += _burnLongTokenLoop(\r\n contractSize,\r\n exerciseValue,\r\n longTokenId,\r\n isCall\r\n );\r\n } else {\r\n // burn long option tokens from sender\r\n _burn(holder, longTokenId, contractSize);\r\n\r\n uint256 fee;\r\n\r\n if (exerciseValue > 0) {\r\n fee = _getFeeWithDiscount(\r\n holder,\r\n FEE_64x64.mulu(exerciseValue)\r\n );\r\n totalFee += fee;\r\n\r\n _pushTo(holder, _getPoolToken(isCall), exerciseValue - fee);\r\n }\r\n\r\n emit Exercise(\r\n holder,\r\n longTokenId,\r\n contractSize,\r\n exerciseValue,\r\n fee\r\n );\r\n }\r\n\r\n totalFee += _burnShortTokenLoop(\r\n contractSize,\r\n exerciseValue,\r\n PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n ),\r\n isCall\r\n );\r\n\r\n _mint(\r\n FEE_RECEIVER_ADDRESS,\r\n _getReservedLiquidityTokenId(isCall),\r\n totalFee\r\n );\r\n }\r\n\r\n function _mintShortTokenLoop(\r\n PoolStorage.Layout storage l,\r\n address buyer,\r\n uint256 contractSize,\r\n uint256 premium,\r\n uint256 shortTokenId,\r\n bool isCall\r\n ) internal {\r\n uint256 freeLiqTokenId = _getFreeLiquidityTokenId(isCall);\r\n (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);\r\n\r\n uint256 toPay = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));\r\n\r\n while (toPay > 0) {\r\n address underwriter = l.liquidityQueueAscending[isCall][address(0)];\r\n uint256 balance = _balanceOf(underwriter, freeLiqTokenId);\r\n\r\n // If dust left, we remove underwriter and skip to next\r\n if (balance < _getMinimumAmount(l, isCall)) {\r\n l.removeUnderwriter(underwriter, isCall);\r\n continue;\r\n }\r\n\r\n if (!l.getReinvestmentStatus(underwriter, isCall)) {\r\n _burn(underwriter, freeLiqTokenId, balance);\r\n _mint(\r\n underwriter,\r\n _getReservedLiquidityTokenId(isCall),\r\n balance\r\n );\r\n _subUserTVL(l, underwriter, isCall, balance);\r\n continue;\r\n }\r\n\r\n // amount of liquidity provided by underwriter, accounting for reinvested premium\r\n uint256 intervalContractSize = ((balance -\r\n l.pendingDeposits[underwriter][l.nextDeposits[isCall].eta][\r\n isCall\r\n ]) * (toPay + premium)) / toPay;\r\n if (intervalContractSize == 0) continue;\r\n if (intervalContractSize > toPay) intervalContractSize = toPay;\r\n\r\n // amount of premium paid to underwriter\r\n uint256 intervalPremium = (premium * intervalContractSize) / toPay;\r\n premium -= intervalPremium;\r\n toPay -= intervalContractSize;\r\n _addUserTVL(l, underwriter, isCall, intervalPremium);\r\n\r\n // burn free liquidity tokens from underwriter\r\n _burn(\r\n underwriter,\r\n freeLiqTokenId,\r\n intervalContractSize - intervalPremium\r\n );\r\n\r\n if (isCall == false) {\r\n // For PUT, conversion to contract amount is done here (Prior to this line, it is token amount)\r\n intervalContractSize = l.fromBaseToUnderlyingDecimals(\r\n strike64x64.inv().mulu(intervalContractSize)\r\n );\r\n }\r\n\r\n // mint short option tokens for underwriter\r\n // toPay == 0 ? contractSize : intervalContractSize : To prevent minting less than amount,\r\n // because of rounding (Can happen for put, because of fixed point precision)\r\n _mint(\r\n underwriter,\r\n shortTokenId,\r\n toPay == 0 ? contractSize : intervalContractSize\r\n );\r\n\r\n emit Underwrite(\r\n underwriter,\r\n buyer,\r\n shortTokenId,\r\n toPay == 0 ? contractSize : intervalContractSize,\r\n intervalPremium,\r\n false\r\n );\r\n\r\n contractSize -= intervalContractSize;\r\n }\r\n }\r\n\r\n function _burnLongTokenLoop(\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 longTokenId,\r\n bool isCall\r\n ) internal returns (uint256 totalFee) {\r\n EnumerableSet.AddressSet storage holders = ERC1155EnumerableStorage\r\n .layout()\r\n .accountsByToken[longTokenId];\r\n\r\n while (contractSize > 0) {\r\n address longTokenHolder = holders.at(holders.length() - 1);\r\n\r\n uint256 intervalContractSize = _balanceOf(\r\n longTokenHolder,\r\n longTokenId\r\n );\r\n if (intervalContractSize > contractSize)\r\n intervalContractSize = contractSize;\r\n\r\n uint256 intervalExerciseValue;\r\n\r\n uint256 fee;\r\n if (exerciseValue > 0) {\r\n intervalExerciseValue =\r\n (exerciseValue * intervalContractSize) /\r\n contractSize;\r\n\r\n fee = _getFeeWithDiscount(\r\n longTokenHolder,\r\n FEE_64x64.mulu(intervalExerciseValue)\r\n );\r\n totalFee += fee;\r\n\r\n exerciseValue -= intervalExerciseValue;\r\n _pushTo(\r\n longTokenHolder,\r\n _getPoolToken(isCall),\r\n intervalExerciseValue - fee\r\n );\r\n }\r\n\r\n contractSize -= intervalContractSize;\r\n\r\n emit Exercise(\r\n longTokenHolder,\r\n longTokenId,\r\n intervalContractSize,\r\n intervalExerciseValue - fee,\r\n fee\r\n );\r\n\r\n _burn(longTokenHolder, longTokenId, intervalContractSize);\r\n }\r\n }\r\n\r\n function _burnShortTokenLoop(\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 shortTokenId,\r\n bool isCall\r\n ) internal returns (uint256 totalFee) {\r\n EnumerableSet.AddressSet storage underwriters = ERC1155EnumerableStorage\r\n .layout()\r\n .accountsByToken[shortTokenId];\r\n (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);\r\n\r\n while (contractSize > 0) {\r\n address underwriter = underwriters.at(underwriters.length() - 1);\r\n\r\n // amount of liquidity provided by underwriter\r\n uint256 intervalContractSize = _balanceOf(\r\n underwriter,\r\n shortTokenId\r\n );\r\n if (intervalContractSize > contractSize)\r\n intervalContractSize = contractSize;\r\n\r\n // amount of value claimed by buyer\r\n uint256 intervalExerciseValue = (exerciseValue *\r\n intervalContractSize) / contractSize;\r\n exerciseValue -= intervalExerciseValue;\r\n contractSize -= intervalContractSize;\r\n\r\n uint256 freeLiq = isCall\r\n ? intervalContractSize - intervalExerciseValue\r\n : PoolStorage.layout().fromUnderlyingToBaseDecimals(\r\n strike64x64.mulu(intervalContractSize)\r\n ) - intervalExerciseValue;\r\n\r\n uint256 fee = _getFeeWithDiscount(\r\n underwriter,\r\n FEE_64x64.mulu(freeLiq)\r\n );\r\n totalFee += fee;\r\n\r\n uint256 tvlToSubtract = intervalExerciseValue;\r\n\r\n // mint free liquidity tokens for underwriter\r\n if (\r\n PoolStorage.layout().getReinvestmentStatus(underwriter, isCall)\r\n ) {\r\n _addToDepositQueue(underwriter, freeLiq - fee, isCall);\r\n tvlToSubtract += fee;\r\n } else {\r\n _mint(\r\n underwriter,\r\n _getReservedLiquidityTokenId(isCall),\r\n freeLiq - fee\r\n );\r\n tvlToSubtract += freeLiq;\r\n }\r\n\r\n _subUserTVL(\r\n PoolStorage.layout(),\r\n underwriter,\r\n isCall,\r\n tvlToSubtract\r\n );\r\n\r\n // burn short option tokens from underwriter\r\n _burn(underwriter, shortTokenId, intervalContractSize);\r\n\r\n emit AssignExercise(\r\n underwriter,\r\n shortTokenId,\r\n freeLiq - fee,\r\n intervalContractSize,\r\n fee\r\n );\r\n }\r\n }\r\n\r\n function _addToDepositQueue(\r\n address account,\r\n uint256 amount,\r\n bool isCallPool\r\n ) internal {\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n _mint(account, _getFreeLiquidityTokenId(isCallPool), amount);\r\n\r\n uint256 nextBatch = (block.timestamp / BATCHING_PERIOD) *\r\n BATCHING_PERIOD +\r\n BATCHING_PERIOD;\r\n l.pendingDeposits[account][nextBatch][isCallPool] += amount;\r\n\r\n PoolStorage.BatchData storage batchData = l.nextDeposits[isCallPool];\r\n batchData.totalPendingDeposits += amount;\r\n batchData.eta = nextBatch;\r\n }\r\n\r\n function _processPendingDeposits(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n {\r\n PoolStorage.BatchData storage data = l.nextDeposits[isCall];\r\n\r\n if (data.eta == 0 || block.timestamp < data.eta) return;\r\n\r\n int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n\r\n _setCLevel(\r\n l,\r\n oldLiquidity64x64,\r\n oldLiquidity64x64.add(\r\n ABDKMath64x64Token.fromDecimals(\r\n data.totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n )\r\n ),\r\n isCall\r\n );\r\n\r\n delete l.nextDeposits[isCall];\r\n }\r\n\r\n function _getFreeLiquidityTokenId(bool isCall)\r\n internal\r\n view\r\n returns (uint256 freeLiqTokenId)\r\n {\r\n freeLiqTokenId = isCall\r\n ? UNDERLYING_FREE_LIQ_TOKEN_ID\r\n : BASE_FREE_LIQ_TOKEN_ID;\r\n }\r\n\r\n function _getReservedLiquidityTokenId(bool isCall)\r\n internal\r\n view\r\n returns (uint256 reservedLiqTokenId)\r\n {\r\n reservedLiqTokenId = isCall\r\n ? UNDERLYING_RESERVED_LIQ_TOKEN_ID\r\n : BASE_RESERVED_LIQ_TOKEN_ID;\r\n }\r\n\r\n function _getPoolToken(bool isCall) internal view returns (address token) {\r\n token = isCall\r\n ? PoolStorage.layout().underlying\r\n : PoolStorage.layout().base;\r\n }\r\n\r\n function _getTokenType(bool isCall, bool isLong)\r\n internal\r\n pure\r\n returns (PoolStorage.TokenType tokenType)\r\n {\r\n if (isCall) {\r\n tokenType = isLong\r\n ? PoolStorage.TokenType.LONG_CALL\r\n : PoolStorage.TokenType.SHORT_CALL;\r\n } else {\r\n tokenType = isLong\r\n ? PoolStorage.TokenType.LONG_PUT\r\n : PoolStorage.TokenType.SHORT_PUT;\r\n }\r\n }\r\n\r\n function _getMinimumAmount(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint256 minimumAmount)\r\n {\r\n minimumAmount = isCall ? l.underlyingMinimum : l.baseMinimum;\r\n }\r\n\r\n function _getPoolCapAmount(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint256 poolCapAmount)\r\n {\r\n poolCapAmount = isCall ? l.underlyingPoolCap : l.basePoolCap;\r\n }\r\n\r\n function _setCLevel(\r\n PoolStorage.Layout storage l,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64,\r\n bool isCallPool\r\n ) internal {\r\n int128 oldCLevel64x64 = l.getDecayAdjustedCLevel64x64(isCallPool);\r\n\r\n int128 cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64,\r\n isCallPool\r\n );\r\n\r\n l.setCLevel(cLevel64x64, isCallPool);\r\n\r\n emit UpdateCLevel(\r\n isCallPool,\r\n cLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate and store updated market state\r\n * @param l storage layout struct\r\n * @return newPrice64x64 64x64 fixed point representation of current spot price\r\n */\r\n function _update(PoolStorage.Layout storage l)\r\n internal\r\n returns (int128 newPrice64x64)\r\n {\r\n if (l.updatedAt == block.timestamp) {\r\n return (l.getPriceUpdate(block.timestamp));\r\n }\r\n\r\n newPrice64x64 = l.fetchPriceUpdate();\r\n\r\n if (l.getPriceUpdate(block.timestamp) == 0) {\r\n l.setPriceUpdate(block.timestamp, newPrice64x64);\r\n }\r\n\r\n l.updatedAt = block.timestamp;\r\n\r\n _processPendingDeposits(l, true);\r\n _processPendingDeposits(l, false);\r\n }\r\n\r\n /**\r\n * @notice transfer ERC20 tokens to message sender\r\n * @param token ERC20 token address\r\n * @param amount quantity of token to transfer\r\n */\r\n function _pushTo(\r\n address to,\r\n address token,\r\n uint256 amount\r\n ) internal {\r\n if (amount == 0) return;\r\n\r\n require(IERC20(token).transfer(to, amount), \"ERC20 transfer failed\");\r\n }\r\n\r\n /**\r\n * @notice transfer ERC20 tokens from message sender\r\n * @param from address from which tokens are pulled from\r\n * @param token ERC20 token address\r\n * @param amount quantity of token to transfer\r\n * @param skipWethDeposit if false, will not try to deposit weth from attach eth\r\n */\r\n function _pullFrom(\r\n address from,\r\n address token,\r\n uint256 amount,\r\n bool skipWethDeposit\r\n ) internal {\r\n if (!skipWethDeposit) {\r\n if (token == WETH_ADDRESS) {\r\n if (msg.value > 0) {\r\n if (msg.value > amount) {\r\n IWETH(WETH_ADDRESS).deposit{value: amount}();\r\n\r\n (bool success, ) = payable(msg.sender).call{\r\n value: msg.value - amount\r\n }(\"\");\r\n\r\n require(success, \"ETH refund failed\");\r\n\r\n amount = 0;\r\n } else {\r\n unchecked {\r\n amount -= msg.value;\r\n }\r\n\r\n IWETH(WETH_ADDRESS).deposit{value: msg.value}();\r\n }\r\n }\r\n } else {\r\n require(msg.value == 0, \"not WETH deposit\");\r\n }\r\n }\r\n\r\n if (amount > 0) {\r\n require(\r\n IERC20(token).transferFrom(from, address(this), amount),\r\n \"ERC20 transfer failed\"\r\n );\r\n }\r\n }\r\n\r\n function _mint(\r\n address account,\r\n uint256 tokenId,\r\n uint256 amount\r\n ) internal {\r\n _mint(account, tokenId, amount, \"\");\r\n }\r\n\r\n function _addUserTVL(\r\n PoolStorage.Layout storage l,\r\n address user,\r\n bool isCallPool,\r\n uint256 amount\r\n ) internal {\r\n uint256 userTVL = l.userTVL[user][isCallPool];\r\n uint256 totalTVL = l.totalTVL[isCallPool];\r\n\r\n IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(\r\n user,\r\n address(this),\r\n isCallPool,\r\n userTVL,\r\n userTVL + amount,\r\n totalTVL\r\n );\r\n\r\n l.userTVL[user][isCallPool] = userTVL + amount;\r\n l.totalTVL[isCallPool] = totalTVL + amount;\r\n }\r\n\r\n function _subUserTVL(\r\n PoolStorage.Layout storage l,\r\n address user,\r\n bool isCallPool,\r\n uint256 amount\r\n ) internal {\r\n uint256 userTVL = l.userTVL[user][isCallPool];\r\n uint256 totalTVL = l.totalTVL[isCallPool];\r\n\r\n IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(\r\n user,\r\n address(this),\r\n isCallPool,\r\n userTVL,\r\n userTVL - amount,\r\n totalTVL\r\n );\r\n l.userTVL[user][isCallPool] = userTVL - amount;\r\n l.totalTVL[isCallPool] = totalTVL - amount;\r\n }\r\n\r\n /**\r\n * @notice ERC1155 hook: track eligible underwriters\r\n * @param operator transaction sender\r\n * @param from token sender\r\n * @param to token receiver\r\n * @param ids token ids transferred\r\n * @param amounts token quantities transferred\r\n * @param data data payload\r\n */\r\n function _beforeTokenTransfer(\r\n address operator,\r\n address from,\r\n address to,\r\n uint256[] memory ids,\r\n uint256[] memory amounts,\r\n bytes memory data\r\n ) internal virtual override {\r\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\r\n\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n for (uint256 i; i < ids.length; i++) {\r\n uint256 id = ids[i];\r\n uint256 amount = amounts[i];\r\n\r\n if (amount == 0) continue;\r\n\r\n if (from == address(0)) {\r\n l.tokenIds.add(id);\r\n }\r\n\r\n if (\r\n to == address(0) &&\r\n ERC1155EnumerableStorage.layout().totalSupply[id] == 0\r\n ) {\r\n l.tokenIds.remove(id);\r\n }\r\n\r\n // prevent transfer of free and reserved liquidity during waiting period\r\n\r\n if (\r\n id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == BASE_FREE_LIQ_TOKEN_ID ||\r\n id == UNDERLYING_RESERVED_LIQ_TOKEN_ID ||\r\n id == BASE_RESERVED_LIQ_TOKEN_ID\r\n ) {\r\n if (from != address(0) && to != address(0)) {\r\n bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == UNDERLYING_RESERVED_LIQ_TOKEN_ID;\r\n\r\n require(\r\n l.depositedAt[from][isCallPool] + (1 days) <\r\n block.timestamp,\r\n \"liq lock 1d\"\r\n );\r\n }\r\n }\r\n\r\n if (\r\n id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == BASE_FREE_LIQ_TOKEN_ID\r\n ) {\r\n bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID;\r\n uint256 minimum = _getMinimumAmount(l, isCallPool);\r\n\r\n if (from != address(0)) {\r\n uint256 balance = _balanceOf(from, id);\r\n\r\n if (balance > minimum && balance <= amount + minimum) {\r\n require(\r\n balance -\r\n l.pendingDeposits[from][\r\n l.nextDeposits[isCallPool].eta\r\n ][isCallPool] >=\r\n amount,\r\n \"Insuf balance\"\r\n );\r\n l.removeUnderwriter(from, isCallPool);\r\n }\r\n\r\n if (to != address(0)) {\r\n _subUserTVL(l, from, isCallPool, amounts[i]);\r\n _addUserTVL(l, to, isCallPool, amounts[i]);\r\n }\r\n }\r\n\r\n if (to != address(0)) {\r\n uint256 balance = _balanceOf(to, id);\r\n\r\n if (balance <= minimum && balance + amount > minimum) {\r\n l.addUnderwriter(to, isCallPool);\r\n }\r\n }\r\n }\r\n\r\n // Update userTVL on SHORT options transfers\r\n (\r\n PoolStorage.TokenType tokenType,\r\n ,\r\n int128 strike64x64\r\n ) = PoolStorage.parseTokenId(id);\r\n\r\n if (\r\n (from != address(0) && to != address(0)) &&\r\n (tokenType == PoolStorage.TokenType.SHORT_CALL ||\r\n tokenType == PoolStorage.TokenType.SHORT_PUT)\r\n ) {\r\n bool isCall = tokenType == PoolStorage.TokenType.SHORT_CALL;\r\n uint256 collateral = isCall\r\n ? amount\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(amount));\r\n\r\n _subUserTVL(l, from, isCall, collateral);\r\n _addUserTVL(l, to, isCall, collateral);\r\n }\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary ERC1155BaseStorage {\n struct Layout {\n mapping(uint256 => mapping(address => uint256)) balances;\n mapping(address => mapping(address => bool)) operatorApprovals;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.ERC1155Base');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n}\n"},"@solidstate/contracts/utils/EnumerableSet.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Set implementation with enumeration functions\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)\n */\nlibrary EnumerableSet {\n struct Set {\n bytes32[] _values;\n // 1-indexed to allow 0 to signify nonexistence\n mapping(bytes32 => uint256) _indexes;\n }\n\n struct Bytes32Set {\n Set _inner;\n }\n\n struct AddressSet {\n Set _inner;\n }\n\n struct UintSet {\n Set _inner;\n }\n\n function at(Bytes32Set storage set, uint256 index)\n internal\n view\n returns (bytes32)\n {\n return _at(set._inner, index);\n }\n\n function at(AddressSet storage set, uint256 index)\n internal\n view\n returns (address)\n {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n function at(UintSet storage set, uint256 index)\n internal\n view\n returns (uint256)\n {\n return uint256(_at(set._inner, index));\n }\n\n function contains(Bytes32Set storage set, bytes32 value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, value);\n }\n\n function contains(AddressSet storage set, address value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function contains(UintSet storage set, uint256 value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, bytes32(value));\n }\n\n function indexOf(Bytes32Set storage set, bytes32 value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, value);\n }\n\n function indexOf(AddressSet storage set, address value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function indexOf(UintSet storage set, uint256 value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, bytes32(value));\n }\n\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function add(Bytes32Set storage set, bytes32 value)\n internal\n returns (bool)\n {\n return _add(set._inner, value);\n }\n\n function add(AddressSet storage set, address value)\n internal\n returns (bool)\n {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n function remove(Bytes32Set storage set, bytes32 value)\n internal\n returns (bool)\n {\n return _remove(set._inner, value);\n }\n\n function remove(AddressSet storage set, address value)\n internal\n returns (bool)\n {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function remove(UintSet storage set, uint256 value)\n internal\n returns (bool)\n {\n return _remove(set._inner, bytes32(value));\n }\n\n function _at(Set storage set, uint256 index)\n private\n view\n returns (bytes32)\n {\n require(\n set._values.length > index,\n 'EnumerableSet: index out of bounds'\n );\n return set._values[index];\n }\n\n function _contains(Set storage set, bytes32 value)\n private\n view\n returns (bool)\n {\n return set._indexes[value] != 0;\n }\n\n function _indexOf(Set storage set, bytes32 value)\n private\n view\n returns (uint256)\n {\n unchecked {\n return set._indexes[value] - 1;\n }\n }\n\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n uint256 index = valueIndex - 1;\n bytes32 last = set._values[set._values.length - 1];\n\n // move last value to now-vacant index\n\n set._values[index] = last;\n set._indexes[last] = index + 1;\n\n // clear last index\n\n set._values.pop();\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n}\n"},"contracts/mining/PremiaMiningStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nlibrary PremiaMiningStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.PremiaMining\");\r\n\r\n // Info of each pool.\r\n struct PoolInfo {\r\n uint256 allocPoint; // How many allocation points assigned to this pool. PREMIA to distribute per block.\r\n uint256 lastRewardTimestamp; // Last timestamp that PREMIA distribution occurs\r\n uint256 accPremiaPerShare; // Accumulated PREMIA per share, times 1e12. See below.\r\n }\r\n\r\n // Info of each user.\r\n struct UserInfo {\r\n uint256 reward; // Total allocated unclaimed reward\r\n uint256 rewardDebt; // Reward debt. See explanation below.\r\n //\r\n // We do some fancy math here. Basically, any point in time, the amount of PREMIA\r\n // entitled to a user but is pending to be distributed is:\r\n //\r\n // pending reward = (user.amount * pool.accPremiaPerShare) - user.rewardDebt\r\n //\r\n // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:\r\n // 1. The pool's `accPremiaPerShare` (and `lastRewardBlock`) gets updated.\r\n // 2. User receives the pending reward sent to his/her address.\r\n // 3. User's `amount` gets updated.\r\n // 4. User's `rewardDebt` gets updated.\r\n }\r\n\r\n struct Layout {\r\n // Total PREMIA left to distribute\r\n uint256 premiaAvailable;\r\n // Amount of premia distributed per year\r\n uint256 premiaPerYear;\r\n // pool -> isCallPool -> PoolInfo\r\n mapping(address => mapping(bool => PoolInfo)) poolInfo;\r\n // pool -> isCallPool -> user -> UserInfo\r\n mapping(address => mapping(bool => mapping(address => UserInfo))) userInfo;\r\n // Total allocation points. Must be the sum of all allocation points in all pools.\r\n uint256 totalAllocPoint;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/utils/IWETH.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20 } from '../token/ERC20/IERC20.sol';\nimport { IERC20Metadata } from '../token/ERC20/metadata/IERC20Metadata.sol';\n\n/**\n * @title WETH (Wrapped ETH) interface\n */\ninterface IWETH is IERC20, IERC20Metadata {\n /**\n * @notice convert ETH to WETH\n */\n function deposit() external payable;\n\n /**\n * @notice convert WETH to ETH\n * @dev if caller is a contract, it should have a fallback or receive function\n * @param amount quantity of WETH to convert, denominated in wei\n */\n function withdraw(uint256 amount) external;\n}\n"},"@solidstate/contracts/token/ERC1155/IERC1155Internal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @notice Partial ERC1155 interface needed by internal functions\n */\ninterface IERC1155Internal {\n event TransferSingle(\n address indexed operator,\n address indexed from,\n address indexed to,\n uint256 id,\n uint256 value\n );\n\n event TransferBatch(\n address indexed operator,\n address indexed from,\n address indexed to,\n uint256[] ids,\n uint256[] values\n );\n\n event ApprovalForAll(\n address indexed account,\n address indexed operator,\n bool approved\n );\n}\n"},"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n\n function decimals()\n external\n view\n returns (\n uint8\n );\n\n function description()\n external\n view\n returns (\n string memory\n );\n\n function version()\n external\n view\n returns (\n uint256\n );\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(\n uint80 _roundId\n )\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n}\n"},"@solidstate/contracts/access/OwnableStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary OwnableStorage {\n struct Layout {\n address owner;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.Ownable');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n\n function setOwner(Layout storage l, address owner) internal {\n l.owner = owner;\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":200},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"libraries":{"contracts/libraries/OptionMath.sol":{"OptionMath":"0x0f6e8ef18fb5bb61d545fee60f779d8aed60408f"}}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ivolOracle\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"weth\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"premiaMining\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeReceiver\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeDiscountAddress\",\"type\":\"address\"},{\"internalType\":\"int128\",\"name\":\"fee64x64\",\"type\":\"int128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Annihilate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"underwriter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"freedAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalContractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"AssignExercise\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"exerciseValue\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"Exercise\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FeeWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"spot64x64\",\"type\":\"int128\"}],\"name\":\"Purchase\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"}],\"name\":\"TransferBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"TransferSingle\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"underwriter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"longReceiver\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalContractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalPremium\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isManualUnderwrite\",\"type\":\"bool\"}],\"name\":\"Underwrite\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"isCall\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"cLevel64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"oldLiquidity64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"newLiquidity64x64\",\"type\":\"int128\"}],\"name\":\"UpdateCLevel\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"steepness64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"}],\"name\":\"UpdateSteepness\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"depositedAt\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"holder\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"}],\"name\":\"exerciseFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"}],\"name\":\"processExpired\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"PoolExercise","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x0000000000000000000000003a87bb29b984d672664aa1dd2d19d2e8b24f0f2a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009abb27581c2e46a114f8c367355851e0580e9703000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf000000000000000000000000f1bb87563a122211d40d393ebf1c633c330377f900000000000000000000000000000000000000000000000007ae147ae147ae14","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/fixtures/Dir/depth1 b/testdata/fixtures/Dir/depth1 index febdc5997fa40..17c01c6ebd131 100644 --- a/testdata/fixtures/Dir/depth1 +++ b/testdata/fixtures/Dir/depth1 @@ -1 +1 @@ -Wow! 😀 +Wow! 😀 \ No newline at end of file diff --git a/testdata/fixtures/File/symlink b/testdata/fixtures/File/symlink deleted file mode 100644 index c727e632c8883..0000000000000 --- a/testdata/fixtures/File/symlink +++ /dev/null @@ -1,2 +0,0 @@ -hello readable world -this is the second line! \ No newline at end of file diff --git a/testdata/fixtures/File/symlink b/testdata/fixtures/File/symlink new file mode 120000 index 0000000000000..2fe9fd1c6dc4c --- /dev/null +++ b/testdata/fixtures/File/symlink @@ -0,0 +1 @@ +read.txt \ No newline at end of file diff --git a/testdata/fixtures/GetCode/Override.sol b/testdata/fixtures/GetCode/Override.sol index 1c3875650c17a..f520bf0df02f4 100644 --- a/testdata/fixtures/GetCode/Override.sol +++ b/testdata/fixtures/GetCode/Override.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity ^0.8.13; contract Override { diff --git a/testdata/fixtures/GetCode/UnlinkedContract.sol b/testdata/fixtures/GetCode/UnlinkedContract.sol index 1e972782fd05f..93c6a8e261858 100644 --- a/testdata/fixtures/GetCode/UnlinkedContract.sol +++ b/testdata/fixtures/GetCode/UnlinkedContract.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; library SmolLibrary { function add(uint256 a, uint256 b) public pure returns (uint256 c) { diff --git a/testdata/fixtures/GetCode/WorkingContract.sol b/testdata/fixtures/GetCode/WorkingContract.sol index d6a6820dd3d1b..7f4cb79afe73a 100644 --- a/testdata/fixtures/GetCode/WorkingContract.sol +++ b/testdata/fixtures/GetCode/WorkingContract.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; contract WorkingContract { uint256 public constant secret = 42; diff --git a/testdata/fixtures/Json/Issue4402.json b/testdata/fixtures/Json/Issue4402.json new file mode 100644 index 0000000000000..e981817fe9051 --- /dev/null +++ b/testdata/fixtures/Json/Issue4402.json @@ -0,0 +1,4 @@ +{ + "tokens": ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], + "empty": [] +} \ 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 0000000000000..ac6fe7692bb90 --- /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/fixtures/Json/test.json b/testdata/fixtures/Json/test.json index 5277d6d2d454a..1f59ba456269c 100644 --- a/testdata/fixtures/Json/test.json +++ b/testdata/fixtures/Json/test.json @@ -1,28 +1,31 @@ { - "str": "hai", - "uintArray": [42, 43], - "strArray": ["hai", "there"], - "bool": true, - "boolArray": [true, false], + "basicString": "hai", + "null": null, + "stringArray": ["hai", "there"], "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "addressArray": [ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D" ], "H160NotAddress": "0000000000000000000000000000000000001337", + "boolTrue": true, + "boolFalse": false, + "boolArray": [true, false], + "boolString": "true", + "boolStringArray": [true, "false"], + "uintNumber": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "uintString": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "uintHex": "0x12C980", + "uintArray": [42, 43], + "uintStringArray": [1231232, "0x12C980", "1231232"], + "intNumber": -12, + "intString": "-12", + "intHex": "-0xC", + "bytesString": "0x01", + "bytesStringArray": ["0x01", "0x02"], "nestedObject": { "number": 115792089237316195423570985008687907853269984665640564039457584007913129639935, "str": "NEST" }, - "bytesArray": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000", - "hexUint": "0x12C980", - "stringUint": "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "numberUint": 115792089237316195423570985008687907853269984665640564039457584007913129639935, - "arrayUint": [1231232, "0x12C980", "1231232"], - "stringInt": "-12", - "numberInt": -12, - "hexInt": "0x-C", - "booleanString": "true", - "booleanArray": [true, "false"], "advancedJsonPath": [{ "id": 1 }, { "id": 2 }] } diff --git a/testdata/fixtures/Json/test_allocs.json b/testdata/fixtures/Json/test_allocs.json new file mode 100644 index 0000000000000..32be846a5cb80 --- /dev/null +++ b/testdata/fixtures/Json/test_allocs.json @@ -0,0 +1,15 @@ +{ + "0x0000000000000000000000000000000000000420": { + "balance": "0xabcd", + "code": "0x604260005260206000F3", + "storage": { + "0x1000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000000beef" + } + }, + "0x0000000000000000000000000000000000000421": { + "balance": "0x0", + "storage": { + "0x1000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000000beef" + } + } +} diff --git a/testdata/fixtures/Json/wholeJson.json b/testdata/fixtures/Json/whole_json.json similarity index 100% rename from testdata/fixtures/Json/wholeJson.json rename to testdata/fixtures/Json/whole_json.json diff --git a/testdata/fixtures/Rpc/README.md b/testdata/fixtures/Rpc/README.md new file mode 100644 index 0000000000000..0d7abd79cd1f7 --- /dev/null +++ b/testdata/fixtures/Rpc/README.md @@ -0,0 +1,25 @@ +# Fixture Generation Instructions + +### `eth_getLogs.json` + +To generate this fixture, send a POST request to a Eth Mainnet (chainId = 1) RPC + +``` +{ + "jsonrpc": "2.0", + "method": "eth_getLogs", + "id": "1", + "params": [ + { + "address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "fromBlock": "0x10CEB1B", + "toBlock": "0x10CEB1B", + "topics": [ + "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + ] + } + ] +} +``` + +Then you must change the `address` key to `emitter` because in Solidity, a struct's name cannot be `address` as that is a keyword. diff --git a/testdata/fixtures/Rpc/balance_params.json b/testdata/fixtures/Rpc/balance_params.json new file mode 100644 index 0000000000000..5004108761fbc --- /dev/null +++ b/testdata/fixtures/Rpc/balance_params.json @@ -0,0 +1 @@ +["0x8D97689C9818892B700e27F316cc3E41e17fBeb9", "0x117BC09"] \ No newline at end of file diff --git a/testdata/fixtures/Rpc/eth_getLogs.json b/testdata/fixtures/Rpc/eth_getLogs.json new file mode 100644 index 0000000000000..c7c7bac8184c8 --- /dev/null +++ b/testdata/fixtures/Rpc/eth_getLogs.json @@ -0,0 +1,44 @@ +[ + { + "emitter": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "topics": [ + "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65", + "0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d" + ], + "data": "0x0000000000000000000000000000000000000000000000000186faccfe3e2bcc", + "blockNumber": "0x10ceb1b", + "transactionHash": "0xa08f7b4aaa57cb2baec601ad96878d227ae3289a8dd48df98cce30c168588ce7", + "transactionIndex": "0xc", + "blockHash": "0xe4299c95a140ddad351e9831cfb16c35cc0014e8cbd8465de2e5112847d70465", + "logIndex": "0x42", + "removed": false + }, + { + "emitter": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "topics": [ + "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65", + "0x0000000000000000000000002ec705d306b51e486b1bc0d6ebee708e0661add1" + ], + "data": "0x000000000000000000000000000000000000000000000000004befaedcfaea00", + "blockNumber": "0x10ceb1b", + "transactionHash": "0x2cd5355bd917ec5c28194735ad539a4cb58e4b08815a038f6e2373290caeee1d", + "transactionIndex": "0x11", + "blockHash": "0xe4299c95a140ddad351e9831cfb16c35cc0014e8cbd8465de2e5112847d70465", + "logIndex": "0x56", + "removed": false + }, + { + "emitter": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + "topics": [ + "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65", + "0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d" + ], + "data": "0x000000000000000000000000000000000000000000000000003432a29cd0ed22", + "blockNumber": "0x10ceb1b", + "transactionHash": "0x4e762d9a572084e0ec412ddf6c4e6d0b746b10e9714d4e786c13579e2e3c3187", + "transactionIndex": "0x16", + "blockHash": "0xe4299c95a140ddad351e9831cfb16c35cc0014e8cbd8465de2e5112847d70465", + "logIndex": "0x68", + "removed": false + } +] \ No newline at end of file diff --git a/testdata/fixtures/Toml/Issue4402.toml b/testdata/fixtures/Toml/Issue4402.toml new file mode 100644 index 0000000000000..8f7d110238c89 --- /dev/null +++ b/testdata/fixtures/Toml/Issue4402.toml @@ -0,0 +1,2 @@ +tokens = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"] +empty = [] diff --git a/testdata/fixtures/Toml/nested_toml_struct.toml b/testdata/fixtures/Toml/nested_toml_struct.toml new file mode 100644 index 0000000000000..3cef0b7ba1045 --- /dev/null +++ b/testdata/fixtures/Toml/nested_toml_struct.toml @@ -0,0 +1,23 @@ +name = "test" + +[[members]] +a = 100 +arr = [ + [1, -2, -5], + [1000, 2000, 0] +] +str = "some string" +b = "0x" +addr = "0x0000000000000000000000000000000000000000" +fixedBytes = "0x8ae3fc6bd1b150a73ec4afe3ef136fa2f88e9c96131c883c5e4a4714811c1598" + +[[members]] +a = 200 +arr = [] +str = "some other string" +b = "0x0000000000000000000000000000000000000000" +addr = "0x167D91deaEEE3021161502873d3bcc6291081648" +fixedBytes = "0xed1c7beb1f00feaaaec5636950d6edb25a8d4fedc8deb2711287b64c4d27719d" + +[inner] +fixedBytes = "0x12345678" diff --git a/testdata/fixtures/Toml/test.toml b/testdata/fixtures/Toml/test.toml new file mode 100644 index 0000000000000..806dc2224de46 --- /dev/null +++ b/testdata/fixtures/Toml/test.toml @@ -0,0 +1,50 @@ +basicString = "hai" +nullString = "null" +multilineString = """ +hai +there +""" +stringArray = ["hai", "there"] + +address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +addressArray = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D", +] +H160NotAddress = "0000000000000000000000000000000000001337" + +boolTrue = true +boolFalse = false +boolArray = [true, false] +boolString = "true" +boolStringArray = ["true", "false"] # Array values can't have mixed types + +datetime = 2021-08-10T14:48:00Z +datetimeArray = [2021-08-10T14:48:00Z, 2021-08-10T14:48:00Z] + +uintNumber = 9223372036854775807 # TOML is limited to 64-bit integers +uintString = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +uintHex = "0x12C980" +uintArray = [42, 43] +uintStringArray = [ + "1231232", + "0x12C980", + "1231232", +] # Array values can't have mixed types + +intNumber = -12 +intString = "-12" +intHex = "-0xC" + +bytesString = "0x01" +bytesStringArray = ["0x01", "0x02"] + +[nestedObject] +number = 9223372036854775807 # TOML is limited to 64-bit integers +str = "NEST" + +[[advancedTomlPath]] +id = 1 + +[[advancedTomlPath]] +id = 2 diff --git a/testdata/fixtures/Toml/whole_toml.toml b/testdata/fixtures/Toml/whole_toml.toml new file mode 100644 index 0000000000000..badbd9fbbe5cd --- /dev/null +++ b/testdata/fixtures/Toml/whole_toml.toml @@ -0,0 +1,3 @@ +str = "hai" +uintArray = [42, 43] +strArray = ["hai", "there"] diff --git a/testdata/fixtures/Toml/write_complex_test.toml b/testdata/fixtures/Toml/write_complex_test.toml new file mode 100644 index 0000000000000..60692bc750201 --- /dev/null +++ b/testdata/fixtures/Toml/write_complex_test.toml @@ -0,0 +1,6 @@ +a = 123 +b = "test" + +[c] +a = 123 +b = "test" diff --git a/testdata/fixtures/Toml/write_test.toml b/testdata/fixtures/Toml/write_test.toml new file mode 100644 index 0000000000000..6c084e370e5a6 --- /dev/null +++ b/testdata/fixtures/Toml/write_test.toml @@ -0,0 +1,2 @@ +a = 123 +b = "0x000000000000000000000000000000000000bEEF" diff --git a/testdata/forge-std-rev b/testdata/forge-std-rev new file mode 100644 index 0000000000000..ac11c37c7c648 --- /dev/null +++ b/testdata/forge-std-rev @@ -0,0 +1 @@ +3b20d60d14b343ee4f908cb8079495c07f5e8981 \ No newline at end of file diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 5bab89def6622..e9189bb008a32 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc = "0.8.19" +solc = "0.8.18" block_base_fee_per_gas = 0 block_coinbase = "0x0000000000000000000000000000000000000000" block_difficulty = 0 @@ -16,7 +16,7 @@ ffi = false force = false invariant_fail_on_revert = false invariant_call_override = false -invariant_shrink_sequence = true +invariant_shrink_run_limit = 5000 gas_limit = 9223372036854775807 gas_price = 0 gas_reports = ["*"] @@ -36,10 +36,10 @@ remappings = ["ds-test/=lib/ds-test/src/"] sender = "0x00a329c0648769a73afac7f9381e08fb43dbea72" sizes = false sparse_mode = false -src = "src" -test = "test" +src = "./" +test = "./" tx_origin = "0x00a329c0648769a73afac7f9381e08fb43dbea72" -verbosity = 0 +verbosity = 3 via_ir = false fs_permissions = [{ access = "read-write", path = "./" }] @@ -50,3 +50,6 @@ endpoints = "all" [fuzz] runs = 256 max_test_rejects = 65536 + +[fmt] +ignore = ["cheats/Vm.sol"] diff --git a/testdata/fs/Default.t.sol b/testdata/fs/Default.t.sol deleted file mode 100644 index a745608218da1..0000000000000 --- a/testdata/fs/Default.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "../cheats/Vm.sol"; - -contract FsProxy is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function readFile(string calldata path) external returns (string memory) { - return vm.readFile(path); - } - - function readDir(string calldata path) external returns (Vm.DirEntry[] memory) { - return vm.readDir(path); - } - - function readFileBinary(string calldata path) external returns (bytes memory) { - return vm.readFileBinary(path); - } - - function readLine(string calldata path) external returns (string memory) { - return vm.readLine(path); - } - - function writeLine(string calldata path, string calldata data) external { - return vm.writeLine(path, data); - } - - function writeFile(string calldata path, string calldata data) external { - return vm.writeLine(path, data); - } - - function writeFileBinary(string calldata path, bytes calldata data) external { - return vm.writeFileBinary(path, data); - } - - function removeFile(string calldata path) external { - return vm.removeFile(path); - } - - function fsMetadata(string calldata path) external returns (Vm.FsMetadata memory) { - return vm.fsMetadata(path); - } - - function createDir(string calldata path) external { - return vm.createDir(path, false); - } - - function createDir(string calldata path, bool recursive) external { - return vm.createDir(path, recursive); - } -} - -contract DefaultAccessTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - FsProxy public fsProxy; - - bytes constant FOUNDRY_WRITE_ERR = - "The path \"fixtures/File/write_file.txt\" is not allowed to be accessed for write operations."; - - function testReadFile() public { - string memory path = "fixtures/File/read.txt"; - vm.readFile(path); - - vm.readFileBinary(path); - } - - function testReadLine() public { - string memory path = "fixtures/File/read.txt"; - vm.readLine(path); - } - - function testWriteFile() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/write_file.txt"; - string memory data = "hello writable world"; - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeFile(path, data); - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeFileBinary(path, bytes(data)); - } - - function testWriteLine() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/write_file.txt"; - string memory data = "hello writable world"; - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeLine(path, data); - } - - function testRemoveFile() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/write_file.txt"; - - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.removeFile(path); - } -} diff --git a/testdata/fs/Disabled.t.sol b/testdata/fs/Disabled.t.sol deleted file mode 100644 index 6af6d83c8f174..0000000000000 --- a/testdata/fs/Disabled.t.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "../cheats/Vm.sol"; - -contract FsProxy is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - function readFile(string calldata path) external returns (string memory) { - return vm.readFile(path); - } - - function readDir(string calldata path) external returns (Vm.DirEntry[] memory) { - return vm.readDir(path); - } - - function readFileBinary(string calldata path) external returns (bytes memory) { - return vm.readFileBinary(path); - } - - function readLine(string calldata path) external returns (string memory) { - return vm.readLine(path); - } - - function writeLine(string calldata path, string calldata data) external { - return vm.writeLine(path, data); - } - - function writeFile(string calldata path, string calldata data) external { - return vm.writeLine(path, data); - } - - function writeFileBinary(string calldata path, bytes calldata data) external { - return vm.writeFileBinary(path, data); - } - - function removeFile(string calldata path) external { - return vm.removeFile(path); - } - - function fsMetadata(string calldata path) external returns (Vm.FsMetadata memory) { - return vm.fsMetadata(path); - } - - function createDir(string calldata path) external { - return vm.createDir(path, false); - } - - function createDir(string calldata path, bool recursive) external { - return vm.createDir(path, recursive); - } -} - -contract DisabledTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - FsProxy public fsProxy; - - bytes constant FOUNDRY_READ_ERR = - "The path \"fixtures/File/read.txt\" is not allowed to be accessed for read operations."; - bytes constant FOUNDRY_WRITE_ERR = - "The path \"fixtures/File/write_file.txt\" is not allowed to be accessed for write operations."; - - function testReadFile() public { - fsProxy = new FsProxy(); - - string memory path = "fixtures/File/read.txt"; - vm.expectRevert(FOUNDRY_READ_ERR); - fsProxy.readFile(path); - } - - function testReadLine() public { - fsProxy = new FsProxy(); - string memory path = "fixtures/File/read.txt"; - vm.expectRevert(FOUNDRY_READ_ERR); - fsProxy.readLine(path); - } - - function testWriteFile() public { - fsProxy = new FsProxy(); - string memory path = "fixtures/File/write_file.txt"; - string memory data = "hello writable world"; - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeFile(path, data); - } - - function testWriteLine() public { - fsProxy = new FsProxy(); - string memory path = "fixtures/File/write_file.txt"; - string memory data = "hello writable world"; - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.writeLine(path, data); - } - - function testRemoveFile() public { - fsProxy = new FsProxy(); - string memory path = "fixtures/File/write_file.txt"; - vm.expectRevert(FOUNDRY_WRITE_ERR); - fsProxy.removeFile(path); - } -} diff --git a/testdata/inline/FuzzInlineConf.t.sol b/testdata/inline/FuzzInlineConf.t.sol deleted file mode 100644 index 8ab3a16d310bd..0000000000000 --- a/testdata/inline/FuzzInlineConf.t.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity >=0.8.0; - -import "ds-test/test.sol"; - -contract FuzzInlineConf is DSTest { - /** - * forge-config: default.fuzz.runs = 1024 - * forge-config: default.fuzz.max-test-rejects = 500 - */ - function testInlineConfFuzz(uint8 x) public { - require(true, "this is not going to revert"); - } -} diff --git a/testdata/multi-version/Counter.sol b/testdata/multi-version/Counter.sol new file mode 100644 index 0000000000000..4f0c350335f8e --- /dev/null +++ b/testdata/multi-version/Counter.sol @@ -0,0 +1,11 @@ +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/testdata/multi-version/Importer.sol b/testdata/multi-version/Importer.sol new file mode 100644 index 0000000000000..d8e274e8f5447 --- /dev/null +++ b/testdata/multi-version/Importer.sol @@ -0,0 +1,7 @@ +pragma solidity 0.8.17; + +import "./Counter.sol"; + +// Please do not remove or change version pragma for this file. +// If you need to ensure that some of the files are compiled with +// solc 0.8.17, you should add imports of them to this file. diff --git a/testdata/multi-version/cheats/GetCode.t.sol b/testdata/multi-version/cheats/GetCode.t.sol new file mode 100644 index 0000000000000..72dae24e676af --- /dev/null +++ b/testdata/multi-version/cheats/GetCode.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity =0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../Counter.sol"; + +contract GetCodeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetCodeMultiVersion() public { + assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); + require( + keccak256(vm.getCode("Counter.sol")) != keccak256(vm.getCode("Counter.sol:Counter:0.8.17")), + "Invalid artifact" + ); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter.sol:Counter:0.8.18")); + } + + function testGetCodeByNameMultiVersion() public { + assertEq(vm.getCode("Counter"), type(Counter).creationCode); + require(keccak256(vm.getCode("Counter")) != keccak256(vm.getCode("Counter:0.8.17")), "Invalid artifact"); + assertEq(vm.getCode("Counter"), vm.getCode("Counter:0.8.18")); + } +} diff --git a/testdata/multi-version/cheats/GetCode17.t.sol b/testdata/multi-version/cheats/GetCode17.t.sol new file mode 100644 index 0000000000000..f8bf4bb2aee28 --- /dev/null +++ b/testdata/multi-version/cheats/GetCode17.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity =0.8.17; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../Counter.sol"; + +// Same as GetCode.t.sol but for 0.8.17 version +contract GetCodeTest17 is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetCodeMultiVersion() public { + assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); + require( + keccak256(vm.getCode("Counter.sol")) != keccak256(vm.getCode("Counter.sol:Counter:0.8.18")), + "Invalid artifact" + ); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter.sol:Counter:0.8.17")); + } + + function testGetCodeByNameMultiVersion() public { + assertEq(vm.getCode("Counter"), type(Counter).creationCode); + require(keccak256(vm.getCode("Counter")) != keccak256(vm.getCode("Counter:0.8.18")), "Invalid artifact"); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter:0.8.17")); + } +} diff --git a/testdata/paris/cheats/Fork.t.sol b/testdata/paris/cheats/Fork.t.sol new file mode 100644 index 0000000000000..2f2e627de131a --- /dev/null +++ b/testdata/paris/cheats/Fork.t.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IWETH { + function deposit() external payable; + function balanceOf(address) external view returns (uint256); +} + +contract ForkTest is DSTest { + address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + uint256 constant mainblock = 14_608_400; + + Vm constant vm = Vm(HEVM_ADDRESS); + IWETH WETH = IWETH(WETH_TOKEN_ADDR); + + uint256 forkA; + uint256 forkB; + + uint256 testValue; + + // this will create two _different_ forks during setup + function setUp() public { + forkA = vm.createFork("mainnet", mainblock); + forkB = vm.createFork("mainnet2", mainblock - 1); + testValue = 999; + } + + // ensures forks use different ids + function testForkIdDiffer() public { + assert(forkA != forkB); + } + + // ensures we can create and select in one step + function testCreateSelect() public { + uint256 fork = vm.createSelectFork("mainnet"); + assertEq(fork, vm.activeFork()); + } + + // ensures forks use different ids + function testCanSwitchForks() public { + vm.selectFork(forkA); + vm.selectFork(forkB); + vm.selectFork(forkB); + vm.selectFork(forkA); + } + + function testForksHaveSeparatedStorage() public { + vm.selectFork(forkA); + // read state from forkA + assert(WETH.balanceOf(0x0000000000000000000000000000000000000000) != 1); + + vm.selectFork(forkB); + // read state from forkB + uint256 forkBbalance = WETH.balanceOf(0x0000000000000000000000000000000000000000); + assert(forkBbalance != 1); + + vm.selectFork(forkA); + + // modify state + bytes32 value = bytes32(uint256(1)); + // "0x3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff" is the slot storing the balance of zero address for the weth contract + // `cast index address uint 0x0000000000000000000000000000000000000000 3` + bytes32 zero_address_balance_slot = 0x3617319a054d772f909f7c479a2cebe5066e836a939412e32403c99029b92eff; + vm.store(WETH_TOKEN_ADDR, zero_address_balance_slot, value); + assertEq( + WETH.balanceOf(0x0000000000000000000000000000000000000000), + 1, + "Cheatcode did not change value at the storage slot." + ); + + // switch forks and ensure the balance on forkB remains untouched + vm.selectFork(forkB); + assert(forkBbalance != 1); + // balance of forkB is untouched + assertEq( + WETH.balanceOf(0x0000000000000000000000000000000000000000), + forkBbalance, + "Cheatcode did not change value at the storage slot." + ); + } + + function testCanShareDataAcrossSwaps() public { + assertEq(testValue, 999); + + uint256 val = 300; + vm.selectFork(forkA); + assertEq(val, 300); + + testValue = 100; + + vm.selectFork(forkB); + assertEq(val, 300); + assertEq(testValue, 100); + + val = 99; + testValue = 300; + + vm.selectFork(forkA); + assertEq(val, 99); + assertEq(testValue, 300); + } + + // ensures forks use different ids + function testCanChangeChainId() public { + vm.selectFork(forkA); + uint256 newChainId = 1337; + vm.chainId(newChainId); + uint256 expected = block.chainid; + assertEq(newChainId, expected); + } + + // ensures forks change chain ids automatically + function testCanAutoUpdateChainId() public { + vm.createSelectFork("sepolia"); + assertEq(block.chainid, 11155111); + } + + // ensures forks storage is cached at block + function testStorageCaching() public { + vm.createSelectFork("mainnet", 19800000); + } +} diff --git a/testdata/paris/cheats/GasSnapshots.t.sol b/testdata/paris/cheats/GasSnapshots.t.sol new file mode 100644 index 0000000000000..98abfa3e48efe --- /dev/null +++ b/testdata/paris/cheats/GasSnapshots.t.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GasSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public slot0; + Flare public flare; + + function setUp() public { + flare = new Flare(); + } + + function testSnapshotGasSectionExternal() public { + vm.startSnapshotGas("testAssertGasExternal"); + flare.run(1); + uint256 gasUsed = vm.stopSnapshotGas(); + + assertGt(gasUsed, 0); + } + + function testSnapshotGasSectionInternal() public { + vm.startSnapshotGas("testAssertGasInternalA"); + slot0 = 1; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalB"); + slot0 = 2; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalC"); + slot0 = 0; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalD"); + slot0 = 1; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalE"); + slot0 = 2; + vm.stopSnapshotGas(); + } + + // Writes to `GasSnapshotTest` group with custom names. + function testSnapshotValueDefaultGroupA() public { + uint256 a = 123; + uint256 b = 456; + uint256 c = 789; + + vm.snapshotValue("a", a); + vm.snapshotValue("b", b); + vm.snapshotValue("c", c); + } + + // Writes to same `GasSnapshotTest` group with custom names. + function testSnapshotValueDefaultGroupB() public { + uint256 d = 123; + uint256 e = 456; + uint256 f = 789; + + vm.snapshotValue("d", d); + vm.snapshotValue("e", e); + vm.snapshotValue("f", f); + } + + // Writes to `CustomGroup` group with custom names. + // Asserts that the order of the values is alphabetical. + function testSnapshotValueCustomGroupA() public { + uint256 o = 123; + uint256 i = 456; + uint256 q = 789; + + vm.snapshotValue("CustomGroup", "q", q); + vm.snapshotValue("CustomGroup", "i", i); + vm.snapshotValue("CustomGroup", "o", o); + } + + // Writes to `CustomGroup` group with custom names. + // Asserts that the order of the values is alphabetical. + function testSnapshotValueCustomGroupB() public { + uint256 x = 123; + uint256 e = 456; + uint256 z = 789; + + vm.snapshotValue("CustomGroup", "z", z); + vm.snapshotValue("CustomGroup", "x", x); + vm.snapshotValue("CustomGroup", "e", e); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasDefault` name. + function testSnapshotGasSectionDefaultGroupStop() public { + vm.startSnapshotGas("testSnapshotGasSection"); + + flare.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasCustom` name. + function testSnapshotGasSectionCustomGroupStop() public { + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); + + flare.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name, even with custom group. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionName() public { + vm.startSnapshotGas("testSnapshotGasSectionName"); + + flare.run(256); + + uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); + assertGt(gasUsed, 0); + } + + // Writes to `CustomGroup` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionGroupName() public { + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + + flare.run(256); + + uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. + function testSnapshotGasLastCallName() public { + flare.run(1); + + uint256 gasUsed = vm.snapshotGasLastCall("testSnapshotGasLastCallName"); + assertGt(gasUsed, 0); + } + + // Writes to `CustomGroup` group with `testSnapshotGas` name. + function testSnapshotGasLastCallGroupName() public { + flare.run(1); + + uint256 gasUsed = vm.snapshotGasLastCall("CustomGroup", "testSnapshotGasLastCallGroupName"); + assertGt(gasUsed, 0); + } +} + +contract GasComparisonTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public slot0; + uint256 public slot1; + + uint256 public cachedGas; + + function testGasComparisonEmpty() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonEmptyA"); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonEmptyB", b); + + assertEq(a, b); + } + + function testGasComparisonInternalCold() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonInternalColdA"); + slot0 = 1; + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + slot1 = 1; + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonInternalColdB", b); + + vm.assertApproxEqAbs(a, b, 6); + } + + function testGasComparisonInternalWarm() public { + // Warm up the cache. + slot0 = 1; + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonInternalWarmA"); + slot0 = 2; + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + slot0 = 3; + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonInternalWarmB", b); + + vm.assertApproxEqAbs(a, b, 6); + } + + function testGasComparisonExternal() public { + // Warm up the cache. + TargetB target = new TargetB(); + target.update(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonExternalA"); + target.update(2); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + target.update(3); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonExternalB", b); + + assertEq(a, b); + } + + function testGasComparisonCreate() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonCreateA"); + new TargetC(); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + new TargetC(); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonCreateB", b); + + assertEq(a, b); + } + + function testGasComparisonNestedCalls() public { + // Warm up the cache. + TargetA target = new TargetA(); + target.update(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonNestedCallsA"); + target.update(2); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + target.update(3); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonNestedCallsB", b); + + assertEq(a, b); + } + + function testGasComparisonFlare() public { + // Warm up the cache. + Flare flare = new Flare(); + flare.run(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonFlareA"); + flare.run(256); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + flare.run(256); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonFlareB", b); + + assertEq(a, b); + } + + // Internal function to start a Solidity snapshot. + function _snapStart() internal { + cachedGas = 1; + cachedGas = gasleft(); + } + + // Internal function to end a Solidity snapshot. + function _snapEnd() internal returns (uint256 gasUsed) { + gasUsed = cachedGas - gasleft() - 138; + cachedGas = 2; + } +} + +contract Flare { + bytes32[] public data; + + function run(uint256 n_) public { + for (uint256 i = 0; i < n_; i++) { + data.push(keccak256(abi.encodePacked(i))); + } + } +} + +contract TargetA { + TargetB public target; + + constructor() { + target = new TargetB(); + } + + function update(uint256 x_) public { + target.update(x_); + } +} + +contract TargetB { + uint256 public x; + + function update(uint256 x_) public { + x = x_; + } +} + +contract TargetC {} diff --git a/testdata/paris/cheats/LastCallGas.t.sol b/testdata/paris/cheats/LastCallGas.t.sol new file mode 100644 index 0000000000000..23f6df224963f --- /dev/null +++ b/testdata/paris/cheats/LastCallGas.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Target { + uint256 public slot0; + + function expandMemory(uint256 n) public pure returns (uint256) { + uint256[] memory arr = new uint256[](n); + + for (uint256 i = 0; i < n; i++) { + arr[i] = i; + } + + return arr.length; + } + + function setValue(uint256 value) public { + slot0 = value; + } + + function resetValue() public { + slot0 = 0; + } + + fallback() external {} +} + +abstract contract LastCallGasFixture is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Target public target; + + struct Gas { + uint64 gasTotalUsed; + uint64 gasMemoryUsed; + int64 gasRefunded; + } + + function testRevertNoCachedLastCallGas() public { + vm._expectCheatcodeRevert(); + vm.lastCallGas(); + } + + function _setup() internal { + // Cannot be set in `setUp` due to `testRevertNoCachedLastCallGas` + // relying on no calls being made before `lastCallGas` is called. + target = new Target(); + } + + function _performCall() internal returns (bool success) { + (success,) = address(target).call(""); + } + + function _performRefund() internal { + target.setValue(1); + target.resetValue(); + } + + function _assertGas(Vm.Gas memory lhs, Gas memory rhs) internal { + assertGt(lhs.gasLimit, 0); + assertGt(lhs.gasRemaining, 0); + assertEq(lhs.gasTotalUsed, rhs.gasTotalUsed); + assertEq(lhs.gasMemoryUsed, rhs.gasMemoryUsed); + assertEq(lhs.gasRefunded, rhs.gasRefunded); + } +} + +contract LastCallGasIsolatedTest is LastCallGasFixture { + function testRecordLastCallGas() public { + _setup(); + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + } + + function testRecordGasRefund() public { + _setup(); + _performRefund(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21380, gasMemoryUsed: 0, gasRefunded: 4800})); + } +} + +// Without isolation mode enabled the gas usage will be incorrect. +contract LastCallGasDefaultTest is LastCallGasFixture { + function testRecordLastCallGas() public { + _setup(); + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + } + + function testRecordGasRefund() public { + _setup(); + _performRefund(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 216, gasMemoryUsed: 0, gasRefunded: 19900})); + } +} diff --git a/testdata/paris/core/BeforeTest.t.sol b/testdata/paris/core/BeforeTest.t.sol new file mode 100644 index 0000000000000..2b14bcad1d2ef --- /dev/null +++ b/testdata/paris/core/BeforeTest.t.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; + +contract SelfDestructor { + function kill() external { + selfdestruct(payable(msg.sender)); + } +} + +// https://github.com/foundry-rs/foundry/issues/1543 +contract BeforeTestSelfDestructTest is DSTest { + SelfDestructor killer; + uint256 a; + uint256 b; + + function setUp() public { + killer = new SelfDestructor(); + } + + function beforeTestSetup(bytes4 testSelector) public pure returns (bytes[] memory beforeTestCalldata) { + if (testSelector == this.testKill.selector) { + beforeTestCalldata = new bytes[](1); + beforeTestCalldata[0] = abi.encodePacked(this.kill_contract.selector); + } + + if (testSelector == this.testA.selector) { + beforeTestCalldata = new bytes[](3); + beforeTestCalldata[0] = abi.encodePacked(this.testA.selector); + beforeTestCalldata[1] = abi.encodePacked(this.testA.selector); + beforeTestCalldata[2] = abi.encodePacked(this.testA.selector); + } + + if (testSelector == this.testB.selector) { + beforeTestCalldata = new bytes[](1); + beforeTestCalldata[0] = abi.encodePacked(this.setB.selector); + } + + if (testSelector == this.testC.selector) { + beforeTestCalldata = new bytes[](2); + beforeTestCalldata[0] = abi.encodePacked(this.testA.selector); + beforeTestCalldata[1] = abi.encodeWithSignature("setBWithValue(uint256)", 111); + } + } + + function kill_contract() external { + uint256 killer_size = getSize(address(killer)); + require(killer_size == 106); + killer.kill(); + } + + function testKill() public view { + uint256 killer_size = getSize(address(killer)); + require(killer_size == 0); + } + + function getSize(address c) public view returns (uint32) { + uint32 size; + assembly { + size := extcodesize(c) + } + return size; + } + + function testA() public { + require(a <= 3); + a += 1; + } + + function testSimpleA() public view { + require(a == 0); + } + + function setB() public { + b = 100; + } + + function testB() public { + require(b == 100); + } + + function setBWithValue(uint256 value) public { + b = value; + } + + function testC(uint256 h) public { + assertEq(a, 1); + assertEq(b, 111); + } +} diff --git a/testdata/fork/Transact.t.sol b/testdata/paris/fork/Transact.t.sol similarity index 94% rename from testdata/fork/Transact.t.sol rename to testdata/paris/fork/Transact.t.sol index b15553d23c94f..92d595f98c516 100644 --- a/testdata/fork/Transact.t.sol +++ b/testdata/paris/fork/Transact.t.sol @@ -1,9 +1,9 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; -import "../logs/console.sol"; +import "cheats/Vm.sol"; +import "../../default/logs/console.sol"; interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); @@ -20,7 +20,7 @@ contract TransactOnForkTest is DSTest { function testTransact() public { // A random block https://etherscan.io/block/17134913 - uint256 fork = vm.createFork("rpcAlias", 17134913); + uint256 fork = vm.createFork("mainnet", 17134913); vm.selectFork(fork); // a random transfer transaction in the next block: https://etherscan.io/tx/0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771 bytes32 tx = 0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771; @@ -48,7 +48,7 @@ contract TransactOnForkTest is DSTest { function testTransactCooperatesWithCheatcodes() public { // A random block https://etherscan.io/block/16260609 - uint256 fork = vm.createFork("rpcAlias", 16260609); + uint256 fork = vm.createFork("mainnet", 16260609); vm.selectFork(fork); // a random ERC20 USDT transfer transaction in the next block: https://etherscan.io/tx/0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c diff --git a/testdata/paris/spec/ShanghaiCompat.t.sol b/testdata/paris/spec/ShanghaiCompat.t.sol new file mode 100644 index 0000000000000..fd7213b3d0702 --- /dev/null +++ b/testdata/paris/spec/ShanghaiCompat.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShanghaiCompat is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPush0() public { + address target = address(uint160(uint256(0xc4f3))); + + bytes memory bytecode = hex"365f5f37365ff3"; + // 36 CALLDATASIZE + // 5F PUSH0 + // 5F PUSH0 + // 37 CALLDATACOPY -> copies calldata at mem[0..calldatasize] + + // 36 CALLDATASIZE + // 5F PUSH0 + // F3 RETURN -> returns mem[0..calldatasize] + + vm.etch(target, bytecode); + + (bool success, bytes memory result) = target.call(bytes("hello PUSH0")); + assertTrue(success); + assertEq(string(result), "hello PUSH0"); + } +} diff --git a/testdata/repros/Issue2984.t.sol b/testdata/repros/Issue2984.t.sol deleted file mode 100644 index 4f69057fea424..0000000000000 --- a/testdata/repros/Issue2984.t.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "../cheats/Vm.sol"; - -// https://github.com/foundry-rs/foundry/issues/2984 -contract Issue2984Test is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - uint256 fork; - uint256 snapshot; - - function setUp() public { - fork = vm.createSelectFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); - snapshot = vm.snapshot(); - } - - function testForkRevertSnapshot() public { - vm.revertTo(snapshot); - } - - function testForkSelectSnapshot() public { - uint256 fork2 = vm.createSelectFork("https://api.avax-test.network/ext/bc/C/rpc", 12880749); - } -} diff --git a/testdata/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/script/broadcast/deploy.sol/31337/run-latest.json deleted file mode 100644 index 9a05e1cb7ac2f..0000000000000 --- a/testdata/script/broadcast/deploy.sol/31337/run-latest.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "transactions": [ - { - "hash": null, - "type": "CREATE", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "gas": "0x54653", - "value": "0x0", - "data": "0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033", - "nonce": "0x0", - "accessList": [] - } - }, - { - "hash": null, - "type": "CALL", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "to": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "gas": "0xef15", - "value": "0x0", - "data": "0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000", - "nonce": "0x1", - "accessList": [] - } - }, - { - "hash": null, - "type": "CALL", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "to": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "gas": "0xdcde", - "value": "0x0", - "data": "0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b", - "nonce": "0x2", - "accessList": [] - } - } - ], - "receipts": [], - "libraries": [], - "pending": [], - "path": "broadcast/deploy.sol/31337/run-latest.json", - "returns": {}, - "timestamp": 1658913881 -} \ No newline at end of file